diff --git a/dashboard-ui/bower_components/emby-apiclient/.bower.json b/dashboard-ui/bower_components/emby-apiclient/.bower.json index 10b2cfa6e5..14291cd304 100644 --- a/dashboard-ui/bower_components/emby-apiclient/.bower.json +++ b/dashboard-ui/bower_components/emby-apiclient/.bower.json @@ -16,12 +16,12 @@ }, "devDependencies": {}, "ignore": [], - "version": "1.1.98", - "_release": "1.1.98", + "version": "1.1.101", + "_release": "1.1.101", "_resolution": { "type": "version", - "tag": "1.1.98", - "commit": "2c96ef1ead7a5e4a4613f5f73bcf494095c778dc" + "tag": "1.1.101", + "commit": "ee067b87820c7fc8b43499f48b3035fd0e594032" }, "_source": "https://github.com/MediaBrowser/Emby.ApiClient.Javascript.git", "_target": "^1.1.51", diff --git a/dashboard-ui/bower_components/emby-apiclient/nullassetmanager.js b/dashboard-ui/bower_components/emby-apiclient/nullassetmanager.js new file mode 100644 index 0000000000..aef746e9b6 --- /dev/null +++ b/dashboard-ui/bower_components/emby-apiclient/nullassetmanager.js @@ -0,0 +1,114 @@ +define([], function () { + 'use strict'; + + function getLocalMediaSource(serverId, itemId) { + return Promise.resolve(null); + } + + function saveOfflineUser(user) { + return Promise.resolve(); + } + + function deleteOfflineUser(id) { + return Promise.resolve(); + } + + function getCameraPhotos() { + return Promise.resolve([]); + } + + function recordUserAction(action) { + return Promise.resolve(); + } + + function getUserActions(serverId) { + return Promise.resolve([]); + } + + function deleteUserAction(action) { + return Promise.resolve(); + } + + function deleteUserActions(actions) { + //TODO: + return Promise.resolve(); + } + + function getServerItemIds(serverId) { + return Promise.resolve([]); + } + + function removeLocalItem(localItem) { + return Promise.resolve(); + } + + function getLocalItem(itemId, serverId) { + return Promise.resolve(); + } + + function addOrUpdateLocalItem(localItem) { + return Promise.resolve(); + } + + function createLocalItem(libraryItem, serverInfo, jobItem) { + + return Promise.resolve({}); + } + + function downloadFile(url, localPath) { + + return Promise.resolve(); + } + + function downloadSubtitles(url, localItem, subtitleStreamh) { + + return Promise.resolve(''); + } + + function hasImage(serverId, itemId, imageTag) { + return Promise.resolve(false); + } + + function downloadImage(url, serverId, itemId, imageTag) { + return Promise.resolve(false); + } + + function fileExists(path) { + return Promise.resolve(false); + } + + function translateFilePath(path) { + return Promise.resolve(path); + } + + function getLocalFilePath(path) { + return null; + } + + function getLocalItemById(id) { + return null; + } + + return { + getLocalItem: getLocalItem, + saveOfflineUser: saveOfflineUser, + deleteOfflineUser: deleteOfflineUser, + getCameraPhotos: getCameraPhotos, + recordUserAction: recordUserAction, + getUserActions: getUserActions, + deleteUserAction: deleteUserAction, + deleteUserActions: deleteUserActions, + getServerItemIds: getServerItemIds, + removeLocalItem: removeLocalItem, + addOrUpdateLocalItem: addOrUpdateLocalItem, + createLocalItem: createLocalItem, + downloadFile: downloadFile, + downloadSubtitles: downloadSubtitles, + hasImage: hasImage, + downloadImage: downloadImage, + fileExists: fileExists, + translateFilePath: translateFilePath, + getLocalFilePath: getLocalFilePath, + getLocalItemById: getLocalItemById + }; +}); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-apiclient/sync/filerepository.js b/dashboard-ui/bower_components/emby-apiclient/sync/filerepository.js new file mode 100644 index 0000000000..abe51c0c03 --- /dev/null +++ b/dashboard-ui/bower_components/emby-apiclient/sync/filerepository.js @@ -0,0 +1,42 @@ +define([], function () { + 'use strict'; + + function getValidFileName(path) { + + // TODO + return path; + } + + + function getFullLocalPath(pathArray) { + + // TODO + return pathArray.join('/'); + + } + + function deleteFile(path) { + return Promise.resolve(); + } + + function deleteDirectory(path) { + return Promise.resolve(); + } + + function fileExists(path) { + return Promise.resolve(); + } + + function getItemFileSize(path) { + return Promise.resolve(0); + } + + return { + getValidFileName: getValidFileName, + getFullLocalPath: getFullLocalPath, + deleteFile: deleteFile, + deleteDirectory: deleteDirectory, + fileExists: fileExists, + getItemFileSize: getItemFileSize + }; +}); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-apiclient/sync/itemrepository.js b/dashboard-ui/bower_components/emby-apiclient/sync/itemrepository.js new file mode 100644 index 0000000000..f690f0882b --- /dev/null +++ b/dashboard-ui/bower_components/emby-apiclient/sync/itemrepository.js @@ -0,0 +1,97 @@ +define(['idb'], function () { + 'use strict'; + + // Database name + var dbName = "items"; + + // Database version + var dbVersion = 1; + + var dbPromise; + + function setup() { + + dbPromise = idb.open(dbName, dbVersion, function (upgradeDB) { + // Note: we don't use 'break' in this switch statement, + // the fall-through behaviour is what we want. + switch (upgradeDB.oldVersion) { + case 0: + upgradeDB.createObjectStore(dbName); + //case 1: + // upgradeDB.createObjectStore('stuff', { keyPath: '' }); + } + }); //.then(db => console.log("DB opened!", db)); + } + + function getServerItemIds(serverId) { + return dbPromise.then(function (db) { + return db.transaction(dbName).objectStore(dbName).getAll(null, 10000).then(function (all) { + return all.filter(function (item) { + return item.ServerId === serverId; + }).map(function (item2) { + return item2.ItemId; + }); + }); + }); + } + + function getServerIds(serverId) { + return dbPromise.then(function (db) { + return db.transaction(dbName).objectStore(dbName).getAll(null, 10000).then(function (all) { + return all.filter(function (item) { + return item.ServerId === serverId; + }).map(function (item2) { + return item2.Id; + }); + }); + }); + } + + function getAll() { + return dbPromise.then(function (db) { + return db.transaction(dbName).objectStore(dbName).getAll(null, 10000); + }); + } + + function get(key) { + return dbPromise.then(function (db) { + return db.transaction(dbName).objectStore(dbName).get(key); + }); + } + + function set(key, val) { + return dbPromise.then(function (db) { + var tx = db.transaction(dbName, 'readwrite'); + tx.objectStore(dbName).put(val, key); + return tx.complete; + }); + } + + function remove(key) { + return dbPromise.then(function (db) { + var tx = db.transaction(dbName, 'readwrite'); + tx.objectStore(dbName).delete(key); + return tx.complete; + }); + } + + function clear() { + return dbPromise.then(function (db) { + var tx = db.transaction(dbName, 'readwrite'); + tx.objectStore(dbName).clear(key); + return tx.complete; + }); + } + + setup(); + + return { + get: get, + set: set, + remove: remove, + clear: clear, + getAll: getAll, + getServerItemIds: getServerItemIds, + getServerIds: getServerIds + }; +}); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-apiclient/sync/mediasync.js b/dashboard-ui/bower_components/emby-apiclient/sync/mediasync.js index bf00460023..efe3fba548 100644 --- a/dashboard-ui/bower_components/emby-apiclient/sync/mediasync.js +++ b/dashboard-ui/bower_components/emby-apiclient/sync/mediasync.js @@ -1,471 +1,488 @@ -define(['localassetmanager'], function (LocalAssetManager) { +define(['localassetmanager'], function (localassetmanager) { 'use strict'; + function processDownloadStatus(apiClient, serverInfo, options) { + + console.log('[mediasync] Begin processDownloadStatus'); + + return localassetmanager.getServerItems(serverInfo.Id).then(function (items) { + + console.log('[mediasync] Begin processDownloadStatus getServerItems completed'); + + var progressItems = items.filter(function (item) { + return item.SyncStatus === 'transferring' || item.SyncStatus === 'queued'; + }); + + var p = Promise.resolve(); + var cnt = 0; + + progressItems.forEach(function (item) { + p = p.then(function () { + return reportTransfer(apiClient, item); + }); + cnt++; + }); + + return p.then(function () { + console.log('[mediasync] Exit processDownloadStatus. Items reported: ' + cnt.toString()); + return Promise.resolve(); + }); + }); + } + + function reportTransfer(apiClient, item) { + + return localassetmanager.getItemFileSize(item.LocalPath).then(function (size) { + // The background transfer service on Windows leaves the file empty (size = 0) until it + // has been downloaded completely + if (size > 0) { + return apiClient.reportSyncJobItemTransferred(item.SyncJobItemId).then(function () { + item.SyncStatus = 'synced'; + return localassetmanager.addOrUpdateLocalItem(item); + }, function (error) { + console.error("[mediasync] Mediasync error on reportSyncJobItemTransferred", error); + item.SyncStatus = 'error'; + return localassetmanager.addOrUpdateLocalItem(item); + }); + } else { + return localassetmanager.isDownloadInQueue(item.SyncJobItemId).then(function (result) { + if (result) { + // just wait for completion + return Promise.resolve(); + } + + console.log("[mediasync] reportTransfer: Size is 0 and download no longer in queue. Deleting item."); + return localassetmanager.removeLocalItem(item).then(function () { + console.log('[mediasync] reportTransfer: Item deleted.'); + return Promise.resolve(); + }, function (err2) { + console.log('[mediasync] reportTransfer: Failed to delete item.', error); + return Promise.resolve(); + }); + }); + } + + }, function (error) { + + console.error('[mediasync] reportTransfer: error on getItemFileSize. Deleting item.', error); + return localassetmanager.removeLocalItem(item).then(function () { + console.log('[mediasync] reportTransfer: Item deleted.'); + return Promise.resolve(); + }, function (err2) { + console.log('[mediasync] reportTransfer: Failed to delete item.', error); + return Promise.resolve(); + }); + }); + } + + function reportOfflineActions(apiClient, serverInfo) { + + console.log('[mediasync] Begin reportOfflineActions'); + + return localassetmanager.getUserActions(serverInfo.Id).then(function (actions) { + + if (!actions.length) { + console.log('[mediasync] Exit reportOfflineActions (no actions)'); + return Promise.resolve(); + } + + return apiClient.reportOfflineActions(actions).then(function () { + + return localassetmanager.deleteUserActions(actions).then(function () { + console.log('[mediasync] Exit reportOfflineActions (actions reported and deleted.)'); + return Promise.resolve(); + }); + + }, function (err) { + + // delete those actions even on failure, because if the error is caused by + // the action data itself, this could otherwise lead to a situation that + // never gets resolved + console.error('[mediasync] error on apiClient.reportOfflineActions: ' + err.toString()); + return localassetmanager.deleteUserActions(actions); + }); + }); + } + + function syncData(apiClient, serverInfo, syncUserItemAccess) { + + console.log('[mediasync] Begin syncData'); + + return localassetmanager.getServerItems(serverInfo.Id).then(function (items) { + + var completedItems = items.filter(function (item) { + return (item) && ((item.SyncStatus === 'synced') || (item.SyncStatus === 'error')); + }); + + var request = { + TargetId: apiClient.deviceId(), + LocalItemIds: completedItems.map(function (xitem) { return xitem.ItemId; }), + OfflineUserIds: (serverInfo.Users || []).map(function (u) { return u.Id; }) + }; + + return apiClient.syncData(request).then(function (result) { + + return afterSyncData(apiClient, serverInfo, syncUserItemAccess, result).then(function () { + return Promise.resolve(); + }, function () { + return Promise.resolve(); + }); + + }); + }); + } + + function afterSyncData(apiClient, serverInfo, enableSyncUserItemAccess, syncDataResult) { + + console.log('[mediasync] Begin afterSyncData'); + + var p = Promise.resolve(); + + if (syncDataResult.ItemIdsToRemove) { + syncDataResult.ItemIdsToRemove.forEach(function (itemId) { + p = p.then(function () { + return removeLocalItem(itemId, serverInfo.Id); + }); + }); + } + + if (enableSyncUserItemAccess) { + p = p.then(function () { + return syncUserItemAccess(syncDataResult, serverInfo.Id); + }); + } + + return p.then(function () { + console.log('[mediasync] Exit afterSyncData'); + return Promise.resolve(); + }); + } + + function removeLocalItem(itemId, serverId) { + + console.log('[mediasync] Begin removeLocalItem'); + + return localassetmanager.getLocalItem(serverId, itemId).then(function (item) { + + if (item) { + return localassetmanager.removeLocalItem(item); + } + + return Promise.resolve(); + + }); + } + + function getNewMedia(apiClient, serverInfo, options) { + + console.log('[mediasync] Begin getNewMedia'); + + return apiClient.getReadySyncItems(apiClient.deviceId()).then(function (jobItems) { + + var p = Promise.resolve(); + + jobItems.forEach(function (jobItem) { + p = p.then(function () { + return getNewItem(jobItem, apiClient, serverInfo, options); + }); + }); + + return p.then(function () { + console.log('[mediasync] Exit getNewMedia'); + return Promise.resolve(); + }); + }); + } + + function getNewItem(jobItem, apiClient, serverInfo, options) { + + console.log('[mediasync] Begin getNewItem'); + + var libraryItem = jobItem.Item; + + return localassetmanager.getLocalItem(serverInfo.Id, libraryItem.Id).then(function (existingItem) { + + console.log('[mediasync] getNewItem.getLocalItem completed'); + + if (existingItem) { + if (existingItem.SyncStatus === 'queued' || existingItem.SyncStatus === 'transferring' || existingItem.SyncStatus === 'synced') { + console.log('[mediasync] getNewItem.getLocalItem found existing item'); + return Promise.resolve(); + } + } + + console.log('[mediasync] getNewItem.getLocalItem no existing item found'); + + return localassetmanager.createLocalItem(libraryItem, serverInfo, jobItem).then(function (localItem) { + + console.log('[mediasync] getNewItem.createLocalItem completed'); + + localItem.SyncStatus = 'queued'; + + return downloadMedia(apiClient, jobItem, localItem, options).then(function () { + + return getImages(apiClient, jobItem, localItem).then(function () { + + return getSubtitles(apiClient, jobItem, localItem); + + }); + }); + }); + }); + } + + function downloadMedia(apiClient, jobItem, localItem, options) { + + console.log('[mediasync] Begin downloadMedia'); + + var url = apiClient.getUrl("Sync/JobItems/" + jobItem.SyncJobItemId + "/File", { + api_key: apiClient.accessToken() + }); + + var localPath = localItem.LocalPath; + + console.log('[mediasync] Downloading media. Url: ' + url + '. Local path: ' + localPath); + + options = options || {}; + + return localassetmanager.downloadFile(url, localItem).then(function (filename) { + + localItem.SyncStatus = 'transferring'; + return localassetmanager.addOrUpdateLocalItem(localItem); + }); + } + + function getImages(apiClient, jobItem, localItem) { + + console.log('[mediasync] Begin getImages'); + + return getNextImage(0, apiClient, localItem); + } + + function getNextImage(index, apiClient, localItem) { + + console.log('[mediasync] Begin getNextImage'); + + //if (index >= 4) { + + // deferred.resolve(); + // return; + //} + + // Just for now while media syncing gets worked out + return Promise.resolve(); + + //var libraryItem = localItem.Item; + + //var serverId = libraryItem.ServerId; + //var itemId = null; + //var imageTag = null; + //var imageType = "Primary"; + + //switch (index) { + + // case 0: + // itemId = libraryItem.Id; + // imageType = "Primary"; + // imageTag = (libraryItem.ImageTags || {})["Primary"]; + // break; + // case 1: + // itemId = libraryItem.SeriesId; + // imageType = "Primary"; + // imageTag = libraryItem.SeriesPrimaryImageTag; + // break; + // case 2: + // itemId = libraryItem.SeriesId; + // imageType = "Thumb"; + // imageTag = libraryItem.SeriesPrimaryImageTag; + // break; + // case 3: + // itemId = libraryItem.AlbumId; + // imageType = "Primary"; + // imageTag = libraryItem.AlbumPrimaryImageTag; + // break; + // default: + // break; + //} + + //if (!itemId || !imageTag) { + // getNextImage(index + 1, apiClient, localItem, deferred); + // return; + //} + + //downloadImage(apiClient, serverId, itemId, imageTag, imageType).then(function () { + + // // For the sake of simplicity, limit to one image + // deferred.resolve(); + // return; + + // getNextImage(index + 1, apiClient, localItem, deferred); + + //}, getOnFail(deferred)); + } + + function downloadImage(apiClient, serverId, itemId, imageTag, imageType) { + + console.log('[mediasync] Begin downloadImage'); + + return localassetmanager.hasImage(serverId, itemId, imageTag).then(function (hasImage) { + + if (hasImage) { + return Promise.resolve(); + } + + var imageUrl = apiClient.getImageUrl(itemId, { + tag: imageTag, + type: imageType, + api_key: apiClient.accessToken() + }); + + return localassetmanager.downloadImage(imageUrl, serverId, itemId, imageTag); + }); + } + + function getSubtitles(apiClient, jobItem, localItem) { + + console.log('[mediasync] Begin getSubtitles'); + + if (!jobItem.Item.MediaSources.length) { + console.log("[mediasync] Cannot download subtitles because video has no media source info."); + return Promise.resolve(); + } + + var files = jobItem.AdditionalFiles.filter(function (f) { + return f.Type === 'Subtitles'; + }); + + var mediaSource = jobItem.Item.MediaSources[0]; + + var p = Promise.resolve(); + + files.forEach(function (file) { + p = p.then(function () { + return getItemSubtitle(file, apiClient, jobItem, localItem, mediaSource); + }); + }); + + return p.then(function () { + console.log('[mediasync] Exit getSubtitles'); + return Promise.resolve(); + }); + } + + function getItemSubtitle(file, apiClient, jobItem, localItem, mediaSource) { + + console.log('[mediasync] Begin getItemSubtitle'); + + var subtitleStream = mediaSource.MediaStreams.filter(function (m) { + return m.Type === 'Subtitle' && m.Index === file.Index; + })[0]; + + if (!subtitleStream) { + + // We shouldn't get in here, but let's just be safe anyway + console.log("[mediasync] Cannot download subtitles because matching stream info wasn't found."); + return Promise.resolve(); + } + + var url = apiClient.getUrl("Sync/JobItems/" + jobItem.SyncJobItemId + "/AdditionalFiles", { + Name: file.Name, + api_key: apiClient.accessToken() + }); + + var fileName = localassetmanager.getSubtitleSaveFileName(jobItem.OriginalFileName, subtitleStream.Language, subtitleStream.IsForced, subtitleStream.Codec); + var localFilePath = localassetmanager.getLocalFilePath(localItem, fileName); + + return localassetmanager.downloadSubtitles(url, localFilePath).then(function (subtitlePath) { + + subtitleStream.Path = subtitlePath; + return localassetmanager.addOrUpdateLocalItem(localItem); + }); + } + return function () { var self = this; self.sync = function (apiClient, serverInfo, options) { - return reportOfflineActions(apiClient, serverInfo).then(function () { + console.log("[mediasync]************************************* Start sync"); - // Do the first data sync - return syncData(apiClient, serverInfo, false).then(function () { + return processDownloadStatus(apiClient, serverInfo, options).then(function () { + + return reportOfflineActions(apiClient, serverInfo).then(function () { + + //// Do the first data sync + //return syncData(apiClient, serverInfo, false).then(function () { // Download new content return getNewMedia(apiClient, serverInfo, options).then(function () { // Do the second data sync - return syncData(apiClient, serverInfo, false); + return syncData(apiClient, serverInfo, false).then(function () { + console.log("[mediasync]************************************* Exit sync"); + }); }); + //}); }); }); }; - - function reportOfflineActions(apiClient, serverInfo) { - - console.log('Begin reportOfflineActions'); - - return LocalAssetManager.getOfflineActions(serverInfo.Id).then(function (actions) { - - if (!actions.length) { - return Promise.resolve(); - } - - return apiClient.reportOfflineActions(actions).then(function () { - - return LocalAssetManager.deleteOfflineActions(actions); - }); - }); - } - - function syncData(apiClient, serverInfo, syncUserItemAccess) { - - console.log('Begin syncData'); - - var deferred = DeferredBuilder.Deferred(); - - LocalAssetManager.getServerItemIds(serverInfo.Id).then(function (localIds) { - - var request = { - TargetId: apiClient.deviceId(), - LocalItemIds: localIds, - OfflineUserIds: (serverInfo.Users || []).map(function (u) { return u.Id; }) - }; - - apiClient.syncData(request).then(function (result) { - - afterSyncData(apiClient, serverInfo, syncUserItemAccess, result, deferred); - - }, getOnFail(deferred)); - - }, getOnFail(deferred)); - - return deferred.promise(); - } - - function afterSyncData(apiClient, serverInfo, enableSyncUserItemAccess, syncDataResult, deferred) { - - console.log('Begin afterSyncData'); - - removeLocalItems(syncDataResult, serverInfo.Id).then(function (result) { - - if (enableSyncUserItemAccess) { - syncUserItemAccess(syncDataResult, serverInfo.Id).then(function () { - - deferred.resolve(); - - }, getOnFail(deferred)); - } - else { - deferred.resolve(); - } - - }, getOnFail(deferred)); - - deferred.resolve(); - } - - function removeLocalItems(syncDataResult, serverId) { - - console.log('Begin removeLocalItems'); - - var deferred = DeferredBuilder.Deferred(); - - removeNextLocalItem(syncDataResult.ItemIdsToRemove, 0, serverId, deferred); - - return deferred.promise(); - } - - function removeNextLocalItem(itemIdsToRemove, index, serverId, deferred) { - - var length = itemIdsToRemove.length; - - if (index >= length) { - - deferred.resolve(); - return; - } - - removeLocalItem(itemIdsToRemove[index], serverId).then(function () { - - removeNextLocalItem(itemIdsToRemove, index + 1, serverId, deferred); - }, function () { - removeNextLocalItem(itemIdsToRemove, index + 1, serverId, deferred); - }); - } - - function removeLocalItem(itemId, serverId) { - - console.log('Begin removeLocalItem'); - - return LocalAssetManager.removeLocalItem(itemId, serverId); - } - - function getNewMedia(apiClient, serverInfo, options) { - - console.log('Begin getNewMedia'); - - var deferred = DeferredBuilder.Deferred(); - - apiClient.getReadySyncItems(apiClient.deviceId()).then(function (jobItems) { - - getNextNewItem(jobItems, 0, apiClient, serverInfo, options, deferred); - - }, getOnFail(deferred)); - - return deferred.promise(); - } - - function getNextNewItem(jobItems, index, apiClient, serverInfo, options, deferred) { - - var length = jobItems.length; - - if (index >= length) { - - deferred.resolve(); - return; - } - - var hasGoneNext = false; - var goNext = function () { - - if (!hasGoneNext) { - hasGoneNext = true; - getNextNewItem(jobItems, index + 1, apiClient, serverInfo, options, deferred); - } - }; - - getNewItem(jobItems[index], apiClient, serverInfo, options).then(goNext, goNext); - } - - function getNewItem(jobItem, apiClient, serverInfo, options) { - - console.log('Begin getNewItem'); - - var deferred = DeferredBuilder.Deferred(); - - var libraryItem = jobItem.Item; - LocalAssetManager.createLocalItem(libraryItem, serverInfo, jobItem.OriginalFileName).then(function (localItem) { - - downloadMedia(apiClient, jobItem, localItem, options).then(function (isQueued) { - - if (isQueued) { - deferred.resolve(); - return; - } - - getImages(apiClient, jobItem, localItem).then(function () { - - getSubtitles(apiClient, jobItem, localItem).then(function () { - - apiClient.reportSyncJobItemTransferred(jobItem.SyncJobItemId).then(function () { - - deferred.resolve(); - - }, getOnFail(deferred)); - - }, getOnFail(deferred)); - - }, getOnFail(deferred)); - - }, getOnFail(deferred)); - - }, getOnFail(deferred)); - - return deferred.promise(); - } - - function downloadMedia(apiClient, jobItem, localItem, options) { - - console.log('Begin downloadMedia'); - var deferred = DeferredBuilder.Deferred(); - - var url = apiClient.getUrl("Sync/JobItems/" + jobItem.SyncJobItemId + "/File", { - api_key: apiClient.accessToken() - }); - - var localPath = localItem.LocalPath; - - console.log('Downloading media. Url: ' + url + '. Local path: ' + localPath); - - options = options || {}; - - LocalAssetManager.downloadFile(url, localPath, options.enableBackgroundTransfer, options.enableNewDownloads).then(function (path, isQueued) { - - if (isQueued) { - deferred.resolveWith(null, [true]); - return; - } - LocalAssetManager.addOrUpdateLocalItem(localItem).then(function () { - - deferred.resolveWith(null, [false]); - - }, getOnFail(deferred)); - - }, getOnFail(deferred)); - - return deferred.promise(); - } - - function getImages(apiClient, jobItem, localItem) { - - console.log('Begin getImages'); - var deferred = DeferredBuilder.Deferred(); - - getNextImage(0, apiClient, localItem, deferred); - - return deferred.promise(); - } - - function getNextImage(index, apiClient, localItem, deferred) { - - console.log('Begin getNextImage'); - if (index >= 4) { - - deferred.resolve(); - return; - } - - // Just for now while media syncing gets worked out - deferred.resolve(); - - //var libraryItem = localItem.Item; - - //var serverId = libraryItem.ServerId; - //var itemId = null; - //var imageTag = null; - //var imageType = "Primary"; - - //switch (index) { - - // case 0: - // itemId = libraryItem.Id; - // imageType = "Primary"; - // imageTag = (libraryItem.ImageTags || {})["Primary"]; - // break; - // case 1: - // itemId = libraryItem.SeriesId; - // imageType = "Primary"; - // imageTag = libraryItem.SeriesPrimaryImageTag; - // break; - // case 2: - // itemId = libraryItem.SeriesId; - // imageType = "Thumb"; - // imageTag = libraryItem.SeriesPrimaryImageTag; - // break; - // case 3: - // itemId = libraryItem.AlbumId; - // imageType = "Primary"; - // imageTag = libraryItem.AlbumPrimaryImageTag; - // break; - // default: - // break; - //} - - //if (!itemId || !imageTag) { - // getNextImage(index + 1, apiClient, localItem, deferred); - // return; - //} - - //downloadImage(apiClient, serverId, itemId, imageTag, imageType).then(function () { - - // // For the sake of simplicity, limit to one image - // deferred.resolve(); - // return; - - // getNextImage(index + 1, apiClient, localItem, deferred); - - //}, getOnFail(deferred)); - } - - function downloadImage(apiClient, serverId, itemId, imageTag, imageType) { - - console.log('Begin downloadImage'); - var deferred = DeferredBuilder.Deferred(); - - LocalAssetManager.hasImage(serverId, itemId, imageTag).then(function (hasImage) { - - if (hasImage) { - deferred.resolve(); - return; - } - - var imageUrl = apiClient.getImageUrl(itemId, { - tag: imageTag, - type: imageType, - api_key: apiClient.accessToken() - }); - - LocalAssetManager.downloadImage(imageUrl, serverId, itemId, imageTag).then(function () { - - deferred.resolve(); - - }, getOnFail(deferred)); - - }); - - return deferred.promise(); - } - - function getSubtitles(apiClient, jobItem, localItem) { - - console.log('Begin getSubtitles'); - var deferred = DeferredBuilder.Deferred(); - - if (!jobItem.Item.MediaSources.length) { - console.log("Cannot download subtitles because video has no media source info."); - deferred.resolve(); - return; - } - - var files = jobItem.AdditionalFiles.filter(function (f) { - return f.Type === 'Subtitles'; - }); - - var mediaSource = jobItem.Item.MediaSources[0]; - - getNextSubtitle(files, 0, apiClient, jobItem, localItem, mediaSource, deferred); - - return deferred.promise(); - } - - function getNextSubtitle(files, index, apiClient, jobItem, localItem, mediaSource, deferred) { - - var length = files.length; - - if (index >= length) { - - deferred.resolve(); - return; - } - - getItemSubtitle(file, apiClient, jobItem, localItem, mediaSource).then(function () { - - getNextSubtitle(files, index + 1, apiClient, jobItem, localItem, mediaSource, deferred); - - }, function () { - getNextSubtitle(files, index + 1, apiClient, jobItem, localItem, mediaSource, deferred); - }); - } - - function getItemSubtitle(file, apiClient, jobItem, localItem, mediaSource) { - - console.log('Begin getItemSubtitle'); - var deferred = DeferredBuilder.Deferred(); - - var subtitleStream = mediaSource.MediaStreams.filter(function (m) { - return m.Type === 'Subtitle' && m.Index === file.Index; - })[0]; - - if (!subtitleStream) { - - // We shouldn't get in here, but let's just be safe anyway - console.log("Cannot download subtitles because matching stream info wasn't found."); - deferred.reject(); - return; - } - - var url = apiClient.getUrl("Sync/JobItems/" + jobItem.SyncJobItemId + "/AdditionalFiles", { - Name: file.Name, - api_key: apiClient.accessToken() - }); - - LocalAssetManager.downloadSubtitles(url, localItem, subtitleStream).then(function (subtitlePath) { - - subtitleStream.Path = subtitlePath; - LocalAssetManager.addOrUpdateLocalItem(localItem).then(function () { - deferred.resolve(); - }, getOnFail(deferred)); - - }, getOnFail(deferred)); - - return deferred.promise(); - } - - function syncUserItemAccess(syncDataResult, serverId) { - console.log('Begin syncUserItemAccess'); - - var deferred = DeferredBuilder.Deferred(); - - var itemIds = []; - for (var id in syncDataResult.ItemUserAccess) { - itemIds.push(id); - } - - syncNextUserAccessForItem(itemIds, 0, syncDataResult, serverId, deferred); - - return deferred.promise(); - } - - function syncNextUserAccessForItem(itemIds, index, syncDataResult, serverId, deferred) { - - var length = itemIds.length; - - if (index >= length) { - - deferred.resolve(); - return; - } - - syncUserAccessForItem(itemIds[index], syncDataResult).then(function () { - - syncNextUserAccessForItem(itemIds, index + 1, syncDataResult, serverId, deferred); - }, function () { - syncNextUserAccessForItem(itemIds, index + 1, syncDataResult, serverId, deferred); - }); - } - - function syncUserAccessForItem(itemId, syncDataResult) { - console.log('Begin syncUserAccessForItem'); - - var deferred = DeferredBuilder.Deferred(); - - LocalAssetManager.getUserIdsWithAccess(itemId, serverId).then(function (savedUserIdsWithAccess) { - - var userIdsWithAccess = syncDataResult.ItemUserAccess[itemId]; - - if (userIdsWithAccess.join(',') === savedUserIdsWithAccess.join(',')) { - // Hasn't changed, nothing to do - deferred.resolve(); - } - else { - - LocalAssetManager.saveUserIdsWithAccess(itemId, serverId, userIdsWithAccess).then(function () { - deferred.resolve(); - }, getOnFail(deferred)); - } - - }, getOnFail(deferred)); - - return deferred.promise(); - } - - function getOnFail(deferred) { - return function () { - - deferred.reject(); - }; - } }; + + //function syncUserItemAccess(syncDataResult, serverId) { + // console.log('[mediasync] Begin syncUserItemAccess'); + + // var itemIds = []; + // for (var id in syncDataResult.ItemUserAccess) { + // itemIds.push(id); + // } + + // return syncNextUserAccessForItem(itemIds, 0, syncDataResult, serverId); + //} + + //function syncNextUserAccessForItem(itemIds, index, syncDataResult, serverId) { + + // var length = itemIds.length; + + // if (index >= length) { + + // return Promise.resolve + // return; + // } + + // syncUserAccessForItem(itemIds[index], syncDataResult).then(function () { + + // syncNextUserAccessForItem(itemIds, index + 1, syncDataResult, serverId, deferred); + // }, function () { + // syncNextUserAccessForItem(itemIds, index + 1, syncDataResult, serverId, deferred); + // }); + //} + + //function syncUserAccessForItem(itemId, syncDataResult) { + // console.log('[mediasync] Begin syncUserAccessForItem'); + + // var deferred = DeferredBuilder.Deferred(); + + // localassetmanager.getUserIdsWithAccess(itemId, serverId).then(function (savedUserIdsWithAccess) { + + // var userIdsWithAccess = syncDataResult.ItemUserAccess[itemId]; + + // if (userIdsWithAccess.join(',') === savedUserIdsWithAccess.join(',')) { + // // Hasn't changed, nothing to do + // deferred.resolve(); + // } + // else { + + // localassetmanager.saveUserIdsWithAccess(itemId, serverId, userIdsWithAccess).then(function () { + // deferred.resolve(); + // }, getOnFail(deferred)); + // } + + // }, getOnFail(deferred)); + + // return deferred.promise(); + //} + + //} + }); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-apiclient/sync/serversync.js b/dashboard-ui/bower_components/emby-apiclient/sync/serversync.js index ea050ec29f..7c5e81b67c 100644 --- a/dashboard-ui/bower_components/emby-apiclient/sync/serversync.js +++ b/dashboard-ui/bower_components/emby-apiclient/sync/serversync.js @@ -37,7 +37,7 @@ function performSync(server, options) { - console.log("Creating ContentUploader to server: " + server.Id); + console.log("ServerSync.performSync to server: " + server.Id); options = options || {}; @@ -47,34 +47,27 @@ uploadPhotos = false; } - if (!uploadPhotos) { - return syncOfflineUsers(server, options); - } + var pr = syncOfflineUsers(server, options); - return new Promise(function (resolve, reject) { + return pr.then(function () { - require(['contentuploader'], function (ContentUploader) { + if (uploadPhotos) { + return uploadContent(server, options); + } - new ContentUploader(connectionManager).uploadImages(server).then(function () { + return Promise.resolve(); - console.log("ContentUploaded succeeded to server: " + server.Id); + }).then(function () { - syncOfflineUsers(server, options).then(resolve, reject); - - }, function () { - - console.log("ContentUploaded failed to server: " + server.Id); - - syncOfflineUsers(server, options).then(resolve, reject); - }); - }); + return syncMedia(server, options); }); } + function syncOfflineUsers(server, options) { if (options.syncOfflineUsers === false) { - return syncMedia(server, options); + return Promise.resolve(); } return new Promise(function (resolve, reject) { @@ -83,13 +76,19 @@ var apiClient = connectionManager.getApiClient(server.Id); - new OfflineUserSync().sync(apiClient, server).then(function () { + new OfflineUserSync().sync(apiClient, server).then(resolve, reject); + }); + }); + } - console.log("OfflineUserSync succeeded to server: " + server.Id); + function uploadContent(server, options) { - syncMedia(server, options).then(resolve, reject); + return new Promise(function (resolve, reject) { - }, reject); + require(['contentuploader'], function (contentuploader) { + + uploader = new ContentUploader(connectionManager); + uploader.uploadImages(server).then(resolve, reject); }); }); } diff --git a/dashboard-ui/bower_components/emby-apiclient/sync/transfermanager.js b/dashboard-ui/bower_components/emby-apiclient/sync/transfermanager.js new file mode 100644 index 0000000000..f8bea7307b --- /dev/null +++ b/dashboard-ui/bower_components/emby-apiclient/sync/transfermanager.js @@ -0,0 +1,23 @@ +define(['filerepository'], function (filerepository) { + 'use strict'; + + function downloadFile(url, localPath) { + + return Promise.resolve(); + } + + function downloadSubtitles(url, localItem, subtitleStreamh) { + + return Promise.resolve(''); + } + + function downloadImage(url, serverId, itemId, imageTag) { + return Promise.resolve(false); + } + + return { + downloadFile: downloadFile, + downloadSubtitles: downloadSubtitles, + downloadImage: downloadImage + }; +}); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-apiclient/sync/useractionrepository.js b/dashboard-ui/bower_components/emby-apiclient/sync/useractionrepository.js new file mode 100644 index 0000000000..51be27f615 --- /dev/null +++ b/dashboard-ui/bower_components/emby-apiclient/sync/useractionrepository.js @@ -0,0 +1,82 @@ +define(['idb'], function () { + 'use strict'; + + // Database name + var dbName = "useractions"; + + // Database version + var dbVersion = 1; + + var dbPromise; + + function setup() { + + dbPromise = idb.open(dbName, dbVersion, function (upgradeDB) { + // Note: we don't use 'break' in this switch statement, + // the fall-through behaviour is what we want. + switch (upgradeDB.oldVersion) { + case 0: + upgradeDB.createObjectStore(dbName); + //case 1: + // upgradeDB.createObjectStore('stuff', { keyPath: '' }); + } + }); //.then(db => console.log("DB opened!", db)); + } + + function getByServerId(serverId) { + return dbPromise.then(function (db) { + return db.transaction(dbName).objectStore(dbName).getAll(null, 1000).then(function (all) { + return all.filter(function (item) { + return item.ServerId === serverId; + }); + }); + }); + } + + function getAll() { + return dbPromise.then(function (db) { + return db.transaction(dbName).objectStore(dbName).getAll(null, 10000); + }); + } + + function get(key) { + return dbPromise.then(function (db) { + return db.transaction(dbName).objectStore(dbName).get(key); + }); + } + + function set(key, val) { + return dbPromise.then(function (db) { + var tx = db.transaction(dbName, 'readwrite'); + tx.objectStore(dbName).put(val, key); + return tx.complete; + }); + } + + function remove(key) { + return dbPromise.then(function (db) { + var tx = db.transaction(dbName, 'readwrite'); + tx.objectStore(dbName).delete(key); + return tx.complete; + }); + } + + function clear() { + return dbPromise.then(function (db) { + var tx = db.transaction(dbName, 'readwrite'); + tx.objectStore(dbName).clear(key); + return tx.complete; + }); + } + + setup(); + + return { + get: get, + set: set, + remove: remove, + clear: clear, + getAll: getAll, + getByServerId: getByServerId + }; +}); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-apiclient/sync/userrepository.js b/dashboard-ui/bower_components/emby-apiclient/sync/userrepository.js new file mode 100644 index 0000000000..a51f14c384 --- /dev/null +++ b/dashboard-ui/bower_components/emby-apiclient/sync/userrepository.js @@ -0,0 +1,71 @@ +define(['idb'], function () { + 'use strict'; + + // Database name + var dbName = "users"; + + // Database version + var dbVersion = 1; + + var dbPromise; + + function setup() { + + dbPromise = idb.open(dbName, dbVersion, function (upgradeDB) { + // Note: we don't use 'break' in this switch statement, + // the fall-through behaviour is what we want. + switch (upgradeDB.oldVersion) { + case 0: + upgradeDB.createObjectStore(dbName); + //case 1: + // upgradeDB.createObjectStore('stuff', { keyPath: '' }); + } + }); //.then(db => console.log("DB opened!", db)); + } + + function getAll() { + return dbPromise.then(function (db) { + return db.transaction(dbName).objectStore(dbName).getAll(null, 10000); + }); + } + + function get(key) { + return dbPromise.then(function (db) { + return db.transaction(dbName).objectStore(dbName).get(key); + }); + } + + function set(key, val) { + return dbPromise.then(function (db) { + var tx = db.transaction(dbName, 'readwrite'); + tx.objectStore(dbName).put(val, key); + return tx.complete; + }); + } + + function remove(key) { + return dbPromise.then(function (db) { + var tx = db.transaction(dbName, 'readwrite'); + tx.objectStore(dbName).delete(key); + return tx.complete; + }); + } + + function clear() { + return dbPromise.then(function (db) { + var tx = db.transaction(dbName, 'readwrite'); + tx.objectStore(dbName).clear(key); + return tx.complete; + }); + } + + setup(); + + return { + get: get, + set: set, + remove: remove, + clear: clear, + getAll: getAll + }; +}); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-webcomponents/.bower.json b/dashboard-ui/bower_components/emby-webcomponents/.bower.json index 0b5ae94f39..4cb964a891 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/.bower.json +++ b/dashboard-ui/bower_components/emby-webcomponents/.bower.json @@ -14,12 +14,12 @@ }, "devDependencies": {}, "ignore": [], - "version": "1.4.375", - "_release": "1.4.375", + "version": "1.4.380", + "_release": "1.4.380", "_resolution": { "type": "version", - "tag": "1.4.375", - "commit": "728db9b8c27dcea2b8679e4d7ba6f556cfb9dc20" + "tag": "1.4.380", + "commit": "ef70fed326cf70ed366390f05df489943b155c13" }, "_source": "https://github.com/MediaBrowser/emby-webcomponents.git", "_target": "^1.2.1", diff --git a/dashboard-ui/bower_components/emby-webcomponents/browser.js b/dashboard-ui/bower_components/emby-webcomponents/browser.js index ea63053507..f47733e451 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/browser.js +++ b/dashboard-ui/bower_components/emby-webcomponents/browser.js @@ -55,6 +55,11 @@ } function isStyleSupported(prop, value) { + + if (typeof window === 'undefined') { + return false; + } + // If no value is supplied, use "inherit" value = arguments.length === 2 ? value : 'inherit'; // Try the native standard method first @@ -220,7 +225,7 @@ }; }; - var userAgent = window.navigator.userAgent; + var userAgent = navigator.userAgent; var matched = uaMatch(userAgent); var browser = {}; @@ -248,7 +253,7 @@ } browser.xboxOne = userAgent.toLowerCase().indexOf('xbox') !== -1; - browser.animate = document.documentElement.animate != null; + browser.animate = typeof document !== 'undefined' && document.documentElement.animate != null; browser.tizen = userAgent.toLowerCase().indexOf('tizen') !== -1 || userAgent.toLowerCase().indexOf('smarthub') !== -1; browser.web0s = userAgent.toLowerCase().indexOf('Web0S'.toLowerCase()) !== -1; browser.edgeUwp = browser.edge && userAgent.toLowerCase().indexOf('msapphost') !== -1; @@ -264,8 +269,10 @@ browser.slow = true; } - if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { - browser.touch = true; + if (typeof document !== 'undefined') { + if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { + browser.touch = true; + } } browser.keyboard = hasKeyboard(browser); diff --git a/dashboard-ui/bower_components/emby-webcomponents/idb.js b/dashboard-ui/bower_components/emby-webcomponents/idb.js new file mode 100644 index 0000000000..6260652766 --- /dev/null +++ b/dashboard-ui/bower_components/emby-webcomponents/idb.js @@ -0,0 +1,323 @@ +// from https://github.com/jakearchibald/idb + +(function () { + + 'use strict'; + + function toArray(arr) { + return Array.prototype.slice.call(arr); + } + + function promisifyRequest(request) { + return new Promise(function (resolve, reject) { + request.onsuccess = function () { + resolve(request.result); + }; + + request.onerror = function () { + reject(request.error); + }; + }); + } + + function promisifyRequestCall(obj, method, args) { + var request; + var p = new Promise(function (resolve, reject) { + request = obj[method].apply(obj, args); + promisifyRequest(request).then(resolve, reject); + }); + + p.request = request; + return p; + } + + function promisifyCursorRequestCall(obj, method, args) { + var p = promisifyRequestCall(obj, method, args); + return p.then(function (value) { + if (!value) { + return; + } + return new Cursor(value, p.request); + }); + } + + function proxyProperties(ProxyClass, targetProp, properties) { + properties.forEach(function (prop) { + Object.defineProperty(ProxyClass.prototype, prop, { + get: function () { + return this[targetProp][prop]; + } + }); + }); + } + + function proxyRequestMethods(ProxyClass, targetProp, Constructor, properties) { + properties.forEach(function (prop) { + if (!(prop in Constructor.prototype)) { + return; + } + ProxyClass.prototype[prop] = function () { + return promisifyRequestCall(this[targetProp], prop, arguments); + }; + }); + } + + function proxyMethods(ProxyClass, targetProp, Constructor, properties) { + properties.forEach(function (prop) { + if (!(prop in Constructor.prototype)) { + return; + } + ProxyClass.prototype[prop] = function () { + return this[targetProp][prop].apply(this[targetProp], arguments); + }; + }); + } + + function proxyCursorRequestMethods(ProxyClass, targetProp, Constructor, properties) { + properties.forEach(function (prop) { + if (!(prop in Constructor.prototype)) { + return; + } + ProxyClass.prototype[prop] = function () { + return promisifyCursorRequestCall(this[targetProp], prop, arguments); + }; + }); + } + + function Index(index) { + this._index = index; + } + + proxyProperties(Index, '_index', [ + 'name', + 'keyPath', + 'multiEntry', + 'unique' + ]); + + proxyRequestMethods(Index, '_index', IDBIndex, [ + 'get', + 'getKey', + 'getAll', + 'getAllKeys', + 'count' + ]); + + proxyCursorRequestMethods(Index, '_index', IDBIndex, [ + 'openCursor', + 'openKeyCursor' + ]); + + function Cursor(cursor, request) { + this._cursor = cursor; + this._request = request; + } + + proxyProperties(Cursor, '_cursor', [ + 'direction', + 'key', + 'primaryKey', + 'value' + ]); + + proxyRequestMethods(Cursor, '_cursor', IDBCursor, [ + 'update', + 'delete' + ]); + + // proxy 'next' methods + ['advance', 'continue', 'continuePrimaryKey'].forEach(function (methodName) { + if (!(methodName in IDBCursor.prototype)) { + return; + } + Cursor.prototype[methodName] = function () { + var cursor = this; + var args = arguments; + return Promise.resolve().then(function () { + cursor._cursor[methodName].apply(cursor._cursor, args); + return promisifyRequest(cursor._request).then(function (value) { + if (!value) { + return; + } + return new Cursor(value, cursor._request); + }); + }); + }; + }); + + function ObjectStore(store) { + this._store = store; + } + + ObjectStore.prototype.createIndex = function () { + return new Index(this._store.createIndex.apply(this._store, arguments)); + }; + + ObjectStore.prototype.index = function () { + return new Index(this._store.index.apply(this._store, arguments)); + }; + + proxyProperties(ObjectStore, '_store', [ + 'name', + 'keyPath', + 'indexNames', + 'autoIncrement' + ]); + + proxyRequestMethods(ObjectStore, '_store', IDBObjectStore, [ + 'put', + 'add', + 'delete', + 'clear', + 'get', + 'getAll', + 'getAllKeys', + 'count' + ]); + + proxyCursorRequestMethods(ObjectStore, '_store', IDBObjectStore, [ + 'openCursor', + 'openKeyCursor' + ]); + + proxyMethods(ObjectStore, '_store', IDBObjectStore, [ + 'deleteIndex' + ]); + + function Transaction(idbTransaction) { + this._tx = idbTransaction; + this.complete = new Promise(function (resolve, reject) { + idbTransaction.oncomplete = function () { + resolve(); + }; + idbTransaction.onerror = function () { + reject(idbTransaction.error); + }; + idbTransaction.onabort = function () { + reject(idbTransaction.error); + }; + }); + } + + Transaction.prototype.objectStore = function () { + return new ObjectStore(this._tx.objectStore.apply(this._tx, arguments)); + }; + + proxyProperties(Transaction, '_tx', [ + 'objectStoreNames', + 'mode' + ]); + + proxyMethods(Transaction, '_tx', IDBTransaction, [ + 'abort' + ]); + + function UpgradeDB(db, oldVersion, transaction) { + this._db = db; + this.oldVersion = oldVersion; + this.transaction = new Transaction(transaction); + } + + UpgradeDB.prototype.createObjectStore = function () { + return new ObjectStore(this._db.createObjectStore.apply(this._db, arguments)); + }; + + proxyProperties(UpgradeDB, '_db', [ + 'name', + 'version', + 'objectStoreNames' + ]); + + proxyMethods(UpgradeDB, '_db', IDBDatabase, [ + 'deleteObjectStore', + 'close' + ]); + + function DB(db) { + this._db = db; + } + + DB.prototype.transaction = function () { + return new Transaction(this._db.transaction.apply(this._db, arguments)); + }; + + proxyProperties(DB, '_db', [ + 'name', + 'version', + 'objectStoreNames' + ]); + + proxyMethods(DB, '_db', IDBDatabase, [ + 'close' + ]); + + // Add cursor iterators + // TODO: remove this once browsers do the right thing with promises + ['openCursor', 'openKeyCursor'].forEach(function (funcName) { + [ObjectStore, Index].forEach(function (Constructor) { + Constructor.prototype[funcName.replace('open', 'iterate')] = function () { + var args = toArray(arguments); + var callback = args[args.length - 1]; + var nativeObject = this._store || this._index; + var request = nativeObject[funcName].apply(nativeObject, args.slice(0, -1)); + request.onsuccess = function () { + callback(request.result); + }; + }; + }); + }); + + // polyfill getAll + [Index, ObjectStore].forEach(function (Constructor) { + if (Constructor.prototype.getAll) { + return; + } + Constructor.prototype.getAll = function (query, count) { + var instance = this; + var items = []; + + return new Promise(function (resolve) { + instance.iterateCursor(query, function (cursor) { + if (!cursor) { + resolve(items); + return; + } + items.push(cursor.value); + + if (count !== undefined && items.length === count) { + resolve(items); + return; + } + cursor.continue(); + }); + }); + }; + }); + + var exp = { + open: function (name, version, upgradeCallback) { + var p = promisifyRequestCall(indexedDB, 'open', [name, version]); + var request = p.request; + + request.onupgradeneeded = function (event) { + if (upgradeCallback) { + upgradeCallback(new UpgradeDB(request.result, event.oldVersion, request.transaction)); + } + }; + + return p.then(function (db) { + return new DB(db); + }); + }, + delete: function (name) { + return promisifyRequestCall(indexedDB, 'deleteDatabase', [name]); + } + }; + + if (typeof module !== 'undefined') { + module.exports = exp; + } + else { + self.idb = exp; + } +}()); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-webcomponents/strings/en-US.json b/dashboard-ui/bower_components/emby-webcomponents/strings/en-US.json index 6c7ba7b824..c5d86ef688 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/strings/en-US.json +++ b/dashboard-ui/bower_components/emby-webcomponents/strings/en-US.json @@ -353,5 +353,6 @@ "HeaderPlayMyMedia": "Play my Media", "HeaderDiscoverEmbyPremiere": "Discover Emby Premiere", "OneChannel": "One channel", + "ConfirmRemoveDownload": "Remove download?", "AddedOnValue": "Added {0}" } \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-webcomponents/sync/synctoggle.js b/dashboard-ui/bower_components/emby-webcomponents/sync/synctoggle.js new file mode 100644 index 0000000000..37862ea413 --- /dev/null +++ b/dashboard-ui/bower_components/emby-webcomponents/sync/synctoggle.js @@ -0,0 +1,85 @@ +define(['itemHelper', 'globalize', 'apphost', 'connectionManager', 'events', 'emby-checkbox'], function (itemHelper, globalize, appHost, connectionManager, events) { + 'use strict'; + + function updateSyncStatus(container, item) { + + container.querySelector('.chkOffline').checked = item.SyncPercent != null; + } + + function syncToggle(options) { + + var self = this; + + function resetSyncStatus() { + updateSyncStatus(options.container, options.item); + } + + function onSyncLocalClick() { + + if (this.checked) { + require(['syncDialog'], function (syncDialog) { + syncDialog.showMenu({ + items: [options.item], + isLocalSync: true, + serverId: options.item.ServerId + + }).then(function () { + events.trigger(self, 'sync'); + }, resetSyncStatus); + }); + } else { + + require(['confirm'], function (confirm) { + + confirm(globalize.translate('sharedcomponents#ConfirmRemoveDownload')).then(function () { + connectionManager.getApiClient(options.item.ServerId).cancelSyncItems([options.item.Id]); + }, resetSyncStatus); + }); + } + } + + var container = options.container; + var user = options.user; + var item = options.item; + + var html = ''; + html += ''; + + if (itemHelper.canSync(user, item)) { + if (appHost.supports('sync')) { + container.classList.remove('hide'); + } else { + container.classList.add('hide'); + } + + container.innerHTML = html; + + container.querySelector('.chkOffline').addEventListener('change', onSyncLocalClick); + updateSyncStatus(container, item); + + } else { + container.classList.add('hide'); + } + } + + syncToggle.prototype.refresh = function(item) { + + this.options.item = item; + updateSyncStatus(this.options.container, item); + }; + + syncToggle.prototype.destroy = function () { + + var options = this.options; + + if (options) { + options.container.innerHTML = ''; + this.options = null; + } + }; + + return syncToggle; +}); \ No newline at end of file diff --git a/dashboard-ui/css/librarybrowser.css b/dashboard-ui/css/librarybrowser.css index 8932c9b0e6..38f5115f8c 100644 --- a/dashboard-ui/css/librarybrowser.css +++ b/dashboard-ui/css/librarybrowser.css @@ -777,6 +777,13 @@ span.itemCommunityRating:not(:empty) + .userDataIcons { } } +@media all and (max-width: 800px), (max-height: 800px) { + + .detailsHiddenOnMobile { + display: none; + } +} + #criticReviewsContent.hiddenScrollX { white-space: nowrap; } @@ -825,4 +832,4 @@ span.itemCommunityRating:not(:empty) + .userDataIcons { .mediaInfoText-upper { text-transform: uppercase; -} +} \ No newline at end of file diff --git a/dashboard-ui/itemdetails.html b/dashboard-ui/itemdetails.html index f0e52a7812..efb3d1e588 100644 --- a/dashboard-ui/itemdetails.html +++ b/dashboard-ui/itemdetails.html @@ -49,10 +49,6 @@
-
diff --git a/dashboard-ui/scripts/itemdetailpage.js b/dashboard-ui/scripts/itemdetailpage.js index e3a284debf..f85acefd0f 100644 --- a/dashboard-ui/scripts/itemdetailpage.js +++ b/dashboard-ui/scripts/itemdetailpage.js @@ -1,9 +1,6 @@ -define(['layoutManager', 'cardBuilder', 'datetime', 'mediaInfo', 'backdrop', 'listView', 'itemContextMenu', 'itemHelper', 'userdataButtons', 'dom', 'indicators', 'apphost', 'imageLoader', 'libraryMenu', 'shell', 'globalize', 'browser', 'scrollStyles', 'emby-itemscontainer', 'emby-checkbox'], function (layoutManager, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, userdataButtons, dom, indicators, appHost, imageLoader, libraryMenu, shell, globalize, browser) { +define(['layoutManager', 'cardBuilder', 'datetime', 'mediaInfo', 'backdrop', 'listView', 'itemContextMenu', 'itemHelper', 'userdataButtons', 'dom', 'indicators', 'apphost', 'imageLoader', 'libraryMenu', 'shell', 'globalize', 'browser', 'events', 'scrollStyles', 'emby-itemscontainer', 'emby-checkbox'], function (layoutManager, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, userdataButtons, dom, indicators, appHost, imageLoader, libraryMenu, shell, globalize, browser, events) { 'use strict'; - var currentItem; - var currentRecordingFields; - function getPromise(params) { var id = params.id; @@ -40,6 +37,9 @@ } } + var currentItem; + var currentRecordingFields; + function reload(page, params) { Dashboard.showLoadingMsg(); @@ -87,14 +87,25 @@ return options; } - function updateSyncStatus(page, item) { + function renderSyncLocalContainer(page, params, user, item) { - var i, length; - var elems = page.querySelectorAll('.chkOffline'); - for (i = 0, length = elems.length; i < length; i++) { - - elems[i].checked = item.SyncPercent != null; + if (page.syncToggleInstance) { + page.syncToggleInstance.refresh(item); + return; } + + require(['syncToggle'], function (syncToggle) { + + page.syncToggleInstance = new syncToggle({ + user: user, + item: item, + container: page.querySelector('.syncLocalContainer') + }); + + events.on(page.syncToggleInstance, 'sync', function () { + reload(page, params); + }); + }); } function reloadFromItem(page, params, item) { @@ -166,19 +177,7 @@ hideAll(page, 'btnDeleteItem'); } - if (itemHelper.canSync(user, item)) { - if (appHost.supports('sync')) { - hideAll(page, 'syncLocalContainer', true); - hideAll(page, 'btnSync'); - } else { - hideAll(page, 'syncLocalContainer'); - hideAll(page, 'btnSync', true); - } - updateSyncStatus(page, item); - } else { - hideAll(page, 'btnSync'); - hideAll(page, 'syncLocalContainer'); - } + renderSyncLocalContainer(page, params, user, item); if (hasAnyButton || item.Type !== 'Program') { hideAll(page, 'mainDetailButtons', true); @@ -641,6 +640,12 @@ } var overview = page.querySelector('.overview'); + var externalLinksElem = page.querySelector('.itemExternalLinks'); + + if (item.Type === 'Season' || item.Type === 'MusicAlbum' || item.Type === 'MusicArtist') { + overview.classList.add('detailsHiddenOnMobile'); + externalLinksElem.classList.add('detailsHiddenOnMobile'); + } renderOverview([overview], item); @@ -677,7 +682,7 @@ renderStudios(page.querySelector('.itemStudios'), item, isStatic); renderUserDataIcons(page, item); - renderLinks(page.querySelector('.itemExternalLinks'), item); + renderLinks(externalLinksElem, item); page.querySelector('.criticRatingScore').innerHTML = (item.CriticRating || '0') + '%'; @@ -2175,53 +2180,10 @@ return function (view, params) { - function resetSyncStatus() { - updateSyncStatus(view, currentItem); - } - - function onSyncLocalClick() { - - if (this.checked) { - require(['syncDialog'], function (syncDialog) { - syncDialog.showMenu({ - items: [currentItem], - isLocalSync: true, - serverId: ApiClient.serverId() - - }).then(function () { - reload(view, params); - }, resetSyncStatus); - }); - } else { - - require(['confirm'], function (confirm) { - - confirm(globalize.translate('ConfirmRemoveDownload')).then(function () { - ApiClient.cancelSyncItems([currentItem.Id]); - }, resetSyncStatus); - }); - } - } - function onPlayTrailerClick() { playTrailer(view); } - function onRecordClick() { - var id = params.id; - Dashboard.showLoadingMsg(); - - require(['recordingCreator'], function (recordingCreator) { - recordingCreator.show(id, currentItem.ServerId).then(function () { - reload(view, params); - }); - }); - } - - function onCancelRecordingClick() { - deleteTimer(view, params, currentItem.TimerId); - } - function onMoreCommandsClick() { var button = this; @@ -2257,11 +2219,6 @@ splitVersions(view, params); }); - elems = view.querySelectorAll('.chkOffline'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('change', onSyncLocalClick); - } - elems = view.querySelectorAll('.btnMoreCommands'); for (i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onMoreCommandsClick); @@ -2349,7 +2306,7 @@ var page = this; reload(page, params); - Events.on(ApiClient, 'websocketmessage', onWebSocketMessage); + events.on(ApiClient, 'websocketmessage', onWebSocketMessage); }); view.addEventListener('viewbeforehide', function () { @@ -2357,8 +2314,16 @@ currentItem = null; currentRecordingFields = null; - Events.off(ApiClient, 'websocketmessage', onWebSocketMessage); + events.off(ApiClient, 'websocketmessage', onWebSocketMessage); libraryMenu.setTransparentMenu(false); }); + + view.addEventListener('viewdestroy', function () { + + if (view.syncToggleInstance) { + view.syncToggleInstance.destroy(); + view.syncToggleInstance = null; + } + }); }; }); \ No newline at end of file diff --git a/dashboard-ui/scripts/site.js b/dashboard-ui/scripts/site.js index 2e82c2b7b6..26b2d4ddc6 100644 --- a/dashboard-ui/scripts/site.js +++ b/dashboard-ui/scripts/site.js @@ -1293,6 +1293,7 @@ var AppInfo = {}; define("guide-settings-dialog", [embyWebComponentsBowerPath + "/guide/guide-settings"], returnFirstDependency); define("guide-categories-dialog", [embyWebComponentsBowerPath + "/guide/guide-categories"], returnFirstDependency); define("syncDialog", [embyWebComponentsBowerPath + "/sync/sync"], returnFirstDependency); + define("syncToggle", [embyWebComponentsBowerPath + "/sync/synctoggle"], returnFirstDependency); define("voiceDialog", [embyWebComponentsBowerPath + "/voice/voicedialog"], returnFirstDependency); define("voiceReceiver", [embyWebComponentsBowerPath + "/voice/voicereceiver"], returnFirstDependency); define("voiceProcessor", [embyWebComponentsBowerPath + "/voice/voiceprocessor"], returnFirstDependency);