@ -1,10 +1,18 @@
using MediaBrowser.Common.Progress ;
using MediaBrowser.Common.IO ;
using MediaBrowser.Common.Progress ;
using MediaBrowser.Controller ;
using MediaBrowser.Controller.Sync ;
using MediaBrowser.Model.Dto ;
using MediaBrowser.Model.Entities ;
using MediaBrowser.Model.Logging ;
using MediaBrowser.Model.MediaInfo ;
using MediaBrowser.Model.Sync ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Security.Cryptography ;
using System.Text ;
using System.Threading ;
using System.Threading.Tasks ;
@ -15,22 +23,25 @@ namespace MediaBrowser.Server.Implementations.Sync
private readonly ISyncManager _syncManager ;
private readonly IServerApplicationHost _appHost ;
private readonly ILogger _logger ;
private readonly IFileSystem _fileSystem ;
public MediaSync ( ILogger logger , ISyncManager syncManager , IServerApplicationHost appHost )
public MediaSync ( ILogger logger , ISyncManager syncManager , IServerApplicationHost appHost , IFileSystem fileSystem )
{
_logger = logger ;
_syncManager = syncManager ;
_appHost = appHost ;
_fileSystem = fileSystem ;
}
public async Task Sync ( IServerSyncProvider provider ,
ISyncDataProvider dataProvider ,
SyncTarget target ,
IProgress < double > progress ,
CancellationToken cancellationToken )
{
var serverId = _appHost . SystemId ;
await SyncData ( provider , serverId, target , cancellationToken ) . ConfigureAwait ( false ) ;
await SyncData ( provider , dataProvider, serverId, target , cancellationToken ) . ConfigureAwait ( false ) ;
progress . Report ( 3 ) ;
var innerProgress = new ActionableProgress < double > ( ) ;
@ -40,44 +51,46 @@ namespace MediaBrowser.Server.Implementations.Sync
totalProgress + = 1 ;
progress . Report ( totalProgress ) ;
} ) ;
await GetNewMedia ( provider , target, serverId , innerProgress , cancellationToken ) ;
await GetNewMedia ( provider , dataProvider, target, serverId , innerProgress , cancellationToken ) ;
// Do the data sync twice so the server knows what was removed from the device
await SyncData ( provider , serverId, target , cancellationToken ) . ConfigureAwait ( false ) ;
await SyncData ( provider , dataProvider, serverId, target , cancellationToken ) . ConfigureAwait ( false ) ;
progress . Report ( 100 ) ;
}
private async Task SyncData ( IServerSyncProvider provider ,
ISyncDataProvider dataProvider ,
string serverId ,
SyncTarget target ,
CancellationToken cancellationToken )
{
//var localIds = await provider.GetServerItemIds(serverId, target, cancellationToken).ConfigureAwait(false) ;
var localIds = await dataProvider . GetServerItemIds ( target , serverId ) . ConfigureAwait ( false ) ;
//var result = await _syncManager.SyncData(new SyncDataRequest
// {
// TargetId = target.Id,
// LocalItemIds = localIds
var result = await _syncManager . SyncData ( new SyncDataRequest
{
TargetId = target . Id ,
LocalItemIds = localIds
//}).ConfigureAwait(false) ;
} ) . ConfigureAwait ( false ) ;
//cancellationToken.ThrowIfCancellationRequested() ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
//foreach (var itemIdToRemove in result.ItemIdsToRemove )
// {
// try
// {
// await RemoveItem(provider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false);
// }
// catch (Exception ex)
// {
// _logger.ErrorException("Error deleting item from sync target. Id: {0}", ex, itemIdToRemove) ;
// }
// }
foreach ( var itemIdToRemove in result . ItemIdsToRemove )
{
try
{
await RemoveItem ( provider , dataProvider , serverId , itemIdToRemove , target , cancellationToken ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error deleting item from device. Id: {0}" , ex , itemIdToRemove ) ;
}
}
}
private async Task GetNewMedia ( IServerSyncProvider provider ,
ISyncDataProvider dataProvider ,
SyncTarget target ,
string serverId ,
IProgress < double > progress ,
@ -106,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.Sync
progress . Report ( totalProgress ) ;
} ) ;
await GetItem ( provider , target, serverId , jobItem , innerProgress , cancellationToken ) . ConfigureAwait ( false ) ;
await GetItem ( provider , dataProvider, target, serverId , jobItem , innerProgress , cancellationToken ) . ConfigureAwait ( false ) ;
numComplete + + ;
startingPercent = numComplete ;
@ -117,6 +130,7 @@ namespace MediaBrowser.Server.Implementations.Sync
}
private async Task GetItem ( IServerSyncProvider provider ,
ISyncDataProvider dataProvider ,
SyncTarget target ,
string serverId ,
SyncedItem jobItem ,
@ -129,6 +143,8 @@ namespace MediaBrowser.Server.Implementations.Sync
var fileTransferProgress = new ActionableProgress < double > ( ) ;
fileTransferProgress . RegisterAction ( pct = > progress . Report ( pct * . 92 ) ) ;
var localItem = CreateLocalItem ( provider , target , libraryItem , serverId , jobItem . OriginalFileName ) ;
await _syncManager . ReportSyncJobItemTransferBeginning ( internalSyncJobItem . Id ) ;
var transferSuccess = false ;
@ -136,9 +152,10 @@ namespace MediaBrowser.Server.Implementations.Sync
try
{
string [ ] pathParts = GetPathParts ( serverId , libraryItem ) ;
await SendFile ( provider , internalSyncJobItem . OutputPath , localItem , target , cancellationToken ) . ConfigureAwait ( false ) ;
await SendFile ( provider , internalSyncJobItem . OutputPath , pathParts , target , cancellationToken ) . ConfigureAwait ( false ) ;
// Create db record
await dataProvider . AddOrUpdate ( target , localItem ) . ConfigureAwait ( false ) ;
progress . Report ( 92 ) ;
@ -164,25 +181,189 @@ namespace MediaBrowser.Server.Implementations.Sync
}
}
private Task RemoveItem ( IServerSyncProvider provider ,
private async Task RemoveItem ( IServerSyncProvider provider ,
ISyncDataProvider dataProvider ,
string serverId ,
string itemId ,
SyncTarget target ,
CancellationToken cancellationToken )
{
return Task . FromResult ( true ) ;
//return provider.DeleteItem(serverId, itemId, target, cancellationToken);
var localId = GetLocalId ( serverId , itemId ) ;
var localItem = await dataProvider . Get ( target , localId ) ;
if ( localItem = = null )
{
return ;
}
var files = await GetFiles ( provider , localItem , target ) ;
foreach ( var file in files )
{
await provider . DeleteFile ( file . Path , target , cancellationToken ) . ConfigureAwait ( false ) ;
}
await dataProvider . Delete ( target , localId ) . ConfigureAwait ( false ) ;
}
private Task SendFile ( IServerSyncProvider provider , string inputPath , LocalItem item , SyncTarget target , CancellationToken cancellationToken )
{
return provider . SendFile ( inputPath , item . LocalPath , target , new Progress < double > ( ) , cancellationToken ) ;
}
private string GetLocalId ( string serverId , string itemId )
{
var bytes = Encoding . UTF8 . GetBytes ( serverId + itemId ) ;
bytes = CreateMD5 ( bytes ) ;
return BitConverter . ToString ( bytes , 0 , bytes . Length ) . Replace ( "-" , string . Empty ) ;
}
private byte [ ] CreateMD5 ( byte [ ] value )
{
using ( var provider = MD5 . Create ( ) )
{
return provider . ComputeHash ( value ) ;
}
}
private string [ ] GetPathParts ( string serverId , BaseItemDto item )
p ublic LocalItem CreateLocalItem ( IServerSyncProvider provider , SyncTarget target , BaseItemDto libraryItem , string serverId , string originalFileName )
{
return null ;
var path = GetDirectoryPath ( provider , libraryItem , serverId ) ;
path . Add ( GetLocalFileName ( provider , libraryItem , originalFileName ) ) ;
var localPath = provider . GetFullPath ( path , target ) ;
foreach ( var mediaSource in libraryItem . MediaSources )
{
mediaSource . Path = localPath ;
mediaSource . Protocol = MediaProtocol . File ;
}
return new LocalItem
{
Item = libraryItem ,
ItemId = libraryItem . Id ,
ServerId = serverId ,
LocalPath = localPath ,
Id = GetLocalId ( serverId , libraryItem . Id )
} ;
}
private async Task SendFile ( IServerSyncProvider provider , string inputPath , string [ ] path , SyncTarget target , CancellationToken cancellationToken )
private List < string > GetDirectoryPath ( IServerSyncProvider provider , BaseItemDto item , string serverId )
{
await provider . SendFile ( inputPath , path , target , new Progress < double > ( ) , cancellationToken )
. ConfigureAwait ( false ) ;
var parts = new List < string >
{
serverId
} ;
if ( item . IsType ( "episode" ) )
{
parts . Add ( "TV" ) ;
parts . Add ( item . SeriesName ) ;
if ( ! string . IsNullOrWhiteSpace ( item . SeasonName ) )
{
parts . Add ( item . SeasonName ) ;
}
}
else if ( item . IsVideo )
{
parts . Add ( "Videos" ) ;
parts . Add ( item . Name ) ;
}
else if ( item . IsAudio )
{
parts . Add ( "Music" ) ;
if ( ! string . IsNullOrWhiteSpace ( item . AlbumArtist ) )
{
parts . Add ( item . AlbumArtist ) ;
}
if ( ! string . IsNullOrWhiteSpace ( item . Album ) )
{
parts . Add ( item . Album ) ;
}
}
else if ( string . Equals ( item . MediaType , MediaType . Photo , StringComparison . OrdinalIgnoreCase ) )
{
parts . Add ( "Photos" ) ;
if ( ! string . IsNullOrWhiteSpace ( item . Album ) )
{
parts . Add ( item . Album ) ;
}
}
return parts . Select ( i = > GetValidFilename ( provider , i ) ) . ToList ( ) ;
}
private string GetLocalFileName ( IServerSyncProvider provider , BaseItemDto item , string originalFileName )
{
var filename = originalFileName ;
if ( string . IsNullOrEmpty ( filename ) )
{
filename = item . Name ;
}
return GetValidFilename ( provider , filename ) ;
}
private string GetValidFilename ( IServerSyncProvider provider , string filename )
{
// We can always add this method to the sync provider if it's really needed
return _fileSystem . GetValidFilename ( filename ) ;
}
private async Task < List < ItemFileInfo > > GetFiles ( IServerSyncProvider provider , LocalItem item , SyncTarget target )
{
var path = item . LocalPath ;
path = provider . GetParentDirectoryPath ( path , target ) ;
var list = await provider . GetFileSystemEntries ( path , target ) . ConfigureAwait ( false ) ;
var itemFiles = new List < ItemFileInfo > ( ) ;
var name = Path . GetFileNameWithoutExtension ( item . LocalPath ) ;
foreach ( var file in list . Where ( f = > f . Name . Contains ( name ) ) )
{
var itemFile = new ItemFileInfo
{
Path = file . Path ,
Name = file . Name
} ;
if ( IsSubtitleFile ( file . Name ) )
{
itemFile . Type = ItemFileType . Subtitles ;
}
else if ( ! IsImageFile ( file . Name ) )
{
itemFile . Type = ItemFileType . Media ;
}
itemFiles . Add ( itemFile ) ;
}
return itemFiles ;
}
private static readonly string [ ] SupportedImageExtensions = { ".png" , ".jpg" , ".jpeg" , ".webp" } ;
private bool IsImageFile ( string path )
{
var ext = Path . GetExtension ( path ) ? ? string . Empty ;
return SupportedImageExtensions . Contains ( ext , StringComparer . OrdinalIgnoreCase ) ;
}
private static readonly string [ ] SupportedSubtitleExtensions = { ".srt" , ".vtt" } ;
private bool IsSubtitleFile ( string path )
{
var ext = Path . GetExtension ( path ) ? ? string . Empty ;
return SupportedSubtitleExtensions . Contains ( ext , StringComparer . OrdinalIgnoreCase ) ;
}
}
}