├── LICENSE ├── Old_Version └── d-fi.zip ├── README.md ├── assets ├── Albums.png ├── BeatOn_WebUI.jpg ├── Dashboard.jpg ├── Downloads.jpg ├── Downloads_BeatOn_Webui.png ├── Featured.jpg ├── Home_BeatOn_Webui.png ├── Search_BeatOn_Webui.png ├── Settings.png ├── Settings_BeatOn_Webui.png └── downloads_with_urls.gif ├── d-fi.zip └── src ├── AlbumCache.cs ├── App.config ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── BeatOn.csproj ├── BeatOn.csproj.user ├── BeatOn.sln ├── BeatOn_Banner-removebg.png ├── BeatOn_Logo.ico ├── CountToVisibilityConverter.cs ├── DatabaseManager.cs ├── DeezerArlScraper.cs ├── DownloadItem.cs ├── DownloadManagerControl.xaml ├── DownloadManagerControl.xaml.cs ├── DownloadManagerWindow.xaml ├── DownloadManagerWindow.xaml.cs ├── FormatOptionsPopup.xaml ├── FormatOptionsPopup.xaml.cs ├── LidarrDownloader.cs ├── LidarrDownloaderStats.cs ├── Logo_BeatOn.ico ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── OutputWindow.xaml ├── OutputWindow.xaml.cs ├── Properties ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── QobuzCredentialManager.cs ├── QobuzDownloader.cs ├── Qobuz_FormatOptionsPopup.xaml ├── Qobuz_FormatOptionsPopup.xaml.cs ├── SettingsWindow.xaml ├── SettingsWindow.xaml.cs ├── SubtractConverter.cs ├── SystemUsageStats.cs ├── URL.png ├── WebUIServer.cs ├── album_coverholder.png ├── download.png ├── downloads.db ├── filedownload.png ├── img ├── BeatOn_Banner-removebg.png ├── album_coverholder.png ├── download.png ├── filedownload.png └── settings.png ├── settings.png └── wwwroot ├── index.html ├── script.js ├── search.html └── styles.css /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 jaylex32 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Old_Version/d-fi.zip: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BeatOn 2 | 3 | BeatOn is the ultimate GUI for D-Fi, designed to enhance your download experience with Deezer and Qobuz. Built using C# and WPF, BeatOn allows you to effortlessly download albums, songs, and artist catalogs with just a few clicks. 4 | 5 | ## Features 6 | 7 | - Intuitive and visually pleasing interface 8 | - Easy downloading of albums, songs, and artist catalogs 9 | - Support for both Deezer and Qobuz platforms 10 | - Web UI for convenient access and control 11 | 12 | ## Screenshots 13 | 14 | ### Desktop Application 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
Main
Settings
24 | 25 | ### Web UI 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
Web UI HomeWeb UI Search
Web UI DownloadsWeb UI Settings
37 | 38 | ## Tutorials 39 | 40 | ### Download with URLs 41 | ![Download with Urls](./assets/downloads_with_urls.gif) 42 | 43 | 44 | ## License 45 | 46 | This project is licensed under the MIT License - see the LICENSE file for details. 47 | 48 | 49 | --- 50 | 51 | BeatOn - Simplifying your music download experience. 52 | 53 | 54 | 55 | 56 | ### If you like my work and want to buy me a coffee to support me, you can do so here: https://buymeacoffee.com/jayross 57 | 58 | -------------------------------------------------------------------------------- /assets/Albums.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/assets/Albums.png -------------------------------------------------------------------------------- /assets/BeatOn_WebUI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/assets/BeatOn_WebUI.jpg -------------------------------------------------------------------------------- /assets/Dashboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/assets/Dashboard.jpg -------------------------------------------------------------------------------- /assets/Downloads.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/assets/Downloads.jpg -------------------------------------------------------------------------------- /assets/Downloads_BeatOn_Webui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/assets/Downloads_BeatOn_Webui.png -------------------------------------------------------------------------------- /assets/Featured.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/assets/Featured.jpg -------------------------------------------------------------------------------- /assets/Home_BeatOn_Webui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/assets/Home_BeatOn_Webui.png -------------------------------------------------------------------------------- /assets/Search_BeatOn_Webui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/assets/Search_BeatOn_Webui.png -------------------------------------------------------------------------------- /assets/Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/assets/Settings.png -------------------------------------------------------------------------------- /assets/Settings_BeatOn_Webui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/assets/Settings_BeatOn_Webui.png -------------------------------------------------------------------------------- /assets/downloads_with_urls.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/assets/downloads_with_urls.gif -------------------------------------------------------------------------------- /d-fi.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/d-fi.zip -------------------------------------------------------------------------------- /src/AlbumCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WpfApp1 9 | { 10 | public class AlbumCache 11 | { 12 | private Dictionary cache = new Dictionary(); 13 | private readonly TimeSpan cacheDuration = TimeSpan.FromHours(24); // Cache for 24 hours 14 | private readonly string cacheFilePath; 15 | 16 | public AlbumCache(string cacheFilePath) 17 | { 18 | this.cacheFilePath = cacheFilePath; 19 | LoadCache(); 20 | } 21 | 22 | public bool IsAlbumProcessed(string artistName, string albumName) 23 | { 24 | string key = GetCacheKey(artistName, albumName); 25 | return cache.ContainsKey(key); 26 | } 27 | 28 | public void MarkAlbumAsProcessed(string artistName, string albumName) 29 | { 30 | string key = GetCacheKey(artistName, albumName); 31 | cache[key] = true; 32 | SaveCache(); 33 | } 34 | 35 | private string GetCacheKey(string artistName, string albumName) 36 | { 37 | return $"{artistName}|{albumName}".ToLower(); 38 | } 39 | 40 | private void LoadCache() 41 | { 42 | if (File.Exists(cacheFilePath)) 43 | { 44 | var lines = File.ReadAllLines(cacheFilePath); 45 | foreach (var line in lines) 46 | { 47 | var parts = line.Split('|'); 48 | if (parts.Length == 3 && DateTime.TryParse(parts[2], out DateTime timestamp)) 49 | { 50 | if (DateTime.Now - timestamp <= cacheDuration) 51 | { 52 | cache[parts[0] + "|" + parts[1]] = true; 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | private void SaveCache() 60 | { 61 | var lines = cache.Select(kvp => $"{kvp.Key}|{DateTime.Now:O}"); 62 | File.WriteAllLines(cacheFilePath, lines); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Windows; 5 | using System.Diagnostics; 6 | 7 | namespace WpfApp1 8 | { 9 | public partial class App : Application 10 | { 11 | protected override void OnStartup(StartupEventArgs e) 12 | { 13 | AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; 14 | base.OnStartup(e); 15 | EnsureDatabaseFile(); 16 | } 17 | 18 | private void EnsureDatabaseFile() 19 | { 20 | var dbFileName = "downloads.db"; 21 | var dbFilePath = Path.Combine(AppContext.BaseDirectory, dbFileName); 22 | 23 | try 24 | { 25 | //Debug.WriteLine($"Database Path: {dbFilePath}"); 26 | 27 | if (!File.Exists(dbFilePath)) 28 | { 29 | //Debug.WriteLine("Database file not found. Attempting to extract from resources."); 30 | ExtractEmbeddedResource("BeatOn.downloads.db", dbFilePath); 31 | //Debug.WriteLine("Successfully extracted database file."); 32 | } 33 | else 34 | { 35 | //Debug.WriteLine("Database file already exists."); 36 | } 37 | } 38 | catch (Exception ex) 39 | { 40 | // Log the exception 41 | //Debug.WriteLine($"Failed to copy database file: {ex.Message}"); 42 | MessageBox.Show($"Failed to initialize the database. Error: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 43 | Application.Current.Shutdown(); 44 | } 45 | } 46 | 47 | private void ExtractEmbeddedResource(string resourceName, string outputPath) 48 | { 49 | var assembly = Assembly.GetExecutingAssembly(); 50 | using (Stream stream = assembly.GetManifestResourceStream(resourceName)) 51 | { 52 | if (stream == null) 53 | { 54 | throw new FileNotFoundException("Resource not found.", resourceName); 55 | } 56 | 57 | using (FileStream fileStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write)) 58 | { 59 | stream.CopyTo(fileStream); 60 | } 61 | } 62 | } 63 | 64 | private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) 65 | { 66 | 67 | var exception = e.ExceptionObject as Exception; 68 | if (exception != null) 69 | { 70 | //Debug.WriteLine($"Unhandled exception: {exception.Message}"); 71 | } 72 | else 73 | { 74 | //Debug.WriteLine("Unhandled exception: Unknown error"); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /src/BeatOn.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | enable 7 | true 8 | true 9 | true 10 | BeatOn 11 | BeatOn_Logo.ico 12 | AnyCPU;x64 13 | true 14 | win-x64 15 | 16 | true 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | PreserveNewest 34 | 35 | 36 | PreserveNewest 37 | 38 | 39 | PreserveNewest 40 | 41 | 42 | PreserveNewest 43 | 44 | 45 | PreserveNewest 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | True 84 | True 85 | Resources.resx 86 | 87 | 88 | True 89 | True 90 | Settings.settings 91 | 92 | 93 | 94 | 95 | 96 | PublicResXFileCodeGenerator 97 | Resources.Designer.cs 98 | 99 | 100 | 101 | 102 | 103 | SettingsSingleFileGenerator 104 | Settings.Designer.cs 105 | 106 | 107 | PreserveNewest 108 | 109 | 110 | PreserveNewest 111 | 112 | 113 | PreserveNewest 114 | 115 | 116 | 117 | 118 | 119 | PreserveNewest 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /src/BeatOn.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | <_LastSelectedProfileId>E:\Programing Code Projects\C#\WPF\BeatOn\BeatOn\src\Properties\PublishProfiles\FolderProfile.pubxml 5 | 6 | 7 | 8 | Designer 9 | 10 | 11 | 12 | 13 | Code 14 | 15 | 16 | Code 17 | 18 | 19 | Code 20 | 21 | 22 | Code 23 | 24 | 25 | Code 26 | 27 | 28 | Code 29 | 30 | 31 | 32 | 33 | Designer 34 | 35 | 36 | Designer 37 | 38 | 39 | Designer 40 | 41 | 42 | Designer 43 | 44 | 45 | Designer 46 | 47 | 48 | Designer 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/BeatOn.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.10.35122.118 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BeatOn", "BeatOn.csproj", "{EE0C5594-C300-4EBC-B21F-C93C7D4C4824}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Release|Any CPU = Release|Any CPU 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {EE0C5594-C300-4EBC-B21F-C93C7D4C4824}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {EE0C5594-C300-4EBC-B21F-C93C7D4C4824}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {EE0C5594-C300-4EBC-B21F-C93C7D4C4824}.Debug|x64.ActiveCfg = Debug|x64 19 | {EE0C5594-C300-4EBC-B21F-C93C7D4C4824}.Debug|x64.Build.0 = Debug|x64 20 | {EE0C5594-C300-4EBC-B21F-C93C7D4C4824}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {EE0C5594-C300-4EBC-B21F-C93C7D4C4824}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {EE0C5594-C300-4EBC-B21F-C93C7D4C4824}.Release|x64.ActiveCfg = Release|x64 23 | {EE0C5594-C300-4EBC-B21F-C93C7D4C4824}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {18E9CB0E-8AAE-4C57-A4C1-25DE3DD1B192} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/BeatOn_Banner-removebg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/src/BeatOn_Banner-removebg.png -------------------------------------------------------------------------------- /src/BeatOn_Logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaylex32/BeatOn/b1183ea5c68e056e044da4b205908d54079afb1d/src/BeatOn_Logo.ico -------------------------------------------------------------------------------- /src/CountToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace WpfApp1 11 | { 12 | public class CountToVisibilityConverter : IValueConverter 13 | { 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | // Convert item count to visibility: 0 items results in Visible, more than 0 results in Collapsed. 17 | return (int)value > 0 ? Visibility.Collapsed : Visibility.Visible; 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | throw new NotImplementedException(); // Not needed for one-way bindings 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/DatabaseManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SQLite; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | 8 | public class DatabaseManager 9 | { 10 | private string connectionString; 11 | private List downloadItems; 12 | 13 | public DatabaseManager() 14 | { 15 | var dbFileName = "downloads.db"; 16 | var dbFilePath = Path.Combine(AppContext.BaseDirectory, dbFileName); 17 | connectionString = $"Data Source={dbFilePath};Version=3;"; 18 | downloadItems = new List(); 19 | InitializeDatabase(); 20 | } 21 | 22 | private void InitializeDatabase() 23 | { 24 | try 25 | { 26 | using (SQLiteConnection conn = new SQLiteConnection(connectionString)) 27 | { 28 | conn.Open(); 29 | 30 | string sql = @" 31 | CREATE TABLE IF NOT EXISTS Downloads ( 32 | Id INTEGER PRIMARY KEY AUTOINCREMENT, 33 | StartTime TEXT NOT NULL, 34 | EndTime TEXT, 35 | Url TEXT NOT NULL, 36 | Output TEXT, 37 | Status TEXT NOT NULL, 38 | ErrorMessage TEXT, 39 | Log TEXT 40 | )"; 41 | 42 | using (SQLiteCommand command = new SQLiteCommand(sql, conn)) 43 | { 44 | command.ExecuteNonQuery(); 45 | } 46 | 47 | //Debug.WriteLine("Database initialized successfully."); 48 | } 49 | } 50 | catch (Exception ex) 51 | { 52 | // Log the exception 53 | //Debug.WriteLine($"Database initialization failed: {ex.Message}"); 54 | } 55 | } 56 | 57 | public void SaveDownloadItem(DownloadItem downloadItem) 58 | { 59 | using (SQLiteConnection conn = new SQLiteConnection(connectionString)) 60 | { 61 | conn.Open(); 62 | 63 | string sql = @" 64 | INSERT INTO Downloads (StartTime, EndTime, Url, Output, Status, ErrorMessage) 65 | VALUES (@StartTime, @EndTime, @Url, @Output, @Status, @ErrorMessage)"; 66 | 67 | using (SQLiteCommand command = new SQLiteCommand(sql, conn)) 68 | { 69 | command.Parameters.AddWithValue("@StartTime", downloadItem.StartTime.ToString("yyyy-MM-dd HH:mm:ss")); 70 | command.Parameters.AddWithValue("@EndTime", downloadItem.EndTime?.ToString("yyyy-MM-dd HH:mm:ss")); 71 | command.Parameters.AddWithValue("@Url", downloadItem.Url); 72 | command.Parameters.AddWithValue("@Output", downloadItem.Output); 73 | command.Parameters.AddWithValue("@Status", downloadItem.Status); 74 | command.Parameters.AddWithValue("@ErrorMessage", downloadItem.ErrorMessage); 75 | 76 | command.ExecuteNonQuery(); 77 | } 78 | } 79 | } 80 | 81 | // Add this method to get a download item based on artist and album name 82 | public DownloadItem GetDownloadItem(string artistName, string albumName) 83 | { 84 | return downloadItems.FirstOrDefault(item => item.ArtistName == artistName && item.Name == albumName); 85 | } 86 | 87 | public void UpdateDownloadItem(DownloadItem downloadItem) 88 | { 89 | using (SQLiteConnection conn = new SQLiteConnection(connectionString)) 90 | { 91 | conn.Open(); 92 | 93 | string sql = @" 94 | UPDATE Downloads 95 | SET EndTime = @EndTime, Output = @Output, Status = @Status, ErrorMessage = @ErrorMessage 96 | WHERE Id = @Id"; 97 | 98 | using (SQLiteCommand command = new SQLiteCommand(sql, conn)) 99 | { 100 | command.Parameters.AddWithValue("@EndTime", downloadItem.EndTime?.ToString("yyyy-MM-dd HH:mm:ss")); 101 | command.Parameters.AddWithValue("@Output", downloadItem.Output); 102 | command.Parameters.AddWithValue("@Status", downloadItem.Status); 103 | command.Parameters.AddWithValue("@ErrorMessage", downloadItem.ErrorMessage); 104 | command.Parameters.AddWithValue("@Id", downloadItem.Id); 105 | 106 | command.ExecuteNonQuery(); 107 | } 108 | } 109 | } 110 | 111 | public DownloadItem GetDownloadItem(int id) 112 | { 113 | using (SQLiteConnection conn = new SQLiteConnection(connectionString)) 114 | { 115 | conn.Open(); 116 | 117 | string sql = "SELECT * FROM Downloads WHERE Id = @Id"; 118 | 119 | using (SQLiteCommand command = new SQLiteCommand(sql, conn)) 120 | { 121 | command.Parameters.AddWithValue("@Id", id); 122 | 123 | using (SQLiteDataReader reader = command.ExecuteReader()) 124 | { 125 | if (reader.Read()) 126 | { 127 | return new DownloadItem 128 | { 129 | Id = Convert.ToInt32(reader["Id"]), 130 | StartTime = Convert.ToDateTime(reader["StartTime"]), 131 | EndTime = reader["EndTime"] != DBNull.Value ? Convert.ToDateTime(reader["EndTime"]) : (DateTime?)null, 132 | Url = Convert.ToString(reader["Url"]), 133 | Output = Convert.ToString(reader["Output"]), 134 | Status = Convert.ToString(reader["Status"]), 135 | ErrorMessage = Convert.ToString(reader["ErrorMessage"]), 136 | Log = Convert.ToString(reader["Log"]) 137 | }; 138 | } 139 | } 140 | } 141 | } 142 | 143 | return null; // Return null if the download item with the specified id is not found 144 | } 145 | 146 | public void UpdateDownloadItemLog(DownloadItem downloadItem) 147 | { 148 | using (SQLiteConnection conn = new SQLiteConnection(connectionString)) 149 | { 150 | conn.Open(); 151 | 152 | string sql = @" 153 | UPDATE Downloads 154 | SET Log = @Log 155 | WHERE Id = @Id"; 156 | 157 | using (SQLiteCommand command = new SQLiteCommand(sql, conn)) 158 | { 159 | command.Parameters.AddWithValue("@Log", downloadItem.Log); 160 | command.Parameters.AddWithValue("@Id", downloadItem.Id); 161 | 162 | command.ExecuteNonQuery(); 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/DeezerArlScraper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Text.RegularExpressions; 5 | using System.Threading.Tasks; 6 | 7 | public class DeezerArlScraper 8 | { 9 | private const string Url = "https://rentry.org/firehawk52"; 10 | 11 | public async Task> GetAllArlsAsync() 12 | { 13 | using (HttpClient httpClient = new HttpClient()) 14 | { 15 | string content = await httpClient.GetStringAsync(Url); 16 | 17 | // Find the start of the Deezer ARLs section 18 | int startIndex = content.IndexOf("

Deezer ARLs"); 19 | if (startIndex == -1) return new List(); 20 | 21 | // Find the start of the table 22 | startIndex = content.IndexOf("", startIndex); 23 | if (startIndex == -1) return new List(); 24 | 25 | // Find the end of the table 26 | int endIndex = content.IndexOf("
", startIndex); 27 | if (endIndex == -1) return new List(); 28 | 29 | // Extract the table content 30 | string tableContent = content.Substring(startIndex, endIndex - startIndex); 31 | 32 | // Use regex to find all ARL codes 33 | var matches = Regex.Matches(tableContent, "(.*?)"); 34 | 35 | List arls = new List(); 36 | foreach (Match match in matches) 37 | { 38 | if (match.Groups.Count > 1) 39 | { 40 | arls.Add(match.Groups[1].Value); 41 | } 42 | } 43 | 44 | return arls; 45 | } 46 | } 47 | 48 | public async Task GetRandomArlAsync() 49 | { 50 | var arls = await GetAllArlsAsync(); 51 | if (arls.Count == 0) return null; 52 | 53 | Random random = new Random(); 54 | int index = random.Next(arls.Count); 55 | return arls[index]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/DownloadItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | 5 | public class DownloadItem : INotifyPropertyChanged 6 | { 7 | public int Id { get; set; } 8 | 9 | // Property to store the path of the individual track 10 | public string DownloadPath { get; set; } 11 | 12 | // Property to store the path of the album 13 | public string AlbumDownloadPath { get; set; } 14 | 15 | public string SourceType { get; set; } // Add this property 16 | public Process DownloadProcess { get; set; } 17 | private string name; 18 | public string ArtistName { get; set; } 19 | private int progress; 20 | private string output; 21 | private string log; 22 | public bool IsDetailsVisible { get; set; } = false; 23 | public string Log 24 | { 25 | get { return log; } 26 | set 27 | { 28 | if (log != value) 29 | { 30 | log = value; 31 | OnPropertyChanged(nameof(Log)); 32 | } 33 | } 34 | } 35 | private DateTime startTime; 36 | private DateTime? endTime; 37 | private string url; 38 | private string status; 39 | private string errorMessage; 40 | 41 | private string totalProgress; 42 | public string TotalProgress 43 | { 44 | get { return totalProgress; } 45 | set 46 | { 47 | if (totalProgress != value) 48 | { 49 | totalProgress = value; 50 | OnPropertyChanged(nameof(TotalProgress)); 51 | } 52 | } 53 | } 54 | 55 | private double overallProgress; 56 | public double OverallProgress 57 | { 58 | get { return overallProgress; } 59 | set 60 | { 61 | if (overallProgress != value) 62 | { 63 | overallProgress = value; 64 | OnPropertyChanged(nameof(OverallProgress)); 65 | } 66 | } 67 | } 68 | 69 | private double downloadProgress; 70 | public double DownloadProgress 71 | { 72 | get { return downloadProgress; } 73 | set 74 | { 75 | if (downloadProgress != value) 76 | { 77 | downloadProgress = value; 78 | OnPropertyChanged(nameof(DownloadProgress)); 79 | OnPropertyChanged(nameof(IsCompleted)); // Raise PropertyChanged event for IsCompleted when DownloadProgress changes 80 | } 81 | } 82 | } 83 | 84 | private string _rawOutput; 85 | 86 | public string RawOutput 87 | { 88 | get { return _rawOutput; } 89 | set 90 | { 91 | _rawOutput = value; 92 | OnPropertyChanged(nameof(RawOutput)); 93 | } 94 | } 95 | 96 | private bool isCompleted; 97 | public bool IsCompleted 98 | { 99 | get { return isCompleted; } 100 | set 101 | { 102 | if (isCompleted != value) 103 | { 104 | isCompleted = value; 105 | OnPropertyChanged(nameof(IsCompleted)); 106 | OnPropertyChanged(nameof(DownloadProgress)); // Raise PropertyChanged event for DownloadProgress when IsCompleted changes 107 | } 108 | } 109 | } 110 | 111 | public string Name 112 | { 113 | get { return name; } 114 | set 115 | { 116 | if (name != value) 117 | { 118 | name = value; 119 | OnPropertyChanged(nameof(Name)); 120 | } 121 | } 122 | } 123 | 124 | public int Progress 125 | { 126 | get { return progress; } 127 | set 128 | { 129 | if (progress != value) 130 | { 131 | progress = value; 132 | OnPropertyChanged(nameof(Progress)); 133 | } 134 | } 135 | } 136 | 137 | public string Output 138 | { 139 | get { return output; } 140 | set 141 | { 142 | if (output != value) 143 | { 144 | output = value; 145 | OnPropertyChanged(nameof(Output)); 146 | } 147 | } 148 | } 149 | 150 | public DateTime StartTime 151 | { 152 | get { return startTime; } 153 | set 154 | { 155 | if (startTime != value) 156 | { 157 | startTime = value; 158 | OnPropertyChanged(nameof(StartTime)); 159 | } 160 | } 161 | } 162 | 163 | public DateTime? EndTime 164 | { 165 | get { return endTime; } 166 | set 167 | { 168 | if (endTime != value) 169 | { 170 | endTime = value; 171 | OnPropertyChanged(nameof(EndTime)); 172 | } 173 | } 174 | } 175 | 176 | public string Url 177 | { 178 | get { return url; } 179 | set 180 | { 181 | if (url != value) 182 | { 183 | url = value; 184 | OnPropertyChanged(nameof(Url)); 185 | } 186 | } 187 | } 188 | 189 | public string Status 190 | { 191 | get { return status; } 192 | set 193 | { 194 | if (status != value) 195 | { 196 | status = value; 197 | OnPropertyChanged(nameof(Status)); 198 | } 199 | } 200 | } 201 | 202 | public string ErrorMessage 203 | { 204 | get { return errorMessage; } 205 | set 206 | { 207 | if (errorMessage != value) 208 | { 209 | errorMessage = value; 210 | OnPropertyChanged(nameof(ErrorMessage)); 211 | } 212 | } 213 | } 214 | 215 | public event PropertyChangedEventHandler PropertyChanged; 216 | 217 | protected virtual void OnPropertyChanged(string propertyName) 218 | { 219 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 220 | } 221 | } -------------------------------------------------------------------------------- /src/DownloadManagerControl.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 18 | 19 | 20 | 51 | 52 | 88 | 89 | 90 | 137 | 138 | 178 | 179 | 212 | 213 | 226 | 227 | 228 | 238 | 239 | 240 | 253 | 254 | 255 | 266 | 267 | 268 | 278 | 279 | 280 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 304 | 305 | 312 | 319 | 326 | 333 | 340 | 344 | 345 | 346 | 347 | 71 | 78 | 79 | 80 | 81 |
82 |
83 |
84 |
85 |
86 |
Download Statistics
87 |

88 |

89 |
90 |
91 |
92 |
93 |
94 |
95 |
System Usage
96 |

97 |

98 |
99 |
100 |
101 |
102 | 103 |
104 |
105 |
106 |
107 |
Download Trend
108 | 109 |
110 |
111 |
112 |
113 |
114 |
115 |
Storage Information
116 |

117 |

118 |
119 |
120 |
121 |
122 | 123 |
124 |
125 |
126 |
127 |
Active Downloads
128 |
129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 |
ArtistAlbumStatus
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
Failed Downloads
147 |
148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 |
ArtistAlbumTimeError
159 |
160 |
161 |
162 |
163 |
164 | 165 |
166 |
167 |
168 |
169 |
Recent Downloads
170 |
171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 |
ArtistAlbumTimeStatus
182 |
183 |
184 |
185 |
186 |
187 |
188 | 189 | 349 | 350 | 351 | -------------------------------------------------------------------------------- /src/wwwroot/script.js: -------------------------------------------------------------------------------- 1 | function updateStatus() { 2 | fetch('/api/status') 3 | .then(response => response.json()) 4 | .then(data => { 5 | document.getElementById('status').textContent = `Status: ${data.Status}`; 6 | document.getElementById('downloadCount').textContent = `Total Downloads: ${data.DownloadCount}`; 7 | 8 | const recentDownloads = document.getElementById('recentDownloads'); 9 | recentDownloads.innerHTML = ''; 10 | data.RecentDownloads.forEach(download => { 11 | const li = document.createElement('li'); 12 | li.textContent = `${download.ArtistName} - ${download.AlbumName}`; 13 | recentDownloads.appendChild(li); 14 | }); 15 | }) 16 | .catch(error => console.error('Error:', error)); 17 | } 18 | 19 | // Update status every 5 seconds 20 | setInterval(updateStatus, 5000); 21 | 22 | // Initial update 23 | updateStatus(); -------------------------------------------------------------------------------- /src/wwwroot/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | line-height: 1.6; 4 | margin: 0; 5 | padding: 0; 6 | background-color: #f4f4f4; 7 | } 8 | 9 | .container { 10 | width: 80%; 11 | margin: auto; 12 | overflow: hidden; 13 | padding: 20px; 14 | background-color: white; 15 | box-shadow: 0 0 10px rgba(0,0,0,0.1); 16 | margin-top: 20px; 17 | } 18 | 19 | h1, h2 { 20 | color: #333; 21 | } 22 | 23 | #status { 24 | font-weight: bold; 25 | margin-bottom: 10px; 26 | } 27 | 28 | #downloadCount { 29 | margin-bottom: 20px; 30 | } 31 | 32 | ul { 33 | list-style-type: none; 34 | padding: 0; 35 | } 36 | 37 | li { 38 | background: #f4f4f4; 39 | margin-bottom: 5px; 40 | padding: 10px; 41 | border-radius: 5px; 42 | } --------------------------------------------------------------------------------