1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-07-16 00:14:34 +02:00

Desktop, Mobile: Resolves #10664: Allow user to generate deletion logs (#11083)

This commit is contained in:
pedr
2024-11-07 10:45:29 -03:00
committed by GitHub
parent 5a44f62fb6
commit 02a0d0d0cc
8 changed files with 167 additions and 0 deletions

View File

@ -0,0 +1,102 @@
import shim from '@joplin/lib/shim';
import * as exportDeletionLog from './exportDeletionLog';
import Setting from '@joplin/lib/models/Setting';
import { AppState, createAppDefaultState } from '../app.reducer';
jest.mock('../services/bridge', () => ({
__esModule: true,
default: () => ({
openItem: jest.fn,
}),
}));
const logContentWithDeleteAction = `
2024-09-17 18:34:28: Running migration: 20
2024-09-17 18:34:28: DeleteAction: MigrationService: ; Item IDs: 1
2024-09-17 18:34:28: Running migration: 27
2024-09-17 18:34:28: DeleteAction: MigrationService: ; Item IDs: 2
2024-09-17 18:34:28: Running migration: 33
2024-09-17 18:34:28: SearchEngine: Updating FTS table...
2024-09-17 18:34:28: Updating items_normalized from {"updated_time":0,"id":""}
2024-09-17 18:34:28: SearchEngine: Updated FTS table in 1ms. Inserted: 0. Deleted: 0
2024-09-17 18:34:28: DeleteAction: MigrationService: ; Item IDs: 3
2024-09-17 18:34:29: Running migration: 35
2024-09-17 18:34:29: SearchEngine: Updating FTS table...
2024-09-17 18:34:29: Updating items_normalized from {"updated_time":0,"id":""}
2024-09-17 18:34:29: SearchEngine: Updated FTS table in 1ms. Inserted: 0. Deleted: 0
2024-09-17 18:34:29: DeleteAction: MigrationService: ; Item IDs: 4
2024-09-17 18:34:29: Running migration: 42
2024-09-17 18:34:29: DeleteAction: MigrationService: ; Item IDs: 5
2024-09-17 18:34:29: App: "syncInfoCache" was changed - setting up encryption related code
`;
const createFakeLogFile = async (filename: string, content: string) => {
await shim.fsDriver().writeFile(`${Setting.value('profileDir')}/${filename}`, content, 'utf8');
};
describe('exportDeletionLog', () => {
let state: AppState = undefined;
beforeAll(() => {
state = createAppDefaultState({}, {});
jest.useFakeTimers();
jest.setSystemTime(new Date('2024-09-18T12:00:00Z').getTime());
});
it('should get all deletion lines from the log file', async () => {
await createFakeLogFile('log.txt', logContentWithDeleteAction);
await exportDeletionLog.runtime().execute({ state, dispatch: () => {} });
const result = await shim.fsDriver().readFile(`${Setting.value('profileDir')}/deletion_log_20240918.txt`, 'utf-8');
expect(result).toBe(
`2024-09-17 18:34:28: DeleteAction: MigrationService: ; Item IDs: 1
2024-09-17 18:34:28: DeleteAction: MigrationService: ; Item IDs: 2
2024-09-17 18:34:28: DeleteAction: MigrationService: ; Item IDs: 3
2024-09-17 18:34:29: DeleteAction: MigrationService: ; Item IDs: 4
2024-09-17 18:34:29: DeleteAction: MigrationService: ; Item IDs: 5
`);
});
it('should return a empty file if there is not deletion lines', async () => {
await createFakeLogFile('log.txt', '');
await exportDeletionLog.runtime().execute({ state, dispatch: () => {} });
const result = await shim.fsDriver().readFile(`${Setting.value('profileDir')}/deletion_log_20240918.txt`, 'utf-8');
expect(result).toBe('');
});
it('should ignore logs from log files that are not recent', async () => {
const before = new Date('2024-09-16').getTime();
const after = new Date('2024-09-17').getTime();
await createFakeLogFile(`log-${before}.txt`, logContentWithDeleteAction);
await createFakeLogFile(`log-${after}.txt`, '');
await createFakeLogFile('log.txt', '');
await exportDeletionLog.runtime().execute({ state, dispatch: () => {} });
const result = await shim.fsDriver().readFile(`${Setting.value('profileDir')}/deletion_log_20240918.txt`, 'utf-8');
expect(result).toBe('');
});
it('should contain the content from the recent log files', async () => {
const rotateLog = new Date('2024-09-17').getTime();
await createFakeLogFile(`log-${rotateLog}.txt`, '2024-09-17 18:34:29: DeleteAction: MigrationService: ; Item IDs: 4');
await createFakeLogFile('log.txt', '2024-09-17 18:34:29: DeleteAction: MigrationService: ; Item IDs: 5');
await exportDeletionLog.runtime().execute({ state, dispatch: () => {} });
const result = await shim.fsDriver().readFile(`${Setting.value('profileDir')}/deletion_log_20240918.txt`, 'utf-8');
expect(result).toBe(
`2024-09-17 18:34:29: DeleteAction: MigrationService: ; Item IDs: 4
2024-09-17 18:34:29: DeleteAction: MigrationService: ; Item IDs: 5
`);
});
it('should contain the date in the filename', async () => {
await shim.fsDriver().remove(`${Setting.value('profileDir')}/deletion_log_20230111.txt`);
jest.setSystemTime(new Date('2023-01-11T12:00:00Z').getTime());
await createFakeLogFile('log.txt', '');
await exportDeletionLog.runtime().execute({ state, dispatch: () => {} });
const exists = await shim.fsDriver().exists(`${Setting.value('profileDir')}/deletion_log_20230111.txt`);
expect(exists).toBe(true);
});
});

View File

@ -0,0 +1,52 @@
import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import shim from '@joplin/lib/shim';
import Setting from '@joplin/lib/models/Setting';
import bridge from '../services/bridge';
import { formatMsToLocal } from '@joplin/utils/time';
export const declaration: CommandDeclaration = {
name: 'exportDeletionLog',
label: () => _('Export deletion log'),
};
const getDeletionLines = async (filePath: string) => {
const logFile: string = await shim.fsDriver().readFile(`${Setting.value('profileDir')}/${filePath}`);
const deletionLines = logFile
.split('\n')
.filter(line => line.includes('DeleteAction'));
if (!deletionLines.length) return '';
return `${deletionLines.join('\n')}\n`;
};
export const runtime = (): CommandRuntime => {
return {
execute: async () => {
const files = await shim.fsDriver().readDirStats(Setting.value('profileDir'));
// Get all log.txt and log-{timestamp}.txt files but ignore deletion_log.txt
const logFiles = files.filter(f => f.path.match(/^log(-\d+)?\.txt$/gi));
const lastOneAndCurrent = logFiles.sort().slice(logFiles.length - 2);
let allDeletionLines = '';
for (const file of lastOneAndCurrent) {
const deletionLines = await getDeletionLines(file.path);
allDeletionLines += deletionLines;
}
const fileName = `deletion_log_${formatMsToLocal(Date.now(), 'YYYYMMDD')}.txt`;
const deletionLogPath = `${Setting.value('profileDir')}/${fileName}`;
await shim.fsDriver().writeFile(deletionLogPath, allDeletionLines, 'utf8');
await bridge().openItem(deletionLogPath);
},
};
};

View File

@ -2,6 +2,7 @@
import * as copyDevCommand from './copyDevCommand';
import * as editProfileConfig from './editProfileConfig';
import * as emptyTrash from './emptyTrash';
import * as exportDeletionLog from './exportDeletionLog';
import * as exportFolders from './exportFolders';
import * as exportNotes from './exportNotes';
import * as focusElement from './focusElement';
@ -22,6 +23,7 @@ const index: any[] = [
copyDevCommand,
editProfileConfig,
emptyTrash,
exportDeletionLog,
exportFolders,
exportNotes,
focusElement,