diff --git a/CHANGELOG.md b/CHANGELOG.md
index be270555f8..ac9f1ac50c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## v2.2.0
+ - NEW: Map local sync folder to a specific server-side folder
+ - FIX: Performance improvements for Firefox
+ - FIX: Race condition removed that would cause issues because same account would be synced twice in parallel
+
## v2.1.0
- NEW: Allow using an extension key to secure entered credentials
- FIX: Various fixes for Firefox
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
index 5e496b03da..df584abbdc 100644
--- a/ISSUE_TEMPLATE.md
+++ b/ISSUE_TEMPLATE.md
@@ -1,20 +1,21 @@
### Software versions
-* Browser:
-* Nextcloud:
-* Nextcloud Bookmarks app:
-* Floccus:
+* Browser:
+* Nextcloud:
+* Nextcloud Bookmarks app:
+* Floccus:
### Steps to reproduce
1. ...
-2.
+2.
### Expected outcome
diff --git a/manifest.json b/manifest.json
index 576414f638..70208d6a68 100644
--- a/manifest.json
+++ b/manifest.json
@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "floccus",
"short_name": "floccus",
- "version": "2.1.0",
+ "version": "2.2.0",
"description": "Sync your bookmarks with nextcloud",
"icons": {
"48": "icons/logo.png"
diff --git a/package.json b/package.json
index 90fa3bf471..6a70b384dd 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "floccus",
- "version": "2.1.0",
+ "version": "2.2.0",
"description": "Sync your bookmarks with nextcloud",
"main": "index.js",
"scripts": {
diff --git a/src/entries/options.js b/src/entries/options.js
index 198c16b528..3936ee1e9e 100644
--- a/src/entries/options.js
+++ b/src/entries/options.js
@@ -109,7 +109,13 @@ function renderAccounts (accounts, secured) {
})
}
{
- Account.create({type: 'nextcloud', url: 'http://example.org', username: 'bob', password: 'password'})
+ Account.create({
+ type: 'nextcloud'
+ , url: 'http://example.org'
+ , username: 'bob'
+ , password: 'password'
+ , serverRoot: ''
+ })
.then(() => triggerRender())
}}>Add account
diff --git a/src/lib/Account.js b/src/lib/Account.js
index abc79a2c62..bb4547863a 100644
--- a/src/lib/Account.js
+++ b/src/lib/Account.js
@@ -11,8 +11,7 @@ export default class Account {
let storage = new AccountStorage(id)
let background = await browser.runtime.getBackgroundPage()
let data = await storage.getAccountData(background.controller.key)
- let localRoot = data.localRoot
- let tree = new Tree(storage, localRoot)
+ let tree = new Tree(storage, data.localRoot, data.serverRoot)
return new Account(id, storage, Adapter.factory(data), tree)
}
@@ -64,6 +63,10 @@ export default class Account {
...ctl
, update: (data) => {
if (JSON.stringify(data) === JSON.stringify(originalData)) return
+ if (originalData.serverRoot !== data.serverRoot) {
+ this.storage.initCache()
+ this.storage.initMappings()
+ }
ctl.update(data)
}
}
@@ -88,7 +91,7 @@ export default class Account {
}
await this.storage.initMappings()
await this.storage.initCache()
- this.tree = new Tree(this.storage, accData.localRoot)
+ this.tree = new Tree(this.storage, accData.localRoot, accData.serverRoot)
}
async isInitialized () {
@@ -124,7 +127,10 @@ export default class Account {
// Server handles existing URLs that we think are new, client handles new URLs that are bookmarked twice locally
await this.sync_createOnServer(mappings)
- let serverList = await this.server.pullBookmarks()
+ let serverRoot = this.getData().serverRoot
+ let serverList = (await this.server.pullBookmarks())
+ .filter(bm => serverRoot ? bm.path.indexOf(serverRoot) === 0 : true)
+
// deletes everything locally that is not new but doesn't exist on the server anymore
await this.sync_deleteFromTree(serverList)
// Goes through server's list and updates creates things locally as needed
@@ -179,9 +185,10 @@ export default class Account {
// ignore this bookmark as it's not supported by the server
return
}
- bookmark.id = serverMark.id
- await this.storage.addToMappings(bookmark)
- await this.storage.addToCache(bookmark.localId, await serverMark.hash())
+ serverMark.localId = bookmark.localId
+ await this.tree.updateNode(serverMark)
+ await this.storage.addToMappings(serverMark)
+ await this.storage.addToCache(serverMark.localId, await serverMark.hash())
},
BATCH_SIZE
)
diff --git a/src/lib/Bookmark.js b/src/lib/Bookmark.js
index 2ca356d666..73ca1163b4 100644
--- a/src/lib/Bookmark.js
+++ b/src/lib/Bookmark.js
@@ -9,6 +9,10 @@ export default class Bookmark {
this.path = path
}
+ getLocalPath (serverRoot) {
+ return this.path.substr(serverRoot.length)
+ }
+
async hash () {
if (!this.hashValue) {
this.hashValue = Bookmark.murmur2(JSON.stringify({
diff --git a/src/lib/Controller.js b/src/lib/Controller.js
index cb2b33b87f..c68bb808e4 100644
--- a/src/lib/Controller.js
+++ b/src/lib/Controller.js
@@ -27,7 +27,6 @@ class AlarmManger {
export default class Controller {
constructor () {
- this.syncing = {}
this.schedule = {}
this.alarms = new AlarmManger(this)
@@ -126,7 +125,7 @@ export default class Controller {
// Filter out any accounts that are not tracking the bookmark
.filter((account, i) => (trackingAccountsFilter[i]))
// Filter out any accounts that are presently syncing
- .filter(account => !this.syncing[account.id])
+ .filter(account => !account.getData().syncing)
// We should now sync all accounts that are involved in this change (2 at max)
accountsToSync.forEach((account) => {
@@ -142,7 +141,7 @@ export default class Controller {
const containingAccount = await Account.getAccountContainingLocalId(localId, ancestors, allAccounts)
if (containingAccount &&
- !this.syncing[containingAccount.id] &&
+ !containingAccount.getData().syncing &&
!accountsToSync.some(acc => acc.id === containingAccount.id)) {
this.scheduleSyncAccount(containingAccount.id)
}
@@ -155,29 +154,21 @@ export default class Controller {
this.schedule[accountId] = setTimeout(() => this.syncAccount(accountId), INACTIVITY_TIMEOUT)
}
- syncAccount (accountId) {
+ async syncAccount (accountId) {
if (!this.enabled) {
return
}
- if (this.syncing[accountId]) {
- return this.syncing[accountId].then(() => {
- return this.syncAccount(accountId)
- })
+ let account = await Account.get(accountId)
+ if (account.getData().syncing) {
+ return
}
- this.syncing[accountId] = Account.get(accountId)
- .then((account) => {
- setTimeout(() => this.updateBadge(), 500)
- return account.sync()
- })
- .then(() => {
- this.syncing[accountId] = false
- this.updateBadge()
- }, (error) => {
- console.error(error)
- this.syncing[accountId] = false
- this.updateBadge()
- })
- return this.syncing[accountId]
+ setTimeout(() => this.updateBadge(), 500)
+ try {
+ await account.sync()
+ } catch (error) {
+ console.error(error)
+ }
+ this.updateBadge()
}
async updateBadge () {
diff --git a/src/lib/Tree.js b/src/lib/Tree.js
index b7b82f7fcd..49f38e8622 100644
--- a/src/lib/Tree.js
+++ b/src/lib/Tree.js
@@ -7,8 +7,9 @@ const treeLock = new AsyncLock()
const reverseStr = (str) => str.split('').reverse().join('')
export default class Tree {
- constructor (storage, rootId) {
+ constructor (storage, rootId, serverRoot) {
this.rootId = rootId
+ this.serverRoot = serverRoot
this.storage = storage
}
@@ -48,14 +49,14 @@ export default class Tree {
, node.id
, node.url
, node.title
- , parentPath || '/' // only root has a trailing slash
+ , parentPath
)
return
}
- const descendantPath = (parentPath || '') + '/' + node.title.replace(/[/]/g, '\\/') // other paths don't have a trailing slash
+ const descendantPath = parentPath + '/' + node.title.replace(/[/]/g, '\\/') // other paths don't have a trailing slash
node.children.map((node) => recurse(node, descendantPath))
}
- tree.children.forEach(node => recurse(node))
+ tree.children.forEach(node => recurse(node, this.serverRoot))
}
getBookmarkByLocalId (localId) {
@@ -79,7 +80,7 @@ export default class Tree {
throw new Error('trying to create a node for a bookmark that already has one')
}
- const parentId = await this.mkdirpPath(bookmark.path)
+ const parentId = await this.mkdirpPath(bookmark.getLocalPath(this.serverRoot))
const node = await browser.bookmarks.create({
parentId
, title: bookmark.title
@@ -102,7 +103,7 @@ export default class Tree {
title: bookmark.title
, url: bookmark.url
})
- const parentId = await this.mkdirpPath(bookmark.path)
+ const parentId = await this.mkdirpPath(bookmark.getLocalPath(this.serverRoot))
await browser.bookmarks.move(bookmark.localId, {parentId})
}
@@ -170,11 +171,11 @@ export default class Tree {
ancestors = ancestors.slice(ancestors.indexOf(relativeToRoot) + 1)
}
- return '/' + (await Promise.all(
+ return (await Promise.all(
ancestors
.map(async ancestor => {
try {
- let bms = await browser.bookmarks.getSubTree(ancestor)
+ let bms = await browser.bookmarks.get(ancestor)
let bm = bms[0]
return bm.title.replace(/[/]/g, '\\/')
} catch (e) {
@@ -225,7 +226,7 @@ export default class Tree {
return path
}
path.unshift(localId)
- let bms = await browser.bookmarks.getSubTree(localId)
+ let bms = await browser.bookmarks.get(localId)
let bm = bms[0]
if (bm.parentId === localId) {
return path // might be that the root is circular
diff --git a/src/lib/adapters/Nextcloud.js b/src/lib/adapters/Nextcloud.js
index 57b61027ae..57cc2d4952 100644
--- a/src/lib/adapters/Nextcloud.js
+++ b/src/lib/adapters/Nextcloud.js
@@ -23,17 +23,29 @@ export default class NextcloudAdapter {
renderOptions (ctl, rootPath) {
let data = this.getData()
+ const saveTimeout = 1000
let onchangeURL = (e) => {
if (this.saveTimeout) clearTimeout(this.saveTimeout)
- this.saveTimeout = setTimeout(() => ctl.update({...data, url: e.target.value}), 300)
+ this.saveTimeout = setTimeout(() => ctl.update({...data, url: e.target.value}), saveTimeout)
}
let onchangeUsername = (e) => {
if (this.saveTimeout) clearTimeout(this.saveTimeout)
- this.saveTimeout = setTimeout(() => ctl.update({...data, username: e.target.value}), 300)
+ this.saveTimeout = setTimeout(() => ctl.update({...data, username: e.target.value}), saveTimeout)
}
let onchangePassword = (e) => {
if (this.saveTimeout) clearTimeout(this.saveTimeout)
- this.saveTimeout = setTimeout(() => ctl.update({...data, password: e.target.value}), 300)
+ this.saveTimeout = setTimeout(() => ctl.update({...data, password: e.target.value}), saveTimeout)
+ }
+ let onchangeServerRoot = (e) => {
+ if (this.saveTimeout) clearTimeout(this.saveTimeout)
+ this.saveTimeout = setTimeout(() => {
+ let val = e.target.value
+ if (val[val.length - 1] === '/') {
+ val = val.substr(0, val.length - 1)
+ e.target.value = val
+ }
+ ctl.update({...data, serverRoot: e.target.value})
+ }, saveTimeout)
}
return