Skip to content

Commit

Permalink
Desktop, Server: Improved handling of items with duplicate IDs
Browse files Browse the repository at this point in the history
  • Loading branch information
laurent22 committed May 17, 2023
1 parent 737ba82 commit d4c43a8
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 11 deletions.
11 changes: 6 additions & 5 deletions packages/lib/JoplinServerApi.ts
Expand Up @@ -45,12 +45,13 @@ export default class JoplinServerApi {
private options_: Options;
private session_: Session;
private debugRequests_: boolean = false;
private debugRequestsShowPasswords_: boolean = false;

public constructor(options: Options) {
this.options_ = options;

if (options.env === Env.Dev) {
// this.debugRequests_ = true;
if (options.env !== Env.Dev) {
this.debugRequestsShowPasswords_ = false;
}
}

Expand Down Expand Up @@ -97,15 +98,15 @@ export default class JoplinServerApi {
try {
const output = JSON.parse(o);
if (!output) return o;
if (output.password) output.password = '******';
if (output.password && !this.debugRequestsShowPasswords_) output.password = '******';
return JSON.stringify(output);
} catch (error) {
return o;
}
} else {
const output = { ...o };
if (output.password) output.password = '******';
if (output['X-API-AUTH']) output['X-API-AUTH'] = '******';
if (output.password && !this.debugRequestsShowPasswords_) output.password = '******';
if (output['X-API-AUTH'] && !this.debugRequestsShowPasswords_) output['X-API-AUTH'] = '******';
return output;
}
}
Expand Down
16 changes: 14 additions & 2 deletions packages/lib/file-api-driver-joplinServer.ts
Expand Up @@ -175,22 +175,34 @@ export default class FileApiDriverJoplinServer {
// they can have names such as ".resources/xxxxxxxxxx'
}

private isRejectedBySyncTargetError(error: any) {
return error.code === 413 || error.code === 409 || error.httpCode === 413 || error.httpCode === 409;
}

public async put(path: string, content: any, options: any = null) {
try {
const output = await this.api().exec('PUT', `${this.apiFilePath_(path)}/content`, options && options.shareId ? { share_id: options.shareId } : null, content, {
'Content-Type': 'application/octet-stream',
}, options);
return output;
} catch (error) {
if (error.code === 413) {
if (this.isRejectedBySyncTargetError(error)) {
throw new JoplinError(error.message, 'rejectedByTarget');
}
throw error;
}
}

public async multiPut(items: MultiPutItem[], options: any = null) {
return this.api().exec('PUT', 'api/batch_items', null, { items: items }, null, options);
const output = await this.api().exec('PUT', 'api/batch_items', null, { items: items }, null, options);

for (const [, response] of Object.entries<any>(output.items)) {
if (response.error && this.isRejectedBySyncTargetError(response.error)) {
response.error.code = 'rejectedByTarget';
}
}

return output;
}

public async delete(path: string) {
Expand Down
4 changes: 3 additions & 1 deletion packages/lib/services/synchronizer/ItemUploader.ts
@@ -1,5 +1,6 @@
import { ModelType } from '../../BaseModel';
import { FileApi, MultiPutItem } from '../../file-api';
import JoplinError from '../../JoplinError';
import Logger from '../../Logger';
import BaseItem from '../../models/BaseItem';
import { BaseItemEntity } from '../database/types';
Expand Down Expand Up @@ -45,7 +46,8 @@ export default class ItemUploader {
// the regular upload.
logger.warn(`Pre-uploaded item updated_time has changed. It is going to be re-uploaded again: ${path} (From ${this.preUploadedItemUpdatedTimes_[path]} to ${local.updated_time})`);
} else {
if (preUploadItem.error) throw new Error(preUploadItem.error.message ? preUploadItem.error.message : 'Unknown pre-upload error');
const error = preUploadItem.error;
if (error) throw new JoplinError(error.message ? error.message : 'Unknown pre-upload error', error.code);
return;
}
}
Expand Down
17 changes: 14 additions & 3 deletions packages/server/src/models/ItemModel.ts
Expand Up @@ -3,12 +3,12 @@ import { ItemType, databaseSchema, Uuid, Item, ShareType, Share, ChangeType, Use
import { defaultPagination, paginateDbQuery, PaginatedResults, Pagination } from './utils/pagination';
import { isJoplinItemName, isJoplinResourceBlobPath, linkedResourceIds, serializeJoplinItem, unserializeJoplinItem } from '../utils/joplinUtils';
import { ModelType } from '@joplin/lib/BaseModel';
import { ApiError, ErrorCode, ErrorForbidden, ErrorPayloadTooLarge, ErrorUnprocessableEntity } from '../utils/errors';
import { ApiError, ErrorCode, ErrorConflict, ErrorForbidden, ErrorPayloadTooLarge, ErrorUnprocessableEntity } from '../utils/errors';
import { Knex } from 'knex';
import { ChangePreviousItem } from './ChangeModel';
import { unique } from '../utils/array';
import StorageDriverBase, { Context } from './items/storage/StorageDriverBase';
import { DbConnection, returningSupported } from '../db';
import { DbConnection, isUniqueConstraintError, returningSupported } from '../db';
import { Config, StorageDriverConfig, StorageDriverMode } from '../utils/types';
import { NewModelFactoryHandler } from './factory';
import loadStorageDriver from './items/storage/loadStorageDriver';
Expand All @@ -21,6 +21,8 @@ const mimeUtils = require('@joplin/lib/mime-utils.js').mime;
// Converts "root:/myfile.txt:" to "myfile.txt"
const extractNameRegex = /^root:\/(.*):$/;

const modelLogger = Logger.create('ItemModel');

export interface DeleteOptions extends BaseDeleteOptions {
deleteChanges?: boolean;
}
Expand Down Expand Up @@ -928,7 +930,16 @@ export default class ItemModel extends BaseModel<Item> {
}

return this.withTransaction(async () => {
item = await super.save(item, options);
try {
item = await super.save(item, options);
} catch (error) {
if (isUniqueConstraintError(error)) {
modelLogger.error(`Unique constraint error on item: ${JSON.stringify({ id: item.id, name: item.name, jop_id: item.jop_id, owner_id: item.owner_id })}`, error);
throw new ErrorConflict(`This item is already present and cannot be added again: ${item.jop_id}`);
} else {
throw error;
}
}

if (isNew) await this.models().userItem().add(userId, item.id);

Expand Down

0 comments on commit d4c43a8

Please sign in to comment.