diff --git a/src/constants/initializer/setting.ts b/src/constants/initializer/setting.ts index 563f803c..298dcfd9 100644 --- a/src/constants/initializer/setting.ts +++ b/src/constants/initializer/setting.ts @@ -1,22 +1,7 @@ -import { Setting, Timeline, UnreadNotification, UseMarker } from '~/src/types/setting' - -const unreadNotification: UnreadNotification = { - direct: false, - local: true, - public: false -} - -const useMarker: UseMarker = { - home: false, - notifications: true -} - -const timeline: Timeline = { - unreadNotification: unreadNotification, - useMarker: useMarker -} +import { Setting } from '~/src/types/setting' export const DefaultSetting: Setting = { - accountID: '', - timeline: timeline + accountId: 0, + markerHome: false, + markerNotifications: true } diff --git a/src/main/account.ts b/src/main/account.ts index bba9fd34..1789cf6f 100644 --- a/src/main/account.ts +++ b/src/main/account.ts @@ -23,7 +23,7 @@ export const insertAccount = ( } let order = 1 if (row) { - order = row.order + order = row.sort + 1 } db.run( 'INSERT INTO accounts(username, account_id, avatar, client_id, client_secret, access_token, refresh_token, sort) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', @@ -161,3 +161,115 @@ FROM accounts INNER JOIN servers ON servers.account_id = accounts.id WHERE accou ) }) } + +export const removeAccount = (db: sqlite3.Database, id: number): Promise => { + return new Promise((resolve, reject) => { + db.run('DELETE FROM accounts WHERE id = ?', id, err => { + if (err) { + reject(err) + } + resolve(null) + }) + }) +} + +export const removeAllAccounts = (db: sqlite3.Database): Promise => { + return new Promise((resolve, reject) => { + db.run('DELETE FROM accounts', err => { + if (err) { + reject(err) + } + resolve(null) + }) + }) +} + +export const forwardAccount = (db: sqlite3.Database, account: LocalAccount): Promise => { + return new Promise((resolve, reject) => { + db.serialize(() => { + db.run('BEGIN TRANSACTION') + + db.all('SELECT * FROM accounts ORDER BY sort', (err, rows) => { + if (err) { + reject(err) + } + const index = rows.findIndex(r => r.id === account.id) + if (index < 0 || index >= rows.length) { + db.run('ROLLBACK TRANSACTION') + return resolve(account) + } + const target = rows[index + 1] + const base = rows[index] + + db.serialize(() => { + db.run('UPDATE accounts SET sort = ? WHERE id = ?', [-100, base.id], err => { + if (err) { + reject(err) + } + }) + db.run('UPDATE accounts SET sort = ? WHERE id = ?', [base.sort, target.id], err => { + if (err) { + reject(err) + } + }) + db.run('UPDATE accounts SET sort = ? WHERE id = ?', [target.sort, base.id], err => { + if (err) { + reject(err) + } + db.run('COMMIT') + resolve( + Object.assign(account, { + order: target.sort + }) + ) + }) + }) + }) + }) + }) +} + +export const backwardAccount = (db: sqlite3.Database, account: LocalAccount): Promise => { + return new Promise((resolve, reject) => { + db.serialize(() => { + db.run('BEGIN TRANSACTION') + + db.all('SELECT * FROM accounts ORDER BY sort', (err, rows) => { + if (err) { + reject(err) + } + const index = rows.findIndex(r => r.id === account.id) + if (index < 1) { + return resolve(account) + } + const target = rows[index - 1] + const base = rows[index] + + db.serialize(() => { + db.run('UPDATE accounts SET sort = ? WHERE id = ?', [-100, base.id], err => { + if (err) { + db.run('ROLLBACK TRANSACTION') + reject(err) + } + }) + db.run('UPDATE accounts SET sort = ? WHERE id = ?', [base.sort, target.id], err => { + if (err) { + reject(err) + } + }) + db.run('UPDATE accounts SET sort = ? WHERE id = ?', [target.sort, base.id], err => { + if (err) { + reject(err) + } + db.run('COMMIT') + resolve( + Object.assign(account, { + order: target.sort + }) + ) + }) + }) + }) + }) + }) +} diff --git a/src/main/cache/account.ts b/src/main/cache/account.ts deleted file mode 100644 index 9f031758..00000000 --- a/src/main/cache/account.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { isEmpty } from 'lodash' -import Datastore from 'nedb' -import fs from 'fs' -import { CachedAccount } from '~/src/types/cachedAccount' - -export default class AccountCache { - private db: Datastore - - constructor(path: string) { - this.db = new Datastore({ - filename: path, - autoload: true, - onload: (err: Error) => { - if (err) { - fs.unlink(path, err => { - if (err) { - console.error(err) - } - }) - } - } - }) - } - - listAccounts(ownerID: string): Promise> { - return new Promise((resolve, reject) => { - this.db.find({ owner_id: ownerID }, (err, docs) => { - if (err) return reject(err) - resolve(docs) - }) - }) - } - - insertAccount(ownerID: string, acct: string): Promise { - return new Promise((resolve, reject) => { - // At first confirm records for unique. - this.db.findOne({ owner_id: ownerID, acct: acct }, (err, doc) => { - if (err) return err - // Ignore error for unique constraints. - if (!isEmpty(doc)) return err - return this.db.insert({ owner_id: ownerID, acct: acct }, (err, doc) => { - if (err) return reject(err) - return resolve(doc) - }) - }) - }) - } -} diff --git a/src/main/cache/hashtag.ts b/src/main/cache/hashtag.ts deleted file mode 100644 index 821a2d60..00000000 --- a/src/main/cache/hashtag.ts +++ /dev/null @@ -1,45 +0,0 @@ -import Datastore from 'nedb' -import fs from 'fs' -import { LocalTag } from '~/src/types/localTag' - -export default class HashtagCache { - private db: Datastore - - constructor(path: string) { - this.db = new Datastore({ - filename: path, - autoload: true, - onload: (err: Error) => { - if (err) { - fs.unlink(path, err => { - if (err) { - console.error(err) - } - }) - } - } - }) - this.db.ensureIndex({ fieldName: 'tagName', unique: true, sparse: true }, err => { - if (err) console.error(err) - }) - } - - listTags(): Promise> { - return new Promise((resolve, reject) => { - this.db.find({}, (err, docs) => { - if (err) return reject(err) - resolve(docs) - }) - }) - } - - insertHashtag(tag: string): Promise { - return new Promise((resolve, reject) => { - // Ignore error for unique constraints. - this.db.insert({ tagName: tag }, (err, doc) => { - if (err) return reject(err) - resolve(doc) - }) - }) - } -} diff --git a/src/main/database.ts b/src/main/database.ts index ad8976b9..3d9ca44e 100644 --- a/src/main/database.ts +++ b/src/main/database.ts @@ -48,6 +48,19 @@ FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE)', } } ) + db.run( + 'CREATE TABLE IF NOT EXISTS settings(\ +id INTEGER PRIMARY KEY, \ +account_id INTEGER UNIQUE NOT NULL, \ +marker_home BOOLEAN NOT NULL DEFAULT false, \ +marker_notifications BOOLEAN NOT NULL DEFAULT true, \ +FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE)', + err => { + if (err) { + console.error('failed to create settings: ', err) + } + } + ) }) return db diff --git a/src/main/index.ts b/src/main/index.ts index 8c9a4fe7..326a2b46 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -30,7 +30,7 @@ import sanitizeHtml from 'sanitize-html' import AutoLaunch from 'auto-launch' import minimist from 'minimist' -import { getAccount, insertAccount, listAccounts } from './account' +import { backwardAccount, forwardAccount, getAccount, insertAccount, listAccounts, removeAccount, removeAllAccounts } from './account' // import { StreamingURL, UserStreaming, DirectStreaming, LocalStreaming, PublicStreaming, ListStreaming, TagStreaming } from './websocket' import Preferences from './preferences' import Fonts from './fonts' @@ -46,11 +46,11 @@ import ProxyConfiguration from './proxy' import { Menu as MenuPreferences } from '~/src/types/preference' import { General as GeneralPreferences } from '~/src/types/preference' import newDB from './database' -import Settings from './settings' -import { BaseSettings, Setting } from '~/src/types/setting' +import { Setting } from '~/src/types/setting' import { insertServer } from './server' import { LocalServer } from '~src/types/localServer' import { insertTag, listTags, removeTag } from './hashtags' +import { createOrUpdateSetting, getSetting } from './settings' /** * Context menu @@ -113,7 +113,6 @@ const splashURL = ? path.resolve(__dirname, '../../static/splash-screen.html') : path.join(__dirname, '/static/splash-screen.html') -// https://github.com/louischatriot/nedb/issues/459 const userData = app.getPath('userData') const appPath = app.getPath('exe') @@ -122,8 +121,6 @@ const db = newDB(databasePath) const preferencesDBPath = process.env.NODE_ENV === 'production' ? userData + './db/preferences.json' : 'preferences.json' -const settingsDBPath = process.env.NODE_ENV === 'production' ? userData + './db/settings.json' : 'settings.json' - const soundBasePath = process.env.NODE_ENV === 'development' ? path.join(__dirname, '../../build/sounds/') : path.join(process.resourcesPath!, 'build/sounds/') const iconBasePath = @@ -145,7 +142,7 @@ if (process.platform !== 'darwin') { }) } -async function changeAccount([account, _server]: [LocalAccount, LocalServer], index: number) { +async function changeAccount(account: LocalAccount, index: number) { // Sometimes application is closed to tray. // In this time, mainWindow in not exist, so we have to create window. if (mainWindow === null) { @@ -229,7 +226,7 @@ async function createWindow() { return { label: s.domain, accelerator: `CmdOrCtrl+${index + 1}`, - click: () => changeAccount([a, s], index) + click: () => changeAccount(a, index) } }) @@ -497,66 +494,52 @@ ipcMain.handle('get-local-account', async (_: IpcMainInvokeEvent, id: number) => return account }) -// ipcMain.handle('update-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => { -// const proxy = await proxyConfiguration.forMastodon() -// const ac: LocalAccount = await accountRepo.refresh(acct, proxy) -// return ac -// }) +ipcMain.handle('remove-account', async (_: IpcMainInvokeEvent, id: number) => { + await removeAccount(db, id) -// ipcMain.handle('remove-account', async (_: IpcMainInvokeEvent, id: string) => { -// const accountId = await accountRepo.removeAccount(id) + const accounts = await listAccounts(db) + const accountsChange: Array = accounts.map(([account, server], index) => { + return { + label: server.domain, + accelerator: `CmdOrCtrl+${index + 1}`, + click: () => changeAccount(account, index) + } + }) -// const accounts = await listAccounts() -// const accountsChange: Array = accounts.map((a, index) => { -// return { -// label: a.domain, -// accelerator: `CmdOrCtrl+${index + 1}`, -// click: () => changeAccount(a, index) -// } -// }) + await updateApplicationMenu(accountsChange) + await updateDockMenu(accountsChange) + if (process.platform !== 'darwin' && tray !== null) { + tray.setContextMenu(TrayMenu(accountsChange, i18next)) + } -// await updateApplicationMenu(accountsChange) -// await updateDockMenu(accountsChange) -// if (process.platform !== 'darwin' && tray !== null) { -// tray.setContextMenu(TrayMenu(accountsChange, i18next)) -// } + // TODO: stopUserStreaming(accountId) +}) -// stopUserStreaming(accountId) -// }) +ipcMain.handle('forward-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => { + await forwardAccount(db, acct) +}) -// ipcMain.handle('forward-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => { -// await accountRepo.forwardAccount(acct) -// }) +ipcMain.handle('backward-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => { + await backwardAccount(db, acct) +}) -// ipcMain.handle('backward-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => { -// await accountRepo.backwardAccount(acct) -// }) +ipcMain.handle('remove-all-accounts', async (_: IpcMainInvokeEvent) => { + await removeAllAccounts(db) + const accounts = await listAccounts(db) + const accountsChange: Array = accounts.map(([account, server], index) => { + return { + label: server.domain, + accelerator: `CmdOrCtrl+${index + 1}`, + click: () => changeAccount(account, index) + } + }) -// ipcMain.handle('refresh-accounts', async (_: IpcMainInvokeEvent) => { -// const proxy = await proxyConfiguration.forMastodon() -// const accounts = await accountRepo.refreshAccounts(proxy) - -// return accounts -// }) - -// ipcMain.handle('remove-all-accounts', async (_: IpcMainInvokeEvent) => { -// await accountRepo.removeAll() - -// const accounts = await listAccounts() -// const accountsChange: Array = accounts.map((a, index) => { -// return { -// label: a.domain, -// accelerator: `CmdOrCtrl+${index + 1}`, -// click: () => changeAccount(a, index) -// } -// }) - -// await updateApplicationMenu(accountsChange) -// await updateDockMenu(accountsChange) -// if (process.platform !== 'darwin' && tray !== null) { -// tray.setContextMenu(TrayMenu(accountsChange, i18next)) -// } -// }) + await updateApplicationMenu(accountsChange) + await updateDockMenu(accountsChange) + if (process.platform !== 'darwin' && tray !== null) { + tray.setContextMenu(TrayMenu(accountsChange, i18next)) + } +}) ipcMain.handle('change-auto-launch', async (_: IpcMainInvokeEvent, enable: boolean) => { if (launcher) { @@ -1072,7 +1055,7 @@ ipcMain.handle('change-language', async (_: IpcMainInvokeEvent, value: string) = return { label: s.domain, accelerator: `CmdOrCtrl+${index + 1}`, - click: () => changeAccount([a, s], index) + click: () => changeAccount(a, index) } }) @@ -1139,18 +1122,17 @@ ipcMain.handle('list-fonts', async (_: IpcMainInvokeEvent) => { // Settings ipcMain.handle( 'get-account-setting', - async (_: IpcMainInvokeEvent, accountID: string): Promise => { - const settings = new Settings(settingsDBPath) - const setting = await settings.get(accountID) + async (_: IpcMainInvokeEvent, accountId: number): Promise => { + const setting = await getSetting(db, accountId) return setting } ) ipcMain.handle( 'update-account-setting', - async (_: IpcMainInvokeEvent, setting: Setting): Promise => { - const settings = new Settings(settingsDBPath) - const res = await settings.update(setting) + async (_: IpcMainInvokeEvent, setting: Setting): Promise => { + console.log(setting) + const res = await createOrUpdateSetting(db, setting) return res } ) diff --git a/src/main/settings.ts b/src/main/settings.ts index c4c1025e..38bd0f4d 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -1,80 +1,55 @@ -import storage from 'electron-json-storage' -import log from 'electron-log' -import objectAssignDeep from 'object-assign-deep' -import { BaseSettings, Setting } from '~/src/types/setting' +import sqlite3 from 'sqlite3' +import { Setting } from '~/src/types/setting' import { DefaultSetting } from '~/src/constants/initializer/setting' -import { isEmpty } from 'lodash' -export default class Settings { - private path: string - - constructor(path: string) { - this.path = path - } - - public async _load(): Promise { - try { - const settings = await this._get() - if (isEmpty(settings)) { - return [] +export const getSetting = (db: sqlite3.Database, accountId: number): Promise => { + return new Promise((resolve, reject) => { + db.get('SELECT * FROM settings WHERE account_id = ?', accountId, (err, row) => { + if (err) { + reject(err) } - return settings - } catch (err) { - log.error(err) - return [] - } - } - - public async get(accountID: string): Promise { - const current = await this._load() - const find: Setting | undefined = current.find(d => { - return d.accountID === accountID + if (row) { + resolve({ + accountId: row.account_id, + markerHome: Boolean(row.marker_home), + markerNotifications: Boolean(row.marker_notifications) + }) + } + resolve(DefaultSetting) }) - if (find) { - return objectAssignDeep({}, DefaultSetting, find) - } - return objectAssignDeep({}, DefaultSetting, { - accountID: accountID - }) - } - - private _get(): Promise { - return new Promise((resolve, reject) => { - storage.get(this.path, (err, data) => { - if (err) return reject(err) - return resolve(data as BaseSettings) - }) - }) - } - - private _save(data: BaseSettings): Promise { - return new Promise((resolve, reject) => { - storage.set(this.path, data, err => { - if (err) return reject(err) - return resolve(data) - }) - }) - } - - public async update(obj: Setting): Promise { - const current = await this._load() - const find = current.find(d => { - return d.accountID === obj.accountID - }) - if (find) { - const data = current.map(d => { - if (d.accountID !== obj.accountID) { - return d - } - const newData = objectAssignDeep({}, d, obj) - return newData - }) - const result = await this._save(data) - return result - } else { - const data = current.concat([obj]) - const result = await this._save(data) - return result - } - } + }) +} + +export const createOrUpdateSetting = (db: sqlite3.Database, setting: Setting): Promise => { + return new Promise((resolve, reject) => { + db.get('SELECT * FROM settings WHERE account_id = ?', setting.accountId, (err, row) => { + if (err) { + reject(err) + } + if (row) { + db.run( + 'UPDATE settings SET marker_home = ?, marker_notifications = ? WHERE account_id = ?', + [setting.markerHome, setting.markerNotifications, setting.accountId], + err => { + if (err) { + reject(err) + } + resolve(setting) + } + ) + resolve(setting) + } else { + db.run( + 'INSERT INTO settings(account_id, marker_home, marker_notifications) VALUES (?, ?, ?)', + [setting.accountId, setting.markerHome, setting.markerNotifications], + function (err) { + if (err) { + reject(err) + } + resolve(setting) + } + ) + } + }) + }) } diff --git a/src/renderer/components/Settings.vue b/src/renderer/components/Settings.vue index b5c411be..e7f6122e 100644 --- a/src/renderer/components/Settings.vue +++ b/src/renderer/components/Settings.vue @@ -64,7 +64,7 @@ export default defineComponent({ const activeRoute = computed(() => route.path) onMounted(() => { - store.commit(`${space}/${MUTATION_TYPES.CHANGE_ACCOUNT_ID}`, id.value) + store.commit(`${space}/${MUTATION_TYPES.CHANGE_ACCOUNT_ID}`, parseInt(id.value as string)) router.push(`/${id.value}/settings/general`) }) diff --git a/src/renderer/components/Settings/Timeline.vue b/src/renderer/components/Settings/Timeline.vue index ba7f4c60..24a10fee 100644 --- a/src/renderer/components/Settings/Timeline.vue +++ b/src/renderer/components/Settings/Timeline.vue @@ -1,23 +1,6 @@