retold-remote 0.0.6 → 0.0.7

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.
@@ -14,6 +14,11 @@
14
14
  * DELETE /api/collections/:guid/items/:itemId -- Remove an item
15
15
  * PUT /api/collections/:guid/reorder -- Reorder items (manual sort)
16
16
  * POST /api/collections/copy-items -- Copy items between collections
17
+ * POST /api/collections/:guid/execute -- Execute pending operations (move/rename)
18
+ *
19
+ * Collections with CollectionType "operation-plan" represent file sort plans:
20
+ * each item has Operation, DestinationPath, OperationStatus, and OperationError
21
+ * fields that describe a pending file operation.
17
22
  *
18
23
  * @license MIT
19
24
  */
@@ -28,6 +33,19 @@ class RetoldRemoteCollectionService extends libFableServiceProviderBase
28
33
  super(pFable, pOptions, pServiceHash);
29
34
 
30
35
  this.serviceType = 'RetoldRemoteCollectionService';
36
+
37
+ // Will be set by server setup after FileOperationService is created
38
+ this._fileOperationService = null;
39
+ }
40
+
41
+ /**
42
+ * Set the file operation service instance for execute operations.
43
+ *
44
+ * @param {RetoldRemoteFileOperationService} pService
45
+ */
46
+ setFileOperationService(pService)
47
+ {
48
+ this._fileOperationService = pService;
31
49
  }
32
50
 
33
51
  // -- Helpers ----------------------------------------------------------
@@ -50,7 +68,9 @@ class RetoldRemoteCollectionService extends libFableServiceProviderBase
50
68
  ItemCount: (Array.isArray(pRecord.Items)) ? pRecord.Items.length : 0,
51
69
  CreatedAt: pRecord.CreatedAt || '',
52
70
  ModifiedAt: pRecord.ModifiedAt || '',
53
- Tags: pRecord.Tags || []
71
+ Tags: pRecord.Tags || [],
72
+ CollectionType: pRecord.CollectionType || 'bookmark',
73
+ OperationBatchGUID: pRecord.OperationBatchGUID || null
54
74
  });
55
75
  }
56
76
 
@@ -139,6 +159,8 @@ class RetoldRemoteCollectionService extends libFableServiceProviderBase
139
159
  SortMode: 'manual',
140
160
  SortDirection: 'asc',
141
161
  Tags: [],
162
+ CollectionType: 'bookmark',
163
+ OperationBatchGUID: null,
142
164
  Items: []
143
165
  });
144
166
  }
@@ -307,6 +329,8 @@ class RetoldRemoteCollectionService extends libFableServiceProviderBase
307
329
  if (typeof tmpBody.SortMode === 'string') tmpRecord.SortMode = tmpBody.SortMode;
308
330
  if (typeof tmpBody.SortDirection === 'string') tmpRecord.SortDirection = tmpBody.SortDirection;
309
331
  if (Array.isArray(tmpBody.Tags)) tmpRecord.Tags = tmpBody.Tags;
332
+ if (typeof tmpBody.CollectionType === 'string') tmpRecord.CollectionType = tmpBody.CollectionType;
333
+ if (typeof tmpBody.OperationBatchGUID === 'string' || tmpBody.OperationBatchGUID === null) tmpRecord.OperationBatchGUID = tmpBody.OperationBatchGUID;
310
334
  if (Array.isArray(tmpBody.Items)) tmpRecord.Items = tmpBody.Items;
311
335
  tmpRecord.ModifiedAt = new Date().toISOString();
312
336
  }
@@ -320,6 +344,8 @@ class RetoldRemoteCollectionService extends libFableServiceProviderBase
320
344
  if (typeof tmpBody.SortMode === 'string') tmpRecord.SortMode = tmpBody.SortMode;
321
345
  if (typeof tmpBody.SortDirection === 'string') tmpRecord.SortDirection = tmpBody.SortDirection;
322
346
  if (Array.isArray(tmpBody.Tags)) tmpRecord.Tags = tmpBody.Tags;
347
+ if (typeof tmpBody.CollectionType === 'string') tmpRecord.CollectionType = tmpBody.CollectionType;
348
+ if (Array.isArray(tmpBody.Items)) tmpRecord.Items = tmpBody.Items;
323
349
  }
324
350
 
325
351
  tmpSelf.fable.Bibliograph.write(SOURCE_NAME, tmpGUID, tmpRecord,
@@ -431,7 +457,12 @@ class RetoldRemoteCollectionService extends libFableServiceProviderBase
431
457
  CropRegion: tmpItem.CropRegion || null,
432
458
  VideoStart: (typeof tmpItem.VideoStart === 'number') ? tmpItem.VideoStart : null,
433
459
  VideoEnd: (typeof tmpItem.VideoEnd === 'number') ? tmpItem.VideoEnd : null,
434
- FrameTimestamp: (typeof tmpItem.FrameTimestamp === 'number') ? tmpItem.FrameTimestamp : null
460
+ FrameTimestamp: (typeof tmpItem.FrameTimestamp === 'number') ? tmpItem.FrameTimestamp : null,
461
+ // Operation fields (for operation-plan collections)
462
+ Operation: tmpItem.Operation || null,
463
+ DestinationPath: tmpItem.DestinationPath || null,
464
+ OperationStatus: tmpItem.OperationStatus || null,
465
+ OperationError: tmpItem.OperationError || null
435
466
  });
436
467
  }
437
468
 
@@ -677,6 +708,134 @@ class RetoldRemoteCollectionService extends libFableServiceProviderBase
677
708
  });
678
709
  });
679
710
 
711
+ // -----------------------------------------------------------------
712
+ // POST /api/collections/:guid/execute — Execute pending operations
713
+ // Requires a FileOperationService instance on this.fable.FileOperationService
714
+ // -----------------------------------------------------------------
715
+ tmpServer.post('/api/collections/:guid/execute',
716
+ (pRequest, pResponse, fNext) =>
717
+ {
718
+ let tmpGUID = pRequest.params.guid;
719
+ if (!tmpGUID)
720
+ {
721
+ pResponse.send(400, { Error: 'Missing collection GUID.' });
722
+ return fNext();
723
+ }
724
+
725
+ tmpSelf.fable.Bibliograph.read(SOURCE_NAME, tmpGUID,
726
+ (pReadError, pRecord) =>
727
+ {
728
+ if (pReadError || !pRecord)
729
+ {
730
+ pResponse.send(404, { Error: 'Collection not found.' });
731
+ return fNext();
732
+ }
733
+
734
+ if (!Array.isArray(pRecord.Items) || pRecord.Items.length === 0)
735
+ {
736
+ pResponse.send(400, { Error: 'Collection has no items.' });
737
+ return fNext();
738
+ }
739
+
740
+ // Find the FileOperationService
741
+ let tmpFileOpService = tmpSelf._fileOperationService;
742
+ if (!tmpFileOpService)
743
+ {
744
+ pResponse.send(500, { Error: 'File operation service not available.' });
745
+ return fNext();
746
+ }
747
+
748
+ // Build the moves list from pending operation items
749
+ let tmpMoves = [];
750
+ let tmpMoveIndexMap = []; // Maps move index -> item index for status updates
751
+
752
+ for (let i = 0; i < pRecord.Items.length; i++)
753
+ {
754
+ let tmpItem = pRecord.Items[i];
755
+ if (tmpItem.Operation && tmpItem.DestinationPath
756
+ && tmpItem.OperationStatus !== 'completed'
757
+ && tmpItem.OperationStatus !== 'skipped')
758
+ {
759
+ tmpMoves.push(
760
+ {
761
+ Source: tmpItem.Path,
762
+ Destination: tmpItem.DestinationPath
763
+ });
764
+ tmpMoveIndexMap.push(i);
765
+ }
766
+ }
767
+
768
+ if (tmpMoves.length === 0)
769
+ {
770
+ pResponse.send(200, { Success: true, Message: 'No pending operations to execute.', Collection: pRecord });
771
+ return fNext();
772
+ }
773
+
774
+ tmpFileOpService.moveBatch(tmpMoves,
775
+ (pMoveError, pMoveResult) =>
776
+ {
777
+ if (pMoveError)
778
+ {
779
+ pResponse.send(500, { Error: 'Batch move failed: ' + pMoveError.message });
780
+ return fNext();
781
+ }
782
+
783
+ // Update item statuses based on results
784
+ let tmpCompletedSet = {};
785
+ for (let c = 0; c < pMoveResult.Completed.length; c++)
786
+ {
787
+ tmpCompletedSet[pMoveResult.Completed[c].Source] = true;
788
+ }
789
+
790
+ let tmpFailedMap = {};
791
+ for (let f = 0; f < pMoveResult.Failed.length; f++)
792
+ {
793
+ tmpFailedMap[pMoveResult.Failed[f].Source] = pMoveResult.Failed[f].Error;
794
+ }
795
+
796
+ for (let m = 0; m < tmpMoveIndexMap.length; m++)
797
+ {
798
+ let tmpItemIndex = tmpMoveIndexMap[m];
799
+ let tmpItem = pRecord.Items[tmpItemIndex];
800
+
801
+ if (tmpCompletedSet[tmpItem.Path])
802
+ {
803
+ tmpItem.OperationStatus = 'completed';
804
+ tmpItem.OperationError = null;
805
+ }
806
+ else if (tmpFailedMap[tmpItem.Path])
807
+ {
808
+ tmpItem.OperationStatus = 'failed';
809
+ tmpItem.OperationError = tmpFailedMap[tmpItem.Path];
810
+ }
811
+ }
812
+
813
+ // Store batch GUID on collection for undo
814
+ pRecord.OperationBatchGUID = pMoveResult.BatchGUID;
815
+ pRecord.ModifiedAt = new Date().toISOString();
816
+
817
+ tmpSelf.fable.Bibliograph.write(SOURCE_NAME, tmpGUID, pRecord,
818
+ (pWriteError) =>
819
+ {
820
+ if (pWriteError)
821
+ {
822
+ tmpSelf.fable.log.warn('Failed to update collection after execute: ' + pWriteError.message);
823
+ }
824
+
825
+ pResponse.send(200,
826
+ {
827
+ Success: true,
828
+ BatchGUID: pMoveResult.BatchGUID,
829
+ TotalMoved: pMoveResult.TotalMoved,
830
+ TotalFailed: pMoveResult.TotalFailed,
831
+ Collection: pRecord
832
+ });
833
+ return fNext();
834
+ });
835
+ });
836
+ });
837
+ });
838
+
680
839
  this.fable.log.info('Collection Service: routes connected.');
681
840
  }
682
841
  }