mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-18 09:35:20 +02:00
180 lines
4.4 KiB
TypeScript
180 lines
4.4 KiB
TypeScript
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);
|
|
}
|
|
}
|