Skip to content

Commit

Permalink
feat(main: db): migrate from SnippetsLab (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
antonreshetov committed Apr 15, 2022
1 parent 724dfc3 commit 1d48506
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 21 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -43,6 +43,7 @@
"fs-extra": "^10.0.1",
"highlight.js": "^11.5.1",
"interactjs": "^1.10.11",
"lodash": "^4.17.21",
"lowdb": "^3.0.0",
"markdown-it": "^12.3.2",
"markdown-it-link-attributes": "^4.0.0",
Expand Down
3 changes: 2 additions & 1 deletion src/main/preload.ts
@@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from 'electron'
import { isDbExist, migrate, move } from './services/db'
import { isDbExist, migrate, migrateFromSnippetsLab, move } from './services/db'
import { store } from './store'
import type { ElectronBridge } from '@shared/types/main'
import { version } from '../../package.json'
Expand Down Expand Up @@ -29,6 +29,7 @@ contextBridge.exposeInMainWorld('electron', {
},
db: {
migrate: path => migrate(path),
migrateFromSnippetsLab: path => migrateFromSnippetsLab(path),
move: (from, to) => move(from, to),
isExist: path => isDbExist(path)
},
Expand Down
118 changes: 116 additions & 2 deletions src/main/services/db/index.ts
Expand Up @@ -3,8 +3,12 @@ import fs from 'fs-extra'
import readline from 'readline'
import { nestedToFlat } from '../../utils'
import { nanoid } from 'nanoid'
import type { Folder, Snippet, Tag } from '@shared/types/main/db'
import { oldLanguageMap } from '../../../renderer/components/editor/languages'
import type { DB, Folder, Snippet, Tag } from '@shared/types/main/db'
import {
oldLanguageMap,
languages
} from '../../../renderer/components/editor/languages'
import { snakeCase } from 'lodash'

const DB_NAME = 'db.json'

Expand Down Expand Up @@ -207,3 +211,113 @@ export const migrate = async (path: string) => {
writeToFile(db)
console.log('Migrate is done')
}

export const migrateFromSnippetsLab = (path: string) => {
interface SLFragment {
Content: string
'Date Created': string
'Date Modified': string
Note: string
Title: string
Language: string
}
interface SLSnippet {
'Date Created': string
'Date Modified': string
Folder: string
Title: string
Fragments: SLFragment[]
Tags: string[]
}

interface SnippetsLabDbJSON {
Snippets: SLSnippet[]
}

const INBOX = 'Uncategorized'

const file = fs.readFileSync(path, 'utf-8')
const json = JSON.parse(file) as SnippetsLabDbJSON

const folders = new Set<string>()
const tags = new Set<string>()

const db: DB = {
folders: [...DEFAULT_SYSTEM_FOLDERS],
snippets: [],
tags: []
}

json.Snippets.forEach(i => {
if (i.Folder) folders.add(i.Folder)

if (i.Tags.length) {
i.Tags.forEach(t => tags.add(t))
}
})

folders.forEach(i => {
if (i === INBOX) return
db.folders.push({
id: nanoid(8),
name: i,
defaultLanguage: 'plain_text',
parentId: null,
isOpen: false,
isSystem: false,
createdAt: new Date().valueOf(),
updatedAt: new Date().valueOf()
})
})

tags.forEach(i => {
db.tags.push({
id: nanoid(8),
name: i,
createdAt: new Date().valueOf(),
updatedAt: new Date().valueOf()
})
})

json.Snippets.forEach(i => {
const folderId = db.folders.find(f => f.name === i.Folder)?.id || ''
const tagsIds: string[] = []

if (i.Tags.length) {
i.Tags.forEach(t => {
const id = db.tags.find(_t => _t.name === t)?.id
if (id) tagsIds.push(id)
})
}

const snippet: Snippet = {
id: nanoid(8),
name: i.Title,
content: [],
folderId,
tagsIds,
isDeleted: false,
isFavorites: false,
createdAt: new Date(i['Date Created']).valueOf(),
updatedAt: new Date(i['Date Modified']).valueOf()
}

if (i.Fragments.length) {
i.Fragments.forEach(f => {
const _language = snakeCase(f.Language.toLowerCase())
const language = languages.find(i => i.value === _language)?.value

snippet.content.push({
label: f.Title,
value: f.Content,
language: language || 'plain_text'
})
})
}

db.snippets.push(snippet)
})

writeToFile(db)
console.log('Migrate is done')
}
9 changes: 6 additions & 3 deletions src/main/services/ipc/dialog.ts
@@ -1,11 +1,14 @@
import type { MessageBoxRequest } from '@shared/types/main'
import type { DialogRequest, MessageBoxRequest } from '@shared/types/main'
import { dialog, ipcMain } from 'electron'

export const subscribeToDialog = () => {
ipcMain.handle('main:open-dialog', () => {
ipcMain.handle<DialogRequest, any>('main:open-dialog', (event, payload) => {
return new Promise<string>(resolve => {
const { properties, filters } = payload

const dir = dialog.showOpenDialogSync({
properties: ['openDirectory', 'createDirectory']
properties: properties || ['openDirectory', 'createDirectory'],
filters: filters || [{ name: '*', extensions: ['json'] }]
})

if (dir) {
Expand Down
Expand Up @@ -19,10 +19,21 @@
</AppFormItem>
<AppFormItem label="Migrate">
<AppButton @click="onClickMigrate">
Open folder
From massCode v1.0
</AppButton>
<AppButton @click="onClickMigrateFromSnippetsLab">
From SnippetsLab
</AppButton>
<template #desc>
To migrate from v1 select the folder containing the database files.
To migrate from massCode v1.0 select the folder containing the
database files.
<p>To migrate from SnippetsLab select JSON file.</p>
<p>
Some Limitations. During migration from SnippetsLab, snippets with
unsupported languages will be set to default Plain Text. Also since
JSON file does not represent nesting for folders, all folders will
be first level.
</p>
</template>
</AppFormItem>
<AppFormItem label="Count">
Expand All @@ -36,7 +47,7 @@
import { ipc, store, db, track } from '@/electron'
import { useFolderStore } from '@/store/folders'
import { useSnippetStore } from '@/store/snippets'
import type { MessageBoxRequest } from '@shared/types/main'
import type { MessageBoxRequest, DialogRequest } from '@shared/types/main'
import { ref } from 'vue'
const snippetStore = useSnippetStore()
Expand Down Expand Up @@ -95,12 +106,20 @@ const onClickMigrate = async () => {
try {
const path = await ipc.invoke<any, string>('main:open-dialog', {})
if (!path) return
await db.migrate(path)
ipc.invoke('main:restart-api', {})
resetStore()
await snippetStore.getSnippets()
ipc.invoke('main:notification', {
body: 'DB successfully migrated.'
})
snippetStore.getSnippets()
track('app/migrate')
} catch (err) {
const e = err as Error
Expand All @@ -111,22 +130,64 @@ const onClickMigrate = async () => {
}
}
const onClickMigrateFromSnippetsLab = async () => {
const state = await ipc.invoke<MessageBoxRequest, boolean>(
'main:open-message-box',
{
message: 'Are you sure you want to migrate from SnippetsLab',
detail: 'During migrate, the current library will be overwritten.',
buttons: ['Confirm', 'Cancel']
}
)
if (!state) return
try {
const path = await ipc.invoke<DialogRequest, string>('main:open-dialog', {
properties: ['openFile']
})
if (!path) return
db.migrateFromSnippetsLab(path)
ipc.invoke('main:restart-api', {})
resetStore()
await snippetStore.getSnippets()
ipc.invoke('main:notification', {
body: 'DB successfully migrated.'
})
track('app/migrate', 'from-snippets-lab')
} catch (err) {
const e = err as Error
ipc.invoke('main:notification', {
body: e.message
})
console.error(err)
}
}
const setStorageAndRestartApi = (path: string, reset?: boolean) => {
storagePath.value = path
store.preferences.set('storagePath', path)
if (reset) {
store.app.delete('selectedFolderAlias')
store.app.delete('selectedFolderId')
store.app.delete('selectedFolderIds')
store.app.delete('selectedSnippetId')
snippetStore.$reset()
folderStore.$reset()
}
if (reset) resetStore()
ipc.invoke('main:restart-api', {})
}
const resetStore = () => {
store.app.delete('selectedFolderAlias')
store.app.delete('selectedFolderId')
store.app.delete('selectedFolderIds')
store.app.delete('selectedSnippetId')
snippetStore.$reset()
folderStore.$reset()
}
</script>

<style lang="scss" scoped></style>
2 changes: 1 addition & 1 deletion src/renderer/views/Preferences.vue
Expand Up @@ -12,7 +12,7 @@
name="Storage"
value="storage"
>
<Storage />
<StoragePreferences />
</AppMenuItem>
<AppMenuItem
name="Editor"
Expand Down
8 changes: 7 additions & 1 deletion src/shared/types/main/index.d.ts
@@ -1,4 +1,4 @@
import type { IpcRendererEvent } from 'electron'
import type { IpcRendererEvent, OpenDialogOptions } from 'electron'
import type { TrackEvents } from '../main/analytics'
import type { AppStore, PreferencesStore } from './store'

Expand Down Expand Up @@ -77,6 +77,11 @@ export interface MessageBoxRequest {
buttons: string[]
}

export interface DialogRequest {
properties?: OpenDialogOptions['properties']
filters?: OpenDialogOptions['filters']
}

interface EventCallback {
(event?: IpcRendererEvent, ...args: any[]): void
}
Expand Down Expand Up @@ -107,6 +112,7 @@ export interface ElectronBridge {
}
db: {
migrate: (path: string) => Promise<void>
migrateFromSnippetsLab: (path: string) => void
move: (from: string, to: string) => Promise<void>
isExist: (path: string) => boolean
}
Expand Down

0 comments on commit 1d48506

Please sign in to comment.