1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-12 08:54:00 +02:00
joplin/packages/lib/file-api-driver-joplinServer.ts

180 lines
4.4 KiB
TypeScript
Raw Normal View History

import JoplinServerApi from './JoplinServerApi';
import { trimSlashes, dirname, basename } from './path-utils';
// All input paths should be in the format: "path/to/file". This is converted to
// "root:/path/to/file:" when doing the API call.
export default class FileApiDriverJoplinServer {
private api_: JoplinServerApi;
public constructor(api: JoplinServerApi) {
this.api_ = api;
}
public async initialize(basePath: string) {
const pieces = trimSlashes(basePath).split('/');
if (!pieces.length) return;
const parent: string[] = [];
for (let i = 0; i < pieces.length; i++) {
const p = pieces[i];
const subPath = parent.concat(p).join('/');
parent.push(p);
await this.mkdir(subPath);
}
}
public api() {
return this.api_;
}
public requestRepeatCount() {
return 3;
}
private metadataToStat_(md: any, path: string, isDeleted: boolean = false) {
const output = {
path: path,
updated_time: md.updated_time,
isDir: !!md.is_directory,
isDeleted: isDeleted,
};
// TODO - HANDLE DELETED
// if (md['.tag'] === 'deleted') output.isDeleted = true;
return output;
}
private metadataToStats_(mds: any[]) {
const output = [];
for (let i = 0; i < mds.length; i++) {
output.push(this.metadataToStat_(mds[i], mds[i].name));
}
return output;
}
// Transforms a path such as "Apps/Joplin/file.txt" to a complete a complete
// API URL path: "api/files/root:/Apps/Joplin/file.txt:"
private apiFilePath_(p: string) {
return `api/files/root:/${trimSlashes(p)}:`;
}
public async stat(path: string) {
try {
const response = await this.api().exec('GET', this.apiFilePath_(path));
return this.metadataToStat_(response, path);
} catch (error) {
if (error.code === 404) return null;
throw error;
}
}
public async delta(path: string, options: any) {
const context = options ? options.context : null;
let cursor = context ? context.cursor : null;
while (true) {
try {
const query = cursor ? { cursor } : {};
const response = await this.api().exec('GET', `${this.apiFilePath_(path)}/delta`, query);
const stats = response.items.map((item: any) => {
return this.metadataToStat_(item.item, item.item.name, item.type === 3);
});
const output = {
items: stats,
hasMore: response.has_more,
context: { cursor: response.cursor },
};
return output;
} catch (error) {
// If there's an error related to an invalid cursor, clear the cursor and retry.
if (cursor && error.code === 'resyncRequired') {
cursor = null;
continue;
}
throw error;
}
}
}
public async list(path: string, options: any = null) {
options = {
context: null,
...options,
};
const query = options.context?.cursor ? { cursor: options.context.cursor } : null;
const results = await this.api().exec('GET', `${this.apiFilePath_(path)}/children`, query);
const newContext: any = {};
if (results.cursor) newContext.cursor = results.cursor;
return {
items: this.metadataToStats_(results.items),
hasMore: results.has_more,
context: newContext,
} as any;
}
public async get(path: string, options: any) {
if (!options) options = {};
if (!options.responseFormat) options.responseFormat = 'text';
try {
const response = await this.api().exec('GET', `${this.apiFilePath_(path)}/content`, null, null, null, options);
return response;
} catch (error) {
if (error.code !== 404) throw error;
return null;
}
}
private parentPath_(path: string) {
return dirname(path);
}
private basename_(path: string) {
return basename(path);
}
public async mkdir(path: string) {
const parentPath = this.parentPath_(path);
const filename = this.basename_(path);
try {
const response = await this.api().exec('POST', `${this.apiFilePath_(parentPath)}/children`, null, {
name: filename,
is_directory: 1,
});
return response;
} catch (error) {
// 409 is OK - directory already exists
if (error.code !== 409) throw error;
}
}
public async put(path: string, content: any, options: any = null) {
return this.api().exec('PUT', `${this.apiFilePath_(path)}/content`, null, content, {
'Content-Type': 'application/octet-stream',
}, options);
}
public async delete(path: string) {
return this.api().exec('DELETE', this.apiFilePath_(path));
}
public format() {
throw new Error('Not supported');
}
public async clearRoot(path: string) {
await this.delete(path);
await this.mkdir(path);
}
}