From c90335bdfd3987daf350860dd61fb9b8d9ad39ef Mon Sep 17 00:00:00 2001 From: AkiraFukushima Date: Sat, 1 Jan 2022 18:39:33 +0900 Subject: [PATCH] Change storage of Settings to json-storage --- .../integration/store/TimelineSpace.spec.ts | 48 +++++------ .../renderer/unit/store/TimelineSpace.spec.ts | 8 +- src/constants/initializer/setting.ts | 16 ++++ src/constants/unreadNotification/index.d.ts | 13 --- src/constants/unreadNotification/index.js | 11 --- src/main/index.ts | 36 +++++---- src/main/settings.ts | 81 +++++++++++++++++++ src/main/unreadNotification.ts | 61 -------------- src/renderer/components/Settings/Timeline.vue | 54 ++++++------- .../TimelineSpace/Contents/Public.vue | 2 +- src/renderer/store/Settings/Timeline.ts | 49 +++++------ src/renderer/store/TimelineSpace.ts | 58 ++++++------- src/types/setting.ts | 15 ++++ src/types/unreadNotification.ts | 6 -- 14 files changed, 226 insertions(+), 232 deletions(-) create mode 100644 src/constants/initializer/setting.ts delete mode 100644 src/constants/unreadNotification/index.d.ts delete mode 100644 src/constants/unreadNotification/index.js create mode 100644 src/main/settings.ts delete mode 100644 src/main/unreadNotification.ts create mode 100644 src/types/setting.ts delete mode 100644 src/types/unreadNotification.ts diff --git a/spec/renderer/integration/store/TimelineSpace.spec.ts b/spec/renderer/integration/store/TimelineSpace.spec.ts index d829b075..a08dd363 100644 --- a/spec/renderer/integration/store/TimelineSpace.spec.ts +++ b/spec/renderer/integration/store/TimelineSpace.spec.ts @@ -3,7 +3,6 @@ import { createLocalVue } from '@vue/test-utils' import Vuex from 'vuex' import { ipcMain, ipcRenderer } from '~/spec/mock/electron' import TimelineSpace, { TimelineSpaceState, blankAccount } from '~/src/renderer/store/TimelineSpace' -import unreadSettings from '~/src/constants/unreadNotification' import { MyWindow } from '~/src/types/global' ;((window as any) as MyWindow).ipcRenderer = ipcRenderer @@ -79,10 +78,12 @@ const state = (): TimelineSpaceState => { loading: false, emojis: [], tootMax: 500, - unreadNotification: { - direct: true, - local: true, - public: true + timelineSetting: { + unreadNotification: { + direct: true, + local: true, + public: true + } }, sns: 'mastodon', filters: [] @@ -246,34 +247,27 @@ describe('TimelineSpace', () => { describe('loadUnreadNotification', () => { describe('success', () => { it('should be updated', async () => { - ipcMain.handle('get-unread-notification', () => { + ipcMain.handle('get-account-setting', () => { return { + accountID: 'sample', + timeline: { + unreadNotification: { + direct: false, + local: false, + public: false + } + } + } + }) + await store.dispatch('TimelineSpace/loadTimelineSetting') + expect(store.state.TimelineSpace.timelineSetting).toEqual({ + unreadNotification: { direct: false, local: false, public: false } }) - await store.dispatch('TimelineSpace/loadUnreadNotification') - expect(store.state.TimelineSpace.unreadNotification).toEqual({ - direct: false, - local: false, - public: false - }) - ipcMain.removeHandler('get-unread-notification') - }) - }) - describe('error', () => { - it('should be set default', async () => { - ipcMain.handle('get-unread-notification', async () => { - throw new Error() - }) - await store.dispatch('TimelineSpace/loadUnreadNotification') - expect(store.state.TimelineSpace.unreadNotification).toEqual({ - direct: unreadSettings.Direct.default, - local: unreadSettings.Local.default, - public: unreadSettings.Public.default - }) - ipcMain.removeHandler('get-unread-notification') + ipcMain.removeHandler('get-account-setting') }) }) }) diff --git a/spec/renderer/unit/store/TimelineSpace.spec.ts b/spec/renderer/unit/store/TimelineSpace.spec.ts index 6ed4b820..f3df2032 100644 --- a/spec/renderer/unit/store/TimelineSpace.spec.ts +++ b/spec/renderer/unit/store/TimelineSpace.spec.ts @@ -1,5 +1,5 @@ import TimelineSpace, { TimelineSpaceState, blankAccount, MUTATION_TYPES } from '~/src/renderer/store/TimelineSpace' -import unreadSettings from '~/src/constants/unreadNotification' +import { Base } from '~/src/constants/initializer/setting' describe('TimelineSpace', () => { describe('mutations', () => { @@ -11,11 +11,7 @@ describe('TimelineSpace', () => { loading: false, emojis: [], tootMax: 500, - unreadNotification: { - direct: unreadSettings.Direct.default, - local: unreadSettings.Local.default, - public: unreadSettings.Public.default - }, + timelineSetting: Base.timeline, sns: 'mastodon', filters: [] } diff --git a/src/constants/initializer/setting.ts b/src/constants/initializer/setting.ts new file mode 100644 index 00000000..fab19a08 --- /dev/null +++ b/src/constants/initializer/setting.ts @@ -0,0 +1,16 @@ +import { Setting, Timeline, UnreadNotification } from '~/src/types/setting' + +const unreadNotification: UnreadNotification = { + direct: false, + local: true, + public: false +} + +const timeline: Timeline = { + unreadNotification: unreadNotification +} + +export const Base: Setting = { + accountID: '', + timeline: timeline +} diff --git a/src/constants/unreadNotification/index.d.ts b/src/constants/unreadNotification/index.d.ts deleted file mode 100644 index 6ddbb639..00000000 --- a/src/constants/unreadNotification/index.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type UnreadNotificationType = { - default: boolean -} - -export type UnreadNotificationList = { - Direct: UnreadNotificationType, - Local: UnreadNotificationType, - Public: UnreadNotificationType -} - -declare let u: UnreadNotificationList - -export default u diff --git a/src/constants/unreadNotification/index.js b/src/constants/unreadNotification/index.js deleted file mode 100644 index 929c744a..00000000 --- a/src/constants/unreadNotification/index.js +++ /dev/null @@ -1,11 +0,0 @@ -export default { - Direct: { - default: false - }, - Local: { - default: true - }, - Public: { - default: false - } -} diff --git a/src/main/index.ts b/src/main/index.ts index 70603038..18b32c46 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -36,13 +36,11 @@ import { StreamingURL, UserStreaming, DirectStreaming, LocalStreaming, PublicStr import Preferences from './preferences' import Fonts from './fonts' import Hashtags from './hashtags' -import UnreadNotification from './unreadNotification' import i18next from '~/src/config/i18n' import { i18n as I18n } from 'i18next' import Language, { LanguageType } from '../constants/language' import { LocalAccount } from '~/src/types/localAccount' import { LocalTag } from '~/src/types/localTag' -import { UnreadNotification as UnreadNotificationConfig } from '~/src/types/unreadNotification' import { Notify } from '~/src/types/notify' import { StreamingError } from '~/src/errors/streamingError' import HashtagCache from './cache/hashtag' @@ -56,6 +54,8 @@ import { Menu as MenuPreferences } from '~/src/types/preference' import { LocalMarker } from '~/src/types/localMarker' import Marker from './marker' import newDB from './database' +import Settings from './settings' +import { BaseSettings, Setting } from '~/src/types/setting' /** * Context menu @@ -136,9 +136,7 @@ const hashtagsDB = new Datastore({ autoload: true }) -const unreadNotificationDBPath = process.env.NODE_ENV === 'production' ? userData + '/db/unread_notification.db' : 'unread_notification.db' -const unreadNotification = new UnreadNotification(unreadNotificationDBPath) -unreadNotification.initialize().catch((err: Error) => log.error(err)) +const settingsDBPath = process.env.NODE_ENV === 'production' ? userData + './db/settings.json' : 'settings.json' const preferencesDBPath = process.env.NODE_ENV === 'production' ? userData + './db/preferences.json' : 'preferences.json' @@ -1215,18 +1213,24 @@ ipcMain.handle('list-fonts', async (_: IpcMainInvokeEvent) => { return list }) -// Unread notifications -ipcMain.handle('get-unread-notification', async (_: IpcMainInvokeEvent, accountID: string) => { - const doc = await unreadNotification.findOne({ - accountID: accountID - }) - return doc -}) +// Settings +ipcMain.handle( + 'get-account-setting', + async (_: IpcMainInvokeEvent, accountID: string): Promise => { + const settings = new Settings(settingsDBPath) + const setting = await settings.get(accountID) + return setting + } +) -ipcMain.handle('update-unread-notification', async (_: IpcMainInvokeEvent, config: UnreadNotificationConfig) => { - const { accountID } = config - await unreadNotification.insertOrUpdate(accountID!, config) -}) +ipcMain.handle( + 'update-account-setting', + async (_: IpcMainInvokeEvent, setting: Setting): Promise => { + const settings = new Settings(settingsDBPath) + const res = await settings.update(setting) + return res + } +) // Cache ipcMain.handle('get-cache-hashtags', async (_: IpcMainInvokeEvent) => { diff --git a/src/main/settings.ts b/src/main/settings.ts new file mode 100644 index 00000000..7a3b9171 --- /dev/null +++ b/src/main/settings.ts @@ -0,0 +1,81 @@ +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 { Base } 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 [] + } + 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 (find) { + return find + } + const base = objectAssignDeep({}, Base, { + accountID: accountID + }) + return base + } + + 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 + } + } +} diff --git a/src/main/unreadNotification.ts b/src/main/unreadNotification.ts deleted file mode 100644 index b2cdc242..00000000 --- a/src/main/unreadNotification.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { isEmpty } from 'lodash' -import Datastore from 'nedb' -import { UnreadNotification as Config } from '~/src/types/unreadNotification' - -export default class UnreadNotification { - private db: Datastore - - constructor (path: string) { - this.db = new Datastore({ - filename: path, - autoload: true - }) - } - - async initialize () { - await this.updateUnique() - } - - updateUnique () { - return new Promise((resolve, reject) => { - // At first, remove old index. - this.db.removeIndex('accountID', (err) => { - if (err) reject(err) - // Add unique index. - this.db.ensureIndex({ fieldName: 'accountID', unique: true, sparse: true }, (err) => { - if (err) reject(err) - resolve({}) - }) - }) - }) - } - - insertOrUpdate (accountID: string, config: Config): Promise { - return new Promise((resolve, reject) => { - this.db.update( - { - accountID: accountID - }, - config, - { - upsert: true - }, - (err, num) => { - if (err) return reject(err) - resolve(num) - }) - }) - } - - findOne (obj: any): Promise { - return new Promise((resolve, reject) => { - this.db.findOne(obj, (err, doc) => { - if (err) return reject(err) - if (isEmpty(doc)) return reject(new EmptyRecordError('empty')) - resolve(doc) - }) - }) - } -} - -class EmptyRecordError extends Error {} diff --git a/src/renderer/components/Settings/Timeline.vue b/src/renderer/components/Settings/Timeline.vue index 266f21e1..802c5564 100644 --- a/src/renderer/components/Settings/Timeline.vue +++ b/src/renderer/components/Settings/Timeline.vue @@ -1,21 +1,21 @@ diff --git a/src/renderer/components/TimelineSpace/Contents/Public.vue b/src/renderer/components/TimelineSpace/Contents/Public.vue index 140f6aed..f2ad0046 100644 --- a/src/renderer/components/TimelineSpace/Contents/Public.vue +++ b/src/renderer/components/TimelineSpace/Contents/Public.vue @@ -59,7 +59,7 @@ export default { openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar, backgroundColor: state => state.App.theme.background_color, startReload: state => state.TimelineSpace.HeaderMenu.reload, - unreadNotification: state => state.TimelineSpace.unreadNotification + unreadNotification: state => state.TimelineSpace.timelineSetting.unreadNotification }), ...mapGetters('TimelineSpace/Contents/Public', ['filters']), ...mapGetters('TimelineSpace/Modals', ['modalOpened']), diff --git a/src/renderer/store/Settings/Timeline.ts b/src/renderer/store/Settings/Timeline.ts index f1beed79..20b176f3 100644 --- a/src/renderer/store/Settings/Timeline.ts +++ b/src/renderer/store/Settings/Timeline.ts @@ -1,55 +1,46 @@ -import unreadSettings from '~/src/constants/unreadNotification' import { Module, MutationTree, ActionTree } from 'vuex' import { RootState } from '@/store' -import { UnreadNotification } from '~/src/types/unreadNotification' import { MyWindow } from '~/src/types/global' +import { Setting, UnreadNotification, Timeline as TimelineSetting } from '~src/types/setting' +import { Base } from '~/src/constants/initializer/setting' const win = (window as any) as MyWindow export type TimelineState = { - unreadNotification: UnreadNotification + setting: TimelineSetting } const state = (): TimelineState => ({ - unreadNotification: { - direct: unreadSettings.Direct.default, - local: unreadSettings.Local.default, - public: unreadSettings.Public.default - } + setting: Base.timeline }) export const MUTATION_TYPES = { - UPDATE_UNREAD_NOTIFICATION: 'updateUnreadNotification' + UPDATE_TIMELINE_SETTING: 'updateTimelineSetting' } const mutations: MutationTree = { - [MUTATION_TYPES.UPDATE_UNREAD_NOTIFICATION]: (state, settings: UnreadNotification) => { - state.unreadNotification = settings + [MUTATION_TYPES.UPDATE_TIMELINE_SETTING]: (state, setting: TimelineSetting) => { + state.setting = setting } } const actions: ActionTree = { - loadUnreadNotification: async ({ commit, rootState }): Promise => { - try { - const settings: UnreadNotification = await win.ipcRenderer.invoke('get-unread-notification', rootState.Settings.accountID) - commit(MUTATION_TYPES.UPDATE_UNREAD_NOTIFICATION, settings) - return true - } catch (err) { - const settings: UnreadNotification = { - direct: unreadSettings.Direct.default, - local: unreadSettings.Local.default, - public: unreadSettings.Public.default - } - commit(MUTATION_TYPES.UPDATE_UNREAD_NOTIFICATION, settings) - return false - } + loadTimelineSetting: async ({ commit, rootState }): Promise => { + const setting: Setting = await win.ipcRenderer.invoke('get-account-setting', rootState.Settings.accountID) + commit(MUTATION_TYPES.UPDATE_TIMELINE_SETTING, setting.timeline) + return true }, changeUnreadNotification: async ({ dispatch, state, rootState }, timeline: { key: boolean }): Promise => { - const settings: UnreadNotification = Object.assign({}, state.unreadNotification, timeline, { - accountID: rootState.Settings.accountID + const unread: UnreadNotification = Object.assign({}, state.setting.unreadNotification, timeline) + const tl: TimelineSetting = Object.assign({}, state.setting, { + unreadNotification: unread }) - await win.ipcRenderer.invoke('update-unread-notification', settings) - dispatch('loadUnreadNotification') + const setting: Setting = { + accountID: rootState.Settings.accountID!, + timeline: tl + } + await win.ipcRenderer.invoke('update-account-setting', setting) + dispatch('loadTimelineSetting') return true } } diff --git a/src/renderer/store/TimelineSpace.ts b/src/renderer/store/TimelineSpace.ts index 18b97265..63b0bf7e 100644 --- a/src/renderer/store/TimelineSpace.ts +++ b/src/renderer/store/TimelineSpace.ts @@ -3,14 +3,14 @@ import SideMenu, { SideMenuState } from './TimelineSpace/SideMenu' import HeaderMenu, { HeaderMenuState } from './TimelineSpace/HeaderMenu' import Modals, { ModalsModuleState } from './TimelineSpace/Modals' import Contents, { ContentsModuleState } from './TimelineSpace/Contents' -import unreadSettings from '~/src/constants/unreadNotification' import { Module, MutationTree, ActionTree } from 'vuex' import { LocalAccount } from '~/src/types/localAccount' import { RootState } from '@/store' -import { UnreadNotification } from '~/src/types/unreadNotification' import { AccountLoadError } from '@/errors/load' import { TimelineFetchError } from '@/errors/fetch' import { MyWindow } from '~/src/types/global' +import { Timeline, Setting } from '~src/types/setting' +import { Base } from '~/src/constants/initializer/setting' const win = (window as any) as MyWindow @@ -20,7 +20,7 @@ export type TimelineSpaceState = { loading: boolean emojis: Array tootMax: number - unreadNotification: UnreadNotification + timelineSetting: Timeline sns: 'mastodon' | 'pleroma' | 'misskey' filters: Array } @@ -45,11 +45,7 @@ const state = (): TimelineSpaceState => ({ loading: false, emojis: [], tootMax: 500, - unreadNotification: { - direct: unreadSettings.Direct.default, - local: unreadSettings.Local.default, - public: unreadSettings.Public.default - }, + timelineSetting: Base.timeline, sns: 'mastodon', filters: [] }) @@ -60,7 +56,7 @@ export const MUTATION_TYPES = { CHANGE_LOADING: 'changeLoading', UPDATE_EMOJIS: 'updateEmojis', UPDATE_TOOT_MAX: 'updateTootMax', - UPDATE_UNREAD_NOTIFICATION: 'updateUnreadNotification', + UPDATE_TIMELINE_SETTING: 'updateTimelineSetting', CHANGE_SNS: 'changeSNS', UPDATE_FILTERS: 'updateFilters' } @@ -85,8 +81,8 @@ const mutations: MutationTree = { state.tootMax = 500 } }, - [MUTATION_TYPES.UPDATE_UNREAD_NOTIFICATION]: (state, settings: UnreadNotification) => { - state.unreadNotification = settings + [MUTATION_TYPES.UPDATE_TIMELINE_SETTING]: (state, setting: Timeline) => { + state.timelineSetting = setting }, [MUTATION_TYPES.CHANGE_SNS]: (state, sns: 'mastodon' | 'pleroma' | 'misskey') => { state.sns = sns @@ -109,7 +105,7 @@ const actions: ActionTree = { dispatch('TimelineSpace/SideMenu/fetchLists', account, { root: true }) dispatch('TimelineSpace/SideMenu/fetchFollowRequests', account, { root: true }) dispatch('TimelineSpace/SideMenu/confirmTimelines', account, { root: true }) - await dispatch('loadUnreadNotification', accountId) + await dispatch('loadTimelineSetting', accountId) await dispatch('fetchFilters') commit(MUTATION_TYPES.CHANGE_LOADING, false) await dispatch('fetchContentsTimelines').catch(_ => { @@ -204,17 +200,9 @@ const actions: ActionTree = { commit(MUTATION_TYPES.UPDATE_TOOT_MAX, res.data.max_toot_chars) return true }, - loadUnreadNotification: async ({ commit }, accountID: string) => { - try { - const settings: UnreadNotification = await win.ipcRenderer.invoke('get-unread-notification', accountID) - commit(MUTATION_TYPES.UPDATE_UNREAD_NOTIFICATION, settings) - } catch (err) { - commit(MUTATION_TYPES.UPDATE_UNREAD_NOTIFICATION, { - direct: unreadSettings.Direct.default, - local: unreadSettings.Local.default, - public: unreadSettings.Public.default - } as UnreadNotification) - } + loadTimelineSetting: async ({ commit }, accountID: string) => { + const setting: Setting = await win.ipcRenderer.invoke('get-account-setting', accountID) + commit(MUTATION_TYPES.UPDATE_TIMELINE_SETTING, setting.timeline) }, fetchContentsTimelines: async ({ dispatch, state }) => { dispatch('TimelineSpace/Contents/changeLoading', true, { root: true }) @@ -224,13 +212,13 @@ const actions: ActionTree = { await dispatch('TimelineSpace/Contents/Notifications/fetchNotifications', {}, { root: true }) await dispatch('TimelineSpace/Contents/Mentions/fetchMentions', {}, { root: true }) - if (state.unreadNotification.direct) { + if (state.timelineSetting.unreadNotification.direct) { await dispatch('TimelineSpace/Contents/DirectMessages/fetchTimeline', {}, { root: true }) } - if (state.unreadNotification.local) { + if (state.timelineSetting.unreadNotification.local) { await dispatch('TimelineSpace/Contents/Local/fetchLocalTimeline', {}, { root: true }) } - if (state.unreadNotification.public) { + if (state.timelineSetting.unreadNotification.public) { await dispatch('TimelineSpace/Contents/Public/fetchPublicTimeline', {}, { root: true }) } }, @@ -244,24 +232,24 @@ const actions: ActionTree = { }, bindStreamings: ({ dispatch, state }) => { dispatch('bindUserStreaming') - if (state.unreadNotification.direct) { + if (state.timelineSetting.unreadNotification.direct) { dispatch('bindDirectMessagesStreaming') } - if (state.unreadNotification.local) { + if (state.timelineSetting.unreadNotification.local) { dispatch('bindLocalStreaming') } - if (state.unreadNotification.public) { + if (state.timelineSetting.unreadNotification.public) { dispatch('bindPublicStreaming') } }, startStreamings: ({ dispatch, state }) => { - if (state.unreadNotification.direct) { + if (state.timelineSetting.unreadNotification.direct) { dispatch('startDirectMessagesStreaming') } - if (state.unreadNotification.local) { + if (state.timelineSetting.unreadNotification.local) { dispatch('startLocalStreaming') } - if (state.unreadNotification.public) { + if (state.timelineSetting.unreadNotification.public) { dispatch('startPublicStreaming') } }, @@ -429,13 +417,13 @@ const actions: ActionTree = { commit('TimelineSpace/Contents/Home/updateToot', status, { root: true }) commit('TimelineSpace/Contents/Notifications/updateToot', status, { root: true }) commit('TimelineSpace/Contents/Mentions/updateToot', status, { root: true }) - if (state.unreadNotification.direct) { + if (state.timelineSetting.unreadNotification.direct) { commit('TimelineSpace/Contents/DirectMessages/updateToot', status, { root: true }) } - if (state.unreadNotification.local) { + if (state.timelineSetting.unreadNotification.local) { commit('TimelineSpace/Contents/Local/updateToot', status, { root: true }) } - if (state.unreadNotification.public) { + if (state.timelineSetting.unreadNotification.public) { commit('TimelineSpace/Contents/Public/updateToot', status, { root: true }) } return true diff --git a/src/types/setting.ts b/src/types/setting.ts new file mode 100644 index 00000000..fa68b23b --- /dev/null +++ b/src/types/setting.ts @@ -0,0 +1,15 @@ +export type UnreadNotification = { + direct: boolean + local: boolean + public: boolean +} + +export type Timeline = { + unreadNotification: UnreadNotification +} +export type Setting = { + accountID: string + timeline: Timeline +} + +export type BaseSettings = Array diff --git a/src/types/unreadNotification.ts b/src/types/unreadNotification.ts deleted file mode 100644 index 8269cf5d..00000000 --- a/src/types/unreadNotification.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type UnreadNotification = { - accountID?: string, - direct: boolean, - local: boolean, - public: boolean -}