Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
TelegramDownloader/userData.json
TelegramDownloader/.claude/
.claude/
3 changes: 2 additions & 1 deletion TelegramDownloader/Data/FileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1408,7 +1408,8 @@ public async Task UploadFileFromServer(string dbName, string currentPath, List<S
if (GeneralConfigStatic.config.EnableMemorySplitUpload)
{
// Memory-based splitting - read chunks directly without creating temp files
long chunkSizeBytes = (long)GeneralConfigStatic.config.MemorySplitSizeGB * 1024L * 1024L * 1024L;
// Use MaxSize (Telegram's actual limit per GB unit) instead of real GB
long chunkSizeBytes = (long)GeneralConfigStatic.config.MemorySplitSizeGB * MaxSize;
int totalChunks = (int)Math.Ceiling((double)fileInfo.Length / chunkSizeBytes);

_logger.LogInformation("Using memory-based split for {FileName} - {TotalChunks} chunks of {ChunkSizeGB} GB",
Expand Down
1 change: 1 addition & 0 deletions TelegramDownloader/Data/ITelegramService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public interface ITelegramService
Task<List<ChatViewBase>> getAllChats();
Task<List<ChatViewBase>> getAllSavedChats();
Task<ChatsWithFolders> getChatsWithFolders();
Task PreloadChannelsAsync();
Task<List<ChatMessages>> getAllMessages(long id, Boolean onlyFiles = false);
Task<GridDataProviderResult<ChatMessages>> getPaginatedMessages(long id, int page, int size, Boolean onlyFiles = false);
Task<List<ChatMessages>> getAllMediaMessages(long id, Boolean onlyFiles = false);
Expand Down
77 changes: 74 additions & 3 deletions TelegramDownloader/Data/TelegramService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,24 @@ public TelegramService(TransactionInfoService tis, IDbService db, ILogger<IFileS
{
if (client == null && HasValidCredentials())
{
newClient();
try
{
newClient();
}
catch (WTelegram.WTException ex) when (ex.Message.Contains("session file"))
{
// Session file is corrupted - delete it and create a new one
_logger.LogWarning("Session file corrupted during startup, deleting: {Message}", ex.Message);
var sessionPath = UserService.USERDATAFOLDER + "/WTelegram.session";
if (File.Exists(sessionPath))
{
File.Delete(sessionPath);
_logger.LogInformation("Deleted corrupted session file: {Path}", sessionPath);
}
// Create new client with fresh session
newClient();
_logger.LogInformation("Telegram client initialized with new session");
}
}
else if (!HasValidCredentials())
{
Expand Down Expand Up @@ -100,8 +117,25 @@ public void InitializeClient()
if (client == null) // Double-check after acquiring lock
{
_logger.LogInformation("Initializing Telegram client after setup");
newClient();
_logger.LogInformation("Telegram client initialized successfully");
try
{
newClient();
_logger.LogInformation("Telegram client initialized successfully");
}
catch (WTelegram.WTException ex) when (ex.Message.Contains("session file"))
{
// Session file is corrupted - delete it and create a new one
_logger.LogWarning("Session file corrupted, deleting and creating new session: {Message}", ex.Message);
var sessionPath = UserService.USERDATAFOLDER + "/WTelegram.session";
if (File.Exists(sessionPath))
{
File.Delete(sessionPath);
_logger.LogInformation("Deleted corrupted session file: {Path}", sessionPath);
}
// Create new client with fresh session
newClient();
_logger.LogInformation("Telegram client initialized with new session");
}
}
}
catch (Exception ex)
Expand Down Expand Up @@ -599,6 +633,43 @@ public async Task<ChatsWithFolders> getChatsWithFolders()
return result;
}

/// <summary>
/// Preloads channels and configuration at startup if user is logged in.
/// This ensures channels are available for API calls without requiring UI access first.
/// </summary>
public async Task PreloadChannelsAsync()
{
if (!checkUserLogin())
{
_logger.LogInformation("PreloadChannelsAsync: User not logged in, skipping preload");
return;
}

try
{
_logger.LogInformation("PreloadChannelsAsync: Starting channel preload...");

// Load all channels into cache
var channels = await getAllChats();
_logger.LogInformation("PreloadChannelsAsync: Loaded {Count} channels", channels.Count);

// Also preload folders
var folders = await getChatsWithFolders();
_logger.LogInformation("PreloadChannelsAsync: Loaded {FolderCount} folders with {UngroupedCount} ungrouped chats",
folders.Folders.Count, folders.UngroupedChats.Count);

// Preload favourites
var favourites = await GetFouriteChannels(true);
_logger.LogInformation("PreloadChannelsAsync: Loaded {Count} favourite channels", favourites.Count);

_logger.LogInformation("PreloadChannelsAsync: Channel preload completed successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "PreloadChannelsAsync: Error preloading channels");
}
}

public async Task<Message> uploadFile(string chatId, Stream file, string fileName, string mimeType = null, UploadModel um = null, string caption = null)
{
_logger.LogInformation("Starting file upload - FileName: {FileName}, Size: {SizeMB:F2}MB, ChatId: {ChatId}",
Expand Down
10 changes: 7 additions & 3 deletions TelegramDownloader/Models/GeneralConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ public static async Task SaveChanges(IDbService db, GeneralConfig gc)
gc.MemorySplitSizeGB = gc.SplitSize;
}

// Ensure MemorySplitSizeGB is within valid range (1-4 GB)
// Ensure MemorySplitSizeGB is within valid range based on Telegram limits
// Premium: max 4, Non-premium: max 2 (same as Telegram file size limits)
int maxAllowedSize = TelegramService.isPremium ? 4 : 2;
if (gc.MemorySplitSizeGB < 1) gc.MemorySplitSizeGB = 1;
if (gc.MemorySplitSizeGB > 4) gc.MemorySplitSizeGB = 4;
if (gc.MemorySplitSizeGB > maxAllowedSize) gc.MemorySplitSizeGB = maxAllowedSize;

await db.SaveConfig(gc);
config = gc;
Expand Down Expand Up @@ -131,8 +133,10 @@ public class GeneralConfig
public bool EnableMemorySplitUpload { get; set; } = false;

/// <summary>
/// Size in GB for each memory chunk when uploading large files (1-4 GB).
/// Size in GB for each memory chunk when uploading large files.
/// Premium accounts: 1-4 GB, Non-premium: 1-2 GB.
/// Only used when EnableMemorySplitUpload is true.
/// Note: Uses Telegram's actual size limits (1GB = 1024*1024*1000 bytes, not 1024^3).
/// </summary>
public int MemorySplitSizeGB { get; set; } = 2;

Expand Down
16 changes: 13 additions & 3 deletions TelegramDownloader/Pages/Config.razor
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,15 @@
Memory Chunk Size
</div>
<div class="config-item-description">
Size of each chunk when splitting in memory (1-4 GB). Must be ≤ Split Size.
Size of each chunk when splitting in memory. Must be ≤ Split Size.
@if (TelegramService.isPremium)
{
<span class="badge bg-warning text-dark ms-2">Premium: 1-4 GB</span>
}
else
{
<span class="badge bg-secondary ms-2">1-2 GB</span>
}
</div>
</div>
<div class="config-item-control">
Expand Down Expand Up @@ -1156,8 +1164,10 @@

private int GetMaxMemorySplitSize()
{
// MemorySplitSizeGB must be <= SplitSize and <= 4
return Math.Min(Model?.SplitSize ?? 4, 4);
// MemorySplitSizeGB must be <= SplitSize and <= Telegram's max file size
// Premium: max 4, Non-premium: max 2
int telegramMaxSize = TelegramService.isPremium ? 4 : 2;
return Math.Min(Model?.SplitSize ?? telegramMaxSize, telegramMaxSize);
}

public void Dispose()
Expand Down
118 changes: 96 additions & 22 deletions TelegramDownloader/Pages/Partials/impl/FileManagerImpl.razor
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@
OnPathChanged="OnMobilePathChanged"
OnFilterChanged="OnMobileFilterChanged"
InitialSearch="@_initialSearch"
InitialFilters="@_initialFilters" />
InitialFilters="@_initialFilters"
InitialSortBy="@_initialSortBy"
InitialSortAscending="@_initialSortAscending"
InitialPage="@_initialPage" />
</div>
}

Expand Down Expand Up @@ -172,18 +175,30 @@
private bool _firstRenderComplete = false;
private bool _needsRefresh = false;

// URL state for filters and search
// URL state for filters, search, sorting, and pagination
private string _initialSearch = string.Empty;
private HashSet<string> _initialFilters = new();
private string _initialSortBy = "Name";
private bool _initialSortAscending = true;
private int _initialPage = 1;
private string _currentSearch = string.Empty;
private HashSet<string> _currentFilters = new();
private string _currentSortBy = "Name";
private bool _currentSortAscending = true;
private int _currentPage = 1;

protected override async Task OnParametersSetAsync()
{
string currentBsiId = bsi?.Id;
bool idChanged = _previousId != id;
bool bsiChanged = isShared && _previousBsiId != currentBsiId && currentBsiId != null;

// Parse URL params early so they're available before first render
if (!_firstRenderComplete)
{
ParseUrlParameters();
}

if (idChanged || bsiChanged)
{
_previousId = id;
Expand All @@ -198,6 +213,59 @@
}
}

private void ParseUrlParameters()
{
try
{
var uri = new Uri(MyNavigationManager.Uri);
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
var searchFromUrl = query["search"];
var filtersFromUrl = query["filters"];
var sortByFromUrl = query["sortBy"];
var sortAscFromUrl = query["sortAsc"];

// Parse initial search
if (!string.IsNullOrEmpty(searchFromUrl))
{
_initialSearch = Uri.UnescapeDataString(searchFromUrl);
_currentSearch = _initialSearch;
}

// Parse initial filters (comma-separated)
if (!string.IsNullOrEmpty(filtersFromUrl))
{
var filterList = Uri.UnescapeDataString(filtersFromUrl).Split(',', StringSplitOptions.RemoveEmptyEntries);
_initialFilters = new HashSet<string>(filterList);
_currentFilters = new HashSet<string>(filterList);
}

// Parse initial sort
if (!string.IsNullOrEmpty(sortByFromUrl))
{
_initialSortBy = Uri.UnescapeDataString(sortByFromUrl);
_currentSortBy = _initialSortBy;
}

if (!string.IsNullOrEmpty(sortAscFromUrl))
{
_initialSortAscending = sortAscFromUrl.ToLower() != "false";
_currentSortAscending = _initialSortAscending;
}

// Parse initial page
var pageFromUrl = query["page"];
if (!string.IsNullOrEmpty(pageFromUrl) && int.TryParse(pageFromUrl, out int page) && page > 0)
{
_initialPage = page;
_currentPage = page;
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error parsing URL parameters");
}
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
Expand Down Expand Up @@ -261,23 +329,6 @@
var uri = new Uri(MyNavigationManager.Uri);
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
var pathFromUrl = query["path"];
var searchFromUrl = query["search"];
var filtersFromUrl = query["filters"];

// Parse initial search
if (!string.IsNullOrEmpty(searchFromUrl))
{
_initialSearch = Uri.UnescapeDataString(searchFromUrl);
_currentSearch = _initialSearch;
}

// Parse initial filters (comma-separated)
if (!string.IsNullOrEmpty(filtersFromUrl))
{
var filterList = Uri.UnescapeDataString(filtersFromUrl).Split(',', StringSplitOptions.RemoveEmptyEntries);
_initialFilters = new HashSet<string>(filterList);
_currentFilters = new HashSet<string>(filterList);
}

// Decode the path and ensure it ends with /
string decodedPath = "/";
Expand Down Expand Up @@ -310,7 +361,9 @@
{
try
{
UpdateUrlWithState(path, _currentSearch, _currentFilters);
// Reset page to 1 when navigating to a different folder
_currentPage = 1;
UpdateUrlWithState(path, _currentSearch, _currentFilters, _currentSortBy, _currentSortAscending, _currentPage);
}
catch (Exception ex)
{
Expand All @@ -324,18 +377,21 @@
{
_currentSearch = args.SearchText;
_currentFilters = args.TypeFilters;
_currentSortBy = args.SortBy;
_currentSortAscending = args.SortAscending;
_currentPage = args.CurrentPage;

// Get current path from mobile file manager
var currentPath = mobileFileManager?.Path ?? "/";
UpdateUrlWithState(currentPath, _currentSearch, _currentFilters);
UpdateUrlWithState(currentPath, _currentSearch, _currentFilters, _currentSortBy, _currentSortAscending, _currentPage);
}
catch (Exception ex)
{
Logger.LogError(ex, "Error updating URL with filters");
}
}

private void UpdateUrlWithState(string path, string search, HashSet<string> filters)
private void UpdateUrlWithState(string path, string search, HashSet<string> filters, string sortBy = "Name", bool sortAscending = true, int page = 1)
{
var baseUri = MyNavigationManager.Uri.Split('?')[0];
var queryParams = new List<string>();
Expand All @@ -358,6 +414,24 @@
queryParams.Add($"filters={Uri.EscapeDataString(string.Join(",", filters))}");
}

// Add sort if not default
if (sortBy != "Name")
{
queryParams.Add($"sortBy={Uri.EscapeDataString(sortBy)}");
}

// Add sort direction if descending
if (!sortAscending)
{
queryParams.Add("sortAsc=false");
}

// Add page if not first page
if (page > 1)
{
queryParams.Add($"page={page}");
}

var newUrl = queryParams.Count > 0 ? $"{baseUri}?{string.Join("&", queryParams)}" : baseUri;
MyNavigationManager.NavigateTo(newUrl, forceLoad: false, replace: true);
}
Expand Down
Loading
Loading