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.
- package/docs/README.md +181 -0
- package/docs/_cover.md +14 -0
- package/docs/_sidebar.md +10 -0
- package/docs/_topbar.md +3 -0
- package/docs/audio-viewer.md +133 -0
- package/docs/ebook-reader.md +90 -0
- package/docs/image-viewer.md +90 -0
- package/docs/server-setup.md +262 -0
- package/docs/video-viewer.md +134 -0
- package/html/docs.html +59 -0
- package/package.json +21 -7
- package/source/Pict-Application-RetoldRemote.js +143 -2
- package/source/RetoldRemote-ExtensionMaps.js +33 -0
- package/source/cli/RetoldRemote-Server-Setup.js +82 -67
- package/source/cli/commands/RetoldRemote-Command-Serve.js +5 -26
- package/source/providers/Pict-Provider-CollectionManager.js +934 -0
- package/source/providers/Pict-Provider-FormattingUtilities.js +109 -0
- package/source/providers/Pict-Provider-GalleryFilterSort.js +2 -11
- package/source/providers/Pict-Provider-GalleryNavigation.js +270 -353
- package/source/providers/Pict-Provider-RetoldRemoteIcons.js +52 -0
- package/source/providers/Pict-Provider-ToastNotification.js +96 -0
- package/source/providers/keyboard-handlers/KeyHandler-AudioExplorer.js +88 -0
- package/source/providers/keyboard-handlers/KeyHandler-Gallery.js +190 -0
- package/source/providers/keyboard-handlers/KeyHandler-Sidebar.js +65 -0
- package/source/providers/keyboard-handlers/KeyHandler-VideoExplorer.js +57 -0
- package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +197 -0
- package/source/server/RetoldRemote-ArchiveService.js +2 -12
- package/source/server/RetoldRemote-AudioWaveformService.js +7 -16
- package/source/server/RetoldRemote-CollectionService.js +684 -0
- package/source/server/RetoldRemote-EbookService.js +7 -16
- package/source/server/RetoldRemote-MediaService.js +3 -14
- package/source/server/RetoldRemote-ParimeCache.js +349 -0
- package/source/server/RetoldRemote-ThumbnailCache.js +52 -20
- package/source/server/RetoldRemote-VideoFrameService.js +7 -15
- package/source/views/PictView-Remote-AudioExplorer.js +10 -43
- package/source/views/PictView-Remote-CollectionsPanel.js +1087 -0
- package/source/views/PictView-Remote-Gallery.js +237 -44
- package/source/views/PictView-Remote-ImageViewer.js +1 -34
- package/source/views/PictView-Remote-Layout.js +410 -20
- package/source/views/PictView-Remote-MediaViewer.js +338 -51
- package/source/views/PictView-Remote-SettingsPanel.js +155 -138
- package/source/views/PictView-Remote-TopBar.js +615 -14
- package/source/views/PictView-Remote-VLCSetup.js +766 -0
- package/source/views/PictView-Remote-VideoExplorer.js +20 -54
- package/web-application/css/docuserve.css +73 -0
- package/web-application/docs/README.md +181 -0
- package/web-application/docs/_cover.md +14 -0
- package/web-application/docs/_sidebar.md +10 -0
- package/web-application/docs/_topbar.md +3 -0
- package/web-application/docs/audio-viewer.md +133 -0
- package/web-application/docs/ebook-reader.md +90 -0
- package/web-application/docs/image-viewer.md +90 -0
- package/web-application/docs/server-setup.md +262 -0
- package/web-application/docs/video-viewer.md +134 -0
- package/web-application/docs.html +59 -0
- package/web-application/js/pict-docuserve.min.js +58 -0
- package/web-application/js/pict.min.js +2 -2
- package/web-application/js/pict.min.js.map +1 -1
- package/web-application/retold-remote.js +2558 -439
- package/web-application/retold-remote.js.map +1 -1
- package/web-application/retold-remote.min.js +41 -11
- package/web-application/retold-remote.min.js.map +1 -1
- 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: []
|
|
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.
|
|
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 =
|
|
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
|
-
// ---
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
120
|
+
tmpParimeStorage.initialize(
|
|
121
|
+
(pStorageError) =>
|
|
122
|
+
{
|
|
123
|
+
if (pStorageError)
|
|
124
|
+
{
|
|
125
|
+
return fCallback(pStorageError);
|
|
126
|
+
}
|
|
129
127
|
|
|
130
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
});
|
|
130
|
+
// Set up the archive service
|
|
131
|
+
let tmpArchiveService = new libRetoldRemoteArchiveService(tmpFable,
|
|
132
|
+
{
|
|
133
|
+
ContentPath: tmpContentPath
|
|
134
|
+
});
|
|
143
135
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
211
|
+
CacheStorage:
|
|
200
212
|
{
|
|
201
213
|
Root: tmpCacheRoot,
|
|
202
|
-
|
|
203
|
-
|
|
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: '
|
|
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-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
90
|
-
CacheArchives: tmpCacheArchives,
|
|
91
|
-
CacheVideoFrames: tmpCacheVideoFrames,
|
|
92
|
-
CacheAudioWaveforms: tmpCacheAudioWaveforms
|
|
71
|
+
CacheServer: tmpCacheServer
|
|
93
72
|
},
|
|
94
73
|
function (pError, pServerInfo)
|
|
95
74
|
{
|