retold-remote 0.0.4 → 0.0.6

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 (63) hide show
  1. package/docs/README.md +181 -0
  2. package/docs/_cover.md +14 -0
  3. package/docs/_sidebar.md +10 -0
  4. package/docs/_topbar.md +3 -0
  5. package/docs/audio-viewer.md +133 -0
  6. package/docs/ebook-reader.md +90 -0
  7. package/docs/image-viewer.md +90 -0
  8. package/docs/server-setup.md +262 -0
  9. package/docs/video-viewer.md +134 -0
  10. package/html/docs.html +59 -0
  11. package/package.json +21 -7
  12. package/source/Pict-Application-RetoldRemote.js +143 -2
  13. package/source/RetoldRemote-ExtensionMaps.js +33 -0
  14. package/source/cli/RetoldRemote-Server-Setup.js +82 -67
  15. package/source/cli/commands/RetoldRemote-Command-Serve.js +5 -26
  16. package/source/providers/Pict-Provider-CollectionManager.js +934 -0
  17. package/source/providers/Pict-Provider-FormattingUtilities.js +109 -0
  18. package/source/providers/Pict-Provider-GalleryFilterSort.js +2 -11
  19. package/source/providers/Pict-Provider-GalleryNavigation.js +270 -353
  20. package/source/providers/Pict-Provider-RetoldRemoteIcons.js +52 -0
  21. package/source/providers/Pict-Provider-ToastNotification.js +96 -0
  22. package/source/providers/keyboard-handlers/KeyHandler-AudioExplorer.js +88 -0
  23. package/source/providers/keyboard-handlers/KeyHandler-Gallery.js +190 -0
  24. package/source/providers/keyboard-handlers/KeyHandler-Sidebar.js +65 -0
  25. package/source/providers/keyboard-handlers/KeyHandler-VideoExplorer.js +57 -0
  26. package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +197 -0
  27. package/source/server/RetoldRemote-ArchiveService.js +2 -12
  28. package/source/server/RetoldRemote-AudioWaveformService.js +7 -16
  29. package/source/server/RetoldRemote-CollectionService.js +684 -0
  30. package/source/server/RetoldRemote-EbookService.js +7 -16
  31. package/source/server/RetoldRemote-MediaService.js +3 -14
  32. package/source/server/RetoldRemote-ParimeCache.js +349 -0
  33. package/source/server/RetoldRemote-ThumbnailCache.js +52 -20
  34. package/source/server/RetoldRemote-VideoFrameService.js +7 -15
  35. package/source/views/PictView-Remote-AudioExplorer.js +10 -43
  36. package/source/views/PictView-Remote-CollectionsPanel.js +1087 -0
  37. package/source/views/PictView-Remote-Gallery.js +237 -44
  38. package/source/views/PictView-Remote-ImageViewer.js +1 -34
  39. package/source/views/PictView-Remote-Layout.js +410 -20
  40. package/source/views/PictView-Remote-MediaViewer.js +338 -51
  41. package/source/views/PictView-Remote-SettingsPanel.js +155 -138
  42. package/source/views/PictView-Remote-TopBar.js +615 -14
  43. package/source/views/PictView-Remote-VLCSetup.js +766 -0
  44. package/source/views/PictView-Remote-VideoExplorer.js +20 -54
  45. package/web-application/css/docuserve.css +73 -0
  46. package/web-application/docs/README.md +181 -0
  47. package/web-application/docs/_cover.md +14 -0
  48. package/web-application/docs/_sidebar.md +10 -0
  49. package/web-application/docs/_topbar.md +3 -0
  50. package/web-application/docs/audio-viewer.md +133 -0
  51. package/web-application/docs/ebook-reader.md +90 -0
  52. package/web-application/docs/image-viewer.md +90 -0
  53. package/web-application/docs/server-setup.md +262 -0
  54. package/web-application/docs/video-viewer.md +134 -0
  55. package/web-application/docs.html +59 -0
  56. package/web-application/js/pict-docuserve.min.js +58 -0
  57. package/web-application/js/pict.min.js +2 -2
  58. package/web-application/js/pict.min.js.map +1 -1
  59. package/web-application/retold-remote.js +2558 -439
  60. package/web-application/retold-remote.js.map +1 -1
  61. package/web-application/retold-remote.min.js +41 -11
  62. package/web-application/retold-remote.min.js.map +1 -1
  63. package/server.js +0 -43
@@ -8,6 +8,9 @@ const libProviderGalleryNavigation = require('./providers/Pict-Provider-GalleryN
8
8
  const libProviderGalleryFilterSort = require('./providers/Pict-Provider-GalleryFilterSort.js');
9
9
  const libProviderRetoldRemoteIcons = require('./providers/Pict-Provider-RetoldRemoteIcons.js');
10
10
  const libProviderRetoldRemoteTheme = require('./providers/Pict-Provider-RetoldRemoteTheme.js');
11
+ const libProviderFormattingUtilities = require('./providers/Pict-Provider-FormattingUtilities.js');
12
+ const libProviderToastNotification = require('./providers/Pict-Provider-ToastNotification.js');
13
+ const libProviderCollectionManager = require('./providers/Pict-Provider-CollectionManager.js');
11
14
 
12
15
  // Views (replace parent views)
13
16
  const libViewLayout = require('./views/PictView-Remote-Layout.js');
@@ -20,6 +23,8 @@ const libViewMediaViewer = require('./views/PictView-Remote-MediaViewer.js');
20
23
  const libViewImageViewer = require('./views/PictView-Remote-ImageViewer.js');
21
24
  const libViewVideoExplorer = require('./views/PictView-Remote-VideoExplorer.js');
22
25
  const libViewAudioExplorer = require('./views/PictView-Remote-AudioExplorer.js');
26
+ const libViewVLCSetup = require('./views/PictView-Remote-VLCSetup.js');
27
+ const libViewCollectionsPanel = require('./views/PictView-Remote-CollectionsPanel.js');
23
28
 
24
29
  // Application configuration
25
30
  const _DefaultConfiguration = require('./Pict-Application-RetoldRemote-Configuration.json');
@@ -51,6 +56,8 @@ class RetoldRemoteApplication extends libContentEditorApplication
51
56
  this.pict.addView('RetoldRemote-SettingsPanel', libViewSettingsPanel.default_configuration, libViewSettingsPanel);
52
57
  this.pict.addView('RetoldRemote-VideoExplorer', libViewVideoExplorer.default_configuration, libViewVideoExplorer);
53
58
  this.pict.addView('RetoldRemote-AudioExplorer', libViewAudioExplorer.default_configuration, libViewAudioExplorer);
59
+ this.pict.addView('RetoldRemote-VLCSetup', libViewVLCSetup.default_configuration, libViewVLCSetup);
60
+ this.pict.addView('RetoldRemote-CollectionsPanel', libViewCollectionsPanel.default_configuration, libViewCollectionsPanel);
54
61
 
55
62
  // Add new providers
56
63
  this.pict.addProvider('RetoldRemote-Provider', libProviderRetoldRemote.default_configuration, libProviderRetoldRemote);
@@ -58,6 +65,9 @@ class RetoldRemoteApplication extends libContentEditorApplication
58
65
  this.pict.addProvider('RetoldRemote-GalleryFilterSort', libProviderGalleryFilterSort.default_configuration, libProviderGalleryFilterSort);
59
66
  this.pict.addProvider('RetoldRemote-Icons', libProviderRetoldRemoteIcons.default_configuration, libProviderRetoldRemoteIcons);
60
67
  this.pict.addProvider('RetoldRemote-Theme', libProviderRetoldRemoteTheme.default_configuration, libProviderRetoldRemoteTheme);
68
+ this.pict.addProvider('RetoldRemote-FormattingUtilities', libProviderFormattingUtilities.default_configuration, libProviderFormattingUtilities);
69
+ this.pict.addProvider('RetoldRemote-ToastNotification', libProviderToastNotification.default_configuration, libProviderToastNotification);
70
+ this.pict.addProvider('RetoldRemote-CollectionManager', libProviderCollectionManager.default_configuration, libProviderCollectionManager);
61
71
  }
62
72
 
63
73
  onAfterInitializeAsync(fCallback)
@@ -96,6 +106,11 @@ class RetoldRemoteApplication extends libContentEditorApplication
96
106
  AutoplayVideo: false,
97
107
  AutoplayAudio: false,
98
108
 
109
+ // List column visibility
110
+ ListShowExtension: true,
111
+ ListShowSize: true,
112
+ ListShowDate: true,
113
+
99
114
  // Filter state
100
115
  FilterState:
101
116
  {
@@ -118,7 +133,19 @@ class RetoldRemoteApplication extends libContentEditorApplication
118
133
  FilterPanelOpen: false,
119
134
 
120
135
  // Saved filter presets
121
- FilterPresets: [] // [{ Name, FilterState, SortField, SortDirection }]
136
+ FilterPresets: [], // [{ Name, FilterState, SortField, SortDirection }]
137
+
138
+ // Collections state
139
+ Collections: [], // Array of collection summaries
140
+ CollectionsPanelOpen: false,
141
+ CollectionsPanelWidth: 300,
142
+ CollectionsPanelMode: 'list', // 'list' | 'detail' | 'edit'
143
+ ActiveCollectionGUID: null,
144
+ ActiveCollection: null,
145
+ CollectionSearchQuery: '',
146
+ LastUsedCollectionGUID: null,
147
+ BrowsingCollection: false, // true when viewer is navigating collection items
148
+ BrowsingCollectionIndex: -1 // index into ActiveCollection.Items
122
149
  };
123
150
 
124
151
  // Load persisted settings
@@ -165,6 +192,23 @@ class RetoldRemoteApplication extends libContentEditorApplication
165
192
  // Render the topbar
166
193
  this.pict.views['ContentEditor-TopBar'].render();
167
194
 
195
+ // Render the collections panel (starts collapsed)
196
+ this.pict.views['RetoldRemote-CollectionsPanel'].render();
197
+
198
+ // Fetch collections list
199
+ let tmpCollManager = this.pict.providers['RetoldRemote-CollectionManager'];
200
+ if (tmpCollManager)
201
+ {
202
+ tmpCollManager.fetchCollections();
203
+ }
204
+
205
+ // Update the collections button icon
206
+ let tmpTopBarView = this.pict.views['ContentEditor-TopBar'];
207
+ if (tmpTopBarView && typeof tmpTopBarView.updateCollectionsIcon === 'function')
208
+ {
209
+ tmpTopBarView.updateCollectionsIcon();
210
+ }
211
+
168
212
  let tmpSelf = this;
169
213
 
170
214
  // Wire up file selection from the file browser sidebar
@@ -357,6 +401,44 @@ class RetoldRemoteApplication extends libContentEditorApplication
357
401
  }
358
402
  }
359
403
 
404
+ /**
405
+ * Navigate to a file with an explicit media type override, bypassing
406
+ * extension-based detection.
407
+ *
408
+ * @param {string} pFilePath - Relative file path
409
+ * @param {string} pMediaType - 'image', 'video', 'audio', or 'text'
410
+ */
411
+ navigateToFileAs(pFilePath, pMediaType)
412
+ {
413
+ if (!pFilePath)
414
+ {
415
+ return;
416
+ }
417
+
418
+ let tmpRemote = this.pict.AppData.RetoldRemote;
419
+
420
+ // Update the hash
421
+ let tmpFragProvider = this.pict.providers['RetoldRemote-Provider'];
422
+ let tmpFragId = tmpFragProvider ? tmpFragProvider.getFragmentIdentifier(pFilePath) : pFilePath;
423
+ window.location.hash = '#/view/' + tmpFragId;
424
+
425
+ // Update parent state for compatibility
426
+ this.pict.AppData.ContentEditor.CurrentFile = pFilePath;
427
+ this.pict.AppData.ContentEditor.ActiveEditor = 'binary';
428
+
429
+ let tmpViewer = this.pict.views['RetoldRemote-MediaViewer'];
430
+ if (tmpViewer)
431
+ {
432
+ tmpViewer.showMedia(pFilePath, pMediaType);
433
+ }
434
+
435
+ let tmpTopBar = this.pict.views['ContentEditor-TopBar'];
436
+ if (tmpTopBar)
437
+ {
438
+ tmpTopBar.updateInfo();
439
+ }
440
+ }
441
+
360
442
  /**
361
443
  * Override loadFileList to also populate the gallery and fetch folder summary.
362
444
  */
@@ -433,6 +515,9 @@ class RetoldRemoteApplication extends libContentEditorApplication
433
515
  tmpListDetailView.render();
434
516
  }
435
517
 
518
+ // Inject the add-folder button at the bottom of the sidebar file list
519
+ tmpSelf._injectSidebarAddFolderButton();
520
+
436
521
  // Populate raw file list and run filter pipeline
437
522
  let tmpRemote = tmpSelf.pict.AppData.RetoldRemote;
438
523
  tmpRemote.RawFileList = pFileList || [];
@@ -579,6 +664,18 @@ class RetoldRemoteApplication extends libContentEditorApplication
579
664
  tmpAEX.showExplorer(tmpFilePath);
580
665
  }
581
666
  }
667
+ else if (tmpParts[0] === 'collection' && tmpParts.length >= 2)
668
+ {
669
+ let tmpCollectionGUID = tmpParts[1];
670
+ let tmpCollManager = this.pict.providers['RetoldRemote-CollectionManager'];
671
+ if (tmpCollManager)
672
+ {
673
+ let tmpRemote = this.pict.AppData.RetoldRemote;
674
+ tmpRemote.CollectionsPanelMode = 'detail';
675
+ tmpCollManager.openPanel();
676
+ tmpCollManager.fetchCollection(tmpCollectionGUID);
677
+ }
678
+ }
582
679
  else if (tmpParts[0] === 'edit' && tmpParts.length >= 2)
583
680
  {
584
681
  let tmpRawPath = tmpParts.slice(1).join('/');
@@ -587,6 +684,38 @@ class RetoldRemoteApplication extends libContentEditorApplication
587
684
  }
588
685
  }
589
686
 
687
+ /**
688
+ * Inject a subtle "Add Folder" button at the bottom of the sidebar file list.
689
+ * Replaces the bright white "+" button from the breadcrumb bar.
690
+ */
691
+ _injectSidebarAddFolderButton()
692
+ {
693
+ let tmpDetailRows = document.getElementById('Pict-FileBrowser-DetailRows');
694
+ if (!tmpDetailRows)
695
+ {
696
+ return;
697
+ }
698
+
699
+ // Remove any existing injected button so we don't duplicate
700
+ let tmpExisting = tmpDetailRows.parentElement.querySelector('.retold-remote-sidebar-addfolder');
701
+ if (tmpExisting)
702
+ {
703
+ tmpExisting.parentElement.removeChild(tmpExisting);
704
+ }
705
+
706
+ let tmpBtn = document.createElement('button');
707
+ tmpBtn.className = 'retold-remote-sidebar-addfolder';
708
+ tmpBtn.textContent = '+ New Folder';
709
+ tmpBtn.title = 'Create a new folder here';
710
+ tmpBtn.onclick = function()
711
+ {
712
+ pict.PictApplication.promptNewFolder();
713
+ };
714
+
715
+ // Insert after the detail rows container
716
+ tmpDetailRows.parentElement.appendChild(tmpBtn);
717
+ }
718
+
590
719
  /**
591
720
  * Save RetoldRemote settings to localStorage.
592
721
  */
@@ -611,7 +740,13 @@ class RetoldRemoteApplication extends libContentEditorApplication
611
740
  FilterPresets: tmpRemote.FilterPresets,
612
741
  FilterPanelOpen: tmpRemote.FilterPanelOpen,
613
742
  AutoplayVideo: tmpRemote.AutoplayVideo,
614
- AutoplayAudio: tmpRemote.AutoplayAudio
743
+ AutoplayAudio: tmpRemote.AutoplayAudio,
744
+ ListShowExtension: tmpRemote.ListShowExtension,
745
+ ListShowSize: tmpRemote.ListShowSize,
746
+ ListShowDate: tmpRemote.ListShowDate,
747
+ CollectionsPanelOpen: tmpRemote.CollectionsPanelOpen,
748
+ CollectionsPanelWidth: tmpRemote.CollectionsPanelWidth,
749
+ LastUsedCollectionGUID: tmpRemote.LastUsedCollectionGUID
615
750
  };
616
751
  localStorage.setItem('retold-remote-settings', JSON.stringify(tmpSettings));
617
752
  }
@@ -653,6 +788,12 @@ class RetoldRemoteApplication extends libContentEditorApplication
653
788
  if (typeof (tmpSettings.FilterPanelOpen) === 'boolean') tmpRemote.FilterPanelOpen = tmpSettings.FilterPanelOpen;
654
789
  if (typeof (tmpSettings.AutoplayVideo) === 'boolean') tmpRemote.AutoplayVideo = tmpSettings.AutoplayVideo;
655
790
  if (typeof (tmpSettings.AutoplayAudio) === 'boolean') tmpRemote.AutoplayAudio = tmpSettings.AutoplayAudio;
791
+ if (typeof (tmpSettings.ListShowExtension) === 'boolean') tmpRemote.ListShowExtension = tmpSettings.ListShowExtension;
792
+ if (typeof (tmpSettings.ListShowSize) === 'boolean') tmpRemote.ListShowSize = tmpSettings.ListShowSize;
793
+ if (typeof (tmpSettings.ListShowDate) === 'boolean') tmpRemote.ListShowDate = tmpSettings.ListShowDate;
794
+ if (typeof (tmpSettings.CollectionsPanelOpen) === 'boolean') tmpRemote.CollectionsPanelOpen = tmpSettings.CollectionsPanelOpen;
795
+ if (tmpSettings.CollectionsPanelWidth) tmpRemote.CollectionsPanelWidth = tmpSettings.CollectionsPanelWidth;
796
+ if (tmpSettings.LastUsedCollectionGUID) tmpRemote.LastUsedCollectionGUID = tmpSettings.LastUsedCollectionGUID;
656
797
  }
657
798
  }
658
799
  catch (pError)
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Shared extension category maps for retold-remote.
3
+ *
4
+ * Used by both client-side providers (GalleryFilterSort) and server-side
5
+ * services (MediaService) to classify files by extension.
6
+ */
7
+
8
+ const ImageExtensions = { 'png': true, 'jpg': true, 'jpeg': true, 'gif': true, 'webp': true, 'svg': true, 'bmp': true, 'ico': true, 'avif': true, 'tiff': true, 'tif': true, 'heic': true, 'heif': true };
9
+ const VideoExtensions = { 'mp4': true, 'webm': true, 'mov': true, 'mkv': true, 'avi': true, 'wmv': true, 'flv': true, 'm4v': true, 'ogv': true, 'mpg': true, 'mpeg': true, 'mpe': true, 'mpv': true, 'm2v': true, 'ts': true, 'mts': true, 'm2ts': true, 'vob': true, '3gp': true, '3g2': true, 'f4v': true, 'rm': true, 'rmvb': true, 'divx': true, 'asf': true, 'mxf': true, 'dv': true, 'nsv': true, 'nuv': true, 'y4m': true, 'wtv': true, 'swf': true, 'dat': true };
10
+ const AudioExtensions = { 'mp3': true, 'wav': true, 'ogg': true, 'flac': true, 'aac': true, 'm4a': true, 'wma': true, 'oga': true };
11
+ const DocumentExtensions = { 'pdf': true, 'epub': true, 'mobi': true, 'doc': true, 'docx': true };
12
+
13
+ /**
14
+ * Get the media category for a file extension.
15
+ *
16
+ * @param {string} pExtension - Extension with or without leading dot (e.g. '.png' or 'png')
17
+ * @returns {string} 'image', 'video', 'audio', 'document', or 'other'
18
+ */
19
+ function getCategory(pExtension)
20
+ {
21
+ let tmpExt = (pExtension || '').replace(/^\./, '').toLowerCase();
22
+ if (ImageExtensions[tmpExt]) return 'image';
23
+ if (VideoExtensions[tmpExt]) return 'video';
24
+ if (AudioExtensions[tmpExt]) return 'audio';
25
+ if (DocumentExtensions[tmpExt]) return 'document';
26
+ return 'other';
27
+ }
28
+
29
+ module.exports.ImageExtensions = ImageExtensions;
30
+ module.exports.VideoExtensions = VideoExtensions;
31
+ module.exports.AudioExtensions = AudioExtensions;
32
+ module.exports.DocumentExtensions = DocumentExtensions;
33
+ module.exports.getCategory = getCategory;
@@ -11,16 +11,16 @@
11
11
  * server setup) so that we control all routes and avoid conflicts with editor-
12
12
  * specific endpoints (save, upload) that aren't needed here.
13
13
  *
14
+ * Cache storage is managed by Parime's BinaryStorage with configurable
15
+ * hash-based subfolder sharding to avoid huge flat directories.
16
+ *
14
17
  * @param {object} pOptions
15
18
  * @param {string} pOptions.ContentPath - Absolute path to the media folder to browse
16
19
  * @param {string} pOptions.DistPath - Absolute path to the built web-application folder
17
20
  * @param {number} pOptions.Port - HTTP port
18
21
  * @param {boolean} [pOptions.HashedFilenames] - Enable hashed filenames mode
19
22
  * @param {string} [pOptions.CacheRoot] - Root cache directory (default: ./dist/retold-cache/)
20
- * @param {string} [pOptions.CacheThumbnails] - Override thumbnails cache directory
21
- * @param {string} [pOptions.CacheArchives] - Override archives cache directory
22
- * @param {string} [pOptions.CacheVideoFrames] - Override video-frames cache directory
23
- * @param {string} [pOptions.CacheAudioWaveforms] - Override audio-waveforms cache directory
23
+ * @param {string} [pOptions.CacheServer] - URL to a remote parime cache server
24
24
  * @param {Function} fCallback - Callback(pError, { Fable, Orator, Port })
25
25
  */
26
26
 
@@ -33,12 +33,16 @@ const libOrator = require('orator');
33
33
  const libOratorServiceServerRestify = require('orator-serviceserver-restify');
34
34
  const libFileBrowserService = require('pict-section-filebrowser').FileBrowserService;
35
35
 
36
+ const libParimeStorage = require('parime/storage');
37
+ const libRetoldRemoteParimeCache = require('../server/RetoldRemote-ParimeCache.js');
38
+
36
39
  const libRetoldRemoteMediaService = require('../server/RetoldRemote-MediaService.js');
37
40
  const libRetoldRemotePathRegistry = require('../server/RetoldRemote-PathRegistry.js');
38
41
  const libRetoldRemoteArchiveService = require('../server/RetoldRemote-ArchiveService.js');
39
42
  const libRetoldRemoteVideoFrameService = require('../server/RetoldRemote-VideoFrameService.js');
40
43
  const libRetoldRemoteAudioWaveformService = require('../server/RetoldRemote-AudioWaveformService.js');
41
44
  const libRetoldRemoteEbookService = require('../server/RetoldRemote-EbookService.js');
45
+ const libRetoldRemoteCollectionService = require('../server/RetoldRemote-CollectionService.js');
42
46
  const libUrl = require('url');
43
47
 
44
48
  function setupRetoldRemoteServer(pOptions, fCallback)
@@ -46,16 +50,33 @@ function setupRetoldRemoteServer(pOptions, fCallback)
46
50
  let tmpContentPath = pOptions.ContentPath;
47
51
  let tmpDistFolder = pOptions.DistPath;
48
52
  let tmpPort = pOptions.Port;
49
- let tmpHashedFilenames = !!(pOptions.HashedFilenames || process.env.RETOLD_HASHED_FILENAMES === 'true');
53
+ let tmpHashedFilenames = (pOptions.HashedFilenames !== false) && (process.env.RETOLD_HASHED_FILENAMES !== 'false');
54
+
55
+ // --- Resolve cache root ---
56
+ let tmpCacheRoot = pOptions.CacheRoot
57
+ || libPath.resolve(process.cwd(), 'dist', 'retold-cache');
50
58
 
51
59
  let tmpSettings =
52
60
  {
53
61
  Product: 'Retold-Remote',
54
62
  ProductVersion: require('../../package.json').version,
55
63
  APIServerPort: tmpPort,
56
- ContentPath: tmpContentPath
64
+ ContentPath: tmpContentPath,
65
+ ParimeBinaryStorageRoot: tmpCacheRoot,
66
+ ParimeBinarySharding:
67
+ {
68
+ Enabled: true,
69
+ SegmentSize: 2,
70
+ Depth: 4
71
+ }
57
72
  };
58
73
 
74
+ // If a remote cache server is specified, route cache operations over HTTP
75
+ if (pOptions.CacheServer)
76
+ {
77
+ tmpSettings.ParimeCacheServer = pOptions.CacheServer;
78
+ }
79
+
59
80
  let tmpFable = new libFable(tmpSettings);
60
81
 
61
82
  // Ensure the content directory exists
@@ -89,68 +110,59 @@ function setupRetoldRemoteServer(pOptions, fCallback)
89
110
  tmpFable.log.info('Hashed filenames mode: ENABLED');
90
111
  }
91
112
 
92
- // --- Resolve cache paths ---
93
- // Default root: ./dist/retold-cache/ relative to process cwd.
94
- // Individual overrides take precedence over root.
95
- let tmpCacheRoot = pOptions.CacheRoot
96
- || libPath.resolve(process.cwd(), 'dist', 'retold-cache');
113
+ // --- Initialize Parime storage ---
114
+ tmpFable.serviceManager.addServiceType('ParimeStorage', libParimeStorage);
115
+ let tmpParimeStorage = tmpFable.serviceManager.instantiateServiceProvider('ParimeStorage');
97
116
 
98
- let tmpCacheThumbnails = pOptions.CacheThumbnails
99
- || libPath.join(tmpCacheRoot, 'thumbnails');
100
- let tmpCacheArchives = pOptions.CacheArchives
101
- || libPath.join(tmpCacheRoot, 'archives');
102
- let tmpCacheVideoFrames = pOptions.CacheVideoFrames
103
- || libPath.join(tmpCacheRoot, 'video-frames');
104
- let tmpCacheAudioWaveforms = pOptions.CacheAudioWaveforms
105
- || libPath.join(tmpCacheRoot, 'audio-waveforms');
106
- let tmpCacheEbooks = pOptions.CacheEbooks
107
- || libPath.join(tmpCacheRoot, 'ebook-conversions');
108
-
109
- tmpFable.log.info(`Cache root: ${tmpCacheRoot}`);
110
- tmpFable.log.info(` Thumbnails: ${tmpCacheThumbnails}`);
111
- tmpFable.log.info(` Archives: ${tmpCacheArchives}`);
112
- tmpFable.log.info(` Video frames: ${tmpCacheVideoFrames}`);
113
- tmpFable.log.info(` Audio waveforms: ${tmpCacheAudioWaveforms}`);
114
- tmpFable.log.info(` Ebook conversions: ${tmpCacheEbooks}`);
115
-
116
- // Set up the archive service
117
- let tmpArchiveService = new libRetoldRemoteArchiveService(tmpFable,
118
- {
119
- ContentPath: tmpContentPath,
120
- CachePath: tmpCacheArchives
121
- });
117
+ tmpFable.serviceManager.addServiceType('RetoldRemoteParimeCache', libRetoldRemoteParimeCache);
118
+ let tmpParimeCache = tmpFable.serviceManager.instantiateServiceProvider('RetoldRemoteParimeCache');
122
119
 
123
- // Set up the video frame service
124
- let tmpVideoFrameService = new libRetoldRemoteVideoFrameService(tmpFable,
125
- {
126
- ContentPath: tmpContentPath,
127
- CachePath: tmpCacheVideoFrames
128
- });
120
+ tmpParimeStorage.initialize(
121
+ (pStorageError) =>
122
+ {
123
+ if (pStorageError)
124
+ {
125
+ return fCallback(pStorageError);
126
+ }
129
127
 
130
- // Set up the audio waveform service
131
- let tmpAudioWaveformService = new libRetoldRemoteAudioWaveformService(tmpFable,
132
- {
133
- ContentPath: tmpContentPath,
134
- CachePath: tmpCacheAudioWaveforms
135
- });
128
+ tmpFable.log.info(`Cache storage: ${tmpParimeCache.isRemote ? 'REMOTE (' + pOptions.CacheServer + ')' : tmpCacheRoot}`);
136
129
 
137
- // Set up the ebook conversion service
138
- let tmpEbookService = new libRetoldRemoteEbookService(tmpFable,
139
- {
140
- ContentPath: tmpContentPath,
141
- CachePath: tmpCacheEbooks
142
- });
130
+ // Set up the archive service
131
+ let tmpArchiveService = new libRetoldRemoteArchiveService(tmpFable,
132
+ {
133
+ ContentPath: tmpContentPath
134
+ });
143
135
 
144
- // Set up the media service
145
- let tmpMediaService = new libRetoldRemoteMediaService(tmpFable,
146
- {
147
- ContentPath: tmpContentPath,
148
- ThumbnailCachePath: tmpCacheThumbnails,
149
- APIRoutePrefix: '/api/media',
150
- PathRegistry: tmpPathRegistry
151
- });
136
+ // Set up the video frame service
137
+ let tmpVideoFrameService = new libRetoldRemoteVideoFrameService(tmpFable,
138
+ {
139
+ ContentPath: tmpContentPath
140
+ });
141
+
142
+ // Set up the audio waveform service
143
+ let tmpAudioWaveformService = new libRetoldRemoteAudioWaveformService(tmpFable,
144
+ {
145
+ ContentPath: tmpContentPath
146
+ });
147
+
148
+ // Set up the ebook conversion service
149
+ let tmpEbookService = new libRetoldRemoteEbookService(tmpFable,
150
+ {
151
+ ContentPath: tmpContentPath
152
+ });
152
153
 
153
- tmpOrator.initialize(
154
+ // Set up the collection service
155
+ let tmpCollectionService = new libRetoldRemoteCollectionService(tmpFable, {});
156
+
157
+ // Set up the media service
158
+ let tmpMediaService = new libRetoldRemoteMediaService(tmpFable,
159
+ {
160
+ ContentPath: tmpContentPath,
161
+ APIRoutePrefix: '/api/media',
162
+ PathRegistry: tmpPathRegistry
163
+ });
164
+
165
+ tmpOrator.initialize(
154
166
  function ()
155
167
  {
156
168
  let tmpServiceServer = tmpOrator.serviceServer;
@@ -196,13 +208,11 @@ function setupRetoldRemoteServer(pOptions, fCallback)
196
208
  {
197
209
  Success: true,
198
210
  HashedFilenames: tmpHashedFilenames,
199
- CachePaths:
211
+ CacheStorage:
200
212
  {
201
213
  Root: tmpCacheRoot,
202
- Thumbnails: tmpCacheThumbnails,
203
- Archives: tmpCacheArchives,
204
- VideoFrames: tmpCacheVideoFrames,
205
- AudioWaveforms: tmpCacheAudioWaveforms
214
+ Remote: tmpParimeCache.isRemote,
215
+ Sharding: true
206
216
  },
207
217
  ArchiveSupport:
208
218
  {
@@ -323,6 +333,9 @@ function setupRetoldRemoteServer(pOptions, fCallback)
323
333
  // Connect media service API routes
324
334
  tmpMediaService.connectRoutes(tmpServiceServer);
325
335
 
336
+ // Connect collection service API routes
337
+ tmpCollectionService.connectRoutes(tmpServiceServer);
338
+
326
339
  // --- GET /api/media/video-frames ---
327
340
  // Extract evenly-spaced frames from a video for the Video Explorer.
328
341
  tmpServiceServer.get('/api/media/video-frames',
@@ -1030,10 +1043,12 @@ function setupRetoldRemoteServer(pOptions, fCallback)
1030
1043
  VideoFrameService: tmpVideoFrameService,
1031
1044
  AudioWaveformService: tmpAudioWaveformService,
1032
1045
  PathRegistry: tmpPathRegistry,
1046
+ ParimeCache: tmpParimeCache,
1033
1047
  Port: tmpPort
1034
1048
  });
1035
1049
  });
1036
1050
  });
1051
+ });
1037
1052
  }
1038
1053
 
1039
1054
  module.exports = setupRetoldRemoteServer;
@@ -19,18 +19,12 @@ class RetoldRemoteCommandServe extends libCommandLineCommand
19
19
  { Name: '-p, --port [port]', Description: 'Port to serve on (defaults to random 7000-7999).', Default: '0' });
20
20
 
21
21
  this.options.CommandOptions.push(
22
- { Name: '-H, --hashed-filenames', Description: 'Enable hashed filenames mode (short hashes instead of full paths in URLs).', Default: false });
22
+ { Name: '--no-hash', Description: 'Disable hashed filenames mode (use plain paths in URLs instead of short hashes).', Default: false });
23
23
 
24
24
  this.options.CommandOptions.push(
25
25
  { Name: '-c, --cache-path [path]', Description: 'Root cache directory (defaults to ./dist/retold-cache/).', Default: '' });
26
26
  this.options.CommandOptions.push(
27
- { Name: '--cache-thumbnails [path]', Description: 'Override thumbnails cache directory.', Default: '' });
28
- this.options.CommandOptions.push(
29
- { Name: '--cache-archives [path]', Description: 'Override archives cache directory.', Default: '' });
30
- this.options.CommandOptions.push(
31
- { Name: '--cache-video-frames [path]', Description: 'Override video-frames cache directory.', Default: '' });
32
- this.options.CommandOptions.push(
33
- { Name: '--cache-audio-waveforms [path]', Description: 'Override audio-waveforms cache directory.', Default: '' });
27
+ { Name: '--cache-server [url]', Description: 'URL of a remote parime cache server (e.g. http://host:9999).', Default: '' });
34
28
 
35
29
  this.addCommand();
36
30
  }
@@ -60,24 +54,12 @@ class RetoldRemoteCommandServe extends libCommandLineCommand
60
54
  let tmpSelf = this;
61
55
  let tmpSetupServer = require('../RetoldRemote-Server-Setup.js');
62
56
 
63
- let tmpHashedFilenames = !!(this.CommandOptions.hashedFilenames);
57
+ let tmpHashedFilenames = !(this.CommandOptions.noHash);
64
58
 
65
- // Resolve cache paths: individual overrides > root override > default
66
59
  let tmpCacheRoot = this.CommandOptions.cachePath
67
60
  ? libPath.resolve(this.CommandOptions.cachePath)
68
61
  : null;
69
- let tmpCacheThumbnails = this.CommandOptions.cacheThumbnails
70
- ? libPath.resolve(this.CommandOptions.cacheThumbnails)
71
- : null;
72
- let tmpCacheArchives = this.CommandOptions.cacheArchives
73
- ? libPath.resolve(this.CommandOptions.cacheArchives)
74
- : null;
75
- let tmpCacheVideoFrames = this.CommandOptions.cacheVideoFrames
76
- ? libPath.resolve(this.CommandOptions.cacheVideoFrames)
77
- : null;
78
- let tmpCacheAudioWaveforms = this.CommandOptions.cacheAudioWaveforms
79
- ? libPath.resolve(this.CommandOptions.cacheAudioWaveforms)
80
- : null;
62
+ let tmpCacheServer = this.CommandOptions.cacheServer || null;
81
63
 
82
64
  tmpSetupServer(
83
65
  {
@@ -86,10 +68,7 @@ class RetoldRemoteCommandServe extends libCommandLineCommand
86
68
  Port: tmpPort,
87
69
  HashedFilenames: tmpHashedFilenames,
88
70
  CacheRoot: tmpCacheRoot,
89
- CacheThumbnails: tmpCacheThumbnails,
90
- CacheArchives: tmpCacheArchives,
91
- CacheVideoFrames: tmpCacheVideoFrames,
92
- CacheAudioWaveforms: tmpCacheAudioWaveforms
71
+ CacheServer: tmpCacheServer
93
72
  },
94
73
  function (pError, pServerInfo)
95
74
  {