|
| 1 | +using BardMusicPlayer.Pigeonhole; |
| 2 | +using BardMusicPlayer.Resources; |
| 3 | +using HtmlAgilityPack; |
| 4 | +using System.IO; |
| 5 | +using System.Net.Http; |
| 6 | +using System.Reflection; |
| 7 | +using System.Windows; |
| 8 | +using System.Windows.Controls; |
| 9 | +using System.Windows.Input; |
| 10 | + |
| 11 | +namespace BardMusicPlayer.Controls; |
| 12 | + |
| 13 | +/// <summary> |
| 14 | +/// Web scraper to scrape the song list from https://songs.bardmusicplayer.com and populate in the listview |
| 15 | +/// </summary> |
| 16 | +public partial class MidiRepository : UserControl |
| 17 | +{ |
| 18 | + private const string songNodeXpath = "//div[contains(@class, 'midi-entry')]"; |
| 19 | + private const string titleNodeXpath = ".//a[contains(@class, 'mtitle')]"; |
| 20 | + private const string authorNodeXpath = ".//span[contains(@class, 'mauthor')]"; |
| 21 | + private const string commentNodeXpath = ".//span[contains(@class, 'r4')]"; |
| 22 | + private readonly HttpClient httpClient; |
| 23 | + private readonly string midiRepoUrl = "https://songs.bardmusicplayer.com"; |
| 24 | + private List<Song> listSong = new List<Song>(); |
| 25 | + public MidiRepository() |
| 26 | + { |
| 27 | + InitializeComponent(); |
| 28 | + httpClient = new HttpClient(); |
| 29 | + LoadingProgressBar.Visibility = Visibility.Hidden; |
| 30 | + DownloadPanel.Visibility = Visibility.Hidden; |
| 31 | + DownloadPath.Text = BmpPigeonhole.Instance.MidiDownloadPath; |
| 32 | + DownloadProgressLabel.Visibility = Visibility.Hidden; |
| 33 | + DownloadProgressBar.Visibility = Visibility.Hidden; |
| 34 | + } |
| 35 | + private class Song |
| 36 | + { |
| 37 | + public string Title { get; set; } = ""; |
| 38 | + public string Author { get; set; } = ""; |
| 39 | + public string Comment { get; set; } = ""; |
| 40 | + public string Url { get; set; } = ""; |
| 41 | + } |
| 42 | + |
| 43 | + /// <summary> |
| 44 | + /// Fetch the html from https://songs.bardmusicplayer.com |
| 45 | + /// </summary> |
| 46 | + /// <returns></returns> |
| 47 | + private async Task<string> FetchSongData() |
| 48 | + { |
| 49 | + var response = await httpClient.GetStringAsync(midiRepoUrl); |
| 50 | + return response; |
| 51 | + } |
| 52 | + |
| 53 | + /// <summary> |
| 54 | + /// Get midi meta data from html result using web scraper |
| 55 | + /// </summary> |
| 56 | + /// <param name="html"></param> |
| 57 | + private void RefreshSongList(string html) |
| 58 | + { |
| 59 | + listSong.Clear(); |
| 60 | + HtmlDocument htmlDoc = new HtmlDocument(); |
| 61 | + htmlDoc.LoadHtml(html); |
| 62 | + |
| 63 | + var songNodes = htmlDoc.DocumentNode.SelectNodes(songNodeXpath); |
| 64 | + |
| 65 | + foreach (var songNode in songNodes) |
| 66 | + { |
| 67 | + var titleNode = songNode.SelectSingleNode(titleNodeXpath); |
| 68 | + var authorNode = songNode.SelectSingleNode(authorNodeXpath); |
| 69 | + var commentNode = songNode.SelectSingleNode(commentNodeXpath); |
| 70 | + |
| 71 | + if (titleNode != null && authorNode != null && commentNode != null) |
| 72 | + { |
| 73 | + listSong.Add(new Song |
| 74 | + { |
| 75 | + Title = titleNode.GetAttributeValue("title", ""), |
| 76 | + Author = authorNode.InnerText, |
| 77 | + Comment = commentNode.InnerText, |
| 78 | + Url = titleNode.GetAttributeValue("href", ""), |
| 79 | + }); |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + /// <summary> |
| 85 | + /// Click button to scrape the web and put the result to listSong |
| 86 | + /// </summary> |
| 87 | + /// <param name="sender"></param> |
| 88 | + /// <param name="e"></param> |
| 89 | + private async void Button_Click(object sender, RoutedEventArgs e) |
| 90 | + { |
| 91 | + BtnGetSongList.IsEnabled = false; |
| 92 | + LoadingProgressBar.Visibility = Visibility.Visible; |
| 93 | + |
| 94 | + var songData = await FetchSongData(); |
| 95 | + |
| 96 | + RefreshSongList(songData); |
| 97 | + MidiRepoContainer.ItemsSource = listSong.Select(song => song.Title).ToList(); |
| 98 | + |
| 99 | + BtnGetSongList.IsEnabled = true; |
| 100 | + BtnGetSongList.Content = "Refresh"; |
| 101 | + LoadingProgressBar.Visibility = Visibility.Hidden; |
| 102 | + } |
| 103 | + |
| 104 | + /// <summary> |
| 105 | + /// Show midi details when clicking the listview |
| 106 | + /// </summary> |
| 107 | + /// <param name="sender"></param> |
| 108 | + /// <param name="e"></param> |
| 109 | + private void MidiRepoContainer_SelectionChanged(object sender, SelectionChangedEventArgs e) |
| 110 | + { |
| 111 | + DownloadPanel.Visibility = Visibility.Visible; |
| 112 | + Song song = listSong[MidiRepoContainer.SelectedIndex]; |
| 113 | + SongTitle.Text = $"({song.Author}) {song.Title}"; |
| 114 | + SongComment.Text = song.Comment; |
| 115 | + } |
| 116 | + |
| 117 | + /// <summary> |
| 118 | + /// Select download path on click button |
| 119 | + /// </summary> |
| 120 | + /// <param name="sender"></param> |
| 121 | + /// <param name="e"></param> |
| 122 | + private void SelectPath_Button_Click(object sender, RoutedEventArgs e) |
| 123 | + { |
| 124 | + var dlg = new FolderPicker |
| 125 | + { |
| 126 | + InputPath = Directory.Exists(BmpPigeonhole.Instance.MidiDownloadPath) ? Path.GetFullPath(BmpPigeonhole.Instance.MidiDownloadPath) : Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) |
| 127 | + }; |
| 128 | + |
| 129 | + if (dlg.ShowDialog() == true) |
| 130 | + { |
| 131 | + var path = dlg.ResultPath; |
| 132 | + if (!Directory.Exists(path)) |
| 133 | + return; |
| 134 | + |
| 135 | + path += path.EndsWith("\\") ? "" : "\\"; |
| 136 | + DownloadPath.Text = path; |
| 137 | + BmpPigeonhole.Instance.MidiDownloadPath = path; |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + /// <summary> |
| 142 | + /// Start download process |
| 143 | + /// </summary> |
| 144 | + /// <param name="url"></param> |
| 145 | + /// <param name="fileName"></param> |
| 146 | + private async void DownloadFile(string url, string fileName) |
| 147 | + { |
| 148 | + HttpClient client = new HttpClient(); |
| 149 | + HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); |
| 150 | + long? contentLength = response.Content.Headers.ContentLength; |
| 151 | + string tempFilePath = Path.GetTempFileName(); |
| 152 | + |
| 153 | + using (FileStream tempFileStream = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write, FileShare.None)) |
| 154 | + { |
| 155 | + using (Stream contentStream = await response.Content.ReadAsStreamAsync()) |
| 156 | + { |
| 157 | + byte[] buffer = new byte[4096]; |
| 158 | + long totalBytesRead = 0; |
| 159 | + int bytesRead = 0; |
| 160 | + while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0) |
| 161 | + { |
| 162 | + await tempFileStream.WriteAsync(buffer, 0, bytesRead); |
| 163 | + totalBytesRead += bytesRead; |
| 164 | + double percentComplete = (double)totalBytesRead / (contentLength ?? totalBytesRead) * 100; |
| 165 | + Dispatcher.Invoke(() => DownloadProgressBar.Value = percentComplete); |
| 166 | + } |
| 167 | + } |
| 168 | + } |
| 169 | + string downloadsPath = BmpPigeonhole.Instance.MidiDownloadPath; |
| 170 | + string finalFilePath = $"{downloadsPath}/{fileName}.mid"; |
| 171 | + |
| 172 | + File.Move(tempFilePath, finalFilePath, true); |
| 173 | + DownloadButton.IsEnabled = true; |
| 174 | + DownloadProgressLabel.Visibility = Visibility.Visible; |
| 175 | + } |
| 176 | + |
| 177 | + /// <summary> |
| 178 | + /// Download selected midi in the listview by clicking download button |
| 179 | + /// </summary> |
| 180 | + /// <param name="sender"></param> |
| 181 | + /// <param name="e"></param> |
| 182 | + private void DownloadButtonClick(object sender, RoutedEventArgs e) |
| 183 | + { |
| 184 | + DownloadSelectedMidi(); |
| 185 | + } |
| 186 | + |
| 187 | + /// <summary> |
| 188 | + /// Download selected midi by double click the list item |
| 189 | + /// </summary> |
| 190 | + /// <param name="sender"></param> |
| 191 | + /// <param name="e"></param> |
| 192 | + private void MidiRepoContainer_MouseDoubleClick(object sender, MouseButtonEventArgs e) |
| 193 | + { |
| 194 | + DownloadSelectedMidi(); |
| 195 | + } |
| 196 | + |
| 197 | + /// <summary> |
| 198 | + /// Download current selected midi in the listview |
| 199 | + /// </summary> |
| 200 | + private void DownloadSelectedMidi() |
| 201 | + { |
| 202 | + if (!Directory.Exists(BmpPigeonhole.Instance.MidiDownloadPath)) |
| 203 | + { |
| 204 | + MessageBox.Show("The downloads directory is not valid.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); |
| 205 | + return; |
| 206 | + } |
| 207 | + Song selectedSong = listSong[MidiRepoContainer.SelectedIndex]; |
| 208 | + DownloadButton.IsEnabled = false; |
| 209 | + DownloadProgressBar.Visibility = Visibility.Visible; |
| 210 | + DownloadProgressBar.Value = 0; |
| 211 | + DownloadFile($"{midiRepoUrl}/{selectedSong.Url}", $"({selectedSong.Author}) {selectedSong.Title}"); |
| 212 | + } |
| 213 | +} |
0 commit comments