mirror of
https://github.com/jellyfin/jellyfin-web.git
synced 2024-11-18 11:28:23 -07:00
623 lines
17 KiB
JavaScript
623 lines
17 KiB
JavaScript
define(['filerepository', 'itemrepository', 'userrepository', 'useractionrepository', 'transfermanager', 'cryptojs-md5'], function (filerepository, itemrepository, userrepository, useractionrepository, transfermanager) {
|
|
'use strict';
|
|
|
|
function getLocalItem(serverId, itemId) {
|
|
var id = getLocalId(serverId, itemId);
|
|
return itemrepository.get(id);
|
|
}
|
|
|
|
function getLocalItemById(id) {
|
|
return itemrepository.get(id);
|
|
}
|
|
|
|
function getLocalId(serverId, itemId) {
|
|
|
|
return CryptoJS.MD5(serverId + itemId).toString();
|
|
}
|
|
|
|
function saveOfflineUser(user) {
|
|
return userrepository.set(user.Id, user);
|
|
}
|
|
|
|
function deleteOfflineUser(id) {
|
|
return userrepository.remove(id);
|
|
}
|
|
|
|
function recordUserAction(action) {
|
|
|
|
action.Id = createGuid();
|
|
return useractionrepository.set(action.Id, action);
|
|
}
|
|
|
|
function getUserActions(serverId) {
|
|
return useractionrepository.getByServerId(serverId);
|
|
}
|
|
|
|
function deleteUserAction(action) {
|
|
return useractionrepository.remove(action.Id);
|
|
}
|
|
|
|
function deleteUserActions(actions) {
|
|
var results = [];
|
|
|
|
actions.forEach(function (action) {
|
|
results.push(deleteUserAction(action));
|
|
});
|
|
|
|
return Promise.all(results);
|
|
}
|
|
|
|
function getServerItemIds(serverId) {
|
|
return itemrepository.getServerItemIds(serverId);
|
|
}
|
|
|
|
function getServerItems(serverId) {
|
|
|
|
return itemrepository.getServerIds(serverId).then(function (localIds) {
|
|
|
|
var actions = localIds.map(function (id) {
|
|
return getLocalItemById(id);
|
|
});
|
|
|
|
return Promise.all(actions).then(function (items) {
|
|
|
|
return Promise.resolve(items);
|
|
});
|
|
});
|
|
}
|
|
|
|
function getItemsFromIds(serverId, ids) {
|
|
|
|
var actions = ids.map(function (id) {
|
|
var strippedId = stripStart(id, 'local:');
|
|
|
|
return getLocalItem(serverId, strippedId);
|
|
});
|
|
|
|
return Promise.all(actions).then(function (items) {
|
|
|
|
var libItems = items.map(function (locItem) {
|
|
|
|
return locItem.Item;
|
|
});
|
|
|
|
|
|
return Promise.resolve(libItems);
|
|
});
|
|
}
|
|
|
|
function getViews(serverId, userId) {
|
|
|
|
return itemrepository.getServerItemTypes(serverId, userId).then(function (types) {
|
|
|
|
var list = [];
|
|
var item;
|
|
|
|
if (types.indexOf('audio') > -1) {
|
|
|
|
item = {
|
|
Name: 'Music',
|
|
ServerId: serverId,
|
|
Id: 'localview:MusicView',
|
|
Type: 'MusicView',
|
|
CollectionType: 'music',
|
|
IsFolder: true
|
|
};
|
|
|
|
list.push(item);
|
|
}
|
|
|
|
if (types.indexOf('photo') > -1) {
|
|
|
|
item = {
|
|
Name: 'Photos',
|
|
ServerId: serverId,
|
|
Id: 'localview:PhotosView',
|
|
Type: 'PhotosView',
|
|
CollectionType: 'photos',
|
|
IsFolder: true
|
|
};
|
|
|
|
list.push(item);
|
|
}
|
|
|
|
if (types.indexOf('episode') > -1) {
|
|
|
|
item = {
|
|
Name: 'TV',
|
|
ServerId: serverId,
|
|
Id: 'localview:TVView',
|
|
Type: 'TVView',
|
|
CollectionType: 'tvshows',
|
|
IsFolder: true
|
|
};
|
|
|
|
list.push(item);
|
|
}
|
|
|
|
if (types.indexOf('movie') > -1) {
|
|
|
|
item = {
|
|
Name: 'Movies',
|
|
ServerId: serverId,
|
|
Id: 'localview:MoviesView',
|
|
Type: 'MoviesView',
|
|
CollectionType: 'movies',
|
|
IsFolder: true
|
|
};
|
|
|
|
list.push(item);
|
|
}
|
|
|
|
if (types.indexOf('video') > -1) {
|
|
|
|
item = {
|
|
Name: 'Videos',
|
|
ServerId: serverId,
|
|
Id: 'localview:VideosView',
|
|
Type: 'VideosView',
|
|
CollectionType: 'videos',
|
|
IsFolder: true
|
|
};
|
|
|
|
list.push(item);
|
|
}
|
|
|
|
if (types.indexOf('musicvideo') > -1) {
|
|
|
|
item = {
|
|
Name: 'Music Videos',
|
|
ServerId: serverId,
|
|
Id: 'localview:MusicVideosView',
|
|
Type: 'MusicVideosView',
|
|
CollectionType: 'videos',
|
|
IsFolder: true
|
|
};
|
|
|
|
list.push(item);
|
|
}
|
|
|
|
return Promise.resolve(list);
|
|
});
|
|
}
|
|
|
|
function getTypeFilterForTopLevelView(parentId) {
|
|
|
|
var typeFilter = null;
|
|
|
|
switch (parentId) {
|
|
case 'localview:MusicView':
|
|
typeFilter = 'audio';
|
|
break;
|
|
case 'localview:PhotosView':
|
|
typeFilter = 'photo';
|
|
break;
|
|
case 'localview:TVView':
|
|
typeFilter = 'episode';
|
|
break;
|
|
case 'localview:VideosView':
|
|
typeFilter = 'video';
|
|
break;
|
|
case 'localview:MoviesView':
|
|
typeFilter = 'movie';
|
|
break;
|
|
case 'localview:MusicVideosView':
|
|
typeFilter = 'musicvideo';
|
|
break;
|
|
}
|
|
|
|
return typeFilter;
|
|
}
|
|
|
|
function getViewItems(serverId, userId, parentId) {
|
|
|
|
var typeFilter = getTypeFilterForTopLevelView(parentId);
|
|
|
|
parentId = stripStart(parentId, 'localview:');
|
|
parentId = stripStart(parentId, 'local:');
|
|
|
|
return getServerItems(serverId).then(function (items) {
|
|
|
|
var resultItemIds = items.filter(function (item) {
|
|
|
|
if (item.SyncStatus && item.SyncStatus !== 'synced') {
|
|
return false;
|
|
}
|
|
|
|
if (typeFilter) {
|
|
var type = (item.Item.Type || '').toLowerCase();
|
|
return typeFilter === type;
|
|
}
|
|
|
|
return item.Item.ParentId === parentId;
|
|
|
|
}).map(function (item2) {
|
|
|
|
switch (typeFilter) {
|
|
case 'audio':
|
|
case 'photo':
|
|
return item2.Item.AlbumId;
|
|
case 'episode':
|
|
return item2.Item.SeriesId;
|
|
}
|
|
|
|
return item2.Item.Id;
|
|
|
|
}).filter(filterDistinct);
|
|
|
|
var resultItems = [];
|
|
|
|
items.forEach(function (item) {
|
|
var found = false;
|
|
|
|
resultItemIds.forEach(function (id) {
|
|
if (item.Item.Id === id) {
|
|
resultItems.push(item.Item);
|
|
}
|
|
});
|
|
});
|
|
|
|
return Promise.resolve(resultItems);
|
|
});
|
|
}
|
|
|
|
|
|
function removeLocalItem(localItem) {
|
|
|
|
return itemrepository.get(localItem.Id).then(function (item) {
|
|
return filerepository.deleteFile(item.LocalPath).then(function () {
|
|
|
|
var p = Promise.resolve(true);
|
|
|
|
if (item.AdditionalFiles) {
|
|
item.AdditionalFiles.forEach(function (file) {
|
|
p = p.then(function () {
|
|
return filerepository.deleteFile(file.Path);
|
|
});
|
|
});
|
|
}
|
|
|
|
return p.then(function (file) {
|
|
return itemrepository.remove(localItem.Id);
|
|
});
|
|
|
|
}, function (error) {
|
|
|
|
var p = Promise.resolve(true);
|
|
|
|
if (item.AdditionalFiles) {
|
|
item.AdditionalFiles.forEach(function (file) {
|
|
p = p.then(function (item) {
|
|
return filerepository.deleteFile(file.Path);
|
|
});
|
|
});
|
|
}
|
|
|
|
return p.then(function (file) {
|
|
return itemrepository.remove(localItem.Id);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function addOrUpdateLocalItem(localItem) {
|
|
console.log('addOrUpdateLocalItem Start');
|
|
return itemrepository.set(localItem.Id, localItem).then(function (res) {
|
|
console.log('addOrUpdateLocalItem Success');
|
|
return Promise.resolve(true);
|
|
}, function (error) {
|
|
console.log('addOrUpdateLocalItem Error');
|
|
return Promise.resolve(false);
|
|
});
|
|
}
|
|
|
|
function createLocalItem(libraryItem, serverInfo, jobItem) {
|
|
|
|
var path = getDirectoryPath(libraryItem, serverInfo);
|
|
var localFolder = filerepository.getFullLocalPath(path);
|
|
|
|
var localPath;
|
|
|
|
if (jobItem) {
|
|
path.push(getLocalFileName(libraryItem, jobItem.OriginalFileName));
|
|
localPath = filerepository.getFullLocalPath(path);
|
|
}
|
|
|
|
if (libraryItem.MediaSources) {
|
|
for (var i = 0; i < libraryItem.MediaSources.length; i++) {
|
|
var mediaSource = libraryItem.MediaSources[i];
|
|
mediaSource.Path = localPath;
|
|
mediaSource.Protocol = 'File';
|
|
}
|
|
}
|
|
|
|
var item = {
|
|
|
|
Item: libraryItem,
|
|
ItemId: libraryItem.Id,
|
|
ServerId: serverInfo.Id,
|
|
LocalPath: localPath,
|
|
LocalFolder: localFolder,
|
|
Id: getLocalId(serverInfo.Id, libraryItem.Id)
|
|
};
|
|
|
|
if (jobItem) {
|
|
item.AdditionalFiles = jobItem.AdditionalFiles.slice(0);
|
|
item.SyncJobItemId = jobItem.SyncJobItemId;
|
|
}
|
|
|
|
return Promise.resolve(item);
|
|
}
|
|
|
|
function getSubtitleSaveFileName(localItem, mediaPath, language, isForced, format) {
|
|
|
|
var name = getNameWithoutExtension(mediaPath);
|
|
|
|
if (language) {
|
|
name += "." + language.toLowerCase();
|
|
}
|
|
|
|
if (isForced) {
|
|
name += ".foreign";
|
|
}
|
|
|
|
name = name + "." + format.toLowerCase();
|
|
|
|
var localPathArray = [localItem.LocalFolder, name];
|
|
var localFilePath = filerepository.getPathFromArray(localPathArray);
|
|
|
|
return localFilePath;
|
|
|
|
}
|
|
|
|
function getItemFileSize(path) {
|
|
return filerepository.getItemFileSize(path);
|
|
}
|
|
|
|
function getNameWithoutExtension(path) {
|
|
|
|
var fileName = path;
|
|
|
|
var pos = fileName.lastIndexOf(".");
|
|
|
|
if (pos > 0) {
|
|
fileName = fileName.substring(0, pos);
|
|
}
|
|
|
|
return fileName;
|
|
}
|
|
|
|
function downloadFile(url, localItem) {
|
|
|
|
var folder = filerepository.getLocalPath();
|
|
return transfermanager.downloadFile(url, folder, localItem);
|
|
}
|
|
|
|
function downloadSubtitles(url, fileName) {
|
|
|
|
var folder = filerepository.getLocalPath();
|
|
return transfermanager.downloadSubtitles(url, folder, fileName);
|
|
}
|
|
|
|
function getImageUrl(serverId, itemId, imageType, index) {
|
|
|
|
var pathArray = getImagePath(serverId, itemId, imageType, index);
|
|
var relPath = pathArray.join('/');
|
|
|
|
var prefix = 'ms-appdata:///local';
|
|
return prefix + '/' + relPath;
|
|
}
|
|
|
|
function hasImage(serverId, itemId, imageType, index) {
|
|
|
|
var pathArray = getImagePath(serverId, itemId, imageType, index);
|
|
var localFilePath = filerepository.getFullMetadataPath(pathArray);
|
|
|
|
return filerepository.fileExists(localFilePath).then(function (exists) {
|
|
// TODO: Maybe check for broken download when file size is 0 and item is not queued
|
|
////if (exists) {
|
|
//// if (!transfermanager.isDownloadFileInQueue(localFilePath)) {
|
|
//// // If file exists but
|
|
//// exists = false;
|
|
//// }
|
|
////}
|
|
|
|
return Promise.resolve(exists);
|
|
}, function (err) {
|
|
return Promise.resolve(false);
|
|
});
|
|
}
|
|
|
|
function downloadImage(localItem, url, serverId, itemId, imageType, index) {
|
|
|
|
var pathArray = getImagePath(serverId, itemId, imageType, index);
|
|
var localFilePath = filerepository.getFullMetadataPath(pathArray);
|
|
|
|
if (!localItem.AdditionalFiles) {
|
|
localItem.AdditionalFiles = [];
|
|
}
|
|
|
|
var fileInfo = {
|
|
Path: localFilePath,
|
|
Type: 'Image',
|
|
Name: imageType + index.toString(),
|
|
ImageType: imageType
|
|
};
|
|
|
|
localItem.AdditionalFiles.push(fileInfo);
|
|
|
|
var folder = filerepository.getMetadataPath();
|
|
return transfermanager.downloadImage(url, folder, localFilePath);
|
|
}
|
|
|
|
function isDownloadFileInQueue(path) {
|
|
|
|
return transfermanager.isDownloadFileInQueue(path);
|
|
}
|
|
|
|
function getDownloadItemCount() {
|
|
|
|
return transfermanager.getDownloadItemCount();
|
|
}
|
|
|
|
function translateFilePath(path) {
|
|
return Promise.resolve(path);
|
|
}
|
|
|
|
// Helpers ***********************************************************
|
|
|
|
function getDirectoryPath(item, server) {
|
|
|
|
var parts = [];
|
|
parts.push(server.Name);
|
|
|
|
var itemtype = item.Type.toLowerCase();
|
|
|
|
if (itemtype === 'episode') {
|
|
|
|
parts.push("TV");
|
|
|
|
var seriesName = item.SeriesName;
|
|
if (seriesName) {
|
|
parts.push(seriesName);
|
|
}
|
|
|
|
var seasonName = item.SeasonName;
|
|
if (seasonName) {
|
|
parts.push(seasonName);
|
|
}
|
|
|
|
} else if (itemtype === 'video') {
|
|
|
|
parts.push("Videos");
|
|
parts.push(item.Name);
|
|
|
|
} else if (itemtype === 'audio') {
|
|
|
|
parts.push("Music");
|
|
|
|
var albumArtist = item.AlbumArtist;
|
|
if (albumArtist) {
|
|
parts.push(albumArtist);
|
|
}
|
|
|
|
if ((item.AlbumId) && (item.Album)) {
|
|
parts.push(item.Album);
|
|
}
|
|
|
|
} else if (itemtype === 'photo') {
|
|
|
|
parts.push("Photos");
|
|
|
|
if ((item.AlbumId) && (item.Album)) {
|
|
parts.push(item.Album);
|
|
}
|
|
|
|
}
|
|
|
|
var finalParts = [];
|
|
for (var i = 0; i < parts.length; i++) {
|
|
|
|
finalParts.push(filerepository.getValidFileName(parts[i]));
|
|
}
|
|
|
|
return finalParts;
|
|
}
|
|
|
|
function getImagePath(serverId, itemId, imageType, index) {
|
|
|
|
var parts = [];
|
|
parts.push('Metadata');
|
|
parts.push(serverId);
|
|
parts.push('images');
|
|
// Store without extension. This allows mixed image types since the browser will
|
|
// detect the type from the content
|
|
parts.push(itemId + '_' + imageType + '_' + index.toString()); // + '.jpg');
|
|
|
|
var finalParts = [];
|
|
for (var i = 0; i < parts.length; i++) {
|
|
|
|
finalParts.push(filerepository.getValidFileName(parts[i]));
|
|
}
|
|
|
|
return finalParts;
|
|
}
|
|
|
|
function getLocalFileName(item, originalFileName) {
|
|
|
|
var filename = originalFileName || item.Name;
|
|
|
|
return filerepository.getValidFileName(filename);
|
|
}
|
|
|
|
function resyncTransfers() {
|
|
return transfermanager.resyncTransfers();
|
|
}
|
|
|
|
function createGuid() {
|
|
var d = new Date().getTime();
|
|
if (window.performance && typeof window.performance.now === "function") {
|
|
d += performance.now(); //use high-precision timer if available
|
|
}
|
|
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
var r = (d + Math.random() * 16) % 16 | 0;
|
|
d = Math.floor(d / 16);
|
|
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
|
});
|
|
return uuid;
|
|
}
|
|
|
|
function startsWith(str, find) {
|
|
|
|
if (str && find && str.length > find.length) {
|
|
if (str.indexOf(find) === 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function stripStart(str, find) {
|
|
if (startsWith(str, find)) {
|
|
return str.substr(find.length);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
function filterDistinct(value, index, self) {
|
|
return self.indexOf(value) === index;
|
|
}
|
|
|
|
return {
|
|
|
|
getLocalItem: getLocalItem,
|
|
saveOfflineUser: saveOfflineUser,
|
|
deleteOfflineUser: deleteOfflineUser,
|
|
recordUserAction: recordUserAction,
|
|
getUserActions: getUserActions,
|
|
deleteUserAction: deleteUserAction,
|
|
deleteUserActions: deleteUserActions,
|
|
getServerItemIds: getServerItemIds,
|
|
removeLocalItem: removeLocalItem,
|
|
addOrUpdateLocalItem: addOrUpdateLocalItem,
|
|
createLocalItem: createLocalItem,
|
|
downloadFile: downloadFile,
|
|
downloadSubtitles: downloadSubtitles,
|
|
hasImage: hasImage,
|
|
downloadImage: downloadImage,
|
|
getImageUrl: getImageUrl,
|
|
translateFilePath: translateFilePath,
|
|
getSubtitleSaveFileName: getSubtitleSaveFileName,
|
|
getLocalItemById: getLocalItemById,
|
|
getServerItems: getServerItems,
|
|
getItemFileSize: getItemFileSize,
|
|
isDownloadFileInQueue: isDownloadFileInQueue,
|
|
getDownloadItemCount: getDownloadItemCount,
|
|
getViews: getViews,
|
|
getViewItems: getViewItems,
|
|
resyncTransfers: resyncTransfers,
|
|
getItemsFromIds: getItemsFromIds
|
|
};
|
|
}); |