braidfs 0.0.131 → 0.0.133

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 +135 -207
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var { diff_main } = require(`${__dirname}/diff.js`),
4
4
  braid_text = require("braid-text"),
5
+ braid_blob = require("braid-blob"),
5
6
  braid_fetch = require('braid-http').fetch
6
7
 
7
8
  // Helper function to check if a file is binary based on its extension
@@ -27,6 +28,8 @@ var braidfs_config_dir = `${sync_base}/.braidfs`,
27
28
  braidfs_config_file = `${braidfs_config_dir}/config`,
28
29
  sync_base_meta = `${braidfs_config_dir}/proxy_base_meta`
29
30
  braid_text.db_folder = `${braidfs_config_dir}/braid-text-db`
31
+ braid_blob.db_folder = { read: () => {}, write: () => {}, delete: () => {} }
32
+ braid_blob.meta_folder = `${braidfs_config_dir}/braid-blob-meta`
30
33
  var trash = `${braidfs_config_dir}/trash`
31
34
  var temp_folder = `${braidfs_config_dir}/temp`
32
35
 
@@ -417,6 +420,7 @@ async function sync_url(url) {
417
420
  url = normalized_url
418
421
 
419
422
  await braid_text.db_folder_init()
423
+ await braid_blob.init()
420
424
 
421
425
  var is_external_link = url.match(/^https?:\/\//),
422
426
  path = is_external_link ? url.replace(/^https?:\/\//, '') : url,
@@ -429,6 +433,8 @@ async function sync_url(url) {
429
433
  if (!sync_url.cache) sync_url.cache = {}
430
434
  if (!sync_url.chain) sync_url.chain = Promise.resolve()
431
435
  if (!sync_url.cache[path]) {
436
+ // console.log(`sync_url: ${url}`)
437
+
432
438
  var self = {url},
433
439
  freed = false,
434
440
  aborts = new Set()
@@ -449,7 +455,10 @@ async function sync_url(url) {
449
455
  await self.disconnect?.()
450
456
  await wait_promise
451
457
 
452
- await braid_text.delete(url)
458
+ if (self.merge_type === 'dt')
459
+ await braid_text.delete(url)
460
+ else if (self.merge_type === 'aww')
461
+ await braid_blob.delete(url)
453
462
 
454
463
  try {
455
464
  console.log(`trying to delete: ${meta_path}`)
@@ -461,8 +470,17 @@ async function sync_url(url) {
461
470
  await require('fs').promises.unlink(fp)
462
471
  } catch (e) {}
463
472
  }
464
- sync_url.cache[path] = sync_url.chain = sync_url.chain.then(init)
473
+
474
+ sync_url.cache[path] = (async () => {
475
+ self.merge_type = await detect_merge_type()
476
+ if (self.merge_type === 'dt') {
477
+ return await (sync_url.chain = sync_url.chain.then(init))
478
+ } else if (self.merge_type === 'aww') {
479
+ return await (sync_url.chain = sync_url.chain.then(init_binary_sync))
480
+ } else throw new Error(`unknown merge-type: ${self.merge_type}`)
481
+ })()
465
482
  }
483
+ return
466
484
 
467
485
  async function detect_merge_type() {
468
486
  // special case for .braidfs/config and .braidfs/error
@@ -500,19 +518,23 @@ async function sync_url(url) {
500
518
  }
501
519
 
502
520
  async function init_binary_sync() {
521
+ await ensure_path_stuff()
503
522
  if (freed) return
504
523
 
505
524
  console.log(`init_binary_sync: ${url}`)
506
525
 
507
526
  async function save_meta() {
508
527
  await require('fs').promises.writeFile(meta_path, JSON.stringify({
528
+ merge_type: self.merge_type,
509
529
  peer: self.peer,
510
- version: self.version,
511
530
  file_mtimeNs_str: self.file_mtimeNs_str
512
531
  }))
513
532
  }
514
533
 
515
- await within_fiber(url, async () => {
534
+ self.file_mtimeNs_str = null
535
+ self.file_read_only = null
536
+
537
+ await within_fiber(fullpath, async () => {
516
538
  try {
517
539
  Object.assign(self, JSON.parse(
518
540
  await require('fs').promises.readFile(meta_path, 'utf8')))
@@ -520,227 +542,137 @@ async function sync_url(url) {
520
542
  if (freed) return
521
543
 
522
544
  if (!self.peer) self.peer = Math.random().toString(36).slice(2)
523
-
524
- // create file if it doesn't exist
525
- var fullpath = await get_fullpath()
526
- if (freed) return
527
- if (!(await file_exists(fullpath))) {
528
- if (freed) return
529
- await wait_on(require('fs').promises.writeFile(fullpath, ''))
530
- if (freed) return
531
- var stat = await require('fs').promises.stat(fullpath, { bigint: true })
532
- if (freed) return
533
- self.file_mtimeNs_str = '' + stat.mtimeNs
534
- self.last_touch = Date.now()
535
- }
536
- if (freed) return
537
-
538
- await save_meta()
539
545
  })
540
546
  if (freed) return
541
547
 
542
- var waitTime = 1
543
- var last_connect_timer = null
548
+ self.signal_file_needs_reading = async () => {
549
+ await within_fiber(fullpath, async () => {
550
+ try {
551
+ if (freed) return
544
552
 
545
- self.signal_file_needs_reading = () => {}
553
+ var fullpath = await get_fullpath()
554
+ if (freed) return
546
555
 
547
- connect()
548
- async function connect() {
549
- if (freed) return
550
- if (last_connect_timer) return
556
+ var stat = await require('fs').promises.stat(fullpath, { bigint: true })
557
+ if (freed) return
551
558
 
552
- var closed = false
553
- var prev_disconnect = self.disconnect
554
- self.disconnect = async () => {
555
- if (closed) return
556
- closed = true
557
- reconnect_rate_limiter.on_diss(url)
558
- for (var a of aborts) a.abort()
559
- aborts.clear()
560
- }
561
- self.reconnect = connect
562
-
563
- await prev_disconnect?.()
564
- if (freed || closed) return
565
-
566
- await reconnect_rate_limiter.get_turn(url)
567
- if (freed || closed) return
568
-
569
- function retry(e) {
570
- if (freed || closed) return
571
- var p = self.disconnect()
572
-
573
- var delay = waitTime * (config.retry_delay_ms ?? 1000)
574
- console.log(`reconnecting in ${(delay / 1000).toFixed(2)}s: ${url} after error: ${e}`)
575
- last_connect_timer = setTimeout(async () => {
576
- await p
577
- last_connect_timer = null
578
- connect()
579
- }, delay)
580
- waitTime = Math.min(waitTime + 1, 3)
581
- }
559
+ if (self.file_mtimeNs_str !== '' + stat.mtimeNs) {
560
+ var data = await require('fs').promises.readFile(fullpath, { encoding: 'utf8' })
561
+ if (freed) return
582
562
 
583
- try {
584
- var a = new AbortController()
585
- aborts.add(a)
586
-
587
- var fork_point
588
- if (self.version) {
589
- // Check if server has our version
590
- var r = await braid_fetch(url, {
591
- signal: a.signal,
592
- method: "HEAD",
593
- version: ['' + self.version]
594
- })
595
- if (r.ok) fork_point = ['' + self.version]
563
+ await braid_blob.put(url, data, { skip_write: true })
564
+ if (freed) return
565
+
566
+ self.file_mtimeNs_str = '' + stat.mtimeNs
567
+ await save_meta()
568
+ }
569
+ } catch (e) {
570
+ if (e.code !== 'ENOENT') throw e
596
571
  }
572
+ })
573
+ }
574
+ await self.signal_file_needs_reading()
597
575
 
598
- var res = await braid_fetch(url, {
599
- signal: a.signal,
600
- headers: {
601
- ...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname]),
602
- },
603
- subscribe: true,
604
- heartbeats: 120,
605
- peer: self.peer,
606
- parents: fork_point || []
607
- })
608
- if (freed || closed) return
609
-
610
- if (res.status < 200 || res.status >= 300) return retry(new Error(`unexpected status: ${res.status}`))
611
-
612
- if (res.status !== 209)
613
- return log_error(`Can't sync ${url} -- got bad response ${res.status} from server (expected 209)`)
614
-
615
- self.file_read_only = res.headers.get('editable') === 'false'
576
+ var db = {
577
+ read: async (_key) => {
578
+ return await within_fiber(fullpath, async () => {
579
+ var fullpath = await get_fullpath()
580
+ if (freed) return
616
581
 
617
- await wait_on(within_fiber(url, async () => {
582
+ try {
583
+ return await require('fs').promises.readFile(fullpath)
584
+ } catch (e) {
585
+ if (e.code === 'ENOENT') return null
586
+ throw e
587
+ }
588
+ })
589
+ },
590
+ write: async (_key, data) => {
591
+ return await within_fiber(fullpath, async () => {
618
592
  var fullpath = await get_fullpath()
619
- if (freed || closed) return
593
+ if (freed) return
620
594
 
621
- await set_read_only(fullpath, self.file_read_only)
622
- }))
595
+ try {
596
+ var temp = `${temp_folder}/${Math.random().toString(36).slice(2)}`
597
+ await require('fs').promises.writeFile(temp, data)
598
+ if (freed) return
623
599
 
624
- console.log(`connected to ${url}${self.file_read_only ? ' (readonly)' : ''}`)
600
+ var stat = await require('fs').promises.stat(temp, { bigint: true })
601
+ if (freed) return
625
602
 
626
- reconnect_rate_limiter.on_conn(url)
627
-
628
- res.subscribe(async update => {
629
- if (freed || closed) return
630
- if (update.version.length === 0) return
631
- if (update.version.length !== 1) throw 'unexpected'
632
- var version = 1*update.version[0]
633
- if (!update.body) return
634
-
635
- if (self.version != null &&
636
- version <= self.version) return
637
- self.version = version
638
-
639
- await within_fiber(url, async () => {
640
- var fullpath = await get_fullpath()
641
- if (freed || closed) return
642
-
643
- await wait_on(set_read_only(fullpath, false))
644
- if (freed || closed) return
645
- await wait_on(require('fs').promises.writeFile(fullpath, update.body))
646
- if (freed || closed) return
647
- await wait_on(set_read_only(fullpath, self.file_read_only))
648
- if (freed || closed) return
649
-
650
- var stat = await require('fs').promises.stat(fullpath, { bigint: true })
651
- if (freed || closed) return
652
- self.file_mtimeNs_str = '' + stat.mtimeNs
653
- self.last_touch = Date.now()
603
+ await require('fs').promises.rename(temp, fullpath)
604
+ if (freed) return
654
605
 
606
+ self.file_mtimeNs_str = '' + stat.mtimeNs
655
607
  await save_meta()
656
- })
657
- }, retry)
658
-
659
- async function send_file(fullpath) {
660
- var body = await require('fs').promises.readFile(fullpath)
661
- if (freed || closed) return
662
-
663
- try {
664
- var a = new AbortController()
665
- aborts.add(a)
666
- var r = await braid_fetch(url, {
667
- method: 'PUT',
668
- signal: a.signal,
669
- version: ['' + self.version],
670
- body,
671
- headers: {
672
- ...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname])
673
- },
674
- })
675
- if (freed || closed) return
676
-
677
- // if we're not authorized,
678
- if (r.status == 401 || r.status == 403) {
679
- // then revert it
680
- console.log(`access denied: reverting local edits`)
681
- unsync_url(url)
682
- sync_url(url)
683
- } else if (!r.ok) {
684
- retry(new Error(`unexpected PUT status: ${r.status}`))
685
- }
686
- } catch (e) { retry(e) }
687
- }
688
-
689
- // if what we have now is newer than what the server has,
690
- // go ahead and send it
691
- await within_fiber(url, async () => {
692
- if (freed || closed) return
608
+ } catch (e) {
609
+ if (e.code === 'ENOENT') return null
610
+ throw e
611
+ }
612
+ })
613
+ },
614
+ delete: async (_key) => {
615
+ return await within_fiber(fullpath, async () => {
693
616
  var fullpath = await get_fullpath()
694
- if (freed || closed) return
617
+ if (freed) return
618
+
695
619
  try {
696
- var stat = await require('fs').promises.stat(fullpath, { bigint: true })
697
- } catch (e) { return }
698
- if (freed || closed) return
699
-
700
- var server_v = JSON.parse(`[${res.headers.get('current-version')}]`)
701
- if (self.version != null &&
702
- '' + stat.mtimeNs === self.file_mtimeNs_str && (
703
- !server_v.length ||
704
- 1*server_v[0] < self.version
705
- )) await send_file(fullpath)
620
+ await require('fs').promises.unlink(fullpath)
621
+ } catch (e) {
622
+ if (e.code !== 'ENOENT') throw e
623
+ }
706
624
  })
625
+ }
626
+ }
707
627
 
708
- self.signal_file_needs_reading = () => {
709
- within_fiber(url, async () => {
710
- if (freed || closed) return
711
-
712
- var fullpath = await get_fullpath()
713
- if (freed || closed) return
714
-
715
- try {
716
- var stat = await require('fs').promises.stat(fullpath, { bigint: true })
717
- } catch (e) { return }
718
- if (freed || closed) return
628
+ var ac
629
+ function start_sync() {
630
+ if (ac) ac.abort()
631
+ if (freed) return
719
632
 
720
- if ('' + stat.mtimeNs !== self.file_mtimeNs_str) {
721
- self.version = Math.max((self.version || 0) + 1,
722
- Math.round(Number(stat.mtimeNs) / 1000000))
633
+ var closed = false
634
+ ac = new AbortController()
723
635
 
724
- self.file_mtimeNs_str = '' + stat.mtimeNs
725
- self.last_touch = Date.now()
636
+ self.disconnect = async () => {
637
+ if (closed) return
638
+ closed = true
639
+ reconnect_rate_limiter.on_diss(url)
640
+ ac.abort()
641
+ }
726
642
 
727
- await save_meta()
728
- await send_file(fullpath)
729
- }
730
- })
731
- }
732
- self.signal_file_needs_reading()
733
- } catch (e) { return retry(e) }
643
+ braid_blob.sync(url, new URL(url), {
644
+ db,
645
+ signal: ac.signal,
646
+ peer: self.peer,
647
+ headers: {
648
+ 'Content-Type': 'text/plain',
649
+ ...(x => x && { Cookie: x })(config.cookies?.[new URL(url).hostname])
650
+ },
651
+ on_pre_connect: () => reconnect_rate_limiter.get_turn(url),
652
+ on_res: res => {
653
+ if (freed) return
654
+ reconnect_rate_limiter.on_conn(url)
655
+ self.file_read_only = res.headers.get('editable') === 'false'
656
+ console.log(`connected to ${url}${self.file_read_only ? ' (readonly)' : ''}`)
657
+ },
658
+ on_unauthorized: async () => {
659
+ console.log(`access denied: reverting local edits`)
660
+ unsync_url(url)
661
+ sync_url(url)
662
+ },
663
+ on_disconnect: () => reconnect_rate_limiter.on_diss(url)
664
+ })
734
665
  }
735
666
 
667
+ self.reconnect = () => start_sync()
668
+
669
+ start_sync()
736
670
  return self
737
671
  }
738
-
739
- async function init() {
740
- if (freed) return
741
-
742
- // console.log(`sync_url: ${url}`)
743
672
 
673
+ async function ensure_path_stuff() {
674
+ if (freed) return
675
+
744
676
  // if we're accessing /blah/index, it will be normalized to /blah,
745
677
  // but we still want to create a directory out of blah in this case
746
678
  if (wasnt_normal && !(await is_dir(fullpath))) {
@@ -751,15 +683,11 @@ async function sync_url(url) {
751
683
 
752
684
  await ensure_path(require("path").dirname(fullpath))
753
685
  if (freed) return
754
-
755
- // Check if this is a binary file
756
- // for now, this will be stuff in the "blobs" directory..
757
- if (is_external_link && path.split('/')[1] === 'blobs') {
758
- // Opts into the code for FS watcher (file_needs_reading, file_needs_writing) & unsyncing (disconnect)
759
- // It notably does NOT handle `.braidfs/set_version/` and `.braidfs/get_version/` correctly!
760
- // Search ` sync_url.cache[` to see how it's all handled.
761
- return await init_binary_sync()
762
- }
686
+ }
687
+
688
+ async function init() {
689
+ await ensure_path_stuff()
690
+ if (freed) return
763
691
 
764
692
  self.peer = Math.random().toString(36).slice(2)
765
693
  self.local_edit_counter = 0
@@ -903,6 +831,7 @@ async function sync_url(url) {
903
831
  async function write_meta_file() {
904
832
  if (freed) return
905
833
  await wait_on(require('fs').promises.writeFile(meta_path, JSON.stringify({
834
+ merge_type: self.merge_type,
906
835
  version: file_last_version,
907
836
  digest: sha256(self.file_last_text),
908
837
  peer: self.peer,
@@ -1123,7 +1052,6 @@ async function sync_url(url) {
1123
1052
  start_sync()
1124
1053
  return self
1125
1054
  }
1126
- return await sync_url.cache[url]
1127
1055
  }
1128
1056
 
1129
1057
  async function ensure_path(path) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braidfs",
3
- "version": "0.0.131",
3
+ "version": "0.0.133",
4
4
  "description": "braid technology synchronizing files and webpages",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braidfs",
@@ -8,7 +8,7 @@
8
8
  "dependencies": {
9
9
  "braid-http": "~1.3.85",
10
10
  "braid-text": "~0.2.97",
11
- "braid-blob": "~0.0.30",
11
+ "braid-blob": "~0.0.42",
12
12
  "chokidar": "^4.0.3"
13
13
  },
14
14
  "bin": {