Merge pull request #2505 from h3poteto/iss-574

refs #574 Save marker in local db
This commit is contained in:
AkiraFukushima 2021-06-10 23:00:44 +09:00 committed by GitHub
commit 76643b7eb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 188 additions and 23 deletions

View File

@ -25,7 +25,7 @@ import path from 'path'
import ContextMenu from 'electron-context-menu'
import { initSplashScreen, Config } from '@trodi/electron-splashscreen'
import openAboutWindow from 'about-window'
import { Entity, detector, NotificationType } from 'megalodon'
import generator, { Entity, detector, NotificationType } from 'megalodon'
import sanitizeHtml from 'sanitize-html'
import AutoLaunch from 'auto-launch'
import minimist from 'minimist'
@ -54,6 +54,8 @@ import ProxyConfiguration from './proxy'
import confirm from './timelines'
import { EnabledTimelines } from '~/src/types/enabledTimelines'
import { Menu as MenuPreferences } from '~/src/types/preference'
import { LocalMarker } from '~/src/types/localMarker'
import Marker from './marker'
/**
* Context menu
@ -125,8 +127,8 @@ const accountDB = new Datastore({
filename: accountDBPath,
autoload: true
})
const accountManager = new Account(accountDB)
accountManager.initialize().catch((err: Error) => log.error(err))
const accountRepo = new Account(accountDB)
accountRepo.initialize().catch((err: Error) => log.error(err))
const hashtagsDBPath = process.env.NODE_ENV === 'production' ? userData + '/db/hashtags.db' : 'hashtags.db'
const hashtagsDB = new Datastore({
@ -140,6 +142,13 @@ unreadNotification.initialize().catch((err: Error) => log.error(err))
const preferencesDBPath = process.env.NODE_ENV === 'production' ? userData + './db/preferences.json' : 'preferences.json'
const markerDBPath = process.env.NODE_ENV === 'production' ? userData + '/db/marker.db' : 'marker.db'
const markerDB = new Datastore({
filename: markerDBPath,
autoload: true
})
const markerRepo = new Marker(markerDB)
/**
* Cache path
*/
@ -172,7 +181,7 @@ if (process.platform !== 'darwin') {
async function listAccounts(): Promise<Array<LocalAccount>> {
try {
const accounts = await accountManager.listAccounts()
const accounts = await accountRepo.listAccounts()
return accounts
} catch (err) {
return []
@ -441,7 +450,7 @@ app.on('activate', () => {
}
})
const auth = new Authentication(accountManager)
const auth = new Authentication(accountRepo)
type AuthRequest = {
instance: string
@ -497,23 +506,23 @@ ipcMain.handle('get-and-update-access-token', async (_: IpcMainInvokeEvent, requ
// nedb
ipcMain.handle('list-accounts', async (_: IpcMainInvokeEvent) => {
const accounts = await accountManager.listAccounts()
const accounts = await accountRepo.listAccounts()
return accounts
})
ipcMain.handle('get-local-account', async (_: IpcMainInvokeEvent, id: string) => {
const account = await accountManager.getAccount(id)
const account = await accountRepo.getAccount(id)
return account
})
ipcMain.handle('update-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => {
const proxy = await proxyConfiguration.forMastodon()
const ac: LocalAccount = await accountManager.refresh(acct, proxy)
const ac: LocalAccount = await accountRepo.refresh(acct, proxy)
return ac
})
ipcMain.handle('remove-account', async (_: IpcMainInvokeEvent, id: string) => {
const accountId = await accountManager.removeAccount(id)
const accountId = await accountRepo.removeAccount(id)
const accounts = await listAccounts()
const accountsChange: Array<MenuItemConstructorOptions> = accounts.map((a, index) => {
@ -534,22 +543,22 @@ ipcMain.handle('remove-account', async (_: IpcMainInvokeEvent, id: string) => {
})
ipcMain.handle('forward-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => {
await accountManager.forwardAccount(acct)
await accountRepo.forwardAccount(acct)
})
ipcMain.handle('backward-account', async (_: IpcMainInvokeEvent, acct: LocalAccount) => {
await accountManager.backwardAccount(acct)
await accountRepo.backwardAccount(acct)
})
ipcMain.handle('refresh-accounts', async (_: IpcMainInvokeEvent) => {
const proxy = await proxyConfiguration.forMastodon()
const accounts = await accountManager.refreshAccounts(proxy)
const accounts = await accountRepo.refreshAccounts(proxy)
return accounts
})
ipcMain.handle('remove-all-accounts', async (_: IpcMainInvokeEvent) => {
await accountManager.removeAll()
await accountRepo.removeAll()
const accounts = await listAccounts()
const accountsChange: Array<MenuItemConstructorOptions> = accounts.map((a, index) => {
@ -605,7 +614,7 @@ ipcMain.on('start-all-user-streamings', (event: IpcMainEvent, accounts: Array<Lo
accounts.map(async account => {
const id: string = account._id!
try {
const acct = await accountManager.getAccount(id)
const acct = await accountRepo.getAccount(id)
// Stop old user streaming
if (userStreamings[id]) {
userStreamings[id]!.stop()
@ -716,7 +725,7 @@ let directMessagesStreaming: DirectStreaming | null = null
ipcMain.on('start-directmessages-streaming', async (event: IpcMainEvent, obj: StreamingSetting) => {
const { account } = obj
try {
const acct = await accountManager.getAccount(account._id!)
const acct = await accountRepo.getAccount(account._id!)
// Stop old directmessages streaming
if (directMessagesStreaming !== null) {
@ -765,7 +774,7 @@ let localStreaming: LocalStreaming | null = null
ipcMain.on('start-local-streaming', async (event: IpcMainEvent, obj: StreamingSetting) => {
const { account } = obj
try {
const acct = await accountManager.getAccount(account._id!)
const acct = await accountRepo.getAccount(account._id!)
// Stop old local streaming
if (localStreaming !== null) {
@ -814,7 +823,7 @@ let publicStreaming: PublicStreaming | null = null
ipcMain.on('start-public-streaming', async (event: IpcMainEvent, obj: StreamingSetting) => {
const { account } = obj
try {
const acct = await accountManager.getAccount(account._id!)
const acct = await accountRepo.getAccount(account._id!)
// Stop old public streaming
if (publicStreaming !== null) {
@ -867,7 +876,7 @@ type ListID = {
ipcMain.on('start-list-streaming', async (event: IpcMainEvent, obj: ListID & StreamingSetting) => {
const { listID, account } = obj
try {
const acct = await accountManager.getAccount(account._id!)
const acct = await accountRepo.getAccount(account._id!)
// Stop old list streaming
if (listStreaming !== null) {
@ -921,7 +930,7 @@ type Tag = {
ipcMain.on('start-tag-streaming', async (event: IpcMainEvent, obj: Tag & StreamingSetting) => {
const { tag, account } = obj
try {
const acct = await accountManager.getAccount(account._id!)
const acct = await accountRepo.getAccount(account._id!)
// Stop old tag streaming
if (tagStreaming !== null) {
@ -1137,6 +1146,51 @@ ipcMain.handle('update-spellchecker-languages', async (_: IpcMainInvokeEvent, la
return conf.language.spellchecker.languages
})
// marker
ipcMain.handle('save-marker', async (_: IpcMainInvokeEvent, marker: LocalMarker) => {
if (marker.owner_id === null || marker.owner_id === undefined || marker.owner_id === '') {
return
}
await markerRepo.save(marker)
})
setTimeout(async () => {
try {
const accounts = await accountRepo.listAccounts()
accounts.map(async acct => {
const proxy = await proxyConfiguration.forMastodon()
const sns = await detector(acct.baseURL, proxy)
if (sns === 'misskey') {
return
}
const client = generator(sns, acct.baseURL, acct.accessToken, 'Whalebird', proxy)
const home = await markerRepo.get(acct._id!, 'home')
const notifications = await markerRepo.get(acct._id!, 'notifications')
let params = {}
if (home !== null && home !== undefined) {
params = Object.assign({}, params, {
home: {
last_read_id: home.last_read_id
}
})
}
if (notifications !== null && notifications !== undefined) {
params = Object.assign({}, params, {
notifications: {
last_read_id: notifications.last_read_id
}
})
}
if (isEmpty(params)) {
return
}
await client.saveMarkers(params)
})
} catch (err) {
console.error(err)
}
}, 120000)
// hashtag
ipcMain.handle('save-hashtag', async (_: IpcMainInvokeEvent, tag: string) => {
const hashtags = new Hashtags(hashtagsDB)

67
src/main/marker.ts Normal file
View File

@ -0,0 +1,67 @@
import { isEmpty } from 'lodash'
import Datastore from 'nedb'
import { LocalMarker } from '~/src/types/localMarker'
export default class Marker {
private db: Datastore
constructor(db: Datastore) {
this.db = db
this.db.persistence.setAutocompactionInterval(60000) // milliseconds
}
private insert(marker: LocalMarker): Promise<LocalMarker> {
return new Promise((resolve, reject) => {
this.db.insert(marker, (err, doc) => {
if (err) return reject(err)
resolve(doc)
})
})
}
private update(marker: LocalMarker): Promise<LocalMarker> {
// @ts-ignore
return new Promise((resolve, reject) => {
// eslint-disable-line no-unused-vars
this.db.update(
{
owner_id: marker.owner_id,
timeline: marker.timeline
},
{ $set: marker },
{ multi: false },
err => {
if (err) return reject(err)
return this.get(marker.owner_id, marker.timeline)
}
)
})
}
public async save(marker: LocalMarker): Promise<LocalMarker> {
return this.get(marker.owner_id, marker.timeline).then(l => {
if (isEmpty(l)) return this.insert(marker)
return this.update(marker)
})
}
public async get(owner_id: string, timeline: 'home' | 'notifications'): Promise<LocalMarker | null> {
return new Promise((resolve, reject) => {
this.db.findOne<LocalMarker>({ owner_id: owner_id, timeline: timeline }, (err, doc) => {
if (err) return reject(err)
resolve(doc)
})
})
}
public async list(owner_id: string): Promise<Array<LocalMarker>> {
return new Promise((resolve, reject) => {
this.db
.find<LocalMarker>({ owner_id: owner_id })
.exec((err, docs) => {
if (err) return reject(err)
resolve(docs)
})
})
}
}

View File

@ -94,6 +94,10 @@ export default {
this.focusedId = previousFocusedId
})
})
if (this.heading && this.timeline.length > 0) {
this.$store.dispatch('TimelineSpace/Contents/Home/saveMarker', this.timeline[0].id)
}
},
beforeUpdate() {
if (this.$store.state.TimelineSpace.SideMenu.unreadHomeTimeline && this.heading) {
@ -127,6 +131,11 @@ export default {
this.$store.commit('TimelineSpace/Contents/Home/changeHeading', true)
this.$store.commit('TimelineSpace/Contents/Home/mergeTimeline')
}
},
timeline: function (newState, _oldState) {
if (this.heading && newState.length > 0) {
this.$store.dispatch('TimelineSpace/Contents/Home/saveMarker', newState[0].id)
}
}
},
methods: {

View File

@ -47,10 +47,12 @@ export default {
...mapState({
openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar,
startReload: state => state.TimelineSpace.HeaderMenu.reload,
backgroundColor: state => state.App.theme.background_color,
lazyLoading: state => state.TimelineSpace.Contents.Notifications.lazyLoading,
heading: state => state.TimelineSpace.Contents.Notifications.heading,
unread: state => state.TimelineSpace.Contents.Notifications.unreadNotifications
backgroundColor: state => state.App.theme.background_color
}),
...mapState('TimelineSpace/Contents/Notifications', {
lazyLoading: state => state.lazyLoading,
heading: state => state.heading,
unread: state => state.unreadNotifications
}),
...mapGetters('TimelineSpace/Contents/Notifications', ['handledNotifications', 'filters']),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
@ -79,6 +81,10 @@ export default {
this.focusedId = previousFocusedId
})
})
if (this.heading && this.handledNotifications.length > 0) {
this.$store.dispatch('TimelineSpace/Contents/Notifications/saveMarker', this.handledNotifications[0].id)
}
},
beforeUpdate() {
if (this.$store.state.TimelineSpace.SideMenu.unreadNotifications) {
@ -113,6 +119,11 @@ export default {
this.$store.commit('TimelineSpace/Contents/Notifications/mergeNotifications')
this.$store.dispatch('TimelineSpace/Contents/Notifications/resetBadge')
}
},
handledNotifications: function (newState, _oldState) {
if (this.heading && newState.length > 0) {
this.$store.dispatch('TimelineSpace/Contents/Notifications/saveMarker', newState[0].id)
}
}
},
methods: {

View File

@ -1,6 +1,10 @@
import generator, { Entity, FilterContext } from 'megalodon'
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
import { RootState } from '@/store'
import { LocalMarker } from '~/src/types/localMarker'
import { MyWindow } from '~/src/types/global'
const win = (window as any) as MyWindow
export type HomeState = {
lazyLoading: boolean
@ -135,6 +139,13 @@ const actions: ActionTree<HomeState, RootState> = {
.finally(() => {
commit(MUTATION_TYPES.CHANGE_LAZY_LOADING, false)
})
},
saveMarker: async ({ rootState }, id: string) => {
await win.ipcRenderer.invoke('save-marker', {
owner_id: rootState.TimelineSpace.account._id,
timeline: 'home',
last_read_id: id
} as LocalMarker)
}
}

View File

@ -1,6 +1,7 @@
import generator, { Entity, FilterContext, NotificationType } from 'megalodon'
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
import { RootState } from '@/store'
import { LocalMarker } from '~/src/types/localMarker'
import { MyWindow } from '~/src/types/global'
const win = (window as any) as MyWindow
@ -135,6 +136,13 @@ const actions: ActionTree<NotificationsState, RootState> = {
},
resetBadge: () => {
win.ipcRenderer.send('reset-badge')
},
saveMarker: async ({ rootState }, id: string) => {
await win.ipcRenderer.invoke('save-marker', {
owner_id: rootState.TimelineSpace.account._id,
timeline: 'notifications',
last_read_id: id
} as LocalMarker)
}
}

5
src/types/localMarker.ts Normal file
View File

@ -0,0 +1,5 @@
export type LocalMarker = {
owner_id: string
timeline: 'home' | 'notifications'
last_read_id: string
}