You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-24 20:19:10 +02:00
Compare commits
4 Commits
v3.4.4
...
note_link_
Author | SHA1 | Date | |
---|---|---|---|
|
29426814e4 | ||
|
a5e18200e8 | ||
|
2c464e89e6 | ||
|
68764bd82e |
@@ -351,7 +351,7 @@ export default class JoplinDatabase extends Database {
|
||||
// must be set in the synchronizer too.
|
||||
|
||||
// Note: v16 and v17 don't do anything. They were used to debug an issue.
|
||||
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41];
|
||||
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42];
|
||||
|
||||
let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion);
|
||||
|
||||
@@ -910,6 +910,22 @@ export default class JoplinDatabase extends Database {
|
||||
queries.push('ALTER TABLE `folders` ADD COLUMN icon TEXT NOT NULL DEFAULT ""');
|
||||
}
|
||||
|
||||
if (targetVersion === 42) {
|
||||
queries.push('ALTER TABLE `note_resources` ADD COLUMN item_type INT NOT NULL DEFAULT 0');
|
||||
queries.push('UPDATE note_resources SET item_type = 4'); // 4 = Resource
|
||||
|
||||
queries = queries.concat(
|
||||
this.alterColumnQueries('note_resources', {
|
||||
id: 'INTEGER PRIMARY KEY',
|
||||
note_id: 'TEXT NOT NULL',
|
||||
resource_id: 'TEXT NOT NULL',
|
||||
is_associated: 'INT NOT NULL',
|
||||
last_seen_time: 'INT NOT NULL',
|
||||
item_type: 'INT NOT NULL',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const updateVersionQuery = { sql: 'UPDATE version SET version = ?', params: [targetVersion] };
|
||||
|
||||
queries.push(updateVersionQuery);
|
||||
|
@@ -5,7 +5,7 @@ import Setting from './Setting';
|
||||
import shim from '../shim';
|
||||
import time from '../time';
|
||||
import markdownUtils from '../markdownUtils';
|
||||
import { NoteEntity } from '../services/database/types';
|
||||
import { BaseItemEntity, NoteEntity } from '../services/database/types';
|
||||
import Tag from './Tag';
|
||||
const { sprintf } = require('sprintf-js');
|
||||
import Resource from './Resource';
|
||||
@@ -119,7 +119,7 @@ export default class Note extends BaseItem {
|
||||
return unique(itemIds);
|
||||
}
|
||||
|
||||
static async linkedItems(body: string) {
|
||||
static async linkedItems(body: string):Promise<BaseItemEntity[]> {
|
||||
const itemIds = this.linkedItemIds(body);
|
||||
const r = await BaseItem.loadItemsByIds(itemIds);
|
||||
return r;
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import BaseModel from '../BaseModel';
|
||||
import { SqlQuery } from '../database';
|
||||
import BaseItem from './BaseItem';
|
||||
import { BaseItemEntity } from '../services/database/types';
|
||||
|
||||
// - If is_associated = 1, note_resources indicates which note_id is currently associated with the given resource_id
|
||||
// - If is_associated = 0, note_resources indicates which note_id *was* associated with the given resource_id
|
||||
// - last_seen_time tells the last time that reosurce was associated with this note.
|
||||
// - last_seen_time tells the last time that resource was associated with this note.
|
||||
// - If last_seen_time is 0, it means the resource has never been associated with any note.
|
||||
|
||||
export default class NoteResource extends BaseModel {
|
||||
@@ -76,25 +77,28 @@ export default class NoteResource extends BaseModel {
|
||||
return rows.map((r: any) => r.note_id);
|
||||
}
|
||||
|
||||
static async setAssociatedResources(noteId: string, resourceIds: string[]) {
|
||||
static async setAssociatedItems(noteId: string, items: BaseItemEntity[]) {
|
||||
const existingRows = await this.modelSelectAll('SELECT * FROM note_resources WHERE note_id = ?', [noteId]);
|
||||
|
||||
const notProcessedResourceIds = resourceIds.slice();
|
||||
const notProcessedItems = items.slice();
|
||||
const queries = [];
|
||||
for (let i = 0; i < existingRows.length; i++) {
|
||||
const row = existingRows[i];
|
||||
const resourceIndex = resourceIds.indexOf(row.resource_id);
|
||||
const resourceIndex = items.findIndex(i => i.id === row.resource_id);
|
||||
|
||||
if (resourceIndex >= 0) {
|
||||
queries.push({ sql: 'UPDATE note_resources SET last_seen_time = ?, is_associated = 1 WHERE id = ?', params: [Date.now(), row.id] });
|
||||
notProcessedResourceIds.splice(notProcessedResourceIds.indexOf(row.resource_id), 1);
|
||||
notProcessedItems.splice(notProcessedItems.indexOf(row.resource_id), 1);
|
||||
} else {
|
||||
queries.push({ sql: 'UPDATE note_resources SET is_associated = 0 WHERE id = ?', params: [row.id] });
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < notProcessedResourceIds.length; i++) {
|
||||
queries.push({ sql: 'INSERT INTO note_resources (note_id, resource_id, is_associated, last_seen_time) VALUES (?, ?, ?, ?)', params: [noteId, notProcessedResourceIds[i], 1, Date.now()] });
|
||||
for (let i = 0; i < notProcessedItems.length; i++) {
|
||||
queries.push({
|
||||
sql: 'INSERT INTO note_resources (note_id, resource_id, item_type, is_associated, last_seen_time) VALUES (?, ?, ?, ?)',
|
||||
params: [noteId, notProcessedItems[i].id, notProcessedItems[i].type_, 1, Date.now()]
|
||||
});
|
||||
}
|
||||
|
||||
await this.db().transactionExecBatch(queries);
|
||||
@@ -136,6 +140,7 @@ export default class NoteResource extends BaseModel {
|
||||
`
|
||||
SELECT resource_id, sum(is_associated)
|
||||
FROM note_resources
|
||||
WHERE item_type = 4
|
||||
GROUP BY resource_id
|
||||
HAVING sum(is_associated) <= 0
|
||||
AND last_seen_time < ?
|
||||
|
@@ -80,7 +80,7 @@ export default class ResourceService extends BaseService {
|
||||
break;
|
||||
}
|
||||
|
||||
await this.setAssociatedResources(note.id, note.body);
|
||||
await this.setAssociatedItems(note.id, note.body);
|
||||
} else {
|
||||
this.logger().warn(`ResourceService::indexNoteResources: A change was recorded for a note that has been deleted: ${change.item_id}`);
|
||||
}
|
||||
@@ -110,9 +110,9 @@ export default class ResourceService extends BaseService {
|
||||
this.logger().info('ResourceService::indexNoteResources: Completed');
|
||||
}
|
||||
|
||||
public async setAssociatedResources(noteId: string, noteBody: string) {
|
||||
const resourceIds = await Note.linkedResourceIds(noteBody);
|
||||
await NoteResource.setAssociatedResources(noteId, resourceIds);
|
||||
public async setAssociatedItems(noteId: string, noteBody: string) {
|
||||
const items = await Note.linkedItems(noteBody);
|
||||
await NoteResource.setAssociatedItems(noteId, items);
|
||||
}
|
||||
|
||||
public async deleteOrphanResources(expiryDelay: number = null) {
|
||||
@@ -126,7 +126,7 @@ export default class ResourceService extends BaseService {
|
||||
const note = await Note.load(results[0].id);
|
||||
if (note) {
|
||||
this.logger().info(sprintf('ResourceService::deleteOrphanResources: Skipping deletion of resource %s because it is still referenced in note %s. Re-indexing note content to fix the issue.', resourceId, note.id));
|
||||
await this.setAssociatedResources(note.id, note.body);
|
||||
await this.setAssociatedItems(note.id, note.body);
|
||||
}
|
||||
} else {
|
||||
await Resource.delete(resourceId);
|
||||
|
@@ -52,6 +52,8 @@ export const defaultFolderIcon = () => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// AUTO-GENERATED BY packages/tools/generate-database-types.js
|
||||
|
||||
/*
|
||||
@@ -59,225 +61,225 @@ export const defaultFolderIcon = () => {
|
||||
* Rerun sql-ts to regenerate this file.
|
||||
*/
|
||||
export interface AlarmEntity {
|
||||
"id"?: number | null
|
||||
"note_id"?: string
|
||||
"trigger_time"?: number
|
||||
"type_"?: number
|
||||
'id'?: number | null;
|
||||
'note_id'?: string;
|
||||
'trigger_time'?: number;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface DeletedItemEntity {
|
||||
"id"?: number | null
|
||||
"item_type"?: number
|
||||
"item_id"?: string
|
||||
"deleted_time"?: number
|
||||
"sync_target"?: number
|
||||
"type_"?: number
|
||||
'id'?: number | null;
|
||||
'item_type'?: number;
|
||||
'item_id'?: string;
|
||||
'deleted_time'?: number;
|
||||
'sync_target'?: number;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface FolderEntity {
|
||||
"id"?: string | null
|
||||
"title"?: string
|
||||
"created_time"?: number
|
||||
"updated_time"?: number
|
||||
"user_created_time"?: number
|
||||
"user_updated_time"?: number
|
||||
"encryption_cipher_text"?: string
|
||||
"encryption_applied"?: number
|
||||
"parent_id"?: string
|
||||
"is_shared"?: number
|
||||
"share_id"?: string
|
||||
"master_key_id"?: string
|
||||
"icon"?: string
|
||||
"type_"?: number
|
||||
'id'?: string | null;
|
||||
'title'?: string;
|
||||
'created_time'?: number;
|
||||
'updated_time'?: number;
|
||||
'user_created_time'?: number;
|
||||
'user_updated_time'?: number;
|
||||
'encryption_cipher_text'?: string;
|
||||
'encryption_applied'?: number;
|
||||
'parent_id'?: string;
|
||||
'is_shared'?: number;
|
||||
'share_id'?: string;
|
||||
'master_key_id'?: string;
|
||||
'icon'?: string;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface ItemChangeEntity {
|
||||
"id"?: number | null
|
||||
"item_type"?: number
|
||||
"item_id"?: string
|
||||
"type"?: number
|
||||
"created_time"?: number
|
||||
"source"?: number
|
||||
"before_change_item"?: string
|
||||
"type_"?: number
|
||||
'id'?: number | null;
|
||||
'item_type'?: number;
|
||||
'item_id'?: string;
|
||||
'type'?: number;
|
||||
'created_time'?: number;
|
||||
'source'?: number;
|
||||
'before_change_item'?: string;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface KeyValueEntity {
|
||||
"id"?: number | null
|
||||
"key"?: string
|
||||
"value"?: string
|
||||
"type"?: number
|
||||
"updated_time"?: number
|
||||
"type_"?: number
|
||||
'id'?: number | null;
|
||||
'key'?: string;
|
||||
'value'?: string;
|
||||
'type'?: number;
|
||||
'updated_time'?: number;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface MigrationEntity {
|
||||
"id"?: number | null
|
||||
"number"?: number
|
||||
"updated_time"?: number
|
||||
"created_time"?: number
|
||||
"type_"?: number
|
||||
'id'?: number | null;
|
||||
'number'?: number;
|
||||
'updated_time'?: number;
|
||||
'created_time'?: number;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface NoteResourceEntity {
|
||||
"id"?: number | null
|
||||
"note_id"?: string
|
||||
"resource_id"?: string
|
||||
"is_associated"?: number
|
||||
"last_seen_time"?: number
|
||||
"type_"?: number
|
||||
'id'?: number | null;
|
||||
'note_id'?: string;
|
||||
'resource_id'?: string;
|
||||
'is_associated'?: number;
|
||||
'last_seen_time'?: number;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface NoteTagEntity {
|
||||
"id"?: string | null
|
||||
"note_id"?: string
|
||||
"tag_id"?: string
|
||||
"created_time"?: number
|
||||
"updated_time"?: number
|
||||
"user_created_time"?: number
|
||||
"user_updated_time"?: number
|
||||
"encryption_cipher_text"?: string
|
||||
"encryption_applied"?: number
|
||||
"is_shared"?: number
|
||||
"type_"?: number
|
||||
'id'?: string | null;
|
||||
'note_id'?: string;
|
||||
'tag_id'?: string;
|
||||
'created_time'?: number;
|
||||
'updated_time'?: number;
|
||||
'user_created_time'?: number;
|
||||
'user_updated_time'?: number;
|
||||
'encryption_cipher_text'?: string;
|
||||
'encryption_applied'?: number;
|
||||
'is_shared'?: number;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface NoteEntity {
|
||||
"id"?: string | null
|
||||
"parent_id"?: string
|
||||
"title"?: string
|
||||
"body"?: string
|
||||
"created_time"?: number
|
||||
"updated_time"?: number
|
||||
"is_conflict"?: number
|
||||
"latitude"?: number
|
||||
"longitude"?: number
|
||||
"altitude"?: number
|
||||
"author"?: string
|
||||
"source_url"?: string
|
||||
"is_todo"?: number
|
||||
"todo_due"?: number
|
||||
"todo_completed"?: number
|
||||
"source"?: string
|
||||
"source_application"?: string
|
||||
"application_data"?: string
|
||||
"order"?: number
|
||||
"user_created_time"?: number
|
||||
"user_updated_time"?: number
|
||||
"encryption_cipher_text"?: string
|
||||
"encryption_applied"?: number
|
||||
"markup_language"?: number
|
||||
"is_shared"?: number
|
||||
"share_id"?: string
|
||||
"conflict_original_id"?: string
|
||||
"master_key_id"?: string
|
||||
"type_"?: number
|
||||
'id'?: string | null;
|
||||
'parent_id'?: string;
|
||||
'title'?: string;
|
||||
'body'?: string;
|
||||
'created_time'?: number;
|
||||
'updated_time'?: number;
|
||||
'is_conflict'?: number;
|
||||
'latitude'?: number;
|
||||
'longitude'?: number;
|
||||
'altitude'?: number;
|
||||
'author'?: string;
|
||||
'source_url'?: string;
|
||||
'is_todo'?: number;
|
||||
'todo_due'?: number;
|
||||
'todo_completed'?: number;
|
||||
'source'?: string;
|
||||
'source_application'?: string;
|
||||
'application_data'?: string;
|
||||
'order'?: number;
|
||||
'user_created_time'?: number;
|
||||
'user_updated_time'?: number;
|
||||
'encryption_cipher_text'?: string;
|
||||
'encryption_applied'?: number;
|
||||
'markup_language'?: number;
|
||||
'is_shared'?: number;
|
||||
'share_id'?: string;
|
||||
'conflict_original_id'?: string;
|
||||
'master_key_id'?: string;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface NotesNormalizedEntity {
|
||||
"id"?: string
|
||||
"title"?: string
|
||||
"body"?: string
|
||||
"user_created_time"?: number
|
||||
"user_updated_time"?: number
|
||||
"is_todo"?: number
|
||||
"todo_completed"?: number
|
||||
"parent_id"?: string
|
||||
"latitude"?: number
|
||||
"longitude"?: number
|
||||
"altitude"?: number
|
||||
"source_url"?: string
|
||||
"todo_due"?: number
|
||||
"type_"?: number
|
||||
'id'?: string;
|
||||
'title'?: string;
|
||||
'body'?: string;
|
||||
'user_created_time'?: number;
|
||||
'user_updated_time'?: number;
|
||||
'is_todo'?: number;
|
||||
'todo_completed'?: number;
|
||||
'parent_id'?: string;
|
||||
'latitude'?: number;
|
||||
'longitude'?: number;
|
||||
'altitude'?: number;
|
||||
'source_url'?: string;
|
||||
'todo_due'?: number;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface ResourceLocalStateEntity {
|
||||
"id"?: number | null
|
||||
"resource_id"?: string
|
||||
"fetch_status"?: number
|
||||
"fetch_error"?: string
|
||||
"type_"?: number
|
||||
'id'?: number | null;
|
||||
'resource_id'?: string;
|
||||
'fetch_status'?: number;
|
||||
'fetch_error'?: string;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface ResourceEntity {
|
||||
"id"?: string | null
|
||||
"title"?: string
|
||||
"mime"?: string
|
||||
"filename"?: string
|
||||
"created_time"?: number
|
||||
"updated_time"?: number
|
||||
"user_created_time"?: number
|
||||
"user_updated_time"?: number
|
||||
"file_extension"?: string
|
||||
"encryption_cipher_text"?: string
|
||||
"encryption_applied"?: number
|
||||
"encryption_blob_encrypted"?: number
|
||||
"size"?: number
|
||||
"is_shared"?: number
|
||||
"share_id"?: string
|
||||
"master_key_id"?: string
|
||||
"type_"?: number
|
||||
'id'?: string | null;
|
||||
'title'?: string;
|
||||
'mime'?: string;
|
||||
'filename'?: string;
|
||||
'created_time'?: number;
|
||||
'updated_time'?: number;
|
||||
'user_created_time'?: number;
|
||||
'user_updated_time'?: number;
|
||||
'file_extension'?: string;
|
||||
'encryption_cipher_text'?: string;
|
||||
'encryption_applied'?: number;
|
||||
'encryption_blob_encrypted'?: number;
|
||||
'size'?: number;
|
||||
'is_shared'?: number;
|
||||
'share_id'?: string;
|
||||
'master_key_id'?: string;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface ResourcesToDownloadEntity {
|
||||
"id"?: number | null
|
||||
"resource_id"?: string
|
||||
"updated_time"?: number
|
||||
"created_time"?: number
|
||||
"type_"?: number
|
||||
'id'?: number | null;
|
||||
'resource_id'?: string;
|
||||
'updated_time'?: number;
|
||||
'created_time'?: number;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface RevisionEntity {
|
||||
"id"?: string | null
|
||||
"parent_id"?: string
|
||||
"item_type"?: number
|
||||
"item_id"?: string
|
||||
"item_updated_time"?: number
|
||||
"title_diff"?: string
|
||||
"body_diff"?: string
|
||||
"metadata_diff"?: string
|
||||
"encryption_cipher_text"?: string
|
||||
"encryption_applied"?: number
|
||||
"updated_time"?: number
|
||||
"created_time"?: number
|
||||
"type_"?: number
|
||||
'id'?: string | null;
|
||||
'parent_id'?: string;
|
||||
'item_type'?: number;
|
||||
'item_id'?: string;
|
||||
'item_updated_time'?: number;
|
||||
'title_diff'?: string;
|
||||
'body_diff'?: string;
|
||||
'metadata_diff'?: string;
|
||||
'encryption_cipher_text'?: string;
|
||||
'encryption_applied'?: number;
|
||||
'updated_time'?: number;
|
||||
'created_time'?: number;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface SettingEntity {
|
||||
"key"?: string | null
|
||||
"value"?: string | null
|
||||
"type_"?: number
|
||||
'key'?: string | null;
|
||||
'value'?: string | null;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface SyncItemEntity {
|
||||
"id"?: number | null
|
||||
"sync_target"?: number
|
||||
"sync_time"?: number
|
||||
"item_type"?: number
|
||||
"item_id"?: string
|
||||
"sync_disabled"?: number
|
||||
"sync_disabled_reason"?: string
|
||||
"force_sync"?: number
|
||||
"item_location"?: number
|
||||
"type_"?: number
|
||||
'id'?: number | null;
|
||||
'sync_target'?: number;
|
||||
'sync_time'?: number;
|
||||
'item_type'?: number;
|
||||
'item_id'?: string;
|
||||
'sync_disabled'?: number;
|
||||
'sync_disabled_reason'?: string;
|
||||
'force_sync'?: number;
|
||||
'item_location'?: number;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface TableFieldEntity {
|
||||
"id"?: number | null
|
||||
"table_name"?: string
|
||||
"field_name"?: string
|
||||
"field_type"?: number
|
||||
"field_default"?: string | null
|
||||
"type_"?: number
|
||||
'id'?: number | null;
|
||||
'table_name'?: string;
|
||||
'field_name'?: string;
|
||||
'field_type'?: number;
|
||||
'field_default'?: string | null;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface TagEntity {
|
||||
"id"?: string | null
|
||||
"title"?: string
|
||||
"created_time"?: number
|
||||
"updated_time"?: number
|
||||
"user_created_time"?: number
|
||||
"user_updated_time"?: number
|
||||
"encryption_cipher_text"?: string
|
||||
"encryption_applied"?: number
|
||||
"is_shared"?: number
|
||||
"parent_id"?: string
|
||||
"type_"?: number
|
||||
'id'?: string | null;
|
||||
'title'?: string;
|
||||
'created_time'?: number;
|
||||
'updated_time'?: number;
|
||||
'user_created_time'?: number;
|
||||
'user_updated_time'?: number;
|
||||
'encryption_cipher_text'?: string;
|
||||
'encryption_applied'?: number;
|
||||
'is_shared'?: number;
|
||||
'parent_id'?: string;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface TagsWithNoteCountEntity {
|
||||
"id"?: string | null
|
||||
"title"?: string | null
|
||||
"created_time"?: number | null
|
||||
"updated_time"?: number | null
|
||||
"note_count"?: any | null
|
||||
"todo_completed_count"?: any | null
|
||||
"type_"?: number
|
||||
'id'?: string | null;
|
||||
'title'?: string | null;
|
||||
'created_time'?: number | null;
|
||||
'updated_time'?: number | null;
|
||||
'note_count'?: any | null;
|
||||
'todo_completed_count'?: any | null;
|
||||
'type_'?: number;
|
||||
}
|
||||
export interface VersionEntity {
|
||||
"version"?: number
|
||||
"table_fields_version"?: number
|
||||
"type_"?: number
|
||||
'version'?: number;
|
||||
'table_fields_version'?: number;
|
||||
'type_'?: number;
|
||||
}
|
||||
|
@@ -6,12 +6,14 @@ const fs = require('fs-extra');
|
||||
async function main() {
|
||||
// Run the CLI app once so as to generate the database file
|
||||
process.chdir(`${rootDir}/packages/app-cli`);
|
||||
const sqliteFilePath = `${require('os').homedir()}/.config/joplindev/database.sqlite`;
|
||||
await fs.remove(sqliteFilePath);
|
||||
await execCommand2('yarn start version');
|
||||
|
||||
const sqlTsConfig = {
|
||||
'client': 'sqlite3',
|
||||
'connection': {
|
||||
'filename': `${require('os').homedir()}/.config/joplindev/database.sqlite`,
|
||||
'filename': sqliteFilePath,
|
||||
},
|
||||
'tableNameCasing': 'pascal',
|
||||
'singularTableNames': true,
|
||||
|
Reference in New Issue
Block a user