@@ -26,18 +26,20 @@ public class TelegramService : ITelegramService
2626 private const int FILESPLITSIZE = 524288 ; // 512 * 1024;
2727 private TransactionInfoService _tis { get ; set ; }
2828 private IDbService _db { get ; set ; }
29+ private ITaskPersistenceService _persistence { get ; set ; }
2930 private static WTelegram . Client client = null ;
3031 private static Messages_Chats chats = null ;
3132 private static List < ChatViewBase > favouriteChannels = new List < ChatViewBase > ( ) ;
3233 private static Mutex mut = new Mutex ( ) ;
3334 private ILogger < IFileService > _logger { get ; set ; }
3435
3536
36- public TelegramService ( TransactionInfoService tis , IDbService db , ILogger < IFileService > logger )
37+ public TelegramService ( TransactionInfoService tis , IDbService db , ILogger < IFileService > logger , ITaskPersistenceService persistence )
3738 {
3839 _tis = tis ;
3940 _db = db ;
4041 _logger = logger ;
42+ _persistence = persistence ;
4143 // createDownloadFolder();
4244 mut . WaitOne ( ) ;
4345 if ( client == null )
@@ -341,16 +343,39 @@ public async Task<Message> uploadFile(string chatId, Stream file, string fileNam
341343 um . startDate = DateTime . Now ;
342344 um . _transmitted = 0 ;
343345 _tis . addToUploadList ( um ) ;
346+
347+ // Persist the upload task
348+ try
349+ {
350+ um . OnProgressPersist = async ( transmitted , progress , state ) =>
351+ {
352+ await _persistence . UpdateProgress ( um . _internalId , transmitted , progress , state ) ;
353+ } ;
354+ await _persistence . PersistUpload ( um , chatId , um . path ) ;
355+ }
356+ catch ( Exception ex )
357+ {
358+ _logger . LogWarning ( ex , "Failed to persist upload task - continuing without persistence" ) ;
359+ }
360+
344361 try
345362 {
346363 var inputFile = await client . UploadFileAsync ( file , fileName , um . ProgressCallback ) ;
347364 var result = await client . SendMediaAsync ( peer , caption ?? fileName , inputFile , mimeType ) ;
348365 _logger . LogInformation ( "File upload completed - FileName: {FileName}, MessageId: {MessageId}" , fileName , result . id ) ;
366+
367+ // Mark task as completed
368+ await _persistence . MarkCompleted ( um . _internalId ) ;
369+
349370 return result ;
350371 }
351372 catch ( Exception ex )
352373 {
353374 _logger . LogError ( ex , "File upload failed - FileName: {FileName}, ChatId: {ChatId}" , fileName , chatId ) ;
375+
376+ // Mark task as error
377+ await _persistence . MarkError ( um . _internalId , ex . Message ) ;
378+
354379 throw ;
355380 }
356381 }
@@ -803,6 +828,129 @@ public async Task<Stream> DownloadFileAndReturn(ChatMessages message, Stream ms
803828 return null ;
804829 }
805830
831+ public async Task < Stream > DownloadFileAndReturnWithOffset ( ChatMessages message , Stream ms = null , string fileName = null , string folder = null , DownloadModel model = null , long offset = 0 )
832+ {
833+ if ( model == null )
834+ {
835+ model = new DownloadModel ( ) ;
836+ model . tis = _tis ;
837+ }
838+
839+ model . m = message ;
840+ model . channel = message . user ;
841+
842+ if ( message . message . media is MessageMediaDocument { document : TL . Document document } )
843+ {
844+ model . _size = document . size ;
845+ model . _transmitted = offset ;
846+ var filename = fileName ?? document . Filename ;
847+ filename ??= $ "{ document . id } .{ document . mime_type [ ( document . mime_type . IndexOf ( '/' ) + 1 ) ..] } ";
848+ if ( model . name == null )
849+ model . name = filename ;
850+
851+ _logger . LogInformation ( "Starting document download with offset - FileName: {FileName}, Size: {SizeMB:F2}MB, Offset: {Offset}" ,
852+ filename , document . size / ( 1024.0 * 1024.0 ) , offset ) ;
853+
854+ if ( offset == 0 )
855+ {
856+ // No offset - use standard download
857+ MemoryStream dest = new MemoryStream ( ) ;
858+ await client . DownloadFileAsync ( document , ms ?? dest , null , model . ProgressCallback ) ;
859+ _logger . LogInformation ( "Document download completed - FileName: {FileName}" , filename ) ;
860+ return ms ?? dest ;
861+ }
862+
863+ // Offset-based download using Upload_GetFile API
864+ Stream targetStream = ms ?? new MemoryStream ( ) ;
865+ long currentOffset = offset ;
866+ long totalSize = document . size ;
867+
868+ InputDocument inputFile = document ;
869+ var location = new InputDocumentFileLocation
870+ {
871+ id = inputFile . id ,
872+ access_hash = inputFile . access_hash ,
873+ file_reference = inputFile . file_reference ,
874+ thumb_size = ""
875+ } ;
876+
877+ _logger . LogInformation ( "Resuming download from offset {Offset} of {TotalSize} bytes" , currentOffset , totalSize ) ;
878+
879+ while ( currentOffset < totalSize )
880+ {
881+ int chunkSize = FILESPLITSIZE ; // 512KB chunks
882+ if ( currentOffset + chunkSize > totalSize )
883+ {
884+ // Adjust last chunk size (must be multiple of 4096 for Telegram API)
885+ long remaining = totalSize - currentOffset ;
886+ chunkSize = ( int ) ( ( remaining + 4095 ) / 4096 * 4096 ) ; // Round up to 4096
887+ if ( chunkSize > FILESPLITSIZE )
888+ chunkSize = FILESPLITSIZE ;
889+ }
890+
891+ Upload_FileBase file = null ;
892+ try
893+ {
894+ file = await client . Upload_GetFile ( location , currentOffset , limit : chunkSize ) ;
895+ }
896+ catch ( RpcException ex ) when ( ex . Code == 303 && ex . Message == "FILE_MIGRATE_X" )
897+ {
898+ _logger . LogWarning ( "DC migration required, switching to DC {DC}" , - ex . X ) ;
899+ client = await client . GetClientForDC ( - ex . X , true ) ;
900+ file = await client . Upload_GetFile ( location , currentOffset , limit : chunkSize ) ;
901+ }
902+ catch ( RpcException ex ) when ( ex . Code == 400 && ex . Message == "OFFSET_INVALID" )
903+ {
904+ _logger . LogError ( ex , "OFFSET_INVALID at offset {Offset} - file may have changed" , currentOffset ) ;
905+ throw ;
906+ }
907+ catch ( Exception ex )
908+ {
909+ _logger . LogError ( ex , "Download error at offset {Offset}" , currentOffset ) ;
910+ throw ;
911+ }
912+
913+ if ( file is Upload_File uploadFile )
914+ {
915+ var fileBytes = uploadFile . bytes ;
916+ if ( fileBytes . Length == 0 )
917+ {
918+ _logger . LogInformation ( "Received empty chunk - download complete at offset {Offset}" , currentOffset ) ;
919+ break ;
920+ }
921+
922+ // Write to stream - for append mode, the stream position should already be at the end
923+ await targetStream . WriteAsync ( fileBytes , 0 , fileBytes . Length ) ;
924+ currentOffset += fileBytes . Length ;
925+ model . _transmitted = currentOffset ;
926+
927+ // Call progress callback
928+ model . ProgressCallback ( currentOffset , totalSize ) ;
929+
930+ continue ;
931+ }
932+
933+ throw new InvalidOperationException ( "Unexpected file type returned from Telegram API." ) ;
934+ }
935+
936+ _logger . LogInformation ( "Document download with offset completed - FileName: {FileName}, FinalOffset: {Offset}" ,
937+ filename , currentOffset ) ;
938+ return targetStream ;
939+ }
940+ else if ( message . message . media is MessageMediaPhoto { photo : Photo photo } )
941+ {
942+ // Photos don't support resume - they're small enough to re-download
943+ _logger . LogInformation ( "Photo download (no resume support) - PhotoId: {PhotoId}" , photo . id ) ;
944+ var filename = $ "{ photo . id } .jpg";
945+ MemoryStream dest = new MemoryStream ( ) ;
946+ var type = await client . DownloadFileAsync ( photo , ms ?? dest , ( PhotoSizeBase ) null , model . ProgressCallback ) ;
947+ dest . Close ( ) ;
948+ return ms ?? dest ;
949+ }
950+
951+ return null ;
952+ }
953+
806954 public async Task < string > DownloadFile ( ChatMessages message , string fileName = null , string folder = null , DownloadModel model = null , bool shouldAddToList = false )
807955 {
808956 if ( model == null )
0 commit comments