2024-03-02 16:25:27 +02:00
|
|
|
import Logger from '@joplin/utils/Logger';
|
|
|
|
import Folder from '../../models/Folder';
|
|
|
|
import Setting from '../../models/Setting';
|
|
|
|
import Note from '../../models/Note';
|
|
|
|
import { Day, Hour } from '@joplin/utils/time';
|
|
|
|
import shim from '../../shim';
|
2024-12-09 17:52:45 +02:00
|
|
|
import { itemIsReadOnlySync } from '../../models/utils/readOnly';
|
|
|
|
import BaseItem from '../../models/BaseItem';
|
|
|
|
import { ModelType } from '../../BaseModel';
|
|
|
|
import ItemChange from '../../models/ItemChange';
|
2024-03-02 16:25:27 +02:00
|
|
|
|
|
|
|
const logger = Logger.create('permanentlyDeleteOldData');
|
|
|
|
|
2024-12-09 17:52:45 +02:00
|
|
|
const readOnlyItemsRemoved = async (itemIds: string[], itemType: ModelType) => {
|
|
|
|
const result = [];
|
|
|
|
for (const id of itemIds) {
|
|
|
|
const item = await BaseItem.loadItem(itemType, id);
|
|
|
|
|
|
|
|
// Only do the share-related read-only checks. If other checks are done,
|
|
|
|
// readOnly will always be true because the item is in the trash.
|
|
|
|
const shareChecksOnly = true;
|
|
|
|
const readOnly = itemIsReadOnlySync(
|
|
|
|
itemType,
|
|
|
|
ItemChange.SOURCE_UNSPECIFIED,
|
|
|
|
item,
|
|
|
|
Setting.value('sync.userId'),
|
|
|
|
BaseItem.syncShareCache,
|
|
|
|
shareChecksOnly,
|
|
|
|
);
|
|
|
|
if (!readOnly) {
|
|
|
|
result.push(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
const itemsToDelete = async (ttl: number|null = null) => {
|
|
|
|
const result = await Folder.trashItemsOlderThan(ttl);
|
|
|
|
const folderIds = await readOnlyItemsRemoved(result.folderIds, ModelType.Folder);
|
|
|
|
const noteIds = await readOnlyItemsRemoved(result.noteIds, ModelType.Note);
|
|
|
|
|
|
|
|
return { folderIds, noteIds };
|
|
|
|
};
|
|
|
|
|
2024-03-02 16:25:27 +02:00
|
|
|
const permanentlyDeleteOldItems = async (ttl: number = null) => {
|
|
|
|
ttl = ttl === null ? Setting.value('trash.ttlDays') * Day : ttl;
|
|
|
|
|
|
|
|
logger.info(`Processing items older than ${ttl}ms...`);
|
|
|
|
|
|
|
|
if (!Setting.value('trash.autoDeletionEnabled')) {
|
|
|
|
logger.info('Auto-deletion is not enabled - skipping');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-12-09 17:52:45 +02:00
|
|
|
const toDelete = await itemsToDelete(ttl);
|
|
|
|
logger.info('Items to permanently delete:', toDelete);
|
2024-03-02 16:25:27 +02:00
|
|
|
|
2024-12-09 17:52:45 +02:00
|
|
|
await Note.batchDelete(toDelete.noteIds, { sourceDescription: 'permanentlyDeleteOldItems' });
|
2024-03-02 16:25:27 +02:00
|
|
|
|
|
|
|
// We only auto-delete folders if they are empty.
|
2024-12-09 17:52:45 +02:00
|
|
|
for (const folderId of toDelete.folderIds) {
|
2024-03-02 16:25:27 +02:00
|
|
|
const noteIds = await Folder.noteIds(folderId, { includeDeleted: true });
|
|
|
|
if (!noteIds.length) {
|
|
|
|
logger.info(`Deleting empty folder: ${folderId}`);
|
|
|
|
await Folder.delete(folderId);
|
|
|
|
} else {
|
|
|
|
logger.info(`Skipping non-empty folder: ${folderId}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const setupAutoDeletion = async () => {
|
|
|
|
await permanentlyDeleteOldItems();
|
|
|
|
|
|
|
|
shim.setInterval(async () => {
|
|
|
|
await permanentlyDeleteOldItems();
|
|
|
|
}, 18 * Hour);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default permanentlyDeleteOldItems;
|