Skip to content

Commit 293fd20

Browse files
committed
feat: add strm to local folder
1 parent c680b03 commit 293fd20

5 files changed

Lines changed: 344 additions & 3 deletions

File tree

TelegramDownloader/Data/FileService.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,45 @@ public async Task<String> CreateStrmFiles(string path, string dbName, string hos
13151315
Directory.Delete(basePath, true);
13161316
return zipPath;
13171317
}
1318+
1319+
public async Task CreateStrmFilesToLocal(string path, string dbName, string host, string destinationFolder)
1320+
{
1321+
String folderPathName = Path.GetFileName(path.TrimEnd('/'));
1322+
String basePath = Path.Combine(LOCALDIR, destinationFolder, folderPathName);
1323+
1324+
// Create destination directory
1325+
Directory.CreateDirectory(basePath);
1326+
1327+
List<BsonFileManagerModel> filesAndFolders = await _db.getAllChildFilesInDirectory(dbName, path);
1328+
foreach (BsonFileManagerModel file in filesAndFolders)
1329+
{
1330+
String filePath = Path.Combine(basePath, file.FilePath.Substring(path.Length));
1331+
if (file.IsFile)
1332+
{
1333+
if (FileExtensionTypeTest.isVideoExtension(file.Type) || FileExtensionTypeTest.isAudioExtension(file.Type))
1334+
{
1335+
string contenido = Path.Combine(host, "api/file/GetFileStream", dbName, file.Id, "file" + file.Type).Replace("\\", "/");
1336+
if (GeneralConfigStatic.config.PreloadFilesOnStream || HelperService.bytesToMegaBytes(file.Size) < GeneralConfigStatic.config.MaxPreloadFileSizeInMb)
1337+
{
1338+
contenido = Path.Combine(host, "api/file/GetFileByTfmId", Uri.EscapeDataString(file.Name)).Replace("\\", "/") + $"?idChannel={dbName}&idFile={file.Id}";
1339+
}
1340+
string pattern = $@"\.({file.Type.Replace(".", "")})$";
1341+
// Ensure parent directory exists
1342+
var parentDir = Path.GetDirectoryName(Regex.Replace(filePath, pattern, ".strm"));
1343+
if (!string.IsNullOrEmpty(parentDir))
1344+
{
1345+
Directory.CreateDirectory(parentDir);
1346+
}
1347+
File.WriteAllText(Regex.Replace(filePath, pattern, ".strm"), contenido);
1348+
}
1349+
}
1350+
else
1351+
{
1352+
Directory.CreateDirectory(filePath);
1353+
}
1354+
}
1355+
}
1356+
13181357
public async Task UploadFileFromServer(string dbName, string currentPath, List<Syncfusion.Blazor.FileManager.FileManagerDirectoryContent> files, InfoDownloadTaksModel dm = null) // ItemsUploadedEventArgs<FileManagerDirectoryContent> args)
13191358
{
13201359
_logger.LogInformation("Starting upload from server - DbName: {DbName}, Path: {Path}, FilesCount: {Count}",

TelegramDownloader/Data/IFileService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public interface IFileService
3131
Task<List<BsonFileManagerModel>> getTelegramFoldersByParentId(string dbName, string? parentId);
3232
Task<List<ExpandoObject>> GetTelegramFoldersExpando(string id, string parentId);
3333
Task<String> CreateStrmFiles(string path, string dbName, string host);
34+
Task CreateStrmFilesToLocal(string path, string dbName, string host, string destinationFolder);
3435
Task importData(string dbName, string path, GenericNotificationProgressModel gnp);
3536
Task importSharedData(ShareFilesModel sfm, GenericNotificationProgressModel gnp);
3637
Task<FileManagerResponse<FileManagerDirectoryContent>> itemDeleteAsync(string dbName, ItemsDeleteEventArgs<FileManagerDirectoryContent> args);
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
@using TelegramDownloader.Data
2+
@using TelegramDownloader.Models
3+
@using TelegramDownloader.Shared
4+
5+
@inject IFileService fs
6+
@inject NavigationManager MyNavigationManager
7+
8+
<div class="modal @ModalClass" tabindex="-1" role="dialog" style="display:@ModalDisplay">
9+
<div class="modal-dialog modal-dialog-centered" role="document">
10+
<div class="modal-content download-modal">
11+
<!-- Header -->
12+
<div class="modal-header download-modal-header">
13+
<div class="modal-title-wrapper">
14+
<div class="modal-icon">
15+
<i class="bi bi-file-earmark-play"></i>
16+
</div>
17+
<div>
18+
<h5 class="modal-title">Export STRM Files</h5>
19+
<p class="modal-subtitle">Choose export method for @folderName</p>
20+
</div>
21+
</div>
22+
<button type="button" class="btn-close-modal" @onclick="() => Close()" aria-label="Close">
23+
<i class="bi bi-x-lg"></i>
24+
</button>
25+
</div>
26+
27+
<!-- Body -->
28+
<div class="modal-body download-modal-body">
29+
@if (ShowBackdrop)
30+
{
31+
<div class="export-options">
32+
<!-- Option 1: Download ZIP -->
33+
<div class="export-option-card @(selectedOption == ExportOption.DownloadZip ? "selected" : "")"
34+
@onclick="() => SelectOption(ExportOption.DownloadZip)">
35+
<div class="option-icon">
36+
<i class="bi bi-file-earmark-zip"></i>
37+
</div>
38+
<div class="option-content">
39+
<h6 class="option-title">Download as ZIP</h6>
40+
<p class="option-description">Download all STRM files compressed in a ZIP file</p>
41+
</div>
42+
<div class="option-check">
43+
@if (selectedOption == ExportOption.DownloadZip)
44+
{
45+
<i class="bi bi-check-circle-fill"></i>
46+
}
47+
else
48+
{
49+
<i class="bi bi-circle"></i>
50+
}
51+
</div>
52+
</div>
53+
54+
<!-- Option 2: Export to Local Folder -->
55+
<div class="export-option-card @(selectedOption == ExportOption.ExportToLocal ? "selected" : "")"
56+
@onclick="() => SelectOption(ExportOption.ExportToLocal)">
57+
<div class="option-icon">
58+
<i class="bi bi-folder2-open"></i>
59+
</div>
60+
<div class="option-content">
61+
<h6 class="option-title">Export to Local Folder</h6>
62+
<p class="option-description">Generate STRM files directly in a local directory</p>
63+
</div>
64+
<div class="option-check">
65+
@if (selectedOption == ExportOption.ExportToLocal)
66+
{
67+
<i class="bi bi-check-circle-fill"></i>
68+
}
69+
else
70+
{
71+
<i class="bi bi-circle"></i>
72+
}
73+
</div>
74+
</div>
75+
76+
<!-- Folder Selection (only visible when ExportToLocal is selected) -->
77+
@if (selectedOption == ExportOption.ExportToLocal)
78+
{
79+
<div class="form-section folder-section mt-3">
80+
<label class="section-label">
81+
<i class="bi bi-folder"></i>
82+
Destination Folder
83+
</label>
84+
<TelegramDownloader.Shared.Ddtree idTree="strmtree" @ref="ddtree" />
85+
</div>
86+
}
87+
</div>
88+
}
89+
</div>
90+
91+
<!-- Footer -->
92+
<div class="modal-footer download-modal-footer">
93+
<button type="button" class="btn btn-cancel" @onclick="() => Close()">
94+
<i class="bi bi-x"></i>
95+
Cancel
96+
</button>
97+
<button type="button" class="btn btn-download" @onclick="() => Submit()" disabled="@isExporting">
98+
@if (isExporting)
99+
{
100+
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
101+
<span>Exporting...</span>
102+
}
103+
else
104+
{
105+
<i class="bi @(selectedOption == ExportOption.DownloadZip ? "bi-download" : "bi-folder-check")"></i>
106+
<span>@(selectedOption == ExportOption.DownloadZip ? "Download" : "Export")</span>
107+
}
108+
</button>
109+
</div>
110+
</div>
111+
</div>
112+
</div>
113+
114+
@if (ShowBackdrop)
115+
{
116+
<div class="modal-backdrop fade show"></div>
117+
}
118+
119+
<style>
120+
.export-options {
121+
display: flex;
122+
flex-direction: column;
123+
gap: 12px;
124+
}
125+
126+
.export-option-card {
127+
display: flex;
128+
align-items: center;
129+
padding: 16px;
130+
border: 2px solid var(--border-color, #e0e0e0);
131+
border-radius: 12px;
132+
cursor: pointer;
133+
transition: all 0.2s ease;
134+
background: var(--card-bg, #fff);
135+
}
136+
137+
.export-option-card:hover {
138+
border-color: var(--primary-color, #0d6efd);
139+
background: var(--hover-bg, #f8f9fa);
140+
}
141+
142+
.export-option-card.selected {
143+
border-color: var(--primary-color, #0d6efd);
144+
background: var(--selected-bg, #e7f1ff);
145+
}
146+
147+
.option-icon {
148+
width: 48px;
149+
height: 48px;
150+
display: flex;
151+
align-items: center;
152+
justify-content: center;
153+
background: var(--icon-bg, #f0f0f0);
154+
border-radius: 10px;
155+
margin-right: 16px;
156+
}
157+
158+
.option-icon i {
159+
font-size: 24px;
160+
color: var(--primary-color, #0d6efd);
161+
}
162+
163+
.option-content {
164+
flex: 1;
165+
}
166+
167+
.option-title {
168+
margin: 0 0 4px 0;
169+
font-weight: 600;
170+
color: var(--text-primary, #212529);
171+
}
172+
173+
.option-description {
174+
margin: 0;
175+
font-size: 13px;
176+
color: var(--text-secondary, #6c757d);
177+
}
178+
179+
.option-check i {
180+
font-size: 20px;
181+
color: var(--primary-color, #0d6efd);
182+
}
183+
184+
.folder-section {
185+
padding-top: 12px;
186+
border-top: 1px solid var(--border-color, #e0e0e0);
187+
}
188+
189+
.section-label {
190+
display: flex;
191+
align-items: center;
192+
gap: 8px;
193+
font-weight: 500;
194+
margin-bottom: 8px;
195+
color: var(--text-primary, #212529);
196+
}
197+
</style>
198+
199+
@code {
200+
public enum ExportOption
201+
{
202+
DownloadZip,
203+
ExportToLocal
204+
}
205+
206+
public Ddtree? ddtree { get; set; }
207+
private ExportOption selectedOption = ExportOption.DownloadZip;
208+
private bool isExporting = false;
209+
210+
public string ModalDisplay = "none;";
211+
public string ModalClass = "";
212+
public bool ShowBackdrop = false;
213+
214+
private string channelId = "";
215+
private string path = "";
216+
private string folderName = "";
217+
private string host = "";
218+
219+
[Parameter]
220+
public EventCallback<string> OnExportCompleted { get; set; }
221+
222+
public void Open(string channelId, string path, string host)
223+
{
224+
this.channelId = channelId;
225+
this.path = path;
226+
this.host = host;
227+
this.folderName = Path.GetFileName(path.TrimEnd('/'));
228+
this.selectedOption = ExportOption.DownloadZip;
229+
this.isExporting = false;
230+
231+
ModalDisplay = "block;";
232+
ModalClass = "Show";
233+
ShowBackdrop = true;
234+
StateHasChanged();
235+
}
236+
237+
public void Close()
238+
{
239+
ModalDisplay = "none";
240+
ModalClass = "";
241+
ShowBackdrop = false;
242+
StateHasChanged();
243+
}
244+
245+
private void SelectOption(ExportOption option)
246+
{
247+
selectedOption = option;
248+
StateHasChanged();
249+
}
250+
251+
private async Task Submit()
252+
{
253+
if (selectedOption == ExportOption.DownloadZip)
254+
{
255+
// Navigate to download ZIP (existing behavior)
256+
MyNavigationManager.NavigateTo($"/api/file/strm?idChannel={channelId}&path={path}&host={host}", true);
257+
Close();
258+
}
259+
else
260+
{
261+
// Export to local folder
262+
var selectedFolder = ddtree?.selectedNode?.FirstOrDefault();
263+
if (string.IsNullOrEmpty(selectedFolder))
264+
{
265+
return;
266+
}
267+
268+
isExporting = true;
269+
StateHasChanged();
270+
271+
try
272+
{
273+
await fs.CreateStrmFilesToLocal(path, channelId, host, selectedFolder);
274+
await OnExportCompleted.InvokeAsync($"STRM files exported successfully to {selectedFolder}");
275+
}
276+
catch (Exception ex)
277+
{
278+
await OnExportCompleted.InvokeAsync($"Error exporting STRM files: {ex.Message}");
279+
}
280+
finally
281+
{
282+
isExporting = false;
283+
Close();
284+
}
285+
}
286+
}
287+
}

TelegramDownloader/Pages/Partials/impl/FileManagerImpl.razor

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
<TelegramDownloader.Pages.Modals.VideoPlayerModal @ref="videoPlayer"></TelegramDownloader.Pages.Modals.VideoPlayerModal>
107107
<TelegramDownloader.Pages.Modals.PdfViewerModal @ref="pdfViewer"></TelegramDownloader.Pages.Modals.PdfViewerModal>
108108
<TelegramDownloader.Pages.Modals.ImageViewerModal @ref="imageViewer"></TelegramDownloader.Pages.Modals.ImageViewerModal>
109+
<TelegramDownloader.Pages.Modals.ExportStrmModal @ref="exportStrmModal" OnExportCompleted="OnStrmExportCompleted"></TelegramDownloader.Pages.Modals.ExportStrmModal>
109110

110111

111112

@@ -130,6 +131,7 @@
130131
private ImageViewerModal imageViewer { get; set; } = default!;
131132

132133
private MediaUrlModal mediaUrlModal { get; set; } = default!;
134+
private ExportStrmModal exportStrmModal { get; set; } = default!;
133135

134136
public string[] ContextItems = new string[] { "Open", "Delete", "Download", "Rename", "Details", "Add to Playlist"};
135137
private bool isMyChannel = false;
@@ -468,7 +470,7 @@
468470
if (args.Item.Text == "Strm" && args.FileDetails.Count() > 0)
469471
{
470472
var file = args.FileDetails.FirstOrDefault();
471-
MyNavigationManager.NavigateTo($"/api/file/strm?idChannel={id}&path={file.FilterPath + file.Name + "/"}&host={MyNavigationManager.BaseUri}", true);
473+
exportStrmModal.Open(id, file.FilterPath + file.Name + "/", MyNavigationManager.BaseUri);
472474
}
473475
if (args.Item.Text == "Preload" && args.FileDetails.Count() > 0)
474476
{
@@ -885,7 +887,7 @@
885887
if (args.Folder != null)
886888
{
887889
var path = args.Folder.FilterPath + args.Folder.Name + "/";
888-
MyNavigationManager.NavigateTo($"/api/file/strm?idChannel={id}&path={path}&host={MyNavigationManager.BaseUri}", true);
890+
exportStrmModal.Open(id, path, MyNavigationManager.BaseUri);
889891
}
890892
}
891893
catch (Exception e)
@@ -896,6 +898,18 @@
896898
await Task.CompletedTask;
897899
}
898900

901+
private void OnStrmExportCompleted(string message)
902+
{
903+
if (message.StartsWith("Error"))
904+
{
905+
nm.sendMessage("Export Error", message, NotificationTypes.Error);
906+
}
907+
else
908+
{
909+
nm.sendMessage("Export Complete", message, NotificationTypes.Success);
910+
}
911+
}
912+
899913
private async Task OnMobilePreloadFilesAsync(MfmPreloadFilesEventArgs args)
900914
{
901915
try

0 commit comments

Comments
 (0)