From ba1574116fc6df795c902ad35629ab2de61e3ca1 Mon Sep 17 00:00:00 2001
From: AkiraFukushima
Date: Thu, 10 Jun 2021 12:03:51 +0900
Subject: [PATCH 1/3] refs #574 Save marker in local db every time receive a
new status in home
---
src/main/index.ts | 14 ++++
src/main/marker.ts | 67 +++++++++++++++++++
.../TimelineSpace/Contents/Home.vue | 5 ++
.../store/TimelineSpace/Contents/Home.ts | 12 ++++
src/types/localMarker.ts | 5 ++
5 files changed, 103 insertions(+)
create mode 100644 src/main/marker.ts
create mode 100644 src/types/localMarker.ts
diff --git a/src/main/index.ts b/src/main/index.ts
index 8aa58d3c..a7f8e86d 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -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
@@ -140,6 +142,12 @@ 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
+})
+
/**
* Cache path
*/
@@ -1137,6 +1145,12 @@ ipcMain.handle('update-spellchecker-languages', async (_: IpcMainInvokeEvent, la
return conf.language.spellchecker.languages
})
+// marker
+ipcMain.handle('save-marker', async (_: IpcMainInvokeEvent, marker: LocalMarker) => {
+ const repo = new Marker(markerDB)
+ await repo.save(marker)
+})
+
// hashtag
ipcMain.handle('save-hashtag', async (_: IpcMainInvokeEvent, tag: string) => {
const hashtags = new Hashtags(hashtagsDB)
diff --git a/src/main/marker.ts b/src/main/marker.ts
new file mode 100644
index 00000000..a000a484
--- /dev/null
+++ b/src/main/marker.ts
@@ -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 {
+ return new Promise((resolve, reject) => {
+ this.db.insert(marker, (err, doc) => {
+ if (err) return reject(err)
+ resolve(doc)
+ })
+ })
+ }
+
+ private update(marker: LocalMarker): Promise {
+ // @ts-ignore
+ return new Promise((resolve, reject) => {
+ // eslint-disable-line no-unused-vars
+ this.db.update(
+ {
+ acct: marker.acct,
+ timeline: marker.timeline
+ },
+ { $set: marker },
+ { multi: false },
+ err => {
+ if (err) return reject(err)
+ return this.get(marker.acct, marker.timeline)
+ }
+ )
+ })
+ }
+
+ public async save(marker: LocalMarker): Promise {
+ return this.get(marker.acct, marker.timeline).then(l => {
+ if (isEmpty(l)) return this.insert(marker)
+ return this.update(marker)
+ })
+ }
+
+ public async get(acct: string, timeline: 'home' | 'notifications'): Promise {
+ return new Promise((resolve, reject) => {
+ this.db.findOne({ acct: acct, timeline: timeline }, (err, doc) => {
+ if (err) return reject(err)
+ resolve(doc)
+ })
+ })
+ }
+
+ public async list(acct: string): Promise> {
+ return new Promise((resolve, reject) => {
+ this.db
+ .find({ acct: acct })
+ .exec((err, docs) => {
+ if (err) return reject(err)
+ resolve(docs)
+ })
+ })
+ }
+}
diff --git a/src/renderer/components/TimelineSpace/Contents/Home.vue b/src/renderer/components/TimelineSpace/Contents/Home.vue
index 413ac1e7..b463bad1 100644
--- a/src/renderer/components/TimelineSpace/Contents/Home.vue
+++ b/src/renderer/components/TimelineSpace/Contents/Home.vue
@@ -127,6 +127,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: {
diff --git a/src/renderer/store/TimelineSpace/Contents/Home.ts b/src/renderer/store/TimelineSpace/Contents/Home.ts
index feadab7f..56659587 100644
--- a/src/renderer/store/TimelineSpace/Contents/Home.ts
+++ b/src/renderer/store/TimelineSpace/Contents/Home.ts
@@ -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,14 @@ const actions: ActionTree = {
.finally(() => {
commit(MUTATION_TYPES.CHANGE_LAZY_LOADING, false)
})
+ },
+ saveMarker: async ({ rootState }, id: string) => {
+ const acct = `@${rootState.TimelineSpace.account.username}@${rootState.TimelineSpace.account.domain}`
+ await win.ipcRenderer.invoke('save-marker', {
+ acct: acct,
+ timeline: 'home',
+ lastReadID: id
+ } as LocalMarker)
}
}
diff --git a/src/types/localMarker.ts b/src/types/localMarker.ts
new file mode 100644
index 00000000..95712762
--- /dev/null
+++ b/src/types/localMarker.ts
@@ -0,0 +1,5 @@
+export type LocalMarker = {
+ acct: string
+ timeline: 'home' | 'notifications'
+ lastReadID: string
+}
From 0e354c474df4cecbd81dff92c03c5af3e69dbf96 Mon Sep 17 00:00:00 2001
From: AkiraFukushima
Date: Thu, 10 Jun 2021 15:27:04 +0900
Subject: [PATCH 2/3] refs #574 Save marker in local db every time receive a
new status in notifications
---
src/main/index.ts | 4 ++--
src/main/marker.ts | 14 +++++++-------
.../TimelineSpace/Contents/Home.vue | 4 ++++
.../TimelineSpace/Contents/Notifications.vue | 19 +++++++++++++++----
.../store/TimelineSpace/Contents/Home.ts | 5 ++---
.../TimelineSpace/Contents/Notifications.ts | 8 ++++++++
src/types/localMarker.ts | 4 ++--
7 files changed, 40 insertions(+), 18 deletions(-)
diff --git a/src/main/index.ts b/src/main/index.ts
index a7f8e86d..0dc0220e 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -147,6 +147,7 @@ const markerDB = new Datastore({
filename: markerDBPath,
autoload: true
})
+const markerRepo = new Marker(markerDB)
/**
* Cache path
@@ -1147,8 +1148,7 @@ ipcMain.handle('update-spellchecker-languages', async (_: IpcMainInvokeEvent, la
// marker
ipcMain.handle('save-marker', async (_: IpcMainInvokeEvent, marker: LocalMarker) => {
- const repo = new Marker(markerDB)
- await repo.save(marker)
+ await markerRepo.save(marker)
})
// hashtag
diff --git a/src/main/marker.ts b/src/main/marker.ts
index a000a484..700c125e 100644
--- a/src/main/marker.ts
+++ b/src/main/marker.ts
@@ -25,39 +25,39 @@ export default class Marker {
// eslint-disable-line no-unused-vars
this.db.update(
{
- acct: marker.acct,
+ owner_id: marker.owner_id,
timeline: marker.timeline
},
{ $set: marker },
{ multi: false },
err => {
if (err) return reject(err)
- return this.get(marker.acct, marker.timeline)
+ return this.get(marker.owner_id, marker.timeline)
}
)
})
}
public async save(marker: LocalMarker): Promise {
- return this.get(marker.acct, marker.timeline).then(l => {
+ return this.get(marker.owner_id, marker.timeline).then(l => {
if (isEmpty(l)) return this.insert(marker)
return this.update(marker)
})
}
- public async get(acct: string, timeline: 'home' | 'notifications'): Promise {
+ public async get(owner_id: string, timeline: 'home' | 'notifications'): Promise {
return new Promise((resolve, reject) => {
- this.db.findOne({ acct: acct, timeline: timeline }, (err, doc) => {
+ this.db.findOne({ owner_id: owner_id, timeline: timeline }, (err, doc) => {
if (err) return reject(err)
resolve(doc)
})
})
}
- public async list(acct: string): Promise> {
+ public async list(owner_id: string): Promise> {
return new Promise((resolve, reject) => {
this.db
- .find({ acct: acct })
+ .find({ owner_id: owner_id })
.exec((err, docs) => {
if (err) return reject(err)
resolve(docs)
diff --git a/src/renderer/components/TimelineSpace/Contents/Home.vue b/src/renderer/components/TimelineSpace/Contents/Home.vue
index b463bad1..d9b598df 100644
--- a/src/renderer/components/TimelineSpace/Contents/Home.vue
+++ b/src/renderer/components/TimelineSpace/Contents/Home.vue
@@ -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) {
diff --git a/src/renderer/components/TimelineSpace/Contents/Notifications.vue b/src/renderer/components/TimelineSpace/Contents/Notifications.vue
index 2e68fd56..b15fde97 100644
--- a/src/renderer/components/TimelineSpace/Contents/Notifications.vue
+++ b/src/renderer/components/TimelineSpace/Contents/Notifications.vue
@@ -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: {
diff --git a/src/renderer/store/TimelineSpace/Contents/Home.ts b/src/renderer/store/TimelineSpace/Contents/Home.ts
index 56659587..8b5aebaa 100644
--- a/src/renderer/store/TimelineSpace/Contents/Home.ts
+++ b/src/renderer/store/TimelineSpace/Contents/Home.ts
@@ -141,11 +141,10 @@ const actions: ActionTree = {
})
},
saveMarker: async ({ rootState }, id: string) => {
- const acct = `@${rootState.TimelineSpace.account.username}@${rootState.TimelineSpace.account.domain}`
await win.ipcRenderer.invoke('save-marker', {
- acct: acct,
+ owner_id: rootState.TimelineSpace.account._id,
timeline: 'home',
- lastReadID: id
+ last_read_id: id
} as LocalMarker)
}
}
diff --git a/src/renderer/store/TimelineSpace/Contents/Notifications.ts b/src/renderer/store/TimelineSpace/Contents/Notifications.ts
index 1b7fe833..4dfd1562 100644
--- a/src/renderer/store/TimelineSpace/Contents/Notifications.ts
+++ b/src/renderer/store/TimelineSpace/Contents/Notifications.ts
@@ -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 = {
},
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)
}
}
diff --git a/src/types/localMarker.ts b/src/types/localMarker.ts
index 95712762..c32e5330 100644
--- a/src/types/localMarker.ts
+++ b/src/types/localMarker.ts
@@ -1,5 +1,5 @@
export type LocalMarker = {
- acct: string
+ owner_id: string
timeline: 'home' | 'notifications'
- lastReadID: string
+ last_read_id: string
}
From adb4ddb1f1c35a65d78b62a8459a5e2576b1a84a Mon Sep 17 00:00:00 2001
From: AkiraFukushima
Date: Thu, 10 Jun 2021 22:12:21 +0900
Subject: [PATCH 3/3] refs #574 Sync local marker to servers
---
src/main/index.ts | 78 +++++++++++++++++++++++++++++++++++-----------
src/main/marker.ts | 2 +-
2 files changed, 60 insertions(+), 20 deletions(-)
diff --git a/src/main/index.ts b/src/main/index.ts
index 0dc0220e..de39cb42 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -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'
@@ -127,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({
@@ -181,7 +181,7 @@ if (process.platform !== 'darwin') {
async function listAccounts(): Promise> {
try {
- const accounts = await accountManager.listAccounts()
+ const accounts = await accountRepo.listAccounts()
return accounts
} catch (err) {
return []
@@ -450,7 +450,7 @@ app.on('activate', () => {
}
})
-const auth = new Authentication(accountManager)
+const auth = new Authentication(accountRepo)
type AuthRequest = {
instance: string
@@ -506,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 = accounts.map((a, index) => {
@@ -543,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 = accounts.map((a, index) => {
@@ -614,7 +614,7 @@ ipcMain.on('start-all-user-streamings', (event: IpcMainEvent, accounts: Array {
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()
@@ -725,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) {
@@ -774,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) {
@@ -823,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) {
@@ -876,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) {
@@ -930,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) {
@@ -1148,9 +1148,49 @@ ipcMain.handle('update-spellchecker-languages', async (_: IpcMainInvokeEvent, la
// 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)
diff --git a/src/main/marker.ts b/src/main/marker.ts
index 700c125e..ef3d0c4c 100644
--- a/src/main/marker.ts
+++ b/src/main/marker.ts
@@ -45,7 +45,7 @@ export default class Marker {
})
}
- public async get(owner_id: string, timeline: 'home' | 'notifications'): Promise {
+ public async get(owner_id: string, timeline: 'home' | 'notifications'): Promise {
return new Promise((resolve, reject) => {
this.db.findOne({ owner_id: owner_id, timeline: timeline }, (err, doc) => {
if (err) return reject(err)