"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.safeModeFlagFilename = void 0;
const Setting_1 = require("./models/Setting");
const Logger_1 = require("@joplin/utils/Logger");
const shim_1 = require("./shim");
const { setupProxySettings } = require('./shim-init-node');
const BaseService_1 = require("./services/BaseService");
const reducer_1 = require("./reducer");
const KeychainServiceDriver_node_1 = require("./services/keychain/KeychainServiceDriver.node");
const KeychainServiceDriver_electron_1 = require("./services/keychain/KeychainServiceDriver.electron");
const locale_1 = require("./locale");
const KvStore_1 = require("./services/KvStore");
const SyncTargetJoplinServer_1 = require("./SyncTargetJoplinServer");
const SyncTargetJoplinServerSAML_1 = require("./SyncTargetJoplinServerSAML");
const SyncTargetOneDrive_1 = require("./SyncTargetOneDrive");
const redux_1 = require("redux");
const reducer_2 = require("./reducer");
const JoplinDatabase_1 = require("./JoplinDatabase");
const folders_screen_utils_1 = require("./folders-screen-utils");
const { DatabaseDriverNode } = require('./database-driver-node.js');
const BaseModel_1 = require("./BaseModel");
const Folder_1 = require("./models/Folder");
const BaseItem_1 = require("./models/BaseItem");
const Note_1 = require("./models/Note");
const Tag_1 = require("./models/Tag");
const utils_1 = require("@joplin/utils");
const time_1 = require("@joplin/utils/time");
const registry_1 = require("./registry");
const time_2 = require("./time");
const BaseSyncTarget_1 = require("./BaseSyncTarget");
const reduxSharedMiddleware_1 = require("./components/shared/reduxSharedMiddleware");
const dns = require("dns");
const fs = require("fs-extra");
const EventEmitter = require('events');
const syswidecas = require('./vendor/syswide-cas');
const SyncTargetRegistry_1 = require("./SyncTargetRegistry");
const SyncTargetFilesystem_1 = require("./SyncTargetFilesystem");
const SyncTargetNextcloud = require('./SyncTargetNextcloud.js');
const SyncTargetWebDAV = require('./SyncTargetWebDAV.js');
const SyncTargetDropbox = require('./SyncTargetDropbox.js');
const SyncTargetAmazonS3 = require('./SyncTargetAmazonS3.js');
const EncryptionService_1 = require("./services/e2ee/EncryptionService");
const ResourceFetcher_1 = require("./services/ResourceFetcher");
const SearchEngineUtils_1 = require("./services/search/SearchEngineUtils");
const SearchEngine_1 = require("./services/search/SearchEngine");
const RevisionService_1 = require("./services/RevisionService");
const ResourceService_1 = require("./services/ResourceService");
const DecryptionWorker_1 = require("./services/DecryptionWorker");
const SettingUtils_1 = require("./services/SettingUtils");
const MigrationService_1 = require("./services/MigrationService");
const ShareService_1 = require("./services/share/ShareService");
const handleSyncStartupOperation_1 = require("./services/synchronizer/utils/handleSyncStartupOperation");
const SyncTargetJoplinCloud_1 = require("./SyncTargetJoplinCloud");
const immer_1 = require("immer");
const syncInfoUtils_1 = require("./services/synchronizer/syncInfoUtils");
const utils_2 = require("./services/e2ee/utils");
const SyncTargetNone_1 = require("./SyncTargetNone");
const ppk_1 = require("./services/e2ee/ppk/ppk");
const RSA_node_1 = require("./services/e2ee/ppk/RSA.node");
const Resource_1 = require("./models/Resource");
const initProfile_1 = require("./services/profileConfig/initProfile");
const reducer_3 = require("./services/share/reducer");
const RotatingLogs_1 = require("./RotatingLogs");
const path_1 = require("path");
const processStartFlags_1 = require("./utils/processStartFlags");
const permanentlyDeleteOldItems_1 = require("./services/trash/permanentlyDeleteOldItems");
const determineBaseAppDirs_1 = require("./determineBaseAppDirs");
const NavService_1 = require("./services/NavService");
const getAppName_1 = require("./getAppName");
const PerformanceLogger_1 = require("./PerformanceLogger");
const appLogger = Logger_1.default.create('App');
const perfLogger = PerformanceLogger_1.default.create();
exports.safeModeFlagFilename = 'force-safe-mode-on-next-start';
class BaseApplication {
    constructor() {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        this.scheduleAutoAddResourcesIID_ = null;
        this.database_ = null;
        this.profileConfig_ = null;
        this.showStackTraces_ = false;
        this.showPromptString_ = false;
        // Note: this is basically a cache of state.selectedFolderId. It should *only*
        // be derived from the state and not set directly since that would make the
        // state and UI out of sync.
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        this.currentFolder_ = null;
        this.store_ = null;
        this.eventEmitter_ = new EventEmitter();
        this.decryptionWorker_resourceMetadataButNotBlobDecrypted = this.decryptionWorker_resourceMetadataButNotBlobDecrypted.bind(this);
    }
    async destroy() {
        if (this.scheduleAutoAddResourcesIID_) {
            shim_1.default.clearTimeout(this.scheduleAutoAddResourcesIID_);
            this.scheduleAutoAddResourcesIID_ = null;
        }
        await ResourceFetcher_1.default.instance().destroy();
        await SearchEngine_1.default.instance().destroy();
        await DecryptionWorker_1.default.instance().destroy();
        await (0, folders_screen_utils_1.cancelTimers)();
        await BaseItem_1.default.revisionService_.cancelTimers();
        await ResourceService_1.default.instance().cancelTimers();
        await registry_1.reg.cancelTimers();
        this.eventEmitter_.removeAllListeners();
        KvStore_1.default.destroyInstance();
        BaseModel_1.default.setDb(null);
        registry_1.reg.setDb(null);
        BaseItem_1.default.revisionService_ = null;
        RevisionService_1.default.instance_ = null;
        ResourceService_1.default.instance_ = null;
        ResourceService_1.default.isRunningInBackground_ = false;
        // ResourceService.isRunningInBackground_ = false;
        ResourceFetcher_1.default.instance_ = null;
        EncryptionService_1.default.instance_ = null;
        DecryptionWorker_1.default.instance_ = null;
        appLogger.info('Base application terminated...');
        this.eventEmitter_ = null;
        this.decryptionWorker_resourceMetadataButNotBlobDecrypted = null;
    }
    logger() {
        return appLogger;
    }
    store() {
        return this.store_;
    }
    currentFolder() {
        return this.currentFolder_;
    }
    async refreshCurrentFolder() {
        let newFolder = null;
        if (this.currentFolder_)
            newFolder = await Folder_1.default.load(this.currentFolder_.id);
        if (!newFolder)
            newFolder = await Folder_1.default.defaultFolder();
        this.switchCurrentFolder(newFolder);
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    switchCurrentFolder(folder) {
        if (!this.hasGui()) {
            this.currentFolder_ = Object.assign({}, folder);
            Setting_1.default.setValue('activeFolderId', folder ? folder.id : '');
        }
        else {
            this.dispatch({
                type: 'FOLDER_SELECT',
                id: folder ? folder.id : '',
            });
        }
    }
    // Handles the initial flags passed to main script and
    // returns the remaining args.
    async handleStartFlags_(argv, setDefaults = true) {
        const flags = await (0, processStartFlags_1.default)(argv, setDefaults);
        if (flags.matched.showStackTraces) {
            this.showStackTraces_ = true;
        }
        // Work around issues with ipv6 resolution -- default to ipv4first.
        // (possibly incorrect URL serialization see https://github.com/mswjs/msw/issues/1388#issuecomment-1241180921).
        // See also https://github.com/node-fetch/node-fetch/issues/1624#issuecomment-1407717012
        if (flags.matched.allowOverridingDnsResultOrder) {
            dns.setDefaultResultOrder('ipv4first');
        }
        return {
            matched: flags.matched,
            argv: flags.argv,
        };
    }
    // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
    on(eventName, callback) {
        return this.eventEmitter_.on(eventName, callback);
    }
    async exit(code = 0) {
        await Setting_1.default.saveAll();
        process.exit(code);
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    async refreshNotes(state, useSelectedNoteId = false, noteHash = '') {
        let parentType = state.notesParentType;
        let parentId = null;
        if (parentType === 'Folder') {
            parentId = state.selectedFolderId;
            parentType = BaseModel_1.default.TYPE_FOLDER;
        }
        else if (parentType === 'Tag') {
            parentId = state.selectedTagId;
            parentType = BaseModel_1.default.TYPE_TAG;
        }
        else if (parentType === 'Search') {
            parentId = state.selectedSearchId;
            parentType = BaseModel_1.default.TYPE_SEARCH;
        }
        else if (parentType === 'SmartFilter') {
            parentId = state.selectedSmartFilterId;
            parentType = BaseModel_1.default.TYPE_SMART_FILTER;
        }
        appLogger.debug('Refreshing notes:', parentType, parentId);
        const options = {
            order: reducer_2.stateUtils.notesOrder(state.settings),
            uncompletedTodosOnTop: Setting_1.default.value('uncompletedTodosOnTop'),
            showCompletedTodos: Setting_1.default.value('showCompletedTodos'),
            caseInsensitive: true,
        };
        const source = JSON.stringify({
            options: options,
            parentId: parentId,
        });
        let notes = [];
        let highlightedWords = [];
        let searchResults = [];
        if (parentId) {
            if (parentType === Folder_1.default.modelType()) {
                notes = await Note_1.default.previews(parentId, options);
            }
            else if (parentType === Tag_1.default.modelType()) {
                notes = await Tag_1.default.notes(parentId, options);
            }
            else if (parentType === BaseModel_1.default.TYPE_SEARCH) {
                const search = BaseModel_1.default.byId(state.searches, parentId);
                const response = await SearchEngineUtils_1.default.notesForQuery(search.query_pattern, true, { appendWildCards: true });
                notes = response.notes;
                searchResults = response.results;
                const parsedQuery = await SearchEngine_1.default.instance().parseQuery(search.query_pattern);
                highlightedWords = SearchEngine_1.default.instance().allParsedQueryTerms(parsedQuery);
            }
            else if (parentType === BaseModel_1.default.TYPE_SMART_FILTER) {
                notes = await Note_1.default.previews(parentId, options);
            }
        }
        this.store().dispatch({
            type: 'SET_HIGHLIGHTED',
            words: highlightedWords,
        });
        this.store().dispatch({
            type: 'SEARCH_RESULTS_SET',
            value: searchResults,
        });
        this.store().dispatch({
            type: 'NOTE_UPDATE_ALL',
            notes: notes,
            notesSource: source,
        });
        if (useSelectedNoteId) {
            this.store().dispatch({
                type: 'NOTE_SELECT',
                id: state.selectedNoteIds && state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
                hash: noteHash,
            });
        }
        else {
            const lastSelectedNoteIds = reducer_2.stateUtils.lastSelectedNoteIds(state);
            const foundIds = [];
            for (let i = 0; i < lastSelectedNoteIds.length; i++) {
                const noteId = lastSelectedNoteIds[i];
                let found = false;
                for (let j = 0; j < notes.length; j++) {
                    if (notes[j].id === noteId) {
                        found = true;
                        break;
                    }
                }
                if (found)
                    foundIds.push(noteId);
            }
            let selectedNoteId = null;
            if (foundIds.length) {
                selectedNoteId = foundIds[0];
            }
            else {
                selectedNoteId = notes.length ? notes[0].id : null;
            }
            this.store().dispatch({
                type: 'NOTE_SELECT',
                id: selectedNoteId,
            });
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    resourceFetcher_downloadComplete(event) {
        if (event.encrypted) {
            void DecryptionWorker_1.default.instance().scheduleStart();
        }
    }
    async decryptionWorker_resourceMetadataButNotBlobDecrypted() {
        ResourceFetcher_1.default.instance().scheduleAutoAddResources();
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    reducerActionToString(action) {
        const o = [action.type];
        if ('id' in action)
            o.push(action.id);
        if ('noteId' in action)
            o.push(action.noteId);
        if ('folderId' in action)
            o.push(action.folderId);
        if ('tagId' in action)
            o.push(action.tagId);
        if ('tag' in action)
            o.push(action.tag.id);
        if ('folder' in action)
            o.push(action.folder.id);
        if ('notesSource' in action)
            o.push(JSON.stringify(action.notesSource));
        return o.join(', ');
    }
    hasGui() {
        return false;
    }
    uiType() {
        return this.hasGui() ? 'gui' : 'cli';
    }
    generalMiddlewareFn() {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        const middleware = (store) => (next) => (action) => {
            return this.generalMiddleware(store, next, action);
        };
        return middleware;
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    async applySettingsSideEffects(action = null) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        const sideEffects = {
            'dateFormat': async () => {
                time_2.default.setLocale(Setting_1.default.value('locale'));
                (0, time_1.setTimeLocale)(Setting_1.default.value('locale'));
                (0, time_1.setDateFormat)(Setting_1.default.value('dateFormat'));
                (0, time_1.setTimeFormat)(Setting_1.default.value('timeFormat'));
                time_2.default.setDateFormat(Setting_1.default.value('dateFormat'));
                time_2.default.setTimeFormat(Setting_1.default.value('timeFormat'));
            },
            'net.ignoreTlsErrors': async () => {
                process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = Setting_1.default.value('net.ignoreTlsErrors') ? '0' : '1';
            },
            'net.customCertificates': async () => {
                const caPaths = Setting_1.default.value('net.customCertificates').split(',');
                for (let i = 0; i < caPaths.length; i++) {
                    const f = caPaths[i].trim();
                    if (!f)
                        continue;
                    syswidecas.addCAs(f);
                }
            },
            'net.proxyEnabled': async () => {
                setupProxySettings({
                    maxConcurrentConnections: Setting_1.default.value('sync.maxConcurrentConnections'),
                    proxyTimeout: Setting_1.default.value('net.proxyTimeout'),
                    proxyEnabled: Setting_1.default.value('net.proxyEnabled'),
                    proxyUrl: Setting_1.default.value('net.proxyUrl'),
                });
            },
            // Note: this used to run when "encryption.enabled" was changed, but
            // now we run it anytime any property of the sync target info is
            // changed. This is not optimal but:
            // - The sync target info rarely changes.
            // - All the calls below are cheap or do nothing if there's nothing
            //   to do.
            'syncInfoCache': async () => {
                appLogger.info('"syncInfoCache" was changed - setting up encryption related code');
                await (0, utils_2.loadMasterKeysFromSettings)(EncryptionService_1.default.instance());
                const loadedMasterKeyIds = EncryptionService_1.default.instance().loadedMasterKeyIds();
                this.dispatch({
                    type: 'MASTERKEY_REMOVE_NOT_LOADED',
                    ids: loadedMasterKeyIds,
                });
                if (this.hasGui()) {
                    void DecryptionWorker_1.default.instance().scheduleStart();
                    // Schedule a sync operation so that items that need to be encrypted
                    // are sent to sync target.
                    void registry_1.reg.scheduleSync();
                }
            },
            'sync.interval': async () => {
                if (this.hasGui())
                    registry_1.reg.setupRecurrentSync();
            },
        };
        sideEffects['timeFormat'] = sideEffects['dateFormat'];
        sideEffects['locale'] = sideEffects['dateFormat'];
        sideEffects['encryption.passwordCache'] = sideEffects['syncInfoCache'];
        sideEffects['encryption.masterPassword'] = sideEffects['syncInfoCache'];
        sideEffects['sync.maxConcurrentConnections'] = sideEffects['net.proxyEnabled'];
        sideEffects['sync.proxyTimeout'] = sideEffects['net.proxyEnabled'];
        sideEffects['sync.proxyUrl'] = sideEffects['net.proxyEnabled'];
        if (action) {
            const effect = sideEffects[action.key];
            if (effect)
                await effect();
        }
        else {
            for (const key in sideEffects) {
                await sideEffects[key]();
            }
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    async generalMiddleware(store, next, action) {
        // appLogger.debug('Reducer action', this.reducerActionToString(action));
        const result = next(action);
        let refreshNotes = false;
        let doRefreshFolders = false;
        let refreshNotesUseSelectedNoteId = false;
        let refreshNotesHash = '';
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        await (0, reduxSharedMiddleware_1.default)(store, next, action, ((action) => { this.dispatch(action); }));
        const newState = store.getState();
        if (this.hasGui() && ['NOTE_UPDATE_ONE', 'NOTE_DELETE', 'FOLDER_UPDATE_ONE', 'FOLDER_DELETE'].indexOf(action.type) >= 0) {
            if (!(await registry_1.reg.syncTarget().syncStarted()))
                void registry_1.reg.scheduleSync(registry_1.reg.syncAsYouTypeInterval(), { syncSteps: ['update_remote', 'delete_remote'] });
            SearchEngine_1.default.instance().scheduleSyncTables();
        }
        // Don't add FOLDER_UPDATE_ALL as refreshFolders() is calling it too, which
        // would cause the sidebar to refresh all the time.
        if (this.hasGui() && ['FOLDER_UPDATE_ONE'].indexOf(action.type) >= 0) {
            doRefreshFolders = true;
        }
        // If a note gets deleted to the trash or gets restored we refresh the folders so that the
        // note count can be updated.
        if (this.hasGui() && ['NOTE_UPDATE_ONE'].includes(action.type)) {
            if (action.changedFields && action.changedFields.includes('deleted_time')) {
                doRefreshFolders = true;
            }
        }
        if (action.type === 'HISTORY_BACKWARD' || action.type === 'HISTORY_FORWARD') {
            refreshNotes = true;
            refreshNotesUseSelectedNoteId = true;
        }
        if (action.type === 'HISTORY_BACKWARD' || action.type === 'HISTORY_FORWARD' || action.type === 'FOLDER_SELECT' || action.type === 'FOLDER_SELECT_ADD' || action.type === 'FOLDER_SELECT_REMOVE' || action.type === 'FOLDER_DELETE' || action.type === 'FOLDER_AND_NOTE_SELECT' || (action.type === 'SEARCH_UPDATE' && newState.notesParentType === 'Folder')) {
            Setting_1.default.setValue('activeFolderId', newState.selectedFolderId);
            this.currentFolder_ = newState.selectedFolderId ? await Folder_1.default.load(newState.selectedFolderId) : null;
            refreshNotes = true;
            if (action.type === 'FOLDER_AND_NOTE_SELECT') {
                refreshNotesUseSelectedNoteId = true;
                refreshNotesHash = action.hash;
            }
        }
        if (['HISTORY_BACKWARD', 'HISTORY_FORWARD', 'FOLDER_SELECT', 'TAG_SELECT', 'SMART_FILTER_SELECT', 'FOLDER_DELETE', 'FOLDER_AND_NOTE_SELECT'].includes(action.type) || (action.type === 'SEARCH_UPDATE' && newState.notesParentType === 'Folder')) {
            Setting_1.default.setValue('notesParent', (0, reducer_1.serializeNotesParent)((0, reducer_1.getNotesParent)(newState)));
        }
        if (this.hasGui() && (action.type === 'NOTE_IS_INSERTING_NOTES' && !action.value)) {
            refreshNotes = true;
        }
        if (this.hasGui() && ((action.type === 'SETTING_UPDATE_ONE' && action.key === 'uncompletedTodosOnTop') || action.type === 'SETTING_UPDATE_ALL')) {
            refreshNotes = true;
        }
        if (this.hasGui() && ((action.type === 'SETTING_UPDATE_ONE' && action.key === 'showCompletedTodos') || action.type === 'SETTING_UPDATE_ALL')) {
            refreshNotes = true;
        }
        if (this.hasGui() && ((action.type === 'SETTING_UPDATE_ONE' && action.key.indexOf('notes.sortOrder') === 0) || action.type === 'SETTING_UPDATE_ALL')) {
            refreshNotes = true;
        }
        if (action.type === 'SETTING_UPDATE_ONE' && action.key === 'locale') {
            refreshNotes = true;
            doRefreshFolders = 'now';
        }
        if (action.type === 'SMART_FILTER_SELECT') {
            refreshNotes = true;
            refreshNotesUseSelectedNoteId = true;
        }
        // Switching windows can also change which note(s) and which note parent type is selected.
        // Refreshing notes after switching windows helps ensure that the selected note/tags/other state
        // is correct for the current window.
        if (action.type === 'WINDOW_FOCUS' && action.lastWindowId !== action.windowId) {
            refreshNotes = true;
            refreshNotesUseSelectedNoteId = true;
        }
        // Should refresh the notes when:
        // - A tag is selected, to show the notes for that tag
        // - When a tag is updated so that when searching by tags, the search results are updated
        // https://github.com/laurent22/joplin/issues/3754
        if (['TAG_SELECT', 'TAG_DELETE', 'TAG_UPDATE_ONE', 'NOTE_TAG_REMOVE'].includes(action.type)) {
            refreshNotes = true;
        }
        if (action.type === 'SEARCH_SELECT' || action.type === 'SEARCH_DELETE') {
            refreshNotes = true;
        }
        if (action.type === 'NOTE_TAG_REMOVE') {
            if (newState.notesParentType === 'Tag' && newState.selectedTagId === action.item.id) {
                if (newState.notes.length === newState.selectedNoteIds.length) {
                    await this.refreshCurrentFolder();
                    refreshNotesUseSelectedNoteId = true;
                }
                refreshNotes = true;
            }
        }
        if (refreshNotes) {
            await this.refreshNotes(newState, refreshNotesUseSelectedNoteId, refreshNotesHash);
        }
        if (action.type === 'NOTE_UPDATE_ONE') {
            if (!action.changedFields.length ||
                action.changedFields.includes('parent_id') ||
                action.changedFields.includes('encryption_applied') ||
                action.changedFields.includes('is_conflict')) {
                doRefreshFolders = true;
            }
        }
        if (action.type === 'NOTE_DELETE') {
            doRefreshFolders = true;
        }
        if (this.hasGui() && action.type === 'SETTING_UPDATE_ALL') {
            doRefreshFolders = 'now';
        }
        if (this.hasGui() && action.type === 'SETTING_UPDATE_ONE' && (action.key.indexOf('folders.sortOrder') === 0 ||
            action.key === 'showNoteCounts' ||
            action.key === 'showCompletedTodos')) {
            doRefreshFolders = 'now';
        }
        if (this.hasGui() && action.type === 'SYNC_GOT_ENCRYPTED_ITEM') {
            void DecryptionWorker_1.default.instance().scheduleStart();
        }
        if (this.hasGui() && action.type === 'SYNC_CREATED_OR_UPDATED_RESOURCE') {
            void ResourceFetcher_1.default.instance().autoAddResources();
        }
        if (action.type === 'SETTING_UPDATE_ONE') {
            await this.applySettingsSideEffects(action);
        }
        else if (action.type === 'SETTING_UPDATE_ALL') {
            await this.applySettingsSideEffects();
        }
        if (doRefreshFolders) {
            if (doRefreshFolders === 'now') {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
                await (0, folders_screen_utils_1.refreshFolders)((action) => this.dispatch(action), newState.selectedFolderId);
            }
            else {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
                await (0, folders_screen_utils_1.scheduleRefreshFolders)((action) => this.dispatch(action), newState.selectedFolderId);
            }
        }
        return result;
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    dispatch(action) {
        if (this.store())
            return this.store().dispatch(action);
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    reducer(state = reducer_2.defaultState, action) {
        return (0, reducer_1.default)(state, action);
    }
    initRedux() {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        this.store_ = (0, redux_1.createStore)(this.reducer, (0, redux_1.applyMiddleware)(this.generalMiddlewareFn()));
        (0, reducer_1.setStore)(this.store_);
        this.store_.dispatch({
            type: 'PROFILE_CONFIG_SET',
            value: this.profileConfig_,
        });
        BaseModel_1.default.dispatch = this.store().dispatch;
        BaseSyncTarget_1.default.dispatch = this.store().dispatch;
        NavService_1.default.dispatch = this.store().dispatch;
        DecryptionWorker_1.default.instance().dispatch = this.store().dispatch;
        ResourceFetcher_1.default.instance().dispatch = this.store().dispatch;
        ShareService_1.default.instance().initialize(this.store(), EncryptionService_1.default.instance());
    }
    deinitRedux() {
        this.store_ = null;
        BaseModel_1.default.dispatch = function () { };
        BaseSyncTarget_1.default.dispatch = function () { };
        DecryptionWorker_1.default.instance().dispatch = function () { };
        ResourceFetcher_1.default.instance().dispatch = function () { };
    }
    async readFlagsFromFile(flagPath) {
        if (!fs.existsSync(flagPath))
            return {};
        let flagContent = fs.readFileSync(flagPath, 'utf8');
        if (!flagContent)
            return {};
        flagContent = flagContent.trim();
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        let flags = (0, utils_1.splitCommandString)(flagContent);
        flags.splice(0, 0, 'cmd');
        flags.splice(0, 0, 'node');
        flags = await this.handleStartFlags_(flags, false);
        return flags.matched;
    }
    startRotatingLogMaintenance(profileDir) {
        this.rotatingLogs = new RotatingLogs_1.default(profileDir);
        const processLogs = async () => {
            try {
                await this.rotatingLogs.cleanActiveLogFile();
                await this.rotatingLogs.deleteNonActiveLogFiles();
            }
            catch (error) {
                appLogger.error(error);
            }
        };
        shim_1.default.setTimeout(() => { void processLogs(); }, 60000);
        shim_1.default.setInterval(() => { void processLogs(); }, 24 * 60 * 60 * 1000);
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    async start(argv, options = null) {
        var _a;
        options = Object.assign({ keychainEnabled: true, setupGlobalLogger: true }, options);
        const startTask = perfLogger.taskStart('BaseApplication/start');
        const startFlags = await this.handleStartFlags_(argv);
        argv = startFlags.argv;
        let initArgs = startFlags.matched;
        if (argv.length)
            this.showPromptString_ = false;
        let appName = options.appName;
        if (!appName) {
            appName = (0, getAppName_1.default)(Setting_1.default.value('appId').indexOf('-desktop') >= 0, initArgs.env === 'dev');
        }
        Setting_1.default.setConstant('appName', appName);
        // https://immerjs.github.io/immer/docs/freezing
        (0, immer_1.setAutoFreeze)(initArgs.env === 'dev');
        const altInstanceId = initArgs.altInstanceId || '';
        const { rootProfileDir, homeDir } = (0, determineBaseAppDirs_1.default)((_a = options.rootProfileDir) !== null && _a !== void 0 ? _a : initArgs.profileDir, appName, altInstanceId);
        const { profileDir, profileConfig, isSubProfile } = await (0, initProfile_1.default)(rootProfileDir);
        this.profileConfig_ = profileConfig;
        const resourceDirName = 'resources';
        const resourceDir = `${profileDir}/${resourceDirName}`;
        const tempDir = `${profileDir}/tmp`;
        const cacheDir = `${profileDir}/cache`;
        Setting_1.default.setConstant('env', initArgs.env);
        Setting_1.default.setConstant('resourceDirName', resourceDirName);
        Setting_1.default.setConstant('resourceDir', resourceDir);
        Setting_1.default.setConstant('tempDir', tempDir);
        Setting_1.default.setConstant('pluginDataDir', `${profileDir}/plugin-data`);
        Setting_1.default.setConstant('cacheDir', cacheDir);
        Setting_1.default.setConstant('pluginDir', `${rootProfileDir}/plugins`);
        Setting_1.default.setConstant('homeDir', homeDir);
        SyncTargetRegistry_1.default.addClass(SyncTargetNone_1.default);
        SyncTargetRegistry_1.default.addClass(SyncTargetFilesystem_1.default);
        SyncTargetRegistry_1.default.addClass(SyncTargetOneDrive_1.default);
        SyncTargetRegistry_1.default.addClass(SyncTargetNextcloud);
        SyncTargetRegistry_1.default.addClass(SyncTargetWebDAV);
        SyncTargetRegistry_1.default.addClass(SyncTargetDropbox);
        SyncTargetRegistry_1.default.addClass(SyncTargetAmazonS3);
        SyncTargetRegistry_1.default.addClass(SyncTargetJoplinServer_1.default);
        SyncTargetRegistry_1.default.addClass(SyncTargetJoplinServerSAML_1.default);
        SyncTargetRegistry_1.default.addClass(SyncTargetJoplinCloud_1.default);
        try {
            await shim_1.default.fsDriver().remove(tempDir);
        }
        catch (error) {
            // Can't do anything in this case, not even log, since the logger
            // is not yet ready. But normally it's not an issue if the temp
            // dir cannot be deleted.
        }
        await fs.mkdirp(profileDir, 0o755);
        await fs.mkdirp(resourceDir, 0o755);
        await fs.mkdirp(tempDir, 0o755);
        await fs.mkdirp(cacheDir, 0o755);
        // Clean up any remaining watched files (they start with "edit-")
        await shim_1.default.fsDriver().removeAllThatStartWith(profileDir, 'edit-');
        const extraFlags = await this.readFlagsFromFile(`${profileDir}/flags.txt`);
        initArgs = Object.assign(Object.assign({}, initArgs), extraFlags);
        const globalLogger = Logger_1.default.globalLogger;
        if (options.setupGlobalLogger) {
            globalLogger.addTarget(Logger_1.TargetType.File, { path: `${profileDir}/log.txt` });
            if (Setting_1.default.value('appType') === 'desktop') {
                globalLogger.addTarget(Logger_1.TargetType.Console);
            }
            globalLogger.setLevel(initArgs.logLevel);
        }
        PerformanceLogger_1.default.setLogger(globalLogger);
        registry_1.reg.setLogger(Logger_1.default.create(''));
        // reg.dispatch = () => {};
        BaseService_1.default.logger_ = globalLogger;
        appLogger.info(`Profile directory: ${profileDir}`);
        appLogger.info(`Root profile directory: ${rootProfileDir}`);
        this.database_ = new JoplinDatabase_1.default(new DatabaseDriverNode());
        this.database_.setLogExcludedQueryTypes(['SELECT']);
        this.database_.setLogger(globalLogger);
        await this.database_.open({ name: `${profileDir}/database.sqlite` });
        // if (Setting.value('env') === 'dev') await this.database_.clearForTesting();
        registry_1.reg.setDb(this.database_);
        BaseModel_1.default.setDb(this.database_);
        KvStore_1.default.instance().setDb(registry_1.reg.db());
        (0, ppk_1.setRSA)(RSA_node_1.default);
        await (0, SettingUtils_1.loadKeychainServiceAndSettings)(options.keychainEnabled ? [KeychainServiceDriver_electron_1.default, KeychainServiceDriver_node_1.default] : []);
        await (0, utils_2.migrateMasterPassword)();
        await (0, utils_2.migratePpk)();
        await (0, handleSyncStartupOperation_1.default)();
        appLogger.info(`Client ID: ${Setting_1.default.value('clientId')}`);
        BaseItem_1.default.syncShareCache = (0, reducer_3.parseShareCache)(Setting_1.default.value('sync.shareCache'));
        if (initArgs === null || initArgs === void 0 ? void 0 : initArgs.isSafeMode) {
            Setting_1.default.setValue('isSafeMode', true);
        }
        Setting_1.default.setValue('altInstanceId', altInstanceId);
        const safeModeFlagFile = (0, path_1.join)(profileDir, exports.safeModeFlagFilename);
        if (await fs.pathExists(safeModeFlagFile) && fs.readFileSync(safeModeFlagFile, 'utf8') === 'true') {
            appLogger.info(`Safe mode enabled because of file: ${safeModeFlagFile}`);
            Setting_1.default.setValue('isSafeMode', true);
            fs.removeSync(safeModeFlagFile);
        }
        if (Setting_1.default.value('firstStart')) {
            // detectAndSetLocale sets the locale to the system default locale.
            // Not calling it when a new profile is created ensures that the
            // the language set by the user is not overridden by the system
            // default language.
            if (!Setting_1.default.value('isSubProfile')) {
                const locale = shim_1.default.detectAndSetLocale(Setting_1.default);
                registry_1.reg.logger().info(`First start: detected locale as ${locale}`);
            }
            Setting_1.default.skipMigrations();
            if (Setting_1.default.value('env') === 'dev') {
                Setting_1.default.setValue('showTrayIcon', false);
                Setting_1.default.setValue('autoUpdateEnabled', false);
                Setting_1.default.setValue('sync.interval', 3600);
            }
            Setting_1.default.setValue('firstStart', false);
        }
        else {
            await Setting_1.default.applyMigrations();
        }
        (0, locale_1.setLocale)(Setting_1.default.value('locale'));
        if (Setting_1.default.value('env') === Setting_1.Env.Dev) {
            // Setting.setValue('sync.10.path', 'https://api.joplincloud.com');
            // Setting.setValue('sync.10.userContentPath', 'https://joplinusercontent.com');
            Setting_1.default.setValue('sync.10.path', 'http://api.joplincloud.local:22300');
            Setting_1.default.setValue('sync.10.userContentPath', 'http://joplinusercontent.local:22300');
            Setting_1.default.setValue('sync.10.website', 'http://joplincloud.local:22300');
        }
        // For now always disable fuzzy search due to performance issues:
        // https://discourse.joplinapp.org/t/1-1-4-keyboard-locks-up-while-typing/11231/11
        // https://discourse.joplinapp.org/t/serious-lagging-when-there-are-tens-of-thousands-of-notes/11215/23
        Setting_1.default.setValue('db.fuzzySearchEnabled', 0);
        if (Setting_1.default.value('encryption.shouldReencrypt') < 0) {
            // We suggest re-encryption if the user has at least one notebook
            // and if encryption is enabled. This code runs only when shouldReencrypt = -1
            // which can be set by a maintenance script for example.
            const folderCount = await Folder_1.default.count();
            const itShould = (0, syncInfoUtils_1.getEncryptionEnabled)() && !!folderCount ? Setting_1.default.SHOULD_REENCRYPT_YES : Setting_1.default.SHOULD_REENCRYPT_NO;
            Setting_1.default.setValue('encryption.shouldReencrypt', itShould);
        }
        if ('welcomeDisabled' in initArgs)
            Setting_1.default.setValue('welcome.enabled', !initArgs.welcomeDisabled);
        if (isSubProfile)
            Setting_1.default.setValue('welcome.enabled', false);
        if (!Setting_1.default.value('api.token')) {
            void EncryptionService_1.default.instance()
                .generateApiToken()
                // eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
                .then((token) => {
                Setting_1.default.setValue('api.token', token);
            });
        }
        time_2.default.setDateFormat(Setting_1.default.value('dateFormat'));
        time_2.default.setTimeFormat(Setting_1.default.value('timeFormat'));
        BaseItem_1.default.revisionService_ = RevisionService_1.default.instance();
        BaseItem_1.default.encryptionService_ = EncryptionService_1.default.instance();
        BaseItem_1.default.shareService_ = ShareService_1.default.instance();
        Resource_1.default.shareService_ = ShareService_1.default.instance();
        DecryptionWorker_1.default.instance().setLogger(globalLogger);
        DecryptionWorker_1.default.instance().setEncryptionService(EncryptionService_1.default.instance());
        DecryptionWorker_1.default.instance().setKvStore(KvStore_1.default.instance());
        await (0, utils_2.loadMasterKeysFromSettings)(EncryptionService_1.default.instance());
        DecryptionWorker_1.default.instance().on('resourceMetadataButNotBlobDecrypted', this.decryptionWorker_resourceMetadataButNotBlobDecrypted);
        ResourceFetcher_1.default.instance().setFileApi(() => {
            return registry_1.reg.syncTarget().fileApi();
        });
        ResourceFetcher_1.default.instance().setLogger(globalLogger);
        ResourceFetcher_1.default.instance().on('downloadComplete', this.resourceFetcher_downloadComplete);
        void ResourceFetcher_1.default.instance().start();
        SearchEngine_1.default.instance().setDb(registry_1.reg.db());
        SearchEngine_1.default.instance().setLogger(registry_1.reg.logger());
        SearchEngine_1.default.instance().scheduleSyncTables();
        const currentFolderId = Setting_1.default.value('activeFolderId');
        let currentFolder = null;
        if (currentFolderId)
            currentFolder = await Folder_1.default.load(currentFolderId);
        if (!currentFolder)
            currentFolder = await Folder_1.default.defaultFolder();
        Setting_1.default.setValue('activeFolderId', currentFolder ? currentFolder.id : '');
        if (currentFolder && !this.hasGui()) {
            this.currentFolder_ = currentFolder;
        }
        await (0, permanentlyDeleteOldItems_1.setupAutoDeletion)();
        await MigrationService_1.default.instance().run();
        startTask.onEnd();
        return argv;
    }
}
exports.default = BaseApplication;
//# sourceMappingURL=BaseApplication.js.map