1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-27 08:21:03 +02:00

Desktop: Fixes #4426: Improved handling of empty paths for Joplin Server sync target

This commit is contained in:
Laurent Cozic 2021-02-01 10:48:37 +00:00
parent a419e1eb7c
commit b1b5069a23
6 changed files with 40 additions and 50 deletions

View File

@ -552,7 +552,7 @@ async function initFileApi(suiteName: string) {
username: () => 'admin@localhost', username: () => 'admin@localhost',
password: () => 'admin', password: () => 'admin',
}); });
fileApi = new FileApi(`root:/Apps/Joplin-${suiteName}`, new FileApiDriverJoplinServer(api)); fileApi = new FileApi(`Apps/Joplin-${suiteName}`, new FileApiDriverJoplinServer(api));
} }
fileApi.setLogger(logger); fileApi.setLogger(logger);

View File

@ -31,6 +31,7 @@ export default class JoplinServerApi {
private options_: Options; private options_: Options;
private session_: any; private session_: any;
private debugRequests_: boolean = false;
public constructor(options: Options) { public constructor(options: Options) {
this.options_ = options; this.options_ = options;
@ -73,22 +74,22 @@ export default class JoplinServerApi {
return `${this.baseUrl()}/shares/${share.id}`; return `${this.baseUrl()}/shares/${share.id}`;
} }
// private requestToCurl_(url: string, options: any) { private requestToCurl_(url: string, options: any) {
// const output = []; const output = [];
// output.push('curl'); output.push('curl');
// output.push('-v'); output.push('-v');
// if (options.method) output.push(`-X ${options.method}`); if (options.method) output.push(`-X ${options.method}`);
// if (options.headers) { if (options.headers) {
// for (const n in options.headers) { for (const n in options.headers) {
// if (!options.headers.hasOwnProperty(n)) continue; if (!options.headers.hasOwnProperty(n)) continue;
// output.push(`${'-H ' + '"'}${n}: ${options.headers[n]}"`); output.push(`${'-H ' + '"'}${n}: ${options.headers[n]}"`);
// } }
// } }
// if (options.body) output.push(`${'--data ' + '\''}${JSON.stringify(options.body)}'`); if (options.body) output.push(`${'--data ' + '\''}${JSON.stringify(options.body)}'`);
// output.push(url); output.push(url);
// return output.join(' '); return output.join(' ');
// } }
public async exec(method: string, path: string = '', query: Record<string, any> = null, body: any = null, headers: any = null, options: ExecOptions = null) { public async exec(method: string, path: string = '', query: Record<string, any> = null, body: any = null, headers: any = null, options: ExecOptions = null) {
if (headers === null) headers = {}; if (headers === null) headers = {};
@ -128,8 +129,10 @@ export default class JoplinServerApi {
let response: any = null; let response: any = null;
// console.info('Joplin API Call', `${method} ${url}`, headers, options); if (this.debugRequests_) {
// console.info(this.requestToCurl_(url, fetchOptions)); console.info('Joplin API Call', `${method} ${url}`, headers, options);
console.info(this.requestToCurl_(url, fetchOptions));
}
if (options.source == 'file' && (method == 'POST' || method == 'PUT')) { if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
if (fetchOptions.path) { if (fetchOptions.path) {

View File

@ -48,7 +48,7 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
const api = new JoplinServerApi(apiOptions); const api = new JoplinServerApi(apiOptions);
const driver = new FileApiDriverJoplinServer(api); const driver = new FileApiDriverJoplinServer(api);
const fileApi = new FileApi(() => `root:/${options.directory()}`, driver); const fileApi = new FileApi(options.directory, driver);
fileApi.setSyncTargetId(this.id()); fileApi.setSyncTargetId(this.id());
await fileApi.initialize(); await fileApi.initialize();
return fileApi; return fileApi;

View File

@ -1,16 +1,8 @@
import JoplinServerApi from './JoplinServerApi'; import JoplinServerApi from './JoplinServerApi';
const { dirname, basename } = require('./path-utils'); import { trimSlashes, dirname, basename } from './path-utils';
function removeTrailingColon(path: string) { // All input paths should be in the format: "path/to/file". This is converted to
if (!path || !path.length) return ''; // "root:/path/to/file:" when doing the API call.
if (path[path.length - 1] === ':') return path.substr(0, path.length - 1);
return path;
}
// All input paths should be in the format: "SPECIAL_DIR:/path/to/file"
// The trailing colon must not be included as it's automatically added
// when doing the API call.
// Only supported special dir at the moment is "root"
export default class FileApiDriverJoplinServer { export default class FileApiDriverJoplinServer {
@ -21,19 +13,16 @@ export default class FileApiDriverJoplinServer {
} }
public async initialize(basePath: string) { public async initialize(basePath: string) {
const pieces = removeTrailingColon(basePath).split('/'); const pieces = trimSlashes(basePath).split('/');
if (!pieces.length) return; if (!pieces.length) return;
let parent = pieces.splice(0, 1)[0]; const parent: string[] = [];
for (const p of pieces) { for (let i = 0; i < pieces.length; i++) {
// Syncing with the root, which is ok, and in that const p = pieces[i];
// case there's no sub-dir to create. const subPath = parent.concat(p).join('/');
if (!p && pieces.length === 1) return; parent.push(p);
const subPath = `${parent}/${p}`;
await this.mkdir(subPath); await this.mkdir(subPath);
parent = subPath;
} }
} }
@ -67,9 +56,10 @@ export default class FileApiDriverJoplinServer {
return output; 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) { private apiFilePath_(p: string) {
if (p !== 'root') p += ':'; return `api/files/root:/${trimSlashes(p)}:`;
return `api/files/${p}`;
} }
public async stat(path: string) { public async stat(path: string) {
@ -145,14 +135,7 @@ export default class FileApiDriverJoplinServer {
} }
private parentPath_(path: string) { private parentPath_(path: string) {
let output = dirname(path); return dirname(path);
// This is the root or a special folder
if (output.split('/').length === 1) {
output = output.substr(0, output.length - 1);
}
return output;
} }
private basename_(path: string) { private basename_(path: string) {

View File

@ -138,6 +138,10 @@ export function ltrimSlashes(path: string) {
return path.replace(/^\/+/, ''); return path.replace(/^\/+/, '');
} }
export function trimSlashes(path: string): string {
return ltrimSlashes(rtrimSlashes(path));
}
export function quotePath(path: string) { export function quotePath(path: string) {
if (!path) return ''; if (!path) return '';
if (path.indexOf('"') < 0 && path.indexOf(' ') < 0) return path; if (path.indexOf('"') < 0 && path.indexOf(' ') < 0) return path;

View File

@ -269,7 +269,7 @@ export default class FileModel extends BaseModel<File> {
} }
if ('name' in file) { if ('name' in file) {
if (this.includesReservedCharacter(file.name)) throw new ErrorUnprocessableEntity(`File name may not contain any of these characters: ${this.reservedCharacters.join('')}`); if (this.includesReservedCharacter(file.name)) throw new ErrorUnprocessableEntity(`File name may not contain any of these characters: ${this.reservedCharacters.join('')}: ${file.name}`);
} }
return file; return file;