mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-08 13:06:15 +02:00
This commit is contained in:
parent
3222b620b9
commit
75cb639ed2
@ -1061,6 +1061,7 @@ packages/lib/themes/solarizedLight.js
|
||||
packages/lib/themes/type.js
|
||||
packages/lib/time.js
|
||||
packages/lib/types.js
|
||||
packages/lib/utils/ActionLogger.js
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/joplinCloud.js
|
||||
packages/lib/utils/processStartFlags.js
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1041,6 +1041,7 @@ packages/lib/themes/solarizedLight.js
|
||||
packages/lib/themes/type.js
|
||||
packages/lib/time.js
|
||||
packages/lib/types.js
|
||||
packages/lib/utils/ActionLogger.js
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/joplinCloud.js
|
||||
packages/lib/utils/processStartFlags.js
|
||||
|
@ -95,7 +95,7 @@ class Application extends BaseApplication {
|
||||
let item = null;
|
||||
if (type === BaseModel.TYPE_NOTE) {
|
||||
if (!parent) throw new Error(_('No notebook has been specified.'));
|
||||
item = await ItemClass.loadFolderNoteByField(parent.id, 'title', pattern);
|
||||
item = await (ItemClass as typeof Note).loadFolderNoteByField(parent.id, 'title', pattern);
|
||||
} else {
|
||||
item = await ItemClass.loadByTitle(pattern);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class Command extends BaseCommand {
|
||||
const ok = force ? true : await this.prompt(msg, { booleanAnswerDefault: 'n' });
|
||||
if (!ok) return;
|
||||
|
||||
await Folder.delete(folder.id, { toTrash: true });
|
||||
await Folder.delete(folder.id, { toTrash: true, sourceDescription: 'rmbook command' });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ class Command extends BaseCommand {
|
||||
if (!ok) return;
|
||||
|
||||
const ids = notes.map(n => n.id);
|
||||
await Note.batchDelete(ids, { toTrash: true });
|
||||
await Note.batchDelete(ids, { toTrash: true, sourceDescription: 'rmnote command' });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ class Command extends BaseCommand {
|
||||
|
||||
for (let i = 0; i < noteCount; i++) {
|
||||
const noteId = randomElement(noteIds);
|
||||
promises.push(Note.delete(noteId));
|
||||
promises.push(Note.delete(noteId, { sourceDescription: 'command-testing' }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ export const runtime = (): CommandRuntime => {
|
||||
const ok = bridge().showConfirmMessageBox(deleteMessage);
|
||||
if (!ok) return;
|
||||
|
||||
await Folder.delete(folderId, { toTrash: true });
|
||||
await Folder.delete(folderId, { toTrash: true, sourceDescription: 'deleteFolder command' });
|
||||
},
|
||||
enabledCondition: '!folderIsReadOnly',
|
||||
};
|
||||
|
@ -13,7 +13,7 @@ export const runtime = (): CommandRuntime => {
|
||||
execute: async (context: CommandContext, noteIds: string[] = null) => {
|
||||
if (noteIds === null) noteIds = context.state.selectedNoteIds;
|
||||
if (!noteIds.length) return;
|
||||
await Note.batchDelete(noteIds, { toTrash: true });
|
||||
await Note.batchDelete(noteIds, { toTrash: true, sourceDescription: 'deleteNote command' });
|
||||
|
||||
context.dispatch({
|
||||
type: 'ITEMS_TRASHED',
|
||||
|
@ -21,7 +21,9 @@ export const runtime = (): CommandRuntime => {
|
||||
defaultId: 1,
|
||||
});
|
||||
|
||||
if (ok) await Note.batchDelete(noteIds, { toTrash: false });
|
||||
if (ok) {
|
||||
await Note.batchDelete(noteIds, { toTrash: false, sourceDescription: 'permanentlyDeleteNote command' });
|
||||
}
|
||||
},
|
||||
enabledCondition: '(!noteIsReadOnly || inTrash) && someNotesSelected',
|
||||
};
|
||||
|
@ -174,9 +174,10 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
Resource.delete(resource.id)
|
||||
Resource.delete(resource.id, { sourceDescription: 'ResourceScreen' })
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
.catch((error: Error) => {
|
||||
console.error(error);
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
})
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
|
@ -277,7 +277,7 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
this.props.dispatch({ type: 'NOTE_SELECTION_END' });
|
||||
|
||||
try {
|
||||
await Note.batchDelete(noteIds, { toTrash: true });
|
||||
await Note.batchDelete(noteIds, { toTrash: true, sourceDescription: 'Delete selected notes button' });
|
||||
} catch (error) {
|
||||
alert(_n('This note could not be deleted: %s', 'These notes could not be deleted: %s', noteIds.length, error.message));
|
||||
}
|
||||
|
@ -682,7 +682,7 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
|
||||
|
||||
const folderId = note.parent_id;
|
||||
|
||||
await Note.delete(note.id, { toTrash: true });
|
||||
await Note.delete(note.id, { toTrash: true, sourceDescription: 'Delete note button' });
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
|
@ -187,7 +187,7 @@ const SideMenuContentComponent = (props: Props) => {
|
||||
{
|
||||
text: _('OK'),
|
||||
onPress: () => {
|
||||
void Folder.delete(folder.id, { toTrash: true });
|
||||
void Folder.delete(folder.id, { toTrash: true, sourceDescription: 'side-menu-content (long-press)' });
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ import uuid from './uuid';
|
||||
import time from './time';
|
||||
import JoplinDatabase, { TableField } from './JoplinDatabase';
|
||||
import { LoadOptions, SaveOptions } from './models/utils/types';
|
||||
import ActionLogger, { ItemActionType as ItemActionType } from './utils/ActionLogger';
|
||||
import { SqlQuery } from './services/database/types';
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
|
||||
@ -41,6 +42,9 @@ export interface DeleteOptions {
|
||||
|
||||
disableReadOnlyCheck?: boolean;
|
||||
|
||||
// Used for logging
|
||||
sourceDescription?: string|ActionLogger;
|
||||
|
||||
// Tells whether the deleted item should be moved to the trash. By default
|
||||
// it is permanently deleted.
|
||||
toTrash?: boolean;
|
||||
@ -688,13 +692,17 @@ class BaseModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
public static delete(id: string) {
|
||||
public static delete(id: string, options?: DeleteOptions) {
|
||||
if (!id) throw new Error('Cannot delete object without an ID');
|
||||
ActionLogger.from(options?.sourceDescription).log(ItemActionType.Delete, id);
|
||||
|
||||
return this.db().exec(`DELETE FROM ${this.tableName()} WHERE id = ?`, [id]);
|
||||
}
|
||||
|
||||
public static async batchDelete(ids: string[], options: DeleteOptions = null) {
|
||||
public static async batchDelete(ids: string[], options?: DeleteOptions) {
|
||||
if (!ids.length) return;
|
||||
ActionLogger.from(options?.sourceDescription).log(ItemActionType.Delete, ids);
|
||||
|
||||
options = this.modOptions(options);
|
||||
const idFieldName = options.idFieldName ? options.idFieldName : 'id';
|
||||
const sql = `DELETE FROM ${this.tableName()} WHERE ${idFieldName} IN ("${ids.join('","')}")`;
|
||||
|
@ -12,7 +12,7 @@ import Resource from './models/Resource';
|
||||
import ItemChange from './models/ItemChange';
|
||||
import ResourceLocalState from './models/ResourceLocalState';
|
||||
import MasterKey from './models/MasterKey';
|
||||
import BaseModel, { ModelType } from './BaseModel';
|
||||
import BaseModel, { DeleteOptions, ModelType } from './BaseModel';
|
||||
import time from './time';
|
||||
import ResourceService from './services/ResourceService';
|
||||
import EncryptionService from './services/e2ee/EncryptionService';
|
||||
@ -404,7 +404,7 @@ export default class Synchronizer {
|
||||
|
||||
this.logSyncOperation('starting', null, null, `Starting synchronisation to target ${syncTargetId}... supportsAccurateTimestamp = ${this.api().supportsAccurateTimestamp}; supportsMultiPut = ${this.api().supportsMultiPut}} [${synchronizationId}]`);
|
||||
|
||||
const handleCannotSyncItem = async (ItemClass: any, syncTargetId: any, item: any, cannotSyncReason: string, itemLocation: any = null) => {
|
||||
const handleCannotSyncItem = async (ItemClass: typeof BaseItem, syncTargetId: any, item: any, cannotSyncReason: string, itemLocation: any = null) => {
|
||||
await ItemClass.saveSyncDisabled(syncTargetId, item, cannotSyncReason, itemLocation);
|
||||
};
|
||||
|
||||
@ -1005,7 +1005,14 @@ export default class Synchronizer {
|
||||
}
|
||||
|
||||
const ItemClass = BaseItem.itemClass(local.type_);
|
||||
await ItemClass.delete(local.id, { trackDeleted: false, changeSource: ItemChange.SOURCE_SYNC });
|
||||
await ItemClass.delete(
|
||||
local.id,
|
||||
{
|
||||
trackDeleted: false,
|
||||
changeSource: ItemChange.SOURCE_SYNC,
|
||||
sourceDescription: 'sync: deleteLocal',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1050,7 +1057,14 @@ export default class Synchronizer {
|
||||
// CONFLICT
|
||||
await Folder.markNotesAsConflict(item.id);
|
||||
}
|
||||
await Folder.delete(item.id, { deleteChildren: false, changeSource: ItemChange.SOURCE_SYNC, trackDeleted: false });
|
||||
|
||||
const deletionOptions: DeleteOptions = {
|
||||
deleteChildren: false,
|
||||
trackDeleted: false,
|
||||
changeSource: ItemChange.SOURCE_SYNC,
|
||||
sourceDescription: 'Sync',
|
||||
};
|
||||
await Folder.delete(item.id, deletionOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ export default async (store: any, _next: any, action: any, dispatch: Dispatch) =
|
||||
for (const noteId of noteIds) {
|
||||
if (action.id === noteId) continue;
|
||||
reg.logger().info('Provisional was not modified - deleting it');
|
||||
await Note.delete(noteId);
|
||||
await Note.delete(noteId, { sourceDescription: 'reduxSharedMiddleware: Delete provisional note' });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +168,7 @@ export default class BaseItem extends BaseModel {
|
||||
return p[0].length === 32 && p[1] === 'md';
|
||||
}
|
||||
|
||||
public static itemClass(item: any): any {
|
||||
public static itemClass(item: any): typeof BaseItem {
|
||||
if (!item) throw new Error('Item cannot be null');
|
||||
|
||||
if (typeof item === 'object') {
|
||||
@ -269,17 +269,17 @@ export default class BaseItem extends BaseModel {
|
||||
return ItemClass.load(id, options);
|
||||
}
|
||||
|
||||
public static deleteItem(itemType: ModelType, id: string) {
|
||||
public static deleteItem(itemType: ModelType, id: string, options: DeleteOptions) {
|
||||
const ItemClass = this.itemClass(itemType);
|
||||
return ItemClass.delete(id);
|
||||
return ItemClass.delete(id, options);
|
||||
}
|
||||
|
||||
public static async delete(id: string, options: DeleteOptions = null) {
|
||||
public static async delete(id: string, options?: DeleteOptions) {
|
||||
return this.batchDelete([id], options);
|
||||
}
|
||||
|
||||
public static async batchDelete(ids: string[], options: DeleteOptions = null) {
|
||||
if (!options) options = {};
|
||||
public static async batchDelete(ids: string[], options: DeleteOptions) {
|
||||
if (!options) options = { sourceDescription: '' };
|
||||
let trackDeleted = true;
|
||||
if (options && options.trackDeleted !== null && options.trackDeleted !== undefined) trackDeleted = options.trackDeleted;
|
||||
|
||||
|
@ -12,6 +12,7 @@ import Logger from '@joplin/utils/Logger';
|
||||
import syncDebugLog from '../services/synchronizer/syncDebugLog';
|
||||
import ResourceService from '../services/ResourceService';
|
||||
import { LoadOptions } from './utils/types';
|
||||
import ActionLogger from '../utils/ActionLogger';
|
||||
import { getTrashFolder, getTrashFolderId } from '../services/trash';
|
||||
const { substrWithEllipsis } = require('../string-utils.js');
|
||||
|
||||
@ -107,7 +108,7 @@ export default class Folder extends BaseItem {
|
||||
}
|
||||
}
|
||||
|
||||
public static async delete(folderId: string, options: DeleteOptions = null) {
|
||||
public static async delete(folderId: string, options?: DeleteOptions) {
|
||||
options = {
|
||||
deleteChildren: true,
|
||||
...options,
|
||||
@ -120,9 +121,14 @@ export default class Folder extends BaseItem {
|
||||
const folder = await Folder.load(folderId);
|
||||
if (!folder) return; // noop
|
||||
|
||||
const actionLogger = ActionLogger.from(options.sourceDescription);
|
||||
actionLogger.addDescription(`folder title: ${JSON.stringify(folder.title)}`);
|
||||
options.sourceDescription = actionLogger;
|
||||
|
||||
if (options.deleteChildren) {
|
||||
const childrenDeleteOptions: DeleteOptions = {
|
||||
disableReadOnlyCheck: options.disableReadOnlyCheck,
|
||||
sourceDescription: actionLogger,
|
||||
deleteChildren: true,
|
||||
toTrash,
|
||||
};
|
||||
|
@ -16,6 +16,7 @@ const { pregQuote, substrWithEllipsis } = require('../string-utils.js');
|
||||
const { _ } = require('../locale');
|
||||
import { pull, removeElement, unique } from '../ArrayUtils';
|
||||
import { LoadOptions, SaveOptions } from './utils/types';
|
||||
import ActionLogger from '../utils/ActionLogger';
|
||||
import { getDisplayParentId, getTrashFolderId } from '../services/trash';
|
||||
const urlUtils = require('../urlUtils.js');
|
||||
const { isImageMimeType } = require('../resourceUtils');
|
||||
@ -827,7 +828,7 @@ export default class Note extends BaseItem {
|
||||
return savedNote;
|
||||
}
|
||||
|
||||
public static async batchDelete(ids: string[], options: DeleteOptions = null) {
|
||||
public static async batchDelete(ids: string[], options: DeleteOptions = {}) {
|
||||
if (!ids.length) return;
|
||||
|
||||
ids = ids.slice();
|
||||
@ -871,7 +872,12 @@ export default class Note extends BaseItem {
|
||||
|
||||
await this.db().exec({ sql, params });
|
||||
} else {
|
||||
await super.batchDelete(processIds, options);
|
||||
// For now, we intentionally log only permanent batchDeletions.
|
||||
const actionLogger = ActionLogger.from(options.sourceDescription);
|
||||
const noteTitles = notes.map(note => note.title);
|
||||
actionLogger.addDescription(`titles: ${JSON.stringify(noteTitles)}`);
|
||||
|
||||
await super.batchDelete(processIds, { ...options, sourceDescription: actionLogger });
|
||||
}
|
||||
|
||||
for (let i = 0; i < processIds.length; i++) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import BaseModel from '../BaseModel';
|
||||
import BaseModel, { DeleteOptions } from '../BaseModel';
|
||||
import BaseItem from './BaseItem';
|
||||
import ItemChange from './ItemChange';
|
||||
import NoteResource from './NoteResource';
|
||||
@ -22,6 +22,7 @@ import { htmlentities } from '@joplin/utils/html';
|
||||
import { RecognizeResultLine } from '../services/ocr/utils/types';
|
||||
import eventManager, { EventName } from '../eventManager';
|
||||
import { unique } from '../array';
|
||||
import ActionLogger from '../utils/ActionLogger';
|
||||
import isSqliteSyntaxError from '../services/database/isSqliteSyntaxError';
|
||||
import { internalUrl, isResourceUrl, isSupportedImageMimeType, resourceFilename, resourceFullPath, resourcePathToId, resourceRelativePath, resourceUrlToId } from './utils/resourceUtils';
|
||||
|
||||
@ -311,7 +312,9 @@ export default class Resource extends BaseItem {
|
||||
return this.db().exec('UPDATE resources set `size` = ? WHERE id = ?', [fileSize, resourceId]);
|
||||
}
|
||||
|
||||
public static async batchDelete(ids: string[], options: any = null) {
|
||||
public static async batchDelete(ids: string[], options: DeleteOptions = {}) {
|
||||
const actionLogger = ActionLogger.from(options.sourceDescription);
|
||||
|
||||
// For resources, there's not really batch deletion since there's the
|
||||
// file data to delete too, so each is processed one by one with the
|
||||
// file data being deleted last since the metadata deletion call may
|
||||
@ -321,14 +324,21 @@ export default class Resource extends BaseItem {
|
||||
const resource = await Resource.load(id);
|
||||
if (!resource) continue;
|
||||
|
||||
// Log just for the current item.
|
||||
const logger = actionLogger.clone();
|
||||
logger.addDescription(`title: ${resource.title}`);
|
||||
|
||||
const path = Resource.fullPath(resource);
|
||||
await super.batchDelete([id], options);
|
||||
await super.batchDelete([id], {
|
||||
...options,
|
||||
sourceDescription: logger,
|
||||
});
|
||||
await this.fsDriver().remove(path);
|
||||
await NoteResource.deleteByResource(id); // Clean up note/resource relationships
|
||||
await this.db().exec('DELETE FROM items_normalized WHERE item_id = ?', [id]);
|
||||
}
|
||||
|
||||
await ResourceLocalState.batchDelete(ids);
|
||||
await ResourceLocalState.batchDelete(ids, { sourceDescription: actionLogger });
|
||||
}
|
||||
|
||||
public static async markForDownload(resourceId: string) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import BaseModel from '../BaseModel';
|
||||
import BaseModel, { DeleteOptions } from '../BaseModel';
|
||||
import { ResourceLocalStateEntity } from '../services/database/types';
|
||||
import Database from '../database';
|
||||
import ActionLogger from '../utils/ActionLogger';
|
||||
|
||||
export default class ResourceLocalState extends BaseModel {
|
||||
public static tableName() {
|
||||
@ -34,9 +35,11 @@ export default class ResourceLocalState extends BaseModel {
|
||||
return this.db().transactionExecBatch(this.saveQueries(o));
|
||||
}
|
||||
|
||||
public static batchDelete(ids: string[], options: any = null) {
|
||||
options = options ? { ...options } : {};
|
||||
public static batchDelete(ids: string[], options: DeleteOptions = {}) {
|
||||
options = { ...options };
|
||||
options.idFieldName = 'resource_id';
|
||||
options.sourceDescription = ActionLogger.from(options.sourceDescription);
|
||||
options.sourceDescription.addDescription('Delete local resource state');
|
||||
return super.batchDelete(ids, options);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { TagEntity, TagsWithNoteCountEntity } from '../services/database/types';
|
||||
|
||||
import BaseModel from '../BaseModel';
|
||||
import BaseModel, { DeleteOptions } from '../BaseModel';
|
||||
import BaseItem from './BaseItem';
|
||||
import NoteTag from './NoteTag';
|
||||
import Note from './Note';
|
||||
import { _ } from '../locale';
|
||||
import ActionLogger from '../utils/ActionLogger';
|
||||
|
||||
export default class Tag extends BaseItem {
|
||||
public static tableName() {
|
||||
@ -45,14 +46,21 @@ export default class Tag extends BaseItem {
|
||||
public static async untagAll(tagId: string) {
|
||||
const noteTags = await NoteTag.modelSelectAll('SELECT id FROM note_tags WHERE tag_id = ?', [tagId]);
|
||||
for (let i = 0; i < noteTags.length; i++) {
|
||||
await NoteTag.delete(noteTags[i].id);
|
||||
await NoteTag.delete(noteTags[i].id, { sourceDescription: 'untagAll/disassociate note' });
|
||||
}
|
||||
|
||||
await Tag.delete(tagId);
|
||||
await Tag.delete(tagId, { sourceDescription: 'untagAll/delete tag' });
|
||||
}
|
||||
|
||||
public static async delete(id: string, options: any = null) {
|
||||
if (!options) options = {};
|
||||
public static async delete(id: string, options: DeleteOptions = {}) {
|
||||
const actionLogger = ActionLogger.from(options.sourceDescription);
|
||||
const tagTitle = (await Tag.load(id)).title;
|
||||
actionLogger.addDescription(`tag title: ${JSON.stringify(tagTitle)}`);
|
||||
|
||||
options = {
|
||||
...options,
|
||||
sourceDescription: actionLogger,
|
||||
};
|
||||
|
||||
await super.delete(id, options);
|
||||
|
||||
@ -93,14 +101,18 @@ export default class Tag extends BaseItem {
|
||||
}
|
||||
|
||||
public static async removeNote(tagId: string, noteId: string) {
|
||||
const tag = await Tag.load(tagId);
|
||||
|
||||
const actionLogger = ActionLogger.from(`Tag/removeNote - tag: ${tag.title}`);
|
||||
|
||||
const noteTags = await NoteTag.modelSelectAll('SELECT id FROM note_tags WHERE tag_id = ? and note_id = ?', [tagId, noteId]);
|
||||
for (let i = 0; i < noteTags.length; i++) {
|
||||
await NoteTag.delete(noteTags[i].id);
|
||||
await NoteTag.delete(noteTags[i].id, { sourceDescription: actionLogger.clone() });
|
||||
}
|
||||
|
||||
this.dispatch({
|
||||
type: 'NOTE_TAG_REMOVE',
|
||||
item: await Tag.load(tagId),
|
||||
item: tag,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ export default async (noteIds: string[], folderIds: string[], targetFolderId: st
|
||||
|
||||
if (!targetFolder) throw new Error(`No such folder: ${targetFolderId}`);
|
||||
|
||||
const defaultDeleteOptions: DeleteOptions = { toTrash: true };
|
||||
const defaultDeleteOptions: DeleteOptions = { toTrash: true, sourceDescription: 'onFolderDrop' };
|
||||
|
||||
if (targetFolder.id !== getTrashFolderId()) {
|
||||
defaultDeleteOptions.toTrashParentId = targetFolder.id;
|
||||
|
@ -45,7 +45,7 @@ export default class AlarmService {
|
||||
this.logger().info(`Clearing notification for non-existing note. Alarm ${alarmIds[i]}`);
|
||||
await this.driver().clearNotification(alarmIds[i]);
|
||||
}
|
||||
await Alarm.batchDelete(alarmIds);
|
||||
await Alarm.batchDelete(alarmIds, { sourceDescription: 'AlarmService/garbageCollect' });
|
||||
}
|
||||
|
||||
// When passing a note, make sure it has all the required properties
|
||||
@ -93,7 +93,7 @@ export default class AlarmService {
|
||||
if (clearAlarm) {
|
||||
this.logger().info(`Clearing notification for note ${noteId}`);
|
||||
await driver.clearNotification(alarm.id);
|
||||
await Alarm.delete(alarm.id);
|
||||
await Alarm.delete(alarm.id, { sourceDescription: 'AlarmService/clearAlarm' });
|
||||
}
|
||||
|
||||
if (isDeleted || !Note.needAlarm(note)) return;
|
||||
|
@ -24,7 +24,7 @@ export default class MigrationService extends BaseService {
|
||||
|
||||
try {
|
||||
await this.runScript(migration.number);
|
||||
await Migration.delete(migration.id);
|
||||
await Migration.delete(migration.id, { sourceDescription: 'MigrationService' });
|
||||
} catch (error) {
|
||||
this.logger().error(`Cannot run migration: ${migration.number}`, error);
|
||||
break;
|
||||
|
@ -132,7 +132,7 @@ export default class ResourceService extends BaseService {
|
||||
await this.setAssociatedResources(note.id, note.body);
|
||||
}
|
||||
} else {
|
||||
await Resource.delete(resourceId);
|
||||
await Resource.delete(resourceId, { sourceDescription: 'deleteOrphanResources' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ export default async function(request: Request, id: string = null, link: string
|
||||
}
|
||||
|
||||
if (request.method === RequestMethod.DELETE) {
|
||||
await Folder.delete(id, { toTrash: request.query.permanent !== '1' });
|
||||
await Folder.delete(id, { toTrash: request.query.permanent !== '1', sourceDescription: 'api/folders DELETE' });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -511,7 +511,7 @@ export default async function(request: Request, id: string = null, link: string
|
||||
}
|
||||
|
||||
if (request.method === RequestMethod.DELETE) {
|
||||
await Note.delete(id, { toTrash: request.query.permanent !== '1' });
|
||||
await Note.delete(id, { toTrash: request.query.permanent !== '1', sourceDescription: 'api/notes DELETE' });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ export default async function(modelType: number, request: Request, id: string =
|
||||
|
||||
if (request.method === 'DELETE' && id) {
|
||||
const model = await getOneModel();
|
||||
await ModelClass.delete(model.id);
|
||||
await ModelClass.delete(model.id, { source: 'API: DELETE method' });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -204,8 +204,9 @@ export default class ShareService {
|
||||
// call deleteAllByShareId()
|
||||
await Folder.updateAllShareIds(ResourceService.instance());
|
||||
|
||||
await Folder.delete(folderId, { deleteChildren: false, disableReadOnlyCheck: true });
|
||||
await Folder.deleteAllByShareId(folder.share_id, { disableReadOnlyCheck: true, trackDeleted: false });
|
||||
const source = 'ShareService.leaveSharedFolder';
|
||||
await Folder.delete(folderId, { deleteChildren: false, disableReadOnlyCheck: true, sourceDescription: source });
|
||||
await Folder.deleteAllByShareId(folder.share_id, { disableReadOnlyCheck: true, trackDeleted: false, sourceDescription: source });
|
||||
}
|
||||
|
||||
// Finds any folder that is associated with a share, but the user no longer
|
||||
|
@ -33,7 +33,7 @@ export default class ItemUploader {
|
||||
this.maxBatchSize_ = v;
|
||||
}
|
||||
|
||||
public async serializeAndUploadItem(ItemClass: any, path: string, local: BaseItemEntity) {
|
||||
public async serializeAndUploadItem(ItemClass: typeof BaseItem, path: string, local: BaseItemEntity) {
|
||||
const preUploadItem = this.preUploadedItems_[path];
|
||||
if (preUploadItem) {
|
||||
if (this.preUploadedItemUpdatedTimes_[path] !== local.updated_time) {
|
||||
|
@ -9,7 +9,7 @@ import { SyncAction, conflictActions } from './types';
|
||||
|
||||
const logger = Logger.create('handleConflictAction');
|
||||
|
||||
export default async (action: SyncAction, ItemClass: any, remoteExists: boolean, remoteContent: any, local: any, syncTargetId: number, itemIsReadOnly: boolean, dispatch: Dispatch) => {
|
||||
export default async (action: SyncAction, ItemClass: typeof BaseItem, remoteExists: boolean, remoteContent: any, local: any, syncTargetId: number, itemIsReadOnly: boolean, dispatch: Dispatch) => {
|
||||
if (!conflictActions.includes(action)) return;
|
||||
|
||||
logger.debug(`Handling conflict: ${action}`);
|
||||
@ -30,6 +30,7 @@ export default async (action: SyncAction, ItemClass: any, remoteExists: boolean,
|
||||
} else {
|
||||
await ItemClass.delete(local.id, {
|
||||
changeSource: ItemChange.SOURCE_SYNC,
|
||||
sourceDescription: 'sync: handleConflictAction: non-note conflict',
|
||||
trackDeleted: false,
|
||||
});
|
||||
}
|
||||
@ -84,7 +85,14 @@ export default async (action: SyncAction, ItemClass: any, remoteExists: boolean,
|
||||
if (local.encryption_applied) dispatch({ type: 'SYNC_GOT_ENCRYPTED_ITEM' });
|
||||
} else {
|
||||
// Remote no longer exists (note deleted) so delete local one too
|
||||
await ItemClass.delete(local.id, { changeSource: ItemChange.SOURCE_SYNC, trackDeleted: false });
|
||||
await ItemClass.delete(
|
||||
local.id,
|
||||
{
|
||||
changeSource: ItemChange.SOURCE_SYNC,
|
||||
trackDeleted: false,
|
||||
sourceDescription: 'sync: handleConflictAction: note/resource conflict',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -4,9 +4,9 @@ import Note from '../../models/Note';
|
||||
|
||||
export default async () => {
|
||||
const result = await BaseItem.allItemsInTrash();
|
||||
await Note.batchDelete(result.noteIds);
|
||||
await Note.batchDelete(result.noteIds, { sourceDescription: 'emptyTrash/notes' });
|
||||
|
||||
for (const folderId of result.folderIds) {
|
||||
await Folder.delete(folderId, { deleteChildren: false });
|
||||
await Folder.delete(folderId, { deleteChildren: false, sourceDescription: 'emptyTrash/folders' });
|
||||
}
|
||||
};
|
||||
|
@ -20,7 +20,7 @@ const permanentlyDeleteOldItems = async (ttl: number = null) => {
|
||||
const result = await Folder.trashItemsOlderThan(ttl);
|
||||
logger.info('Items to permanently delete:', result);
|
||||
|
||||
await Note.batchDelete(result.noteIds);
|
||||
await Note.batchDelete(result.noteIds, { sourceDescription: 'permanentlyDeleteOldItems' });
|
||||
|
||||
// We only auto-delete folders if they are empty.
|
||||
for (const folderId of result.folderIds) {
|
||||
|
44
packages/lib/utils/ActionLogger.ts
Normal file
44
packages/lib/utils/ActionLogger.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
|
||||
export enum ItemActionType {
|
||||
Delete = 'DeleteAction',
|
||||
}
|
||||
|
||||
const actionTypeToLogger = {
|
||||
[ItemActionType.Delete]: Logger.create(ItemActionType.Delete),
|
||||
};
|
||||
|
||||
export default class ActionLogger {
|
||||
private descriptions: string[] = [];
|
||||
|
||||
private constructor(private source: string) { }
|
||||
|
||||
public clone() {
|
||||
const clone = new ActionLogger(this.source);
|
||||
clone.descriptions = [...this.descriptions];
|
||||
return clone;
|
||||
}
|
||||
|
||||
// addDescription is used to add labels with information that may not be available
|
||||
// when .log is called. For example, to include the title of a deleted note.
|
||||
public addDescription(description: string) {
|
||||
this.descriptions.push(description);
|
||||
}
|
||||
|
||||
public log(action: ItemActionType, itemIds: string|string[]) {
|
||||
const logger = actionTypeToLogger[action];
|
||||
logger.info(`${this.source}: ${this.descriptions.join(',')}; Item IDs: ${JSON.stringify(itemIds)}`);
|
||||
}
|
||||
|
||||
public static from(source: ActionLogger|string|undefined) {
|
||||
if (!source) {
|
||||
source = 'Unknown source';
|
||||
}
|
||||
|
||||
if (typeof source === 'string') {
|
||||
return new ActionLogger(source);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
@ -181,8 +181,14 @@ class Logger {
|
||||
|
||||
public objectsToString(...object: any[]) {
|
||||
const output = [];
|
||||
for (let i = 0; i < object.length; i++) {
|
||||
output.push(`"${this.objectToString(object[i])}"`);
|
||||
if (object.length === 1) {
|
||||
// Quoting when there is only one argument can make the log more difficult to read,
|
||||
// particularly when formatting is handled elsewhere.
|
||||
output.push(this.objectToString(object[0]));
|
||||
} else {
|
||||
for (let i = 0; i < object.length; i++) {
|
||||
output.push(`"${this.objectToString(object[i])}"`);
|
||||
}
|
||||
}
|
||||
return output.join(', ');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user