2024-07-18 21:35:17 +03:00
import log from "./log.js" ;
2024-07-18 21:37:45 +03:00
import fs from "fs" ;
2024-07-18 21:35:17 +03:00
import resourceDir from "./resource_dir.js" ;
import sql from "./sql.js" ;
2025-01-02 13:47:44 +01:00
import { isElectron , deferred } from "./utils.js" ;
2024-07-18 21:35:17 +03:00
import optionService from "./options.js" ;
import port from "./port.js" ;
import BOption from "../becca/entities/boption.js" ;
import TaskContext from "./task_context.js" ;
import migrationService from "./migration.js" ;
import cls from "./cls.js" ;
import config from "./config.js" ;
2025-01-09 18:36:24 +02:00
import type { OptionRow } from "../becca/entities/rows.js" ;
2024-07-18 23:06:08 +03:00
import BNote from "../becca/entities/bnote.js" ;
import BBranch from "../becca/entities/bbranch.js" ;
2024-07-18 23:15:01 +03:00
import zipImportService from "./import/zip.js" ;
2024-07-18 23:45:17 +03:00
import becca_loader from "../becca/becca_loader.js" ;
import password from "./encryption/password.js" ;
import backup from "./backup.js" ;
2018-04-02 21:25:20 -04:00
2025-01-02 13:47:44 +01:00
const dbReady = deferred < void > ( ) ;
2020-04-04 21:49:57 +02:00
2020-06-20 12:31:38 +02:00
function schemaExists() {
2025-04-01 23:30:21 +03:00
return ! ! sql . getValue ( /*sql*/ ` SELECT name FROM sqlite_master
2024-12-22 15:42:15 +02:00
WHERE type = 'table' AND name = 'options' ` );
2018-07-22 19:56:20 +02:00
}
2018-07-21 08:55:24 +02:00
2020-06-20 12:31:38 +02:00
function isDbInitialized() {
if ( ! schemaExists ( ) ) {
2018-07-24 08:12:36 +02:00
return false ;
}
2020-06-20 12:31:38 +02:00
const initialized = sql . getValue ( "SELECT value FROM options WHERE name = 'initialized'" ) ;
2018-07-24 08:12:36 +02:00
2025-01-09 18:07:02 +02:00
return initialized === "true" ;
2018-07-24 08:12:36 +02:00
}
2020-07-02 22:57:17 +02:00
async function initDbConnection() {
2020-06-20 21:42:41 +02:00
if ( ! isDbInitialized ( ) ) {
2025-01-22 18:57:06 +01:00
log . info ( ` DB not initialized, please visit setup page ` + ( isElectron ? "" : ` - http://[your-server-host]: ${ port } to see instructions on how to initialize Trilium. ` ) ) ;
2018-04-02 21:25:20 -04:00
2020-06-20 21:42:41 +02:00
return ;
}
2018-04-03 22:15:28 -04:00
2020-07-02 22:57:17 +02:00
await migrationService . migrateIfNecessary ( ) ;
2018-04-03 22:15:28 -04:00
2020-12-10 21:27:21 +01:00
sql . execute ( 'CREATE TEMP TABLE "param_list" (`paramId` TEXT NOT NULL PRIMARY KEY)' ) ;
2024-09-14 10:04:39 -07:00
sql . execute ( `
CREATE TABLE IF NOT EXISTS "user_data"
(
tmpID INT ,
username TEXT ,
email TEXT ,
2025-03-29 01:00:08 +01:00
userIDEncryptedDataKey TEXT ,
2024-09-14 10:04:39 -07:00
userIDVerificationHash TEXT ,
salt TEXT ,
derivedKey TEXT ,
isSetup TEXT DEFAULT "false" ,
UNIQUE ( tmpID ) ,
PRIMARY KEY ( tmpID )
) ; ` )
2024-02-17 20:33:18 +02:00
dbReady . resolve ( ) ;
2018-07-22 19:56:20 +02:00
}
2018-04-02 21:25:20 -04:00
2025-03-31 23:20:14 +03:00
async function createInitialDatabase ( preserveIds? : boolean ) {
2020-06-20 12:31:38 +02:00
if ( isDbInitialized ( ) ) {
2018-07-24 08:12:36 +02:00
throw new Error ( "DB is already initialized" ) ;
}
2024-02-17 19:51:22 +02:00
const schema = fs . readFileSync ( ` ${ resourceDir . DB_INIT_DIR } /schema.sql ` , "utf-8" ) ;
2022-12-21 15:19:05 +01:00
const demoFile = fs . readFileSync ( ` ${ resourceDir . DB_INIT_DIR } /demo.zip ` ) ;
2018-04-02 22:33:54 -04:00
2024-07-18 23:15:01 +03:00
let rootNote ! : BNote ;
2020-07-02 21:08:18 +02:00
2024-09-14 14:34:24 +03:00
// We have to import async since options init requires keyboard actions which require translations.
const optionsInitService = ( await import ( "./options_init.js" ) ) . default ;
2020-06-20 12:31:38 +02:00
sql . transactional ( ( ) = > {
2021-12-28 22:59:38 +01:00
log . info ( "Creating database schema ..." ) ;
2020-06-20 12:31:38 +02:00
sql . executeScript ( schema ) ;
2018-11-16 14:36:50 +01:00
2024-07-18 23:45:17 +03:00
becca_loader . load ( ) ;
2021-05-02 22:47:57 +02:00
2021-12-28 22:59:38 +01:00
log . info ( "Creating root note ..." ) ;
2023-01-03 13:52:37 +01:00
rootNote = new BNote ( {
2025-01-09 18:07:02 +02:00
noteId : "root" ,
title : "root" ,
type : "text" ,
mime : "text/html"
2018-11-16 14:36:50 +01:00
} ) . save ( ) ;
2025-01-09 18:07:02 +02:00
rootNote . setContent ( "" ) ;
2019-02-20 23:07:57 +01:00
2023-01-03 13:52:37 +01:00
new BBranch ( {
2025-01-09 18:07:02 +02:00
noteId : "root" ,
parentNoteId : "none" ,
2018-11-16 14:36:50 +01:00
isExpanded : true ,
2019-10-19 12:36:16 +02:00
notePosition : 10
2018-11-16 14:36:50 +01:00
} ) . save ( ) ;
2021-05-30 22:03:41 +02:00
optionsInitService . initDocumentOptions ( ) ;
2024-11-22 20:15:35 +02:00
optionsInitService . initNotSyncedOptions ( true , { } ) ;
2021-05-30 22:03:41 +02:00
optionsInitService . initStartupOptions ( ) ;
2024-07-18 23:45:17 +03:00
password . resetPassword ( ) ;
2020-07-02 21:08:18 +02:00
} ) ;
2021-05-30 21:21:20 +02:00
log . info ( "Importing demo content ..." ) ;
2025-01-09 18:07:02 +02:00
const dummyTaskContext = new TaskContext ( "no-progress-reporting" , "import" , false ) ;
2018-11-16 14:36:50 +01:00
2025-03-31 23:20:14 +03:00
await zipImportService . importZip ( dummyTaskContext , demoFile , rootNote , {
preserveIds
} ) ;
2019-02-24 12:24:28 +01:00
2020-07-02 21:08:18 +02:00
sql . transactional ( ( ) = > {
2023-05-07 15:23:46 +02:00
// this needs to happen after ZIP import,
// the previous solution was to move option initialization here, but then the important parts of initialization
2021-05-30 22:03:41 +02:00
// are not all in one transaction (because ZIP import is async and thus not transactional)
2018-04-02 22:33:54 -04:00
2021-05-30 22:03:41 +02:00
const startNoteId = sql . getValue ( "SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 ORDER BY notePosition" ) ;
2018-07-23 21:15:32 +02:00
2025-01-09 18:07:02 +02:00
optionService . setOption (
"openNoteContexts" ,
JSON . stringify ( [
{
notePath : startNoteId ,
active : true
}
] )
) ;
2018-04-02 22:33:54 -04:00
} ) ;
2018-07-23 21:15:32 +02:00
log . info ( "Schema and initial content generated." ) ;
2018-04-02 22:33:54 -04:00
2020-06-20 12:31:38 +02:00
initDbConnection ( ) ;
2018-04-02 21:25:20 -04:00
}
2025-01-09 18:07:02 +02:00
async function createDatabaseForSync ( options : OptionRow [ ] , syncServerHost = "" , syncProxy = "" ) {
2018-07-24 20:35:03 +02:00
log . info ( "Creating database for sync" ) ;
2020-06-20 12:31:38 +02:00
if ( isDbInitialized ( ) ) {
2018-07-24 20:35:03 +02:00
throw new Error ( "DB is already initialized" ) ;
}
2018-07-23 21:15:32 +02:00
2024-02-17 19:51:22 +02:00
const schema = fs . readFileSync ( ` ${ resourceDir . DB_INIT_DIR } /schema.sql ` , "utf8" ) ;
2018-07-23 21:15:32 +02:00
2024-09-14 14:34:24 +03:00
// We have to import async since options init requires keyboard actions which require translations.
const optionsInitService = ( await import ( "./options_init.js" ) ) . default ;
2024-12-22 15:42:15 +02:00
2020-06-20 12:31:38 +02:00
sql . transactional ( ( ) = > {
sql . executeScript ( schema ) ;
2018-07-23 21:15:32 +02:00
2024-11-22 20:15:35 +02:00
optionsInitService . initNotSyncedOptions ( false , { syncServerHost , syncProxy } ) ;
2018-07-24 20:35:03 +02:00
// document options required for sync to kick off
for ( const opt of options ) {
2023-01-03 13:52:37 +01:00
new BOption ( opt ) . save ( ) ;
2018-07-24 20:35:03 +02:00
}
2018-07-23 21:15:32 +02:00
} ) ;
log . info ( "Schema and not synced options generated." ) ;
}
2020-06-20 21:42:41 +02:00
function setDbAsInitialized() {
2020-06-20 12:31:38 +02:00
if ( ! isDbInitialized ( ) ) {
2025-01-09 18:07:02 +02:00
optionService . setOption ( "initialized" , "true" ) ;
2018-07-24 20:35:03 +02:00
2020-06-20 12:31:38 +02:00
initDbConnection ( ) ;
2019-11-30 11:36:36 +01:00
}
2018-07-24 20:35:03 +02:00
}
2022-02-01 21:36:52 +01:00
function optimize() {
log . info ( "Optimizing database" ) ;
2022-05-15 20:34:47 +02:00
const start = Date . now ( ) ;
2022-02-01 21:36:52 +01:00
sql . execute ( "PRAGMA optimize" ) ;
2022-05-15 20:34:47 +02:00
log . info ( ` Optimization finished in ${ Date . now ( ) - start } ms. ` ) ;
2022-02-01 21:36:52 +01:00
}
2022-05-31 14:09:46 +02:00
function getDbSize() {
2024-04-03 20:47:41 +03:00
return sql . getValue < number > ( "SELECT page_count * page_size / 1000 as size FROM pragma_page_count(), pragma_page_size()" ) ;
2022-05-31 14:09:46 +02:00
}
2024-07-19 00:47:09 +03:00
function initializeDb() {
cls . init ( initDbConnection ) ;
log . info ( ` DB size: ${ getDbSize ( ) } KB ` ) ;
2024-12-22 15:42:15 +02:00
2024-07-19 00:47:09 +03:00
dbReady . then ( ( ) = > {
if ( config . General && config . General . noBackup === true ) {
log . info ( "Disabling scheduled backups." ) ;
2024-12-22 15:42:15 +02:00
2024-07-19 00:47:09 +03:00
return ;
}
2024-12-22 15:42:15 +02:00
2024-07-19 00:47:09 +03:00
setInterval ( ( ) = > backup . regularBackup ( ) , 4 * 60 * 60 * 1000 ) ;
2024-12-22 15:42:15 +02:00
2024-07-19 00:47:09 +03:00
// kickoff first backup soon after start up
setTimeout ( ( ) = > backup . regularBackup ( ) , 5 * 60 * 1000 ) ;
2024-12-22 15:42:15 +02:00
2024-07-19 00:47:09 +03:00
// optimize is usually inexpensive no-op, so running it semi-frequently is not a big deal
setTimeout ( ( ) = > optimize ( ) , 60 * 60 * 1000 ) ;
2024-12-22 15:42:15 +02:00
2024-07-19 00:47:09 +03:00
setInterval ( ( ) = > optimize ( ) , 10 * 60 * 60 * 1000 ) ;
} ) ;
}
2019-01-15 20:00:24 +01:00
2024-07-18 21:47:30 +03:00
export default {
2018-04-02 21:25:20 -04:00
dbReady ,
2018-07-24 08:12:36 +02:00
schemaExists ,
2018-07-22 19:56:20 +02:00
isDbInitialized ,
2018-07-23 21:15:32 +02:00
createInitialDatabase ,
2018-07-24 20:35:03 +02:00
createDatabaseForSync ,
2022-05-31 14:09:46 +02:00
setDbAsInitialized ,
2024-07-19 00:47:09 +03:00
getDbSize ,
initializeDb
2020-06-17 23:03:46 +02:00
} ;