braidfs 0.0.81 → 0.0.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +186 -149
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -48,14 +48,17 @@ require('fs').mkdirSync(proxy_base_meta, { recursive: true })
48
48
  require('fs').mkdirSync(trash, { recursive: true })
49
49
  require('fs').mkdirSync(temp_folder, { recursive: true })
50
50
 
51
- // process command line args
52
- let to_run_in_background = process.platform === 'darwin' ? `
51
+ // Add instructions for how to run in the background on this OS
52
+ var to_run_in_background = process.platform === 'darwin' ? `
53
53
  To run daemon in background:
54
54
  launchctl submit -l org.braid.braidfs -- braidfs run` : ''
55
- let argv = process.argv.slice(2)
55
+ // ...except this doesn't work yet. So disable.
56
+ to_run_in_background = ''
56
57
 
57
58
  console.log(`braidfs version: ${require(`${__dirname}/package.json`).version}`)
58
59
 
60
+ // process command line args
61
+ var argv = process.argv.slice(2)
59
62
  if (argv.length === 1 && argv[0].match(/^(run|serve)$/)) {
60
63
  return main()
61
64
  } else if (argv.length && argv.length % 2 == 0 && argv.every((x, i) => i % 2 != 0 || x.match(/^(sync|unsync)$/))) {
@@ -255,7 +258,6 @@ function skip_file(path) {
255
258
  async function trash_file(fullpath, path) {
256
259
  // throw this unrecognized file into the trash,
257
260
  let dest = `${trash}/${braid_text.encode_filename(path)}_${Math.random().toString(36).slice(2)}`
258
- console.log(`moving untracked file ${fullpath} to ${dest}`)
259
261
  await require('fs').promises.rename(fullpath, dest)
260
262
 
261
263
  // and log an error
@@ -263,6 +265,8 @@ async function trash_file(fullpath, path) {
263
265
  }
264
266
 
265
267
  async function log_error(text) {
268
+ console.log(`LOGGING ERROR: ${text}`)
269
+
266
270
  var x = await braid_text.get('.braidfs/errors', {}),
267
271
  len = [...x.body].length
268
272
  await braid_text.put('.braidfs/errors', {
@@ -426,6 +430,7 @@ async function proxy_url(url) {
426
430
 
427
431
  self.peer = Math.random().toString(36).slice(2)
428
432
  self.local_edit_counter = 0
433
+ self.fork_point = null
429
434
  var file_last_version = null,
430
435
  file_last_digest = null
431
436
  self.file_last_text = null
@@ -468,37 +473,60 @@ async function proxy_url(url) {
468
473
  file_loop_pump()
469
474
  }
470
475
 
471
- self.signal_file_needs_writing = () => {
476
+ self.signal_file_needs_writing = (just_meta_file) => {
472
477
  if (freed) return
473
- file_needs_writing = true
478
+
479
+ if (!just_meta_file) file_needs_writing = true
480
+ else if (just_meta_file && !file_needs_writing)
481
+ file_needs_writing = 'just_meta_file'
482
+
474
483
  file_loop_pump()
475
484
  }
476
485
 
477
- async function send_out(stuff) {
486
+ async function my_fetch(params) {
478
487
  if (!start_something()) return
479
- if (is_external_link) {
480
- try {
481
- console.log(`send_out ${url} ${JSON.stringify(stuff, null, 4).slice(0, 1000)}`)
482
-
483
- let a = new AbortController()
484
- aborts.add(a)
485
- await braid_fetch(url, {
486
- signal: a.signal,
487
- headers: {
488
- "Merge-Type": "dt",
489
- "Content-Type": 'text/plain',
490
- ...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname])
491
- },
492
- method: "PUT",
493
- retry: true,
494
- ...stuff
495
- })
496
- aborts.delete(a)
497
- } catch (e) {
498
- if (e?.name !== "AbortError") console.log(e)
488
+ try {
489
+ var a = new AbortController()
490
+ aborts.add(a)
491
+ return await braid_fetch(url, {
492
+ signal: a.signal,
493
+ headers: {
494
+ "Merge-Type": "dt",
495
+ "Content-Type": 'text/plain',
496
+ ...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname])
497
+ },
498
+ retry: true,
499
+ ...params
500
+ })
501
+ } catch (e) {
502
+ if (e?.name !== "AbortError") console.log(e)
503
+ } finally {
504
+ aborts.delete(a)
505
+ finish_something()
506
+ }
507
+ }
508
+
509
+ async function send_out(stuff) {
510
+ if (!is_external_link) return
511
+
512
+ console.log(`send_out ${url} ${JSON.stringify(stuff, null, 4).slice(0, 1000)}`)
513
+
514
+ var r = await my_fetch({ method: "PUT", ...stuff })
515
+
516
+ // the server has acknowledged this version,
517
+ // so add it to the fork point
518
+ if (r.ok) await self.update_fork_point(stuff.version[0], stuff.parents)
519
+
520
+ // if we're not authorized,
521
+ if (r.status == 401 || r.status == 403) {
522
+ // and it's one of our versions (a local edit),
523
+ if (self.peer === braid_text.decode_version(stuff.version[0])[0]) {
524
+ // then revert it
525
+ console.log(`access denied: reverting local edits`)
526
+ unproxy_url(url)
527
+ proxy_url(url)
499
528
  }
500
529
  }
501
- finish_something()
502
530
  }
503
531
 
504
532
  if (!start_something()) return
@@ -513,7 +541,8 @@ async function proxy_url(url) {
513
541
  version: file_last_version,
514
542
  digest: file_last_digest,
515
543
  peer: self.peer,
516
- local_edit_counter: self.local_edit_counter
544
+ local_edit_counter: self.local_edit_counter,
545
+ fork_point: self.fork_point
517
546
  } = Array.isArray(meta) ? { version: meta } : meta)
518
547
 
519
548
  if (!self.peer) self.peer = Math.random().toString(36).slice(2)
@@ -559,6 +588,16 @@ async function proxy_url(url) {
559
588
  var fullpath = await get_fullpath()
560
589
 
561
590
  while (file_needs_reading || file_needs_writing) {
591
+ async function write_meta_file() {
592
+ await require('fs').promises.writeFile(meta_path, JSON.stringify({
593
+ version: file_last_version,
594
+ digest: sha256(self.file_last_text),
595
+ peer: self.peer,
596
+ local_edit_counter: self.local_edit_counter,
597
+ fork_point: self.fork_point
598
+ }))
599
+ }
600
+
562
601
  if (file_needs_reading) {
563
602
  console.log(`reading file: ${fullpath}`)
564
603
 
@@ -601,12 +640,7 @@ async function proxy_url(url) {
601
640
 
602
641
  await braid_text.put(url, { version, parents, patches, peer: self.peer, merge_type: 'dt' })
603
642
 
604
- await require('fs').promises.writeFile(meta_path, JSON.stringify({
605
- version: file_last_version,
606
- digest: sha256(self.file_last_text),
607
- peer: self.peer,
608
- local_edit_counter: self.local_edit_counter
609
- }))
643
+ await write_meta_file()
610
644
  } else {
611
645
  add_to_version_cache(text, file_last_version)
612
646
 
@@ -620,7 +654,10 @@ async function proxy_url(url) {
620
654
  self.file_last_stat = stat
621
655
  self.file_ignore_until = Date.now() + 1000
622
656
  }
623
- if (file_needs_writing) {
657
+ if (file_needs_writing === 'just_meta_file') {
658
+ file_needs_writing = false
659
+ await write_meta_file()
660
+ } else if (file_needs_writing) {
624
661
  file_needs_writing = false
625
662
  let { version, body } = await braid_text.get(url, {})
626
663
  if (!v_eq(version, file_last_version)) {
@@ -644,16 +681,10 @@ async function proxy_url(url) {
644
681
  self.file_last_text = body
645
682
  self.file_ignore_until = Date.now() + 1000
646
683
  await require('fs').promises.writeFile(fullpath, self.file_last_text)
647
-
648
-
649
- await require('fs').promises.writeFile(meta_path, JSON.stringify({
650
- version: file_last_version,
651
- digest: sha256(self.file_last_text),
652
- peer: self.peer,
653
- local_edit_counter: self.local_edit_counter
654
- }))
655
684
  }
656
685
 
686
+ await write_meta_file()
687
+
657
688
  if (await is_read_only(fullpath) !== self.file_read_only) {
658
689
  self.file_ignore_until = Date.now() + 1000
659
690
  await set_read_only(fullpath, self.file_read_only)
@@ -672,8 +703,90 @@ async function proxy_url(url) {
672
703
  file_loop_pump_lock--
673
704
  }
674
705
 
675
- if (is_external_link) connect()
676
- function connect() {
706
+ self.update_fork_point = async (event, parents) => {
707
+ var resource = await braid_text.get_resource(url)
708
+
709
+ // special case:
710
+ // if current fork point has all parents,
711
+ // then we can just remove those
712
+ // and add event
713
+ var fork_set = new Set(self.fork_point)
714
+ if (parents.every(p => fork_set.has(p))) {
715
+ parents.forEach(p => fork_set.delete(p))
716
+ fork_set.add(event)
717
+ self.fork_point = [...fork_set.values()]
718
+ } else {
719
+ // full-proof approach..
720
+ var looking_for = fork_set
721
+ looking_for.add(event)
722
+
723
+ self.fork_point = []
724
+ var shadow = new Set()
725
+
726
+ var bytes = resource.doc.toBytes()
727
+ var [_, events, parentss] = braid_text.dt_parse([...bytes])
728
+ for (var i = events.length - 1; i >= 0 && looking_for.size; i--) {
729
+ var e = events[i].join('-')
730
+ if (looking_for.has(e)) {
731
+ looking_for.delete(e)
732
+ if (!shadow.has(e)) self.fork_point.push(e)
733
+ shadow.add(e)
734
+ }
735
+ if (shadow.has(e))
736
+ parentss[i].forEach(p => shadow.add(p.join('-')))
737
+ }
738
+ }
739
+ self.fork_point.sort()
740
+ self.signal_file_needs_writing(true)
741
+ }
742
+
743
+ async function find_fork_point() {
744
+ console.log(`[find_fork_point] url: ${url}`)
745
+
746
+ // see if they have the fork point
747
+ if (self.fork_point) {
748
+ var r = await my_fetch({ method: "HEAD", version: self.fork_point })
749
+ if (r.ok) {
750
+ console.log(`[find_fork_point] they have our latest fork point, horray!`)
751
+ return self.fork_point
752
+ }
753
+ }
754
+
755
+ // otherwise let's binary search for new fork point..
756
+ var resource = await braid_text.get_resource(url)
757
+ var bytes = resource.doc.toBytes()
758
+ var [_, events, __] = braid_text.dt_parse([...bytes])
759
+ events = events.map(x => x.join('-'))
760
+
761
+ var min = -1
762
+ var max = events.length
763
+ self.fork_point = []
764
+ while (min + 1 < max) {
765
+ var i = Math.floor((min + max)/2)
766
+ var version = [events[i]]
767
+
768
+ console.log(`min=${min}, max=${max}, i=${i}, version=${version}`)
769
+
770
+ var st = Date.now()
771
+ var r = await my_fetch({ method: "HEAD", version })
772
+ console.log(`fetched in ${Date.now() - st}`)
773
+
774
+ if (r.ok) {
775
+ min = i
776
+ self.fork_point = version
777
+ } else max = i
778
+ }
779
+ console.log(`[find_fork_point] settled on: ${JSON.stringify(self.fork_point)}`)
780
+ self.signal_file_needs_writing(true)
781
+ return self.fork_point
782
+ }
783
+
784
+ if (is_external_link) find_fork_point().then(fork_point => {
785
+ connect(fork_point)
786
+ send_new_stuff(fork_point)
787
+ })
788
+
789
+ function connect(fork_point) {
677
790
  let a = new AbortController()
678
791
  aborts.add(a)
679
792
  self.reconnect = () => {
@@ -681,7 +794,7 @@ async function proxy_url(url) {
681
794
 
682
795
  aborts.delete(a)
683
796
  a.abort()
684
- connect()
797
+ connect(fork_point)
685
798
  }
686
799
 
687
800
  console.log(`connecting to ${url}`)
@@ -695,12 +808,8 @@ async function proxy_url(url) {
695
808
  subscribe: true,
696
809
  retry: {
697
810
  onRes: (res) => {
698
- if (res.status !== 209) {
699
- log_error(`Can't sync ${url} -- got bad response ${res.status} from server (expected 209)`)
700
- return console.log(
701
- `FAILED TO CONNECT TO: ${url}\n` +
702
- `GOT STATUS CODE: ${res.status}, expected 209.`)
703
- }
811
+ if (res.status !== 209)
812
+ return log_error(`Can't sync ${url} -- got bad response ${res.status} from server (expected 209)`)
704
813
 
705
814
  console.log(`connected to ${url}`)
706
815
  console.log(` editable = ${res.headers.get('editable')}`)
@@ -711,8 +820,9 @@ async function proxy_url(url) {
711
820
  },
712
821
  heartbeats: 120,
713
822
  parents: async () => {
714
- let cur = await braid_text.get(url, {})
715
- if (cur.version.length) return cur.version
823
+ var x = fork_point || await find_fork_point()
824
+ fork_point = null
825
+ return x
716
826
  },
717
827
  peer: self.peer
718
828
  }).then(x => {
@@ -730,6 +840,10 @@ async function proxy_url(url) {
730
840
 
731
841
  await braid_text.put(url, { ...update, peer: self.peer, merge_type: 'dt' })
732
842
 
843
+ // the server is giving us this version,
844
+ // so they must have it,
845
+ // so let's add it to our fork point
846
+ await self.update_fork_point(update.version[0], update.parents)
733
847
 
734
848
  self.signal_file_needs_writing()
735
849
  finish_something()
@@ -738,107 +852,30 @@ async function proxy_url(url) {
738
852
  }
739
853
 
740
854
  // send them stuff we have but they don't
741
- if (is_external_link) send_new_stuff()
742
- async function send_new_stuff() {
743
- if (!start_something()) return
744
- try {
745
- var a = new AbortController()
746
- aborts.add(a)
747
- var r = await braid_fetch(url, {
748
- signal: a.signal,
749
- method: "HEAD",
750
- headers: {
751
- Accept: 'text/plain',
752
- ...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname]),
753
- },
754
- retry: true
755
- })
756
- aborts.delete(a)
757
-
758
- if (r.headers.get('editable') === 'false') {
759
- console.log('do not send updates for read-only file: ' + url)
760
- return
761
- }
762
-
763
- if (r.headers.get('version') == null) {
764
- log_error(`Can't sync ${url} -- got no version from server`)
765
- return console.log(`GOT NO VERSION FROM: ${url}`)
766
- }
767
- var parents = JSON.parse(`[${r.headers.get('version')}]`)
768
-
769
- var bytes = (await braid_text.get_resource(url)).doc.toBytes()
770
- var [_, versions, __] = braid_text.dt_parse([...bytes])
771
- var agents = {}
772
- for (var v of versions) agents[v[0]] = v[1]
773
-
774
- function we_have_it(version) {
775
- var m = version.match(/^(.*)-(\d+)$/s)
776
- var agent = m[1]
777
- var seq = 1 * m[2]
778
- return (agents[agent] ?? -1) >= seq
779
- }
780
-
781
- if (parents.length && !parents.some(we_have_it)) {
782
- var min = 0
783
- var max = versions.length
784
- var last_good_parents = []
785
- while (min < max) {
786
- var i = Math.ceil((min + max)/2)
787
- parents = i ? [versions[i - 1].join('-')] : []
788
-
789
- console.log(`min=${min}, max=${max}, i=${i}, parents=${parents}`)
790
-
791
- var a = new AbortController()
792
- aborts.add(a)
793
- var st = Date.now()
794
- var r = await braid_fetch(url, {
795
- signal: a.signal,
796
- method: "HEAD",
797
- parents,
798
- headers: {
799
- Accept: 'text/plain',
800
- ...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname]),
801
- },
802
- retry: true
803
- })
804
- console.log(`fetched in ${Date.now() - st}`)
805
- aborts.delete(a)
806
-
807
- if (r.ok) {
808
- min = i
809
- last_good_parents = parents
810
- } else {
811
- max = i - 1
812
- }
855
+ async function send_new_stuff(fork_point) {
856
+ var r = await my_fetch({ method: "HEAD" })
857
+ if (r.headers.get('editable') === 'false')
858
+ return console.log('do not send updates for read-only file: ' + url)
859
+
860
+ var in_parallel = create_parallel_promises(10)
861
+ braid_text.get(url, braid_text_get_options = {
862
+ parents: fork_point,
863
+ merge_type: 'dt',
864
+ peer: self.peer,
865
+ subscribe: async (u) => {
866
+ if (u.version.length) {
867
+ self.signal_file_needs_writing()
868
+ in_parallel(() => send_out({...u, peer: self.peer}))
813
869
  }
814
- parents = last_good_parents
815
-
816
- console.log(`found good parents: ${parents}: ${url}`)
817
- }
818
-
819
- var in_parallel = create_parallel_promises(10)
820
- braid_text.get(url, braid_text_get_options = {
821
- parents,
822
- merge_type: 'dt',
823
- peer: self.peer,
824
- subscribe: async (u) => {
825
- if (u.version.length) {
826
- self.signal_file_needs_writing()
827
- in_parallel(() => send_out({...u, peer: self.peer}))
828
- }
829
- },
830
- })
831
- } catch (e) {
832
- if (e?.name !== "AbortError") console.log(e)
833
- }
834
- finish_something()
870
+ },
871
+ })
835
872
  }
836
873
 
837
874
  // for config and errors file, listen for web changes
838
875
  if (!is_external_link) braid_text.get(url, braid_text_get_options = {
839
876
  merge_type: 'dt',
840
877
  peer: self.peer,
841
- subscribe: self.signal_file_needs_writing,
878
+ subscribe: () => self.signal_file_needs_writing(),
842
879
  })
843
880
 
844
881
  return self
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "braidfs",
3
- "version": "0.0.81",
3
+ "version": "0.0.84",
4
4
  "description": "braid technology synchronizing files and webpages",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braidfs",
7
7
  "homepage": "https://braid.org",
8
8
  "dependencies": {
9
9
  "braid-http": "^1.3.73",
10
- "braid-text": "^0.2.22",
10
+ "braid-text": "^0.2.23",
11
11
  "chokidar": "^3.6.0"
12
12
  },
13
13
  "bin": {