1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-27 20:29:45 +02:00

Compare commits

...

4 Commits

Author SHA1 Message Date
Laurent Cozic
29426814e4 item type 2022-08-04 10:49:19 +02:00
Laurent Cozic
a5e18200e8 Merge branch 'dev' into note_link_indexer 2022-07-30 14:46:44 +02:00
Laurent Cozic
2c464e89e6 update db 2022-07-12 15:25:33 +01:00
Laurent Cozic
68764bd82e update db 2022-07-12 15:18:43 +01:00
6 changed files with 224 additions and 199 deletions

View File

@@ -351,7 +351,7 @@ export default class JoplinDatabase extends Database {
// must be set in the synchronizer too. // must be set in the synchronizer too.
// Note: v16 and v17 don't do anything. They were used to debug an issue. // 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); 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 ""'); 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] }; const updateVersionQuery = { sql: 'UPDATE version SET version = ?', params: [targetVersion] };
queries.push(updateVersionQuery); queries.push(updateVersionQuery);

View File

@@ -5,7 +5,7 @@ import Setting from './Setting';
import shim from '../shim'; import shim from '../shim';
import time from '../time'; import time from '../time';
import markdownUtils from '../markdownUtils'; import markdownUtils from '../markdownUtils';
import { NoteEntity } from '../services/database/types'; import { BaseItemEntity, NoteEntity } from '../services/database/types';
import Tag from './Tag'; import Tag from './Tag';
const { sprintf } = require('sprintf-js'); const { sprintf } = require('sprintf-js');
import Resource from './Resource'; import Resource from './Resource';
@@ -119,7 +119,7 @@ export default class Note extends BaseItem {
return unique(itemIds); return unique(itemIds);
} }
static async linkedItems(body: string) { static async linkedItems(body: string):Promise<BaseItemEntity[]> {
const itemIds = this.linkedItemIds(body); const itemIds = this.linkedItemIds(body);
const r = await BaseItem.loadItemsByIds(itemIds); const r = await BaseItem.loadItemsByIds(itemIds);
return r; return r;

View File

@@ -1,10 +1,11 @@
import BaseModel from '../BaseModel'; import BaseModel from '../BaseModel';
import { SqlQuery } from '../database'; import { SqlQuery } from '../database';
import BaseItem from './BaseItem'; 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 = 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 // - 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. // - If last_seen_time is 0, it means the resource has never been associated with any note.
export default class NoteResource extends BaseModel { export default class NoteResource extends BaseModel {
@@ -76,25 +77,28 @@ export default class NoteResource extends BaseModel {
return rows.map((r: any) => r.note_id); 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 existingRows = await this.modelSelectAll('SELECT * FROM note_resources WHERE note_id = ?', [noteId]);
const notProcessedResourceIds = resourceIds.slice(); const notProcessedItems = items.slice();
const queries = []; const queries = [];
for (let i = 0; i < existingRows.length; i++) { for (let i = 0; i < existingRows.length; i++) {
const row = existingRows[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) { if (resourceIndex >= 0) {
queries.push({ sql: 'UPDATE note_resources SET last_seen_time = ?, is_associated = 1 WHERE id = ?', params: [Date.now(), row.id] }); 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 { } else {
queries.push({ sql: 'UPDATE note_resources SET is_associated = 0 WHERE id = ?', params: [row.id] }); queries.push({ sql: 'UPDATE note_resources SET is_associated = 0 WHERE id = ?', params: [row.id] });
} }
} }
for (let i = 0; i < notProcessedResourceIds.length; i++) { for (let i = 0; i < notProcessedItems.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()] }); 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); await this.db().transactionExecBatch(queries);
@@ -136,6 +140,7 @@ export default class NoteResource extends BaseModel {
` `
SELECT resource_id, sum(is_associated) SELECT resource_id, sum(is_associated)
FROM note_resources FROM note_resources
WHERE item_type = 4
GROUP BY resource_id GROUP BY resource_id
HAVING sum(is_associated) <= 0 HAVING sum(is_associated) <= 0
AND last_seen_time < ? AND last_seen_time < ?

View File

@@ -80,7 +80,7 @@ export default class ResourceService extends BaseService {
break; break;
} }
await this.setAssociatedResources(note.id, note.body); await this.setAssociatedItems(note.id, note.body);
} else { } else {
this.logger().warn(`ResourceService::indexNoteResources: A change was recorded for a note that has been deleted: ${change.item_id}`); 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'); this.logger().info('ResourceService::indexNoteResources: Completed');
} }
public async setAssociatedResources(noteId: string, noteBody: string) { public async setAssociatedItems(noteId: string, noteBody: string) {
const resourceIds = await Note.linkedResourceIds(noteBody); const items = await Note.linkedItems(noteBody);
await NoteResource.setAssociatedResources(noteId, resourceIds); await NoteResource.setAssociatedItems(noteId, items);
} }
public async deleteOrphanResources(expiryDelay: number = null) { public async deleteOrphanResources(expiryDelay: number = null) {
@@ -126,7 +126,7 @@ export default class ResourceService extends BaseService {
const note = await Note.load(results[0].id); const note = await Note.load(results[0].id);
if (note) { 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)); 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 { } else {
await Resource.delete(resourceId); await Resource.delete(resourceId);

View File

@@ -52,6 +52,8 @@ export const defaultFolderIcon = () => {
// AUTO-GENERATED BY packages/tools/generate-database-types.js // AUTO-GENERATED BY packages/tools/generate-database-types.js
/* /*
@@ -59,225 +61,225 @@ export const defaultFolderIcon = () => {
* Rerun sql-ts to regenerate this file. * Rerun sql-ts to regenerate this file.
*/ */
export interface AlarmEntity { export interface AlarmEntity {
"id"?: number | null 'id'?: number | null;
"note_id"?: string 'note_id'?: string;
"trigger_time"?: number 'trigger_time'?: number;
"type_"?: number 'type_'?: number;
} }
export interface DeletedItemEntity { export interface DeletedItemEntity {
"id"?: number | null 'id'?: number | null;
"item_type"?: number 'item_type'?: number;
"item_id"?: string 'item_id'?: string;
"deleted_time"?: number 'deleted_time'?: number;
"sync_target"?: number 'sync_target'?: number;
"type_"?: number 'type_'?: number;
} }
export interface FolderEntity { export interface FolderEntity {
"id"?: string | null 'id'?: string | null;
"title"?: string 'title'?: string;
"created_time"?: number 'created_time'?: number;
"updated_time"?: number 'updated_time'?: number;
"user_created_time"?: number 'user_created_time'?: number;
"user_updated_time"?: number 'user_updated_time'?: number;
"encryption_cipher_text"?: string 'encryption_cipher_text'?: string;
"encryption_applied"?: number 'encryption_applied'?: number;
"parent_id"?: string 'parent_id'?: string;
"is_shared"?: number 'is_shared'?: number;
"share_id"?: string 'share_id'?: string;
"master_key_id"?: string 'master_key_id'?: string;
"icon"?: string 'icon'?: string;
"type_"?: number 'type_'?: number;
} }
export interface ItemChangeEntity { export interface ItemChangeEntity {
"id"?: number | null 'id'?: number | null;
"item_type"?: number 'item_type'?: number;
"item_id"?: string 'item_id'?: string;
"type"?: number 'type'?: number;
"created_time"?: number 'created_time'?: number;
"source"?: number 'source'?: number;
"before_change_item"?: string 'before_change_item'?: string;
"type_"?: number 'type_'?: number;
} }
export interface KeyValueEntity { export interface KeyValueEntity {
"id"?: number | null 'id'?: number | null;
"key"?: string 'key'?: string;
"value"?: string 'value'?: string;
"type"?: number 'type'?: number;
"updated_time"?: number 'updated_time'?: number;
"type_"?: number 'type_'?: number;
} }
export interface MigrationEntity { export interface MigrationEntity {
"id"?: number | null 'id'?: number | null;
"number"?: number 'number'?: number;
"updated_time"?: number 'updated_time'?: number;
"created_time"?: number 'created_time'?: number;
"type_"?: number 'type_'?: number;
} }
export interface NoteResourceEntity { export interface NoteResourceEntity {
"id"?: number | null 'id'?: number | null;
"note_id"?: string 'note_id'?: string;
"resource_id"?: string 'resource_id'?: string;
"is_associated"?: number 'is_associated'?: number;
"last_seen_time"?: number 'last_seen_time'?: number;
"type_"?: number 'type_'?: number;
} }
export interface NoteTagEntity { export interface NoteTagEntity {
"id"?: string | null 'id'?: string | null;
"note_id"?: string 'note_id'?: string;
"tag_id"?: string 'tag_id'?: string;
"created_time"?: number 'created_time'?: number;
"updated_time"?: number 'updated_time'?: number;
"user_created_time"?: number 'user_created_time'?: number;
"user_updated_time"?: number 'user_updated_time'?: number;
"encryption_cipher_text"?: string 'encryption_cipher_text'?: string;
"encryption_applied"?: number 'encryption_applied'?: number;
"is_shared"?: number 'is_shared'?: number;
"type_"?: number 'type_'?: number;
} }
export interface NoteEntity { export interface NoteEntity {
"id"?: string | null 'id'?: string | null;
"parent_id"?: string 'parent_id'?: string;
"title"?: string 'title'?: string;
"body"?: string 'body'?: string;
"created_time"?: number 'created_time'?: number;
"updated_time"?: number 'updated_time'?: number;
"is_conflict"?: number 'is_conflict'?: number;
"latitude"?: number 'latitude'?: number;
"longitude"?: number 'longitude'?: number;
"altitude"?: number 'altitude'?: number;
"author"?: string 'author'?: string;
"source_url"?: string 'source_url'?: string;
"is_todo"?: number 'is_todo'?: number;
"todo_due"?: number 'todo_due'?: number;
"todo_completed"?: number 'todo_completed'?: number;
"source"?: string 'source'?: string;
"source_application"?: string 'source_application'?: string;
"application_data"?: string 'application_data'?: string;
"order"?: number 'order'?: number;
"user_created_time"?: number 'user_created_time'?: number;
"user_updated_time"?: number 'user_updated_time'?: number;
"encryption_cipher_text"?: string 'encryption_cipher_text'?: string;
"encryption_applied"?: number 'encryption_applied'?: number;
"markup_language"?: number 'markup_language'?: number;
"is_shared"?: number 'is_shared'?: number;
"share_id"?: string 'share_id'?: string;
"conflict_original_id"?: string 'conflict_original_id'?: string;
"master_key_id"?: string 'master_key_id'?: string;
"type_"?: number 'type_'?: number;
} }
export interface NotesNormalizedEntity { export interface NotesNormalizedEntity {
"id"?: string 'id'?: string;
"title"?: string 'title'?: string;
"body"?: string 'body'?: string;
"user_created_time"?: number 'user_created_time'?: number;
"user_updated_time"?: number 'user_updated_time'?: number;
"is_todo"?: number 'is_todo'?: number;
"todo_completed"?: number 'todo_completed'?: number;
"parent_id"?: string 'parent_id'?: string;
"latitude"?: number 'latitude'?: number;
"longitude"?: number 'longitude'?: number;
"altitude"?: number 'altitude'?: number;
"source_url"?: string 'source_url'?: string;
"todo_due"?: number 'todo_due'?: number;
"type_"?: number 'type_'?: number;
} }
export interface ResourceLocalStateEntity { export interface ResourceLocalStateEntity {
"id"?: number | null 'id'?: number | null;
"resource_id"?: string 'resource_id'?: string;
"fetch_status"?: number 'fetch_status'?: number;
"fetch_error"?: string 'fetch_error'?: string;
"type_"?: number 'type_'?: number;
} }
export interface ResourceEntity { export interface ResourceEntity {
"id"?: string | null 'id'?: string | null;
"title"?: string 'title'?: string;
"mime"?: string 'mime'?: string;
"filename"?: string 'filename'?: string;
"created_time"?: number 'created_time'?: number;
"updated_time"?: number 'updated_time'?: number;
"user_created_time"?: number 'user_created_time'?: number;
"user_updated_time"?: number 'user_updated_time'?: number;
"file_extension"?: string 'file_extension'?: string;
"encryption_cipher_text"?: string 'encryption_cipher_text'?: string;
"encryption_applied"?: number 'encryption_applied'?: number;
"encryption_blob_encrypted"?: number 'encryption_blob_encrypted'?: number;
"size"?: number 'size'?: number;
"is_shared"?: number 'is_shared'?: number;
"share_id"?: string 'share_id'?: string;
"master_key_id"?: string 'master_key_id'?: string;
"type_"?: number 'type_'?: number;
} }
export interface ResourcesToDownloadEntity { export interface ResourcesToDownloadEntity {
"id"?: number | null 'id'?: number | null;
"resource_id"?: string 'resource_id'?: string;
"updated_time"?: number 'updated_time'?: number;
"created_time"?: number 'created_time'?: number;
"type_"?: number 'type_'?: number;
} }
export interface RevisionEntity { export interface RevisionEntity {
"id"?: string | null 'id'?: string | null;
"parent_id"?: string 'parent_id'?: string;
"item_type"?: number 'item_type'?: number;
"item_id"?: string 'item_id'?: string;
"item_updated_time"?: number 'item_updated_time'?: number;
"title_diff"?: string 'title_diff'?: string;
"body_diff"?: string 'body_diff'?: string;
"metadata_diff"?: string 'metadata_diff'?: string;
"encryption_cipher_text"?: string 'encryption_cipher_text'?: string;
"encryption_applied"?: number 'encryption_applied'?: number;
"updated_time"?: number 'updated_time'?: number;
"created_time"?: number 'created_time'?: number;
"type_"?: number 'type_'?: number;
} }
export interface SettingEntity { export interface SettingEntity {
"key"?: string | null 'key'?: string | null;
"value"?: string | null 'value'?: string | null;
"type_"?: number 'type_'?: number;
} }
export interface SyncItemEntity { export interface SyncItemEntity {
"id"?: number | null 'id'?: number | null;
"sync_target"?: number 'sync_target'?: number;
"sync_time"?: number 'sync_time'?: number;
"item_type"?: number 'item_type'?: number;
"item_id"?: string 'item_id'?: string;
"sync_disabled"?: number 'sync_disabled'?: number;
"sync_disabled_reason"?: string 'sync_disabled_reason'?: string;
"force_sync"?: number 'force_sync'?: number;
"item_location"?: number 'item_location'?: number;
"type_"?: number 'type_'?: number;
} }
export interface TableFieldEntity { export interface TableFieldEntity {
"id"?: number | null 'id'?: number | null;
"table_name"?: string 'table_name'?: string;
"field_name"?: string 'field_name'?: string;
"field_type"?: number 'field_type'?: number;
"field_default"?: string | null 'field_default'?: string | null;
"type_"?: number 'type_'?: number;
} }
export interface TagEntity { export interface TagEntity {
"id"?: string | null 'id'?: string | null;
"title"?: string 'title'?: string;
"created_time"?: number 'created_time'?: number;
"updated_time"?: number 'updated_time'?: number;
"user_created_time"?: number 'user_created_time'?: number;
"user_updated_time"?: number 'user_updated_time'?: number;
"encryption_cipher_text"?: string 'encryption_cipher_text'?: string;
"encryption_applied"?: number 'encryption_applied'?: number;
"is_shared"?: number 'is_shared'?: number;
"parent_id"?: string 'parent_id'?: string;
"type_"?: number 'type_'?: number;
} }
export interface TagsWithNoteCountEntity { export interface TagsWithNoteCountEntity {
"id"?: string | null 'id'?: string | null;
"title"?: string | null 'title'?: string | null;
"created_time"?: number | null 'created_time'?: number | null;
"updated_time"?: number | null 'updated_time'?: number | null;
"note_count"?: any | null 'note_count'?: any | null;
"todo_completed_count"?: any | null 'todo_completed_count'?: any | null;
"type_"?: number 'type_'?: number;
} }
export interface VersionEntity { export interface VersionEntity {
"version"?: number 'version'?: number;
"table_fields_version"?: number 'table_fields_version'?: number;
"type_"?: number 'type_'?: number;
} }

View File

@@ -6,12 +6,14 @@ const fs = require('fs-extra');
async function main() { async function main() {
// Run the CLI app once so as to generate the database file // Run the CLI app once so as to generate the database file
process.chdir(`${rootDir}/packages/app-cli`); process.chdir(`${rootDir}/packages/app-cli`);
const sqliteFilePath = `${require('os').homedir()}/.config/joplindev/database.sqlite`;
await fs.remove(sqliteFilePath);
await execCommand2('yarn start version'); await execCommand2('yarn start version');
const sqlTsConfig = { const sqlTsConfig = {
'client': 'sqlite3', 'client': 'sqlite3',
'connection': { 'connection': {
'filename': `${require('os').homedir()}/.config/joplindev/database.sqlite`, 'filename': sqliteFilePath,
}, },
'tableNameCasing': 'pascal', 'tableNameCasing': 'pascal',
'singularTableNames': true, 'singularTableNames': true,