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.
- package/css/retold-remote.css +3 -0
- package/html/index.html +1 -1
- package/package.json +1 -1
- package/source/Pict-Application-RetoldRemote.js +21 -2
- package/source/cli/RetoldRemote-Server-Setup.js +129 -0
- package/source/providers/Pict-Provider-AISortManager.js +456 -0
- package/source/providers/Pict-Provider-CollectionManager.js +266 -0
- package/source/server/RetoldRemote-AISortService.js +879 -0
- package/source/server/RetoldRemote-CollectionService.js +161 -2
- package/source/server/RetoldRemote-FileOperationService.js +560 -0
- package/source/server/RetoldRemote-MediaService.js +12 -0
- package/source/server/RetoldRemote-MetadataCache.js +411 -0
- package/source/views/PictView-Remote-CollectionsPanel.js +435 -36
- package/source/views/PictView-Remote-Layout.js +2 -0
- package/source/views/PictView-Remote-SettingsPanel.js +156 -0
- package/source/views/PictView-Remote-TopBar.js +86 -0
- package/web-application/css/retold-remote.css +3 -0
- package/web-application/index.html +1 -1
- package/web-application/retold-remote.js +402 -34
- package/web-application/retold-remote.js.map +1 -1
- package/web-application/retold-remote.min.js +32 -15
- package/web-application/retold-remote.min.js.map +1 -1
|
@@ -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
|
}
|