public static void LogFull(string message, DebugStack ds) { if (ds != null) { ds.LogFull(message); } else { LogFull(message); } }
public static void LogWarning(string message, DebugStack ds, bool showMsgBox = false) { if (ds != null) { ds.LogWarning(message, showMsgBox); } else { LogWarning(message, showMsgBox); } }
public static void LogLine(DebugStack ds) { if (ds != null) { ds.LogLine(); } else { LogLine(); } }
// --- Main Process Method --- private static TagWriterReturnCode WriteTagsForRow(Entry entry, int processed, DebugStack ds) { TagLib.File file; ds.Log("#" + processed + " - " + entry.fileName); // Check if the entry is mapped to a file if (entry.isMapped == false) { string message = "Entry is not matched to file"; ds.LogWarning(" -> " + message); entryErrors.TryAdd(entry, message); return(TagWriterReturnCode.Unmapped); } bool isRetry = false; Retry: // Create TagLib file representation, catch to see if the file is valid try { file = TagLib.File.Create(entry.mappedFilePath, isRetry ? ReadStyle.Average : ReadStyle.PictureLazy); } catch (Exception exception) { var type = exception.GetType(); string errorMessage = ""; TagWriterReturnCode returnCode; if (type == typeof(UnsupportedFormatException)) { errorMessage = "File format with extension \"" + Path.GetExtension(entry.mappedFilePath) + "\" is unsupported (" + exception.Message + ")"; returnCode = TagWriterReturnCode.UnsupportedFormat; } else if (type == typeof(CorruptFileException)) { errorMessage = "File appears corrupt! " + exception.Message; returnCode = TagWriterReturnCode.CorruptFile; } else if (type == typeof(IOException)) { errorMessage = "An IO exception occured while loading the file! " + exception.Message; // Get the locking processes (if any) var lockingProcesses = FileLockUtility.GetProcessesLockingFile(entry.mappedFilePath); // Log locking processes if (lockingProcesses != null && lockingProcesses.Count > 0) { Debug.LogError("\tThe file appears to be locked by the process(es): " + string.Join(", ", lockingProcesses.Select(p => p.ProcessName).ToArray())); } returnCode = TagWriterReturnCode.IOEError; } else { errorMessage = "An error occured while loading the file! " + exception.Message; returnCode = TagWriterReturnCode.OtherError; } ds.LogError(" -> " + errorMessage); entryErrors.TryAdd(entry, errorMessage); return(returnCode); } // Before doing anything, remove any tags that aren't on the disk. TagTypes tagLibCreatedTags = file.TagTypes ^ file.TagTypesOnDisk; file.RemoveTags(tagLibCreatedTags); // Then create a new tag type if needed TagLibUtility.CreateTagIfRequired(file, out TagTypes usedTagTypes); // Remove the ID3v1 tag if set in the settings at this point if (Settings.Current.removeID3v1 && file.TagTypes.HasFlag(TagTypes.Id3v1)) { file.RemoveTags(TagTypes.Id3v1); } // --- RESUME COPYING OVER FROM HERE --- // If we're in remove move if (args.removeTags) { if (!args.skipDateAdded) { TagLibUtility.RemoveCustomTag(file, Consts.TAG_DATE_ADDED, ds); } if (!args.skipLastPlayed) { TagLibUtility.RemoveCustomTag(file, Consts.TAG_LAST_PLAYED, ds); } if (!args.skipPlayCount) { TagLibUtility.RemoveCustomTag(file, Consts.TAG_PLAY_COUNT, ds); } if (!args.skipRating) { TagLibUtility.RemoveRating(file, ds); } entry.wroteTags = false; } // If we're in writing mode else { // Date Added if (!args.skipDateAdded && entry.dateAdded != DateTime.MinValue) { // Create LDAP/Windows File Time long long dateAddedWinTime = entry.dateAdded.ToFileTime(); if (Settings.Current.fullLogging) { ds.Log(string.Format("\tWriting {0}: {1}\n\t-> Converted to {3} (or {2})", Consts.PLIST_KEY_DATE_ADDED, entry.dateAdded.ToString("o", System.Globalization.CultureInfo.InvariantCulture), entry.dateAdded.ToString(), dateAddedWinTime)); } // Write the tag TagLibUtility.WriteCustomTag(file, Consts.TAG_DATE_ADDED, dateAddedWinTime.ToString(), ds); } // Last Played if (!args.skipLastPlayed && entry.lastPlayed != DateTime.MinValue) { // Create LDAP/Windows File Time long long lastPlayedWinTime = entry.lastPlayed.ToFileTime(); if (Settings.Current.fullLogging) { ds.Log(string.Format("\tWriting {0}: {1}\n\t-> Converted to {3} (or {2})", Consts.PLIST_KEY_LAST_PLAYED, entry.lastPlayed.ToString("o", System.Globalization.CultureInfo.InvariantCulture), entry.lastPlayed.ToString(), lastPlayedWinTime)); } // Write the tag TagLibUtility.WriteCustomTag(file, Consts.TAG_LAST_PLAYED, lastPlayedWinTime.ToString(), ds); } // Play Count if (!args.skipPlayCount && entry.playCount > 0) { if (Settings.Current.fullLogging) { ds.Log(string.Format("\tWriting {0}: {1}", Consts.PLIST_KEY_PLAY_COUNT, entry.playCount)); } // Write the tag TagLibUtility.WriteCustomTag(file, Consts.TAG_PLAY_COUNT, entry.playCount.ToString(), ds); } // Rating if (!args.skipRating && entry.rating != Rating.Unrated) { int ratingInStars = (int)entry.rating; if (Settings.Current.fullLogging) { ds.Log(string.Format("\tWriting {0}: {1} ({2} stars)", Consts.PLIST_KEY_RATING, ratingInStars * 20, ratingInStars)); } // Write the tag TagLibUtility.WriteRating(file, ratingInStars, ds); } // Process WAV files (after writing stats, since an ID3v2 tag should exist then) if (Settings.Current.writeInfoToWavFiles && file.MimeType == "taglib/wav") { // This will compare the file with the entry, and will write any missing info that is present in the entry - but not present in the file. Limited to the following tags: Name, Artist, Album Artist, Album, Genre, Comments, Year, Disc Number, Disc Count, Track Number, Track Count ds.Log("\tWriting WAV info:"); // Create an Id3v2 tag if it doesn't already exist. var id3v2tag = (TagLib.Id3v2.Tag)file.GetTag(TagTypes.Id3v2, true); // Create a RIFF INFO tag if it doesn't already exist // foobar2000 seems to require a RIFF INFO chunk with at least one tag to detect the ID3v2 tags var riffInfo = (TagLib.Riff.InfoTag)file.GetTag(TagTypes.RiffInfo, true); // Write track title if (string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.ID3v2_FRAME_TITLE)) && string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.RIFF_ID_TITLE)) && !string.IsNullOrEmpty(entry.trackTitle)) { TagLibUtility.WriteTextTag(id3v2tag, Consts.ID3v2_FRAME_TITLE, entry.trackTitle, ds); //TagLibUtility.WriteTextTag(riffInfo, Consts.RIFF_ID_TITLE, entry.trackTitle, ds); } // Write artist if (string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.ID3v2_FRAME_ARTIST)) && string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.RIFF_ID_ARTIST)) && !string.IsNullOrEmpty(entry.artist)) { TagLibUtility.WriteTextTag(id3v2tag, Consts.ID3v2_FRAME_ARTIST, entry.artist, ds); //TagLibUtility.WriteTextTag(riffInfo, Consts.RIFF_ID_ARTIST, entry.artist, ds); } // Write album artist (ID3v2 only) if (string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.ID3v2_FRAME_ALBUM_ARTIST)) && string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.RIFF_ID_ALBUM)) && !string.IsNullOrEmpty(entry.albumArtist)) { TagLibUtility.WriteTextTag(id3v2tag, Consts.ID3v2_FRAME_ALBUM_ARTIST, entry.albumArtist, ds); } // Write album if (string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.ID3v2_FRAME_ALBUM)) && string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.RIFF_ID_ALBUM)) && !string.IsNullOrEmpty(entry.album)) { TagLibUtility.WriteTextTag(id3v2tag, Consts.ID3v2_FRAME_ALBUM, entry.album); //TagLibUtility.WriteTextTag(riffInfo, Consts.RIFF_ID_ALBUM, entry.album); } // Write genre if (string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.ID3v2_FRAME_GENRE)) && string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.RIFF_ID_GENRE)) && !string.IsNullOrEmpty(entry.genre)) { TagLibUtility.WriteTextTag(id3v2tag, Consts.ID3v2_FRAME_GENRE, entry.genre, ds); //TagLibUtility.WriteTextTag(riffInfo, Consts.RIFF_ID_GENRE, entry.genre, ds); } // Write year if (string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.ID3v2_FRAME_DATE)) && string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.RIFF_ID_YEAR)) && !string.IsNullOrEmpty(entry.year)) { TagLibUtility.WriteTextTag(id3v2tag, Consts.ID3v2_FRAME_DATE, entry.year, ds); //TagLibUtility.WriteTextTag(riffInfo, Consts.RIFF_ID_YEAR, entry.year, ds); } // Write track number if (string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.ID3v2_FRAME_TRACK_NUMBER)) && string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.RIFF_ID_TRACK_NUMBER)) && entry.trackNumber != null) { TagLibUtility.WriteTextTag(id3v2tag, Consts.ID3v2_FRAME_TRACK_NUMBER, entry.trackNumberDisplay, ds); //TagLibUtility.WriteTextTag(riffInfo, Consts.RIFF_ID_TRACK_NUMBER, entry.trackNumberDisplay, ds); } // Write disc number (ID3v2 only) if (string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.ID3v2_FRAME_DISC_NUMBER)) && entry.discNumber != null) { TagLibUtility.WriteTextTag(id3v2tag, Consts.ID3v2_FRAME_DISC_NUMBER, entry.discNumberDisplay, ds); } // Write comments if (string.IsNullOrEmpty(TagLibUtility.GetCommentsTag(file)) && string.IsNullOrEmpty(TagLibUtility.GetTextTag(file, Consts.RIFF_ID_COMMENTS)) && !string.IsNullOrEmpty(entry.comments)) { TagLibUtility.WriteCommentsTag(file, entry.comments, ds); //TagLibUtility.WriteTextTag(riffInfo, Consts.RIFF_ID_COMMENTS, entry.comments, ds); } } entry.wroteTags = true; } // Write the modified tags to the file try { if (Settings.Current.fullLogging) { TagTypes newTags = file.TagTypesOnDisk ^ file.TagTypes; ds.Log("\tTags for writing:\t" + file.TagTypes.ToString()); ds.Log("\t New tags:\t\t" + newTags.ToString()); ds.Log("\t Existing tags:\t" + file.TagTypesOnDisk.ToString()); } if (!Settings.Current.dryRun) { file.Save(); } // If we're here, it means the TagLib.File was saved correctly if (isRetry) { ds.LogSuccess("\tReattempt succeeded"); } } catch (IOException ioex) { // If this was a retry... if (isRetry) { string message = "Could not save file after retry. An exception occurred: " + ioex.Message; ds.LogError("\t" + message); entryErrors.TryAdd(entry, message); return(TagWriterReturnCode.IOEError); } else { ds.LogError("\t-> An IO exception occured while saving tags to the file \"" + entry.fileName + "\""); // Get the locking processes (if any) var lockingProcesses = FileLockUtility.GetProcessesLockingFile(entry.mappedFilePath); // Print the process(es) that are locking the file if (lockingProcesses != null && lockingProcesses.Count > 0) { string message = "The file appears to be locked by the process: " + string.Join(", ", lockingProcesses.Select(p => p.ProcessName).ToArray()); ds.LogError("\t" + message); entryErrors.TryAdd(entry, message); return(TagWriterReturnCode.IOEError); } // Otherwise if there is no locking process, this is a known issue in TagLib while using ReadStyle.PictureLazy. // Reattempt, using the default ReadStyle.Average else { ds.LogError("\t-> Reattempting load with different parameters..."); isRetry = true; goto Retry; } } } catch (Exception ex) { string message = "An error occurred while saving tags to \"" + entry.fileName + "\": (" + ex.Message + ")"; ds.LogError("\t" + message); entryErrors.TryAdd(entry, message); return(TagWriterReturnCode.OtherError); } file.Dispose(); return(TagWriterReturnCode.Success); }
private static void worker_DoWork(object sender, DoWorkEventArgs e) { args = (TagWriterWorkerArgs)e.Argument; // Assign args to static variable. This is mainly used for cancellation/reverting, to understand what files were targeted in the operation lastArgs = args; int processed = 0; int count = args.rows.Count(); int rowIndex = 0; // Misc int success = 0; // <- File was successfully written to int unmapped = 0; // <- Entry didn't have a file mapped to it int unsupportedFormat = 0; // <- File was of an unsupported format (UnsupportedFormatException) int corruptFile = 0; // <- File was corrupted (CorruptFileException) int ioError = 0; // <- Error occured while loading the file (IOException) int otherError = 0; // <- Another exception occurred Debug.Log(string.Format("\nWriting tags for {0} files, using the following parameters:\nfilter:\t\t{1}\nskipDateAdded:\t{3}\nskipLastPlayed:\t{4}\nskipPlayCount:\t{5}\nskipRating:\t{6}\nremoveTags:\t{7}\n\nUse Ctrl-C to terminate", count, args.filter, args.filterNot, args.skipDateAdded, args.skipLastPlayed, args.skipPlayCount, args.skipRating, args.removeTags)); // Exit here if we have nothing to process if (count == 0) { Debug.LogError("No entries to process!", true); return; } // Wait 3 seconds before starting if (Settings.Current.workerDelayStart) { for (int i = 0; i < 3; i++) { Console.Write("."); System.Threading.Thread.Sleep(1000); } } // A dictionary of entries and errors that have occured entryErrors = new ConcurrentDictionary <Entry, string>(); // Progress report counter, gets set to the sw.ElapsedMilliseconds at every report progress interval long progressReportCounter = 0; // Start a new stopwatch var sw = System.Diagnostics.Stopwatch.StartNew(); // Set the TaskbarProgressState TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Normal); Console.Write("\n\n"); void ReportProgress() { // Report progress to main thread to update DataGridView selection worker.ReportProgress(0, new ProgressArgs() { processed = processed, count = count, timeMs = sw.ElapsedMilliseconds, currentRowIndex = rowIndex, }); } int lowestBreakIndex = 0; // Set up the ParallelOptions var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = Settings.Current.maxParallelThreads }; Resume: // Split the work into multiple parallel threads ParallelLoopResult parallelLoopResult = Parallel.ForEach(args.rows.Skip(lowestBreakIndex), parallelOptions, (row, parallelLoopState) => { // Firstly, do some processing stuffs that can only be done in the worker // Check if we should pause if (isPaused) { // Breaking allows current parallel iterations to complete before stopping parallelLoopState.Break(); return; } // Check if we should cancel if (worker.CancellationPending) { e.Cancel = true; parallelLoopState.Stop(); return; } // Check if we should report progress // Only report progress every <progressReportInterval> milliseconds if (Settings.Current.workerReportsProgress && sw.ElapsedMilliseconds - progressReportCounter > Settings.Current.workerReportProgressInterval) { rowIndex = row.Index; progressReportCounter = sw.ElapsedMilliseconds; ReportProgress(); } // Fetch the DataBoundItem from the DataGridViewRow var entry = (Entry)row.DataBoundItem; // Check if the entry hasn't already been processed in this session // This may be the case if we paused previously if (!entry.processed) { // Create a new DebugStack // We're unable to log from a parallel for, as the logs would be out of sync - and wouldn't be clustered together // So instead of logging normally, logging within a Parallel is done to a DebugStack, which essentially just collects the logs so that we can fire them all at once // This is done here instead of in WriteTagsForRow, as that method has many exit points, therefore we would have to return a debugstack object from it anyway var ds = new DebugStack(); // Write the tags for this Entry var result = WriteTagsForRow(entry, ++processed, ds); // Increment the error/success counter switch (result) { case TagWriterReturnCode.Success: success++; break; case TagWriterReturnCode.Unmapped: unmapped++; break; case TagWriterReturnCode.UnsupportedFormat: unsupportedFormat++; break; case TagWriterReturnCode.CorruptFile: corruptFile++; break; case TagWriterReturnCode.IOEError: ioError++; break; case TagWriterReturnCode.OtherError: otherError++; break; } // Set the processed flag on this entry to true entry.processed = true; // Log the DebugStack ds.Push(); } }); // If the Parallel.ForEach was paused, isPaused will be set if (isPaused) { // Save the index of the lowest last item processed lowestBreakIndex = (int)parallelLoopResult.LowestBreakIteration; Debug.Log("--- PAUSED ---"); sw.Stop(); // Set the taskbar ProgressState to Paused TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Paused); // Halt execution until unpaused (Reset) manualResetEvent.WaitOne(); // If we're here, the operation was unpaused // Handle cancelling while paused if (worker.CancellationPending || e.Cancel) { e.Cancel = true; return; } // Otherwise resume else { Debug.Log("--- RESUMED ---"); sw.Start(); TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Normal); // Jump back up to Resume goto Resume; } } // Return if we cancelled if (e.Cancel) { return; } sw.Stop(); // Do final ReportProgress rowIndex = args.rows.Last().Index; ReportProgress(); string resultStr = string.Format("Took {0}ms ({1})\n {2} files were written to successfully\n {3} files could not be written to\n {4} unsupported format\n {5} corrupt\n {6} IO errors\n {7} unmapped\n {8} other errors occurred", sw.ElapsedMilliseconds, TimeSpan.FromMilliseconds(sw.ElapsedMilliseconds).ToString("m\\:ss"), success, unsupportedFormat + corruptFile + ioError + unmapped + otherError, unsupportedFormat, corruptFile, ioError, unmapped, otherError); Debug.Log("Done! " + resultStr); mainForm.Invoke(() => mainForm.Flash(false)); System.Media.SystemSounds.Exclamation.Play(); MessageBox.Show(resultStr, "Done!", MessageBoxButtons.OK, MessageBoxIcon.Information); if (args.removeTags) { Debug.Log("\n" + Consts.REMOVE_TAGS_FINISHED); } else { Debug.Log("\n" + Consts.WRITE_TAGS_FINISHED); } }