public async Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file) { var device = GetDevice(deviceId, false); var uploadPathInfo = GetUploadPath(device); var path = uploadPathInfo.Item1; if (!string.IsNullOrWhiteSpace(file.Album)) { path = Path.Combine(path, _fileSystem.GetValidFilename(file.Album)); } path = Path.Combine(path, file.Name); path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg"); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); await EnsureLibraryFolder(uploadPathInfo.Item2, uploadPathInfo.Item3).ConfigureAwait(false); _libraryMonitor.ReportFileSystemChangeBeginning(path); try { using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) { await stream.CopyToAsync(fs).ConfigureAwait(false); } AddCameraUpload(deviceId, file); } finally { _libraryMonitor.ReportFileSystemChangeComplete(path, true); } if (CameraImageUploaded != null) { CameraImageUploaded?.Invoke(this, new GenericEventArgs <CameraImageUploadInfo> { Argument = new CameraImageUploadInfo { Device = device, FileInfo = file } }); } }
public async Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file) { var device = GetDevice(deviceId); var path = GetUploadPath(device); if (!string.IsNullOrWhiteSpace(file.Album)) { path = Path.Combine(path, _fileSystem.GetValidFilename(file.Album)); } path = Path.Combine(path, file.Name); path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg"); _libraryMonitor.ReportFileSystemChangeBeginning(path); _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); try { using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) { await stream.CopyToAsync(fs).ConfigureAwait(false); } _repo.AddCameraUpload(deviceId, file); } finally { _libraryMonitor.ReportFileSystemChangeComplete(path, true); } if (CameraImageUploaded != null) { EventHelper.FireEventIfNotNull(CameraImageUploaded, this, new GenericEventArgs <CameraImageUploadInfo> { Argument = new CameraImageUploadInfo { Device = device, FileInfo = file } }, _logger); } }
public async Task CreateCollection(CollectionCreationOptions options) { var name = options.Name; var folderName = _fileSystem.GetValidFilename(name); var parentFolder = _libraryManager.GetItemById(options.ParentId) as Folder; if (parentFolder == null) { throw new ArgumentException(); } var path = Path.Combine(parentFolder.Path, folderName); _iLibraryMonitor.ReportFileSystemChangeBeginning(path); try { Directory.CreateDirectory(path); var collection = new BoxSet { Name = name, Parent = parentFolder, DisplayMediaType = "Collection", Path = path, DontFetchMeta = options.IsLocked }; await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false); await collection.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None) .ConfigureAwait(false); } finally { // Refresh handled internally _iLibraryMonitor.ReportFileSystemChangeComplete(path, false); } }
public async Task <BoxSet> CreateCollection(CollectionCreationOptions options) { var name = options.Name; // Need to use the [boxset] suffix // If internet metadata is not found, or if xml saving is off there will be no collection.xml // This could cause it to get re-resolved as a plain folder var folderName = _fileSystem.GetValidFilename(name) + " [boxset]"; var parentFolder = GetParentFolder(options.ParentId); if (parentFolder == null) { throw new ArgumentException(); } var path = Path.Combine(parentFolder.Path, folderName); _iLibraryMonitor.ReportFileSystemChangeBeginning(path); try { _fileSystem.CreateDirectory(path); var collection = new BoxSet { Name = name, Path = path, IsLocked = options.IsLocked, ProviderIds = options.ProviderIds, Shares = options.UserIds.Select(i => new Share { UserId = i.ToString("N"), CanEdit = true }).ToList() }; await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false); if (options.ItemIdList.Count > 0) { await AddToCollection(collection.Id, options.ItemIdList, false, new MetadataRefreshOptions(_fileSystem) { // The initial adding of items is going to create a local metadata file // This will cause internet metadata to be skipped as a result MetadataRefreshMode = MetadataRefreshMode.FullRefresh }); } else { _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.High); } EventHelper.FireEventIfNotNull(CollectionCreated, this, new CollectionCreatedEventArgs { Collection = collection, Options = options }, _logger); return(collection); } finally { // Refresh handled internally _iLibraryMonitor.ReportFileSystemChangeComplete(path, false); } }
public async Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int?imageIndex, bool?saveLocallyWithMedia, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(mimeType)) { throw new ArgumentNullException("mimeType"); } var saveLocally = item.SupportsLocalMetadata && item.IsSaveLocalMetadataEnabled() && !item.ExtraType.HasValue && !(item is Audio); if (item is User) { saveLocally = true; } if (type != ImageType.Primary && item is Episode) { saveLocally = false; } if (!item.IsFileProtocol) { saveLocally = false; var season = item as Season; // If season is virtual under a physical series, save locally if using compatible convention if (season != null && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Compatible) { var series = season.Series; if (series != null && series.SupportsLocalMetadata && series.IsSaveLocalMetadataEnabled()) { saveLocally = true; } } } if (saveLocallyWithMedia.HasValue && !saveLocallyWithMedia.Value) { saveLocally = saveLocallyWithMedia.Value; } if (!imageIndex.HasValue && item.AllowsMultipleImages(type)) { imageIndex = item.GetImages(type).Count(); } var index = imageIndex ?? 0; var paths = GetSavePaths(item, type, imageIndex, mimeType, saveLocally); var retryPaths = GetSavePaths(item, type, imageIndex, mimeType, false); // If there are more than one output paths, the stream will need to be seekable var memoryStream = new MemoryStream(); using (source) { await source.CopyToAsync(memoryStream).ConfigureAwait(false); } source = memoryStream; var currentImage = GetCurrentImage(item, type, index); var currentImageIsLocalFile = currentImage != null && currentImage.IsLocalFile; var currentImagePath = currentImage == null ? null : currentImage.Path; var savedPaths = new List <string>(); using (source) { var currentPathIndex = 0; foreach (var path in paths) { source.Position = 0; string retryPath = null; if (paths.Length == retryPaths.Length) { retryPath = retryPaths[currentPathIndex]; } var savedPath = await SaveImageToLocation(source, path, retryPath, cancellationToken).ConfigureAwait(false); savedPaths.Add(savedPath); currentPathIndex++; } } // Set the path into the item SetImagePath(item, type, imageIndex, savedPaths[0]); // Delete the current path if (currentImageIsLocalFile && !savedPaths.Contains(currentImagePath, StringComparer.OrdinalIgnoreCase)) { var currentPath = currentImagePath; _logger.Info("Deleting previous image {0}", currentPath); _libraryMonitor.ReportFileSystemChangeBeginning(currentPath); try { _fileSystem.DeleteFile(currentPath); } catch (FileNotFoundException) { } finally { _libraryMonitor.ReportFileSystemChangeComplete(currentPath, false); } } }
private async Task OrganizeEpisode(string sourcePath, Series series, int?seasonNumber, int?episodeNumber, int?endingEpiosdeNumber, DateTime?premiereDate, AutoOrganizeOptions options, bool overwriteExisting, bool rememberCorrection, FileOrganizationResult result, CancellationToken cancellationToken) { _logger.Info("Sorting file {0} into series {1}", sourcePath, series.Path); var originalExtractedSeriesString = result.ExtractedName; bool isNew = string.IsNullOrWhiteSpace(result.Id); if (isNew) { await _organizationService.SaveResult(result, cancellationToken); } if (!_organizationService.AddToInProgressList(result, isNew)) { throw new Exception("File is currently processed otherwise. Please try again later."); } try { // Proceed to sort the file var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options.TvOptions, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(newPath)) { var msg = string.Format("Unable to sort {0} because target path could not be determined.", sourcePath); throw new Exception(msg); } _logger.Info("Sorting file {0} to new path {1}", sourcePath, newPath); result.TargetPath = newPath; var fileExists = _fileSystem.FileExists(result.TargetPath); var otherDuplicatePaths = GetOtherDuplicatePaths(result.TargetPath, series, seasonNumber, episodeNumber, endingEpiosdeNumber); if (!overwriteExisting) { if (options.TvOptions.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath)) { var msg = string.Format("File '{0}' already copied to new path '{1}', stopping organization", sourcePath, newPath); _logger.Info(msg); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = msg; return; } if (fileExists) { var msg = string.Format("File '{0}' already exists as '{1}', stopping organization", sourcePath, newPath); _logger.Info(msg); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = msg; result.TargetPath = newPath; return; } if (otherDuplicatePaths.Count > 0) { var msg = string.Format("File '{0}' already exists as these:'{1}'. Stopping organization", sourcePath, string.Join("', '", otherDuplicatePaths)); _logger.Info(msg); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = msg; result.DuplicatePaths = otherDuplicatePaths; return; } } PerformFileSorting(options.TvOptions, result); if (overwriteExisting) { var hasRenamedFiles = false; foreach (var path in otherDuplicatePaths) { _logger.Debug("Removing duplicate episode {0}", path); _libraryMonitor.ReportFileSystemChangeBeginning(path); var renameRelatedFiles = !hasRenamedFiles && string.Equals(_fileSystem.GetDirectoryName(path), _fileSystem.GetDirectoryName(result.TargetPath), StringComparison.OrdinalIgnoreCase); if (renameRelatedFiles) { hasRenamedFiles = true; } try { DeleteLibraryFile(path, renameRelatedFiles, result.TargetPath); } catch (IOException ex) { _logger.ErrorException("Error removing duplicate episode", ex, path); } finally { _libraryMonitor.ReportFileSystemChangeComplete(path, true); } } } } catch (Exception ex) { result.Status = FileSortingStatus.Failure; result.StatusMessage = ex.Message; _logger.Warn(ex.Message); return; } finally { _organizationService.RemoveFromInprogressList(result); } if (rememberCorrection) { SaveSmartMatchString(originalExtractedSeriesString, series, options); } }
private async Task OrganizeEpisode(string sourcePath, Series series, int seasonNumber, int episodeNumber, int?endingEpiosdeNumber, TvFileOrganizationOptions options, bool overwriteExisting, FileOrganizationResult result, CancellationToken cancellationToken) { _logger.Info("Sorting file {0} into series {1}", sourcePath, series.Path); // Proceed to sort the file var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, options, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(newPath)) { var msg = string.Format("Unable to sort {0} because target path could not be determined.", sourcePath); result.Status = FileSortingStatus.Failure; result.StatusMessage = msg; _logger.Warn(msg); return; } _logger.Info("Sorting file {0} to new path {1}", sourcePath, newPath); result.TargetPath = newPath; var fileExists = File.Exists(result.TargetPath); var otherDuplicatePaths = GetOtherDuplicatePaths(result.TargetPath, series, seasonNumber, episodeNumber, endingEpiosdeNumber); if (!overwriteExisting) { if (options.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath)) { _logger.Info("File {0} already copied to new path {1}, stopping organization", sourcePath, newPath); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = string.Empty; return; } if (fileExists || otherDuplicatePaths.Count > 0) { result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = string.Empty; result.DuplicatePaths = otherDuplicatePaths; return; } } PerformFileSorting(options, result); if (overwriteExisting) { foreach (var path in otherDuplicatePaths) { _logger.Debug("Removing duplicate episode {0}", path); _libraryMonitor.ReportFileSystemChangeBeginning(path); try { File.Delete(path); } catch (IOException ex) { _logger.ErrorException("Error removing duplicate episode", ex, path); } finally { _libraryMonitor.ReportFileSystemChangeComplete(path, true); } } } }
public async Task <BoxSet> CreateCollectionAsync(CollectionCreationOptions options) { var name = options.Name; // Need to use the [boxset] suffix // If internet metadata is not found, or if xml saving is off there will be no collection.xml // This could cause it to get re-resolved as a plain folder var folderName = _fileSystem.GetValidFilename(name) + " [boxset]"; var parentFolder = await GetCollectionsFolder(true).ConfigureAwait(false); if (parentFolder == null) { throw new ArgumentException(); } var path = Path.Combine(parentFolder.Path, folderName); _iLibraryMonitor.ReportFileSystemChangeBeginning(path); try { Directory.CreateDirectory(path); var collection = new BoxSet { Name = name, Path = path, IsLocked = options.IsLocked, ProviderIds = options.ProviderIds, DateCreated = DateTime.UtcNow }; parentFolder.AddChild(collection); if (options.ItemIdList.Count > 0) { await AddToCollectionAsync( collection.Id, options.ItemIdList.Select(x => new Guid(x)), false, new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { // The initial adding of items is going to create a local metadata file // This will cause internet metadata to be skipped as a result MetadataRefreshMode = MetadataRefreshMode.FullRefresh }).ConfigureAwait(false); } else { _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); } CollectionCreated?.Invoke(this, new CollectionCreatedEventArgs { Collection = collection, Options = options }); return(collection); } finally { // Refresh handled internally _iLibraryMonitor.ReportFileSystemChangeComplete(path, false); } }
public async Task DownloadSubtitles(Video video, string subtitleId, CancellationToken cancellationToken) { var parts = subtitleId.Split(new[] { '_' }, 2); var provider = GetProvider(parts.First()); try { var response = await GetRemoteSubtitles(subtitleId, cancellationToken).ConfigureAwait(false); using (var stream = response.Stream) { var savePath = Path.Combine(Path.GetDirectoryName(video.Path), _fileSystem.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower()); if (response.IsForced) { savePath += ".forced"; } savePath += "." + response.Format.ToLower(); _logger.Info("Saving subtitles to {0}", savePath); _monitor.ReportFileSystemChangeBeginning(savePath); try { using (var fs = _fileSystem.GetFileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { await stream.CopyToAsync(fs).ConfigureAwait(false); } EventHelper.FireEventIfNotNull(SubtitlesDownloaded, this, new SubtitleDownloadEventArgs { Item = video, Format = response.Format, Language = response.Language, IsForced = response.IsForced, Provider = provider.Name }, _logger); } finally { _monitor.ReportFileSystemChangeComplete(savePath, false); } } } catch (Exception ex) { EventHelper.FireEventIfNotNull(SubtitleDownloadFailure, this, new SubtitleDownloadFailureEventArgs { Item = video, Exception = ex, Provider = provider.Name }, _logger); throw; } }
private void OrganizeEpisode(string sourcePath, Series series, Episode episode, TvFileOrganizationOptions options, bool rememberCorrection, FileOrganizationResult result, CancellationToken cancellationToken) { _logger.Info("Sorting file {0} into series {1}", sourcePath, series.Path); var originalExtractedSeriesString = result.ExtractedName; bool isNew = string.IsNullOrWhiteSpace(result.Id); if (isNew) { _organizationService.SaveResult(result, cancellationToken); } if (!_organizationService.AddToInProgressList(result, isNew)) { var msg = string.Format("File {0} is currently processed otherwise. Please try again later.", sourcePath); _logger.Warn(msg + " Stopping organization"); result.Status = FileSortingStatus.Failure; result.StatusMessage = msg; return; } try { // Proceed to sort the file var newPath = episode.Path; if (string.IsNullOrEmpty(newPath)) { var msg = string.Format("Unable to sort {0} because target path could not be determined.", sourcePath); _logger.Info(msg + " Stopping organization"); result.Status = FileSortingStatus.Failure; result.StatusMessage = msg; return; } _logger.Info("Sorting file {0} to new path {1}", sourcePath, newPath); result.TargetPath = newPath; var fileExists = _fileSystem.FileExists(result.TargetPath); if (options.SingleEpisodeVersion) //add value here to ensure returned to user regardless of result below { result.DuplicatePaths = GetOtherDuplicatePaths(result.TargetPath, series, episode); _logger.Info(string.Format("otherDuplicatePaths: '{0}'", string.Join("', '", result.DuplicatePaths))); } if (!options.OverwriteExistingEpisodes) { if (options.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath) && result.DuplicatePaths.Count == 1) { var msg = string.Format("File '{0}' already copied to new path '{1}.'", sourcePath, newPath); _logger.Info(msg + " Stopping organization"); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = msg; return; } if (result.DuplicatePaths.Count > 0) { var msg = string.Format("File '{0}' already exists as: '{1}'.", sourcePath, string.Join("', '", result.DuplicatePaths), (result.DuplicatePaths.Count > 1 ? "these" : "")); _logger.Info(msg + " Stopping organization"); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = msg; return; } if (fileExists) { var msg = string.Format("File '{0}' already exists as '{1}'.", sourcePath, newPath); _logger.Info(msg + " Stopping organization"); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = msg; result.TargetPath = newPath; return; } } PerformFileSorting(options, result); if (options.SingleEpisodeVersion) { var hasRenamedFiles = false; foreach (var path in result.DuplicatePaths) { if (!string.Equals(path, newPath, StringComparison.OrdinalIgnoreCase))//dont remove file matching destination path { _logger.Info("Removing duplicate episode {0}", path); _libraryMonitor.ReportFileSystemChangeBeginning(path); var renameRelatedFiles = !hasRenamedFiles && string.Equals(_fileSystem.GetDirectoryName(path), _fileSystem.GetDirectoryName(result.TargetPath), StringComparison.OrdinalIgnoreCase); if (renameRelatedFiles) { hasRenamedFiles = true; } try { DeleteLibraryFile(path, renameRelatedFiles, result.TargetPath); } catch (IOException ex) { _logger.ErrorException("Error removing duplicate episode {0}", ex, path); } finally { _libraryMonitor.ReportFileSystemChangeComplete(path, true); } } } } } catch (Exception ex) { result.Status = FileSortingStatus.Failure; result.StatusMessage = string.Format("Error sorting episode: '{0}'.", ex.Message); _logger.ErrorException("Error sorting episode: {0}", ex, episode.Path); return; } finally { _organizationService.RemoveFromInprogressList(result); } if (rememberCorrection) { SaveSmartMatchString(originalExtractedSeriesString, series.Name, cancellationToken); } }
/// <summary> /// Downloads the trailer for item. /// </summary> /// <param name="item">The item.</param> /// <param name="contentType">Type of the content.</param> /// <param name="providersToMatch">The providers to match.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> public async Task DownloadTrailerForItem(BaseItem item, ChannelMediaContentType contentType, List <MetadataProviders> providersToMatch, CancellationToken cancellationToken) { var providerValues = providersToMatch.Select(item.GetProviderId) .ToList(); if (providerValues.All(string.IsNullOrWhiteSpace)) { return; } var channelTrailers = await _channelManager.GetAllMediaInternal(new AllChannelMediaQuery { ContentTypes = new[] { contentType }, ExtraTypes = new[] { ExtraType.Trailer } }, CancellationToken.None); var channelItem = channelTrailers .Items .OfType <IChannelMediaItem>() .FirstOrDefault(i => { var currentProviderValues = providersToMatch.Select(i.GetProviderId).ToList(); var index = 0; foreach (var val in providerValues) { if (!string.IsNullOrWhiteSpace(val) && string.Equals(currentProviderValues[index], val, StringComparison.OrdinalIgnoreCase)) { return(true); } index++; } return(false); }); if (channelItem == null) { return; } var destination = Directory.Exists(item.Path) ? Path.Combine(item.Path, Path.GetFileName(item.Path) + "-trailer") : Path.Combine(Path.GetDirectoryName(item.Path), Path.GetFileNameWithoutExtension(item.Path) + "-trailer"); _libraryMonitor.ReportFileSystemChangeBeginning(Path.GetDirectoryName(destination)); try { await _channelManager.DownloadChannelItem(channelItem, destination, new Progress <double>(), cancellationToken) .ConfigureAwait(false); } catch (OperationCanceledException) { } catch (ChannelDownloadException) { // Logged at lower levels } catch (Exception ex) { _logger.ErrorException("Error downloading channel content for {0}", ex, item.Name); } finally { _libraryMonitor.ReportFileSystemChangeComplete(Path.GetDirectoryName(destination), true); } }
public async Task <PlaylistCreationResult> CreatePlaylist(PlaylistCreationRequest options) { var name = options.Name; var folderName = _fileSystem.GetValidFilename(name); var parentFolder = GetPlaylistsFolder(Guid.Empty); if (parentFolder == null) { throw new ArgumentException(nameof(parentFolder)); } if (string.IsNullOrEmpty(options.MediaType)) { foreach (var itemId in options.ItemIdList) { var item = _libraryManager.GetItemById(itemId); if (item == null) { throw new ArgumentException("No item exists with the supplied Id"); } if (!string.IsNullOrEmpty(item.MediaType)) { options.MediaType = item.MediaType; } else if (item is MusicArtist || item is MusicAlbum || item is MusicGenre) { options.MediaType = MediaType.Audio; } else if (item is Genre) { options.MediaType = MediaType.Video; } else { if (item is Folder folder) { options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist) .Select(i => i.MediaType) .FirstOrDefault(i => !string.IsNullOrEmpty(i)); } } if (!string.IsNullOrEmpty(options.MediaType)) { break; } } } if (string.IsNullOrEmpty(options.MediaType)) { options.MediaType = "Audio"; } var user = _userManager.GetUserById(options.UserId); var path = Path.Combine(parentFolder.Path, folderName); path = GetTargetPath(path); _iLibraryMonitor.ReportFileSystemChangeBeginning(path); try { Directory.CreateDirectory(path); var playlist = new Playlist { Name = name, Path = path, Shares = new[]
public async Task <Playlist> CreatePlaylist(PlaylistCreationOptions options) { var name = options.Name; var folderName = _fileSystem.GetValidFilename(name) + " [playlist]"; var parentFolder = GetPlaylistsFolder(null); if (parentFolder == null) { throw new ArgumentException(); } if (string.IsNullOrWhiteSpace(options.MediaType)) { foreach (var itemId in options.ItemIdList) { var item = _libraryManager.GetItemById(itemId); if (item == null) { throw new ArgumentException("No item exists with the supplied Id"); } if (!string.IsNullOrWhiteSpace(item.MediaType)) { options.MediaType = item.MediaType; } else if (item is MusicArtist || item is MusicAlbum || item is MusicGenre) { options.MediaType = MediaType.Audio; } else if (item is Genre) { options.MediaType = MediaType.Video; } else { var folder = item as Folder; if (folder != null) { options.MediaType = folder.GetRecursiveChildren() .Where(i => !i.IsFolder) .Select(i => i.MediaType) .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); } } if (!string.IsNullOrWhiteSpace(options.MediaType)) { break; } } } if (string.IsNullOrWhiteSpace(options.MediaType)) { throw new ArgumentException("A playlist media type is required."); } var path = Path.Combine(parentFolder.Path, folderName); path = GetTargetPath(path); _iLibraryMonitor.ReportFileSystemChangeBeginning(path); try { Directory.CreateDirectory(path); var playlist = new Playlist { Name = name, Parent = parentFolder, Path = path, OwnerUserId = options.UserId }; playlist.SetMediaType(options.MediaType); await parentFolder.AddChild(playlist, CancellationToken.None).ConfigureAwait(false); await playlist.RefreshMetadata(new MetadataRefreshOptions { ForceSave = true }, CancellationToken.None) .ConfigureAwait(false); if (options.ItemIdList.Count > 0) { await AddToPlaylist(playlist.Id.ToString("N"), options.ItemIdList); } return(playlist); } finally { // Refresh handled internally _iLibraryMonitor.ReportFileSystemChangeComplete(path, false); } }
public async Task <BoxSet> CreateCollection(CollectionCreationOptions options) { var name = options.Name; // Need to use the [boxset] suffix // If internet metadata is not found, or if xml saving is off there will be no collection.xml // This could cause it to get re-resolved as a plain folder var folderName = _fileSystem.GetValidFilename(name) + " [boxset]"; var parentFolder = GetParentFolder(options.ParentId); if (parentFolder == null) { throw new ArgumentException(); } var path = Path.Combine(parentFolder.Path, folderName); _iLibraryMonitor.ReportFileSystemChangeBeginning(path); try { Directory.CreateDirectory(path); var collection = new BoxSet { Name = name, Parent = parentFolder, DisplayMediaType = "Collection", Path = path, IsLocked = options.IsLocked, ProviderIds = options.ProviderIds, Shares = options.UserIds.Select(i => new Share { UserId = i.ToString("N") }).ToList() }; await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false); await collection.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService()), CancellationToken.None) .ConfigureAwait(false); if (options.ItemIdList.Count > 0) { await AddToCollection(collection.Id, options.ItemIdList, false); } EventHelper.FireEventIfNotNull(CollectionCreated, this, new CollectionCreatedEventArgs { Collection = collection, Options = options }, _logger); return(collection); } finally { // Refresh handled internally _iLibraryMonitor.ReportFileSystemChangeComplete(path, false); } }
public async Task <PlaylistCreationResult> CreatePlaylist(PlaylistCreationRequest options) { var name = options.Name; var folderName = _fileSystem.GetValidFilename(name) + " [playlist]"; var parentFolder = GetPlaylistsFolder(null); if (parentFolder == null) { throw new ArgumentException(); } if (string.IsNullOrWhiteSpace(options.MediaType)) { foreach (var itemId in options.ItemIdList) { var item = _libraryManager.GetItemById(itemId); if (item == null) { throw new ArgumentException("No item exists with the supplied Id"); } if (!string.IsNullOrWhiteSpace(item.MediaType)) { options.MediaType = item.MediaType; } else if (item is MusicArtist || item is MusicAlbum || item is MusicGenre) { options.MediaType = MediaType.Audio; } else if (item is Genre) { options.MediaType = MediaType.Video; } else { var folder = item as Folder; if (folder != null) { options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist) .Select(i => i.MediaType) .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); } } if (!string.IsNullOrWhiteSpace(options.MediaType)) { break; } } } if (string.IsNullOrWhiteSpace(options.MediaType)) { options.MediaType = "Audio"; } var user = _userManager.GetUserById(options.UserId); var path = Path.Combine(parentFolder.Path, folderName); path = GetTargetPath(path); _iLibraryMonitor.ReportFileSystemChangeBeginning(path); try { _fileSystem.CreateDirectory(path); var playlist = new Playlist { Name = name, Path = path }; playlist.Shares.Add(new Share { UserId = options.UserId, CanEdit = true }); playlist.SetMediaType(options.MediaType); parentFolder.AddChild(playlist, CancellationToken.None); await playlist.RefreshMetadata(new MetadataRefreshOptions(_fileSystem) { ForceSave = true }, CancellationToken.None) .ConfigureAwait(false); if (options.ItemIdList.Length > 0) { await AddToPlaylistInternal(playlist.Id.ToString("N"), options.ItemIdList, user, new DtoOptions(false) { EnableImages = true }); } return(new PlaylistCreationResult { Id = playlist.Id.ToString("N") }); } finally { // Refresh handled internally _iLibraryMonitor.ReportFileSystemChangeComplete(path, false); } }
private Task OrganizeEpisode( string sourcePath, Series series, Episode episode, TvFileOrganizationOptions options, bool rememberCorrection, FileOrganizationResult result, CancellationToken cancellationToken) { _logger.LogInformation("Sorting file {0} into series {1}", sourcePath, series.Path); var originalExtractedSeriesString = result.ExtractedName; bool isNew = string.IsNullOrWhiteSpace(result.Id); if (isNew) { _organizationService.SaveResult(result, cancellationToken); } if (!_organizationService.AddToInProgressList(result, isNew)) { throw new OrganizationException("File is currently processed otherwise. Please try again later."); } try { // Proceed to sort the file var newPath = episode.Path; if (string.IsNullOrEmpty(newPath)) { var msg = $"Unable to sort {sourcePath} because target path could not be determined."; throw new OrganizationException(msg); } _logger.LogInformation("Sorting file {0} to new path {1}", sourcePath, newPath); result.TargetPath = newPath; var fileExists = File.Exists(result.TargetPath); var otherDuplicatePaths = GetOtherDuplicatePaths(result.TargetPath, series, episode); if (!options.OverwriteExistingEpisodes) { if (options.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath)) { var msg = $"File '{sourcePath}' already copied to new path '{newPath}', stopping organization"; _logger.LogInformation(msg); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = msg; return(Task.CompletedTask); } if (fileExists) { var msg = $"File '{sourcePath}' already exists as '{newPath}', stopping organization"; _logger.LogInformation(msg); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = msg; result.TargetPath = newPath; return(Task.CompletedTask); } if (otherDuplicatePaths.Count > 0) { var msg = $"File '{sourcePath}' already exists as these:'{string.Join("', '", otherDuplicatePaths)}'. Stopping organization"; _logger.LogInformation(msg); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = msg; result.DuplicatePaths = otherDuplicatePaths; return(Task.CompletedTask); } } PerformFileSorting(options, result); if (options.OverwriteExistingEpisodes) { var hasRenamedFiles = false; foreach (var path in otherDuplicatePaths) { _logger.LogDebug("Removing duplicate episode {0}", path); _libraryMonitor.ReportFileSystemChangeBeginning(path); var renameRelatedFiles = !hasRenamedFiles && string.Equals(Path.GetDirectoryName(path), Path.GetDirectoryName(result.TargetPath), StringComparison.OrdinalIgnoreCase); if (renameRelatedFiles) { hasRenamedFiles = true; } try { DeleteLibraryFile(path, renameRelatedFiles, result.TargetPath); } catch (IOException ex) { _logger.LogError(ex, "Error removing duplicate episode: {0}", path); } finally { _libraryMonitor.ReportFileSystemChangeComplete(path, true); } } } } catch (Exception ex) { result.Status = FileSortingStatus.Failure; result.StatusMessage = ex.Message; _logger.LogError(ex, "Caught a generic exception while organizing an episode"); return(Task.CompletedTask); } finally { _organizationService.RemoveFromInprogressList(result); } if (rememberCorrection) { SaveSmartMatchString(originalExtractedSeriesString, series, cancellationToken); } return(Task.CompletedTask); }
/// <summary> /// Saves the image. /// </summary> /// <param name="item">The item.</param> /// <param name="source">The source.</param> /// <param name="mimeType">Type of the MIME.</param> /// <param name="type">The type.</param> /// <param name="imageIndex">Index of the image.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> /// <exception cref="System.ArgumentNullException">mimeType</exception> public async Task SaveImage(IHasImages item, Stream source, string mimeType, ImageType type, int?imageIndex, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(mimeType)) { throw new ArgumentNullException("mimeType"); } var saveLocally = item.SupportsLocalMetadata && item.IsSaveLocalMetadataEnabled() && !item.IsOwnedItem && !(item is Audio); if (item is IItemByName || item is User) { saveLocally = true; } if (type != ImageType.Primary && item is Episode) { saveLocally = false; } var locationType = item.LocationType; if (locationType == LocationType.Remote || locationType == LocationType.Virtual) { saveLocally = false; var season = item as Season; // If season is virtual under a physical series, save locally if using compatible convention if (season != null && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Compatible) { var series = season.Series; if (series != null && series.SupportsLocalMetadata && series.IsSaveLocalMetadataEnabled()) { saveLocally = true; } } } if (!imageIndex.HasValue && item.AllowsMultipleImages(type)) { imageIndex = item.GetImages(type).Count(); } var index = imageIndex ?? 0; var paths = GetSavePaths(item, type, imageIndex, mimeType, saveLocally); // If there are more than one output paths, the stream will need to be seekable if (paths.Length > 1 && !source.CanSeek) { var memoryStream = new MemoryStream(); using (source) { await source.CopyToAsync(memoryStream).ConfigureAwait(false); } memoryStream.Position = 0; source = memoryStream; } var currentPath = GetCurrentImagePath(item, type, index); using (source) { var isFirst = true; foreach (var path in paths) { // Seek back to the beginning if (!isFirst) { source.Position = 0; } await SaveImageToLocation(source, path, cancellationToken).ConfigureAwait(false); isFirst = false; } } // Set the path into the item SetImagePath(item, type, imageIndex, paths[0]); // Delete the current path if (!string.IsNullOrEmpty(currentPath) && !paths.Contains(currentPath, StringComparer.OrdinalIgnoreCase)) { _libraryMonitor.ReportFileSystemChangeBeginning(currentPath); try { var currentFile = new FileInfo(currentPath); // This will fail if the file is hidden if (currentFile.Exists) { if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) { currentFile.Attributes &= ~FileAttributes.Hidden; } currentFile.Delete(); } } finally { _libraryMonitor.ReportFileSystemChangeComplete(currentPath, false); } } }
public async Task SaveImage(IHasImages item, Stream source, string mimeType, ImageType type, int?imageIndex, string internalCacheKey, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(mimeType)) { throw new ArgumentNullException("mimeType"); } var saveLocally = item.SupportsLocalMetadata && item.IsSaveLocalMetadataEnabled() && !item.IsOwnedItem && !(item is Audio); if (item is User) { saveLocally = true; } if (type != ImageType.Primary && item is Episode) { saveLocally = false; } var locationType = item.LocationType; if (locationType == LocationType.Remote || locationType == LocationType.Virtual) { saveLocally = false; var season = item as Season; // If season is virtual under a physical series, save locally if using compatible convention if (season != null && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Compatible) { var series = season.Series; if (series != null && series.SupportsLocalMetadata && series.IsSaveLocalMetadataEnabled()) { saveLocally = true; } } } if (!string.IsNullOrEmpty(internalCacheKey)) { saveLocally = false; } if (!imageIndex.HasValue && item.AllowsMultipleImages(type)) { imageIndex = item.GetImages(type).Count(); } var index = imageIndex ?? 0; var paths = GetSavePaths(item, type, imageIndex, mimeType, saveLocally); var retryPaths = GetSavePaths(item, type, imageIndex, mimeType, false); // If there are more than one output paths, the stream will need to be seekable var memoryStream = _memoryStreamProvider.CreateNew(); using (source) { await source.CopyToAsync(memoryStream).ConfigureAwait(false); } source = memoryStream; var currentImage = GetCurrentImage(item, type, index); var currentImageIsLocalFile = currentImage != null && currentImage.IsLocalFile; var currentImagePath = currentImage == null ? null : currentImage.Path; var savedPaths = new List <string>(); using (source) { var currentPathIndex = 0; foreach (var path in paths) { source.Position = 0; string retryPath = null; if (paths.Length == retryPaths.Length) { retryPath = retryPaths[currentPathIndex]; } var savedPath = await SaveImageToLocation(source, path, retryPath, cancellationToken).ConfigureAwait(false); savedPaths.Add(savedPath); currentPathIndex++; } } // Set the path into the item SetImagePath(item, type, imageIndex, savedPaths[0]); // Delete the current path if (currentImageIsLocalFile && !savedPaths.Contains(currentImagePath, StringComparer.OrdinalIgnoreCase)) { var currentPath = currentImagePath; _logger.Debug("Deleting previous image {0}", currentPath); _libraryMonitor.ReportFileSystemChangeBeginning(currentPath); try { var currentFile = new FileInfo(currentPath); // This will fail if the file is hidden if (currentFile.Exists) { if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) { currentFile.Attributes &= ~FileAttributes.Hidden; } _fileSystem.DeleteFile(currentFile.FullName); } } finally { _libraryMonitor.ReportFileSystemChangeComplete(currentPath, false); } } }