Skip to content

Commit 5eae4da

Browse files
committed
Merge branch 'develop'
2 parents 83f23cc + b0f8a39 commit 5eae4da

File tree

16 files changed

+602
-67
lines changed

16 files changed

+602
-67
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## v2.0.1
2+
- FIX: Don't remove folders beyond the sync folder when the last bookmark is remove
3+
- FIX: Declare incompatibility with Fx < v57
4+
- FIX: Improve error reporting
5+
16
## v2.0.0
27
- NEW: Sync folder hierarchy
38
- NEW: Allow custom folders to be chosen for syncing

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,39 @@ If you want to sync all bookmarks in your browser you need to select the topmost
4141
If something goes wrong during the sync process the floccus icon will sport a red exclamation mark. In the options pane you can then hover over the status text to get more information about the error.
4242

4343
### Limitations
44-
* Note that currently you cannot sync the same folder with multiple accounts in order to avoid data corruption. If you sync the root folder with one account and sync a sub folder with a different account, that sub-folder will not be synced with the account connected to the root folder anymore.
44+
* Note that currently you cannot sync the same folder with multiple nextcloud accounts in order to avoid data corruption. If you sync the root folder with one account and sync a sub folder with a different account, that sub-folder will not be synced with the account connected to the root folder anymore.
4545
* Floccus yields an error if you attempt to sync a folder with duplicate bookmarks (two or more bookmarks of the same URL). Remove one of the bookmarks for floccus to resume normal functionality.
4646

47+
## Goals and Limitations aka. Is this a good idea?
48+
As there have been debates about whether this software product is a good idea, I've made a little section here with my considerations.
49+
50+
### Goals
51+
The goals of this piece of software
52+
53+
* provide an open cross-platform sync solution for browser data with nextcloud
54+
* performance is a plus, but not necessary
55+
* (eventual) consistency is more important than intention preservation (i.e. when ever a mistake happens during sync, it's guaranteed to be eventually consistent on all sites)
56+
57+
58+
### Current status and Limitations
59+
The WebExtensions bookmarks API has a few limitations:
60+
61+
1. No support for batching or transactions
62+
2. Record GUIDs can change, but are only known to change when Firefox Sync is used.
63+
3. The data format doesn't represent descriptions, tags or separators
64+
4. No way to create a per-device folder
65+
5. It's impossible to express safe operations, because there are no compare-and-set primitives.
66+
6. Triggering a sync after the first change, causing repeated syncs and inconsistency to spread to other devices.
67+
68+
Nonetheless, I've chosen to utilize the WebExtensions API for implementing this sync client. As I'm aware, this decision has (at least) the following consequences:
69+
1. No transaction support (\#1) leads to bad performance
70+
2. No support for transactions (\#1) also can potentially cause intermediate states to be synced. However, all necessary precautions are taken to prevent this and even in the case that this happens, all sites will be eventually consistent, allowing you to manually resolve possible problems after the fact.
71+
3. Due to the modification of GUIDs (\#2), usage of Firefox Sync along with Floccus is discouraged.
72+
4. The incomplete data format (\#3) is an open problem, but doesn't impact the synchronization of the remaining accessible data.
73+
5. The inability to exclude folders from sync in 3rd-party extensions (\#4) is a problem, but manageable when users are able to manually choose folders to ignore. (Currently not implemented)
74+
6. The lack of safe write operations (\#5) can be dealt with similarly to the missing transaction support: Changes made during sync could lead to an unintended but consistent state, which can be resolved manually. Additionally, precautions are taken to prevent this.
75+
7. In order to avoid syncing prematurely (\#6) floccus can employ a timeout to wait until all pending bookmarks operations are done. (Currently not implemented.)
76+
4777
## What's with the name?
4878
[Cirrus floccus](https://en.wikipedia.org/wiki/Cirrus_floccus) is a type of cloud, that <del>can sync your browser data</del> looks very nice.
4979

gulpfile.js

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,47 @@
1-
var gulp = require('gulp');
2-
var browserify = require('browserify');
3-
var babelify = require('babelify');
4-
var tap = require('gulp-tap');
1+
var gulp = require('gulp')
2+
var browserify = require('browserify')
3+
var babelify = require('babelify')
4+
var tap = require('gulp-tap')
55

6-
gulp.task('js', ['src', 'polyfill'])
6+
gulp.task('default', ['html', 'js', '3rd-party'])
77

8-
gulp.task('src', function () {
8+
gulp.task('js', function () {
99
return gulp.src('src/entries/*.js', {read: false}) // no need of reading file because browserify does.
1010
// transform file objects using gulp-tap plugin
1111
.pipe(tap(function (file) {
1212
// replace file contents with browserify's bundle stream
1313
file.contents = browserify(file.path, {
14-
debug: true,
15-
})
16-
.transform(babelify, {presets: ["es2015"], plugins: [
17-
"transform-object-rest-spread"
18-
, "syntax-jsx"
19-
, "transform-react-jsx"
20-
, "transform-async-to-generator"
21-
]})
22-
.bundle()
14+
debug: true,
15+
})
16+
.transform(babelify, {
17+
presets: ['es2015']
18+
, plugins: [
19+
'transform-object-rest-spread'
20+
, 'syntax-jsx'
21+
, 'transform-react-jsx'
22+
, 'transform-async-to-generator'
23+
]
24+
})
25+
.bundle()
2326
}))
24-
.pipe(gulp.dest('./dist/js'));
27+
.pipe(gulp.dest('./dist/js/'))
2528
})
2629

27-
gulp.task('polyfill', function() {
28-
return gulp.src('./node_modules/babel-polyfill/dist/polyfill.js').pipe(gulp.dest('./dist/js/'))
30+
gulp.task('html', function () {
31+
return gulp.src('./views/*.html').pipe(gulp.dest('./dist/html/'))
2932
})
3033

31-
gulp.task('html', function() {
32-
return gulp.src('./views/*.html').pipe(gulp.dest('./dist/html/'))
34+
gulp.task('3rd-party', ['polyfill', 'mocha'])
35+
36+
gulp.task('polyfill', function () {
37+
return gulp.src('./node_modules/babel-polyfill/dist/polyfill.js').pipe(gulp.dest('./dist/js/'))
3338
})
3439

35-
gulp.task('default', ['html', 'js'])
40+
gulp.task('mocha', ['mochajs', 'mochacss'])
41+
42+
gulp.task('mochajs', function () {
43+
return gulp.src('./node_modules/mocha/mocha.js').pipe(gulp.dest('./dist/js/'))
44+
})
45+
gulp.task('mochacss', function () {
46+
return gulp.src('./node_modules/mocha/mocha.css').pipe(gulp.dest('./dist/css/'))
47+
})

manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"manifest_version": 2,
33
"name": "Floccus (nextcloud Sync)",
44
"short_name": "Floccus",
5-
"version": "2.0.0",
5+
"version": "2.0.1",
66
"description": "Sync your browser with nextcloud (currently only bookmarks; more to come)",
77
"icons": {
88
"48": "icons/logo.png"
@@ -11,7 +11,7 @@
1111
"applications": {
1212
"gecko": {
1313
"id": "floccus@handmadeideas.org",
14-
"strict_min_version": "48.0"
14+
"strict_min_version": "57.0"
1515
}
1616
},
1717

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "floccus",
3-
"version": "2.0.0",
3+
"version": "2.0.1",
44
"description": "The goal of this project is to build a browser extension that syncs your browser data with [OwnCloud](http://owncloud.org).",
55
"main": "index.js",
66
"scripts": {
@@ -24,8 +24,11 @@
2424
"babel-preset-es2015": "^6.24.1",
2525
"babelify": "^7.3.0",
2626
"browserify": "^14.3.0",
27+
"chai": "^4.1.2",
28+
"chai-as-promised": "^7.1.1",
2729
"gulp": "^3.9.1",
2830
"gulp-tap": "^1.0.1",
31+
"mocha": "^5.0.4",
2932
"through2": "^2.0.3"
3033
},
3134
"dependencies": {

src/entries/background-script.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,17 @@ class Controller {
5454
})
5555

5656
window.syncAccount = (accountId) => this.syncAccount(accountId)
57+
this.setEnabled(true)
58+
}
59+
60+
setEnabled(enabled) {
61+
this.enabled = enabled
5762
}
5863

5964
async onchange (localId, details) {
65+
if (!this.enabled) {
66+
return
67+
}
6068
const allAccounts = await Account.getAllAccounts()
6169

6270
// Check which accounts contain the bookmark and which used to contain (track) it
@@ -94,6 +102,9 @@ class Controller {
94102
}
95103

96104
syncAccount (accountId) {
105+
if (!this.enabled) {
106+
return
107+
}
97108
if (this.syncing[accountId]) {
98109
return this.syncing[accountId].then(() => {
99110
return this.syncAccount(accountId)
@@ -148,4 +159,4 @@ class Controller {
148159
}
149160
}
150161

151-
var controller = new Controller()
162+
window.controller = new Controller()

src/entries/options.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ function renderAccounts (accounts) {
9292
Account.create({type: 'nextcloud', url: 'http://example.org', username: 'bob', password: 'password'})
9393
.then(() => triggerRender())
9494
}}>Add account</a>
95+
<a className="test-link" href="./test.html">run tests</a>
9596
</div>
9697
}
9798
function renderPicker (cb, tree) {

src/entries/test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mocha.setup('bdd')
2+
require('../test/index.js')
3+
mocha.run();

src/lib/Account.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default class Account {
1616
}
1717

1818
static async create (data) {
19-
let id = Math.floor(Math.random() * 10000000000)
19+
let id = '' + Math.floor(Math.random() * 10000000000)
2020
let storage = new AccountStorage(id)
2121

2222
await storage.setAccountData(data)
@@ -44,7 +44,7 @@ export default class Account {
4444
}
4545

4646
async setData (data) {
47-
this.server = Adapter.factory(data)
47+
this.server.setData(data)
4848
await this.storage.setAccountData(data)
4949
}
5050

@@ -156,7 +156,7 @@ export default class Account {
156156
let nodes = await this.tree.getAllNodes()
157157
await ParallelArray.from(
158158
nodes
159-
.filter(node => !mappings.LocalToServer[node.id])
159+
.filter(node => typeof mappings.LocalToServer[node.id] === 'undefined')
160160
)
161161
.asyncForEach(async node => {
162162
if (mappings.UrlToLocal[node.url]) {
@@ -179,15 +179,17 @@ export default class Account {
179179
var [
180180
serverMarks
181181
, cache
182+
, mappings
182183
] = await Promise.all([
183184
this.server.pullBookmarks()
184185
, this.storage.getCache()
186+
, this.storage.getMappings() // For detecting duplicates
185187
])
186188
var received = {}
187189
// Update known ones and create new ones
188190
await ParallelArray.from(serverMarks)
189191
.asyncForEach(async serverMark => {
190-
if (await this.tree.getLocalIdOf(serverMark)) {
192+
if (typeof (await this.tree.getLocalIdOf(serverMark)) !== 'undefined') {
191193
// known to mappings: (LOCAL|SERVER)UPDATE
192194
received[serverMark.localId] = serverMark // .localId is only avaiable after Tree#getLocalIdOf(...)
193195

@@ -220,6 +222,10 @@ export default class Account {
220222
} else {
221223
// Not yet known:
222224
// CREATE
225+
if (mappings.UrlToLocal[serverMark.url]) {
226+
console.error('Trying to create a URL that is already bookmarked. This shouldn\'t happen! Please tell the developer about this! url=' + serverMark.url)
227+
throw new Error('Trying to create a URL that is already bookmarked. This shouldn\'t happen! Please tell the developer about this! url=' + serverMark.url)
228+
}
223229
const node = await this.tree.createNode(serverMark)
224230
await this.storage.addToCache(node.id, await serverMark.hash())
225231
received[node.id] = serverMark

src/lib/Adapter.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import NextcloudAdapter from './adapters/Nextcloud'
2+
import FakeAdapter from './adapters/Fake'
23

34
export default class Adapter {
45
static factory (data) {
@@ -7,6 +8,9 @@ export default class Adapter {
78
case 'nextcloud':
89
adapter = new NextcloudAdapter(data)
910
break
11+
case 'fake':
12+
adapter = new FakeAdapter(data)
13+
break
1014
default:
1115
throw new Error('Unknown account type')
1216
}
@@ -17,6 +21,10 @@ export default class Adapter {
1721
throw new Error('Cannot instantiate abstract class')
1822
}
1923

24+
setData () {
25+
throw new Error('Not implemented')
26+
}
27+
2028
getData () {
2129
throw new Error('Not implemented')
2230
}

0 commit comments

Comments
 (0)