]> granicus.if.org Git - handbrake/commitdiff
Fixing importing and exporting of chapters via CSV files. Adding proper handling...
authorSverrir Sigmundarson <sverrirs@gmail.com>
Wed, 18 Nov 2015 22:57:03 +0000 (23:57 +0100)
committerSverrir Sigmundarson <sverrirs@gmail.com>
Sat, 21 Nov 2015 00:16:57 +0000 (01:16 +0100)
win/CS/HandBrakeWPF/HandBrakeWPF.csproj
win/CS/HandBrakeWPF/Properties/Resources.Designer.cs
win/CS/HandBrakeWPF/Properties/Resources.resx
win/CS/HandBrakeWPF/Utilities/Output/CsvHelper.cs [new file with mode: 0644]
win/CS/HandBrakeWPF/ViewModels/ChaptersViewModel.cs

index 2af3ba4121c690f4f514115f7832b83a36ae1289..00b0560e93926bf2e11833f1d3f94b0520c5eaef 100644 (file)
     <Reference Include="GongSolutions.Wpf.DragDrop">\r
       <HintPath>..\libraries\WPFDragDrop\GongSolutions.Wpf.DragDrop.dll</HintPath>\r
     </Reference>\r
-    <Reference Include="LumenWorks.Framework.IO">\r
-      <HintPath>..\libraries\CsvReader\LumenWorks.Framework.IO.dll</HintPath>\r
-    </Reference>\r
     <Reference Include="Microsoft.CSharp" />\r
+    <Reference Include="Microsoft.VisualBasic" />\r
     <Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">\r
       <SpecificVersion>False</SpecificVersion>\r
       <HintPath>..\libraries\json\Newtonsoft.Json.dll</HintPath>\r
     <Compile Include="Utilities\GeneralUtilities.cs" />\r
     <Compile Include="Utilities\HandBrakeApp.cs" />\r
     <Compile Include="Utilities\Interfaces\INotifyPropertyChangedEx.cs" />\r
+    <Compile Include="Utilities\Output\CsvHelper.cs" />\r
     <Compile Include="Utilities\PropertyChangedBase.cs" />\r
     <Compile Include="Utilities\Win7.cs" />\r
     <Compile Include="ViewModels\CountdownAlertViewModel.cs" />\r
index 6438431327fe8e23c4d24ba2266ca90a814db259..b53620537ede7c7e814ac0163ca066c789e8f1e5 100644 (file)
@@ -448,7 +448,7 @@ namespace HandBrakeWPF.Properties {
         }\r
         \r
         /// <summary>\r
-        ///   Looks up a localized string similar to Unable to save Chapter Makrers file! .\r
+        ///   Looks up a localized string similar to Unable to save Chapter Markers file! .\r
         /// </summary>\r
         public static string ChaptersViewModel_UnableToExportChaptersWarning {\r
             get {\r
@@ -456,6 +456,42 @@ namespace HandBrakeWPF.Properties {
             }\r
         }\r
         \r
+        /// <summary>\r
+        ///   Looks up a localized string similar to First column in chapters file must only contain a integer number value higher than zero (0).\r
+        /// </summary>\r
+        public static string ChaptersViewModel_UnableToImportChaptersFirstColumnMustContainOnlyIntegerNumber {\r
+            get {\r
+                return ResourceManager.GetString("ChaptersViewModel_UnableToImportChaptersFirstColumnMustContainOnlyIntegerNumber", resourceCulture);\r
+            }\r
+        }\r
+        \r
+        /// <summary>\r
+        ///   Looks up a localized string similar to All lines in chapters file must have at least 2 columns of data.\r
+        /// </summary>\r
+        public static string ChaptersViewModel_UnableToImportChaptersLineDoesNotHaveAtLeastTwoColumns {\r
+            get {\r
+                return ResourceManager.GetString("ChaptersViewModel_UnableToImportChaptersLineDoesNotHaveAtLeastTwoColumns", resourceCulture);\r
+            }\r
+        }\r
+        \r
+        /// <summary>\r
+        ///   Looks up a localized string similar to Line {0} is invalid. Nothing will be imported..\r
+        /// </summary>\r
+        public static string ChaptersViewModel_UnableToImportChaptersMalformedLineMsg {\r
+            get {\r
+                return ResourceManager.GetString("ChaptersViewModel_UnableToImportChaptersMalformedLineMsg", resourceCulture);\r
+            }\r
+        }\r
+        \r
+        /// <summary>\r
+        ///   Looks up a localized string similar to Unable to import chapter file.\r
+        /// </summary>\r
+        public static string ChaptersViewModel_UnableToImportChaptersWarning {\r
+            get {\r
+                return ResourceManager.GetString("ChaptersViewModel_UnableToImportChaptersWarning", resourceCulture);\r
+            }\r
+        }\r
+        \r
         /// <summary>\r
         ///   Looks up a localized string similar to Confirm.\r
         /// </summary>\r
index 0ce417b62f4cef6183953a743795ef0c16b0e926..a8b2cb46963208ab2ae38bddf3136b4ab189c024 100644 (file)
@@ -584,7 +584,7 @@ The Activity log may have further information.</value>
     <value>Switch Back To Tracks</value>\r
   </data>\r
   <data name="ChaptersViewModel_UnableToExportChaptersWarning" xml:space="preserve">\r
-    <value>Unable to save Chapter Makrers file! </value>\r
+    <value>Unable to save Chapter Markers file! </value>\r
   </data>\r
   <data name="ChaptersViewModel_UnableToExportChaptersMsg" xml:space="preserve">\r
     <value>Chapter marker names will NOT be saved in your encode.</value>\r
@@ -739,4 +739,16 @@ Your old presets file was archived to:</value>
   <data name="MainViewModel_LowDiskSpaceWarning" xml:space="preserve">\r
     <value>Warning, you are running low on disk space. HandBrake will not be able to complete this encode if you run out of space. </value>\r
   </data>\r
+  <data name="ChaptersViewModel_UnableToImportChaptersMalformedLineMsg" xml:space="preserve">\r
+    <value>Line {0} is invalid. Nothing will be imported.</value>\r
+  </data>\r
+  <data name="ChaptersViewModel_UnableToImportChaptersWarning" xml:space="preserve">\r
+    <value>Unable to import chapter file</value>\r
+  </data>\r
+  <data name="ChaptersViewModel_UnableToImportChaptersLineDoesNotHaveAtLeastTwoColumns" xml:space="preserve">\r
+    <value>All lines in chapters file must have at least 2 columns of data</value>\r
+  </data>\r
+  <data name="ChaptersViewModel_UnableToImportChaptersFirstColumnMustContainOnlyIntegerNumber" xml:space="preserve">\r
+    <value>First column in chapters file must only contain a integer number value higher than zero (0)</value>\r
+  </data>\r
 </root>
\ No newline at end of file
diff --git a/win/CS/HandBrakeWPF/Utilities/Output/CsvHelper.cs b/win/CS/HandBrakeWPF/Utilities/Output/CsvHelper.cs
new file mode 100644 (file)
index 0000000..cf2d7a3
--- /dev/null
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace HandBrakeWPF.Utilities.Output
+{
+    /// <summary>
+    /// Utilitiy functions for writing CSV files
+    /// </summary>
+    internal sealed class CsvHelper
+    {
+        private const string QUOTE = "\"";
+        private const string ESCAPED_QUOTE = "\"\"";
+        private static readonly char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' };
+
+        /// <summary>
+        /// Properly escapes a string value containing reserved characters with double quotes "..." before it is written to a CSV file.
+        /// </summary>
+        /// <param name="value">Value to be escaped</param>
+        /// <returns>Fully escaped value</returns>
+        public static string Escape(string value)
+        {
+            if (value.Contains(QUOTE))
+                value = value.Replace(QUOTE, ESCAPED_QUOTE);
+
+            if (value.IndexOfAny(CHARACTERS_THAT_MUST_BE_QUOTED) > -1)
+                value = QUOTE + value + QUOTE;
+
+            return value;
+        }
+    }
+}
index a76d24547deeb3073603cbb99046a0dac0fa9a20..4815de20d949e2045b221e9fe80113948e98395e 100644 (file)
@@ -13,6 +13,8 @@ namespace HandBrakeWPF.ViewModels
     using System.Collections.Generic;\r
     using System.Collections.ObjectModel;\r
     using System.IO;\r
+    using System.Text;\r
+    using System.Windows.Forms;\r
 \r
     using Caliburn.Micro;\r
 \r
@@ -20,11 +22,10 @@ namespace HandBrakeWPF.ViewModels
     using HandBrakeWPF.Services.Interfaces;\r
     using HandBrakeWPF.Services.Presets.Model;\r
     using HandBrakeWPF.Services.Scan.Model;\r
+    using HandBrakeWPF.Utilities.Output;\r
     using HandBrakeWPF.ViewModels.Interfaces;\r
 \r
-    using LumenWorks.Framework.IO.Csv;\r
-\r
-    using Microsoft.Win32;\r
+    using Microsoft.VisualBasic.FileIO;\r
 \r
     using ChapterMarker = HandBrakeWPF.Services.Encode.Model.Models.ChapterMarker;\r
     using EncodeTask = HandBrakeWPF.Services.Encode.Model.EncodeTask;\r
@@ -93,50 +94,40 @@ namespace HandBrakeWPF.ViewModels
 \r
         #region Public Methods\r
 \r
-        /// <summary>\r
-        /// Export a CSV file.\r
-        /// </summary>\r
-        public void Export()\r
-        {\r
-            var saveFileDialog = new OpenFileDialog\r
-            {\r
-                Filter = "Csv File|*.csv",\r
-                DefaultExt = "csv",\r
-                CheckPathExists = true\r
-            };\r
-            bool? dialogResult = saveFileDialog.ShowDialog();\r
-            if (dialogResult.HasValue && dialogResult.Value && !string.IsNullOrEmpty(saveFileDialog.FileName))\r
-            {\r
-                this.ExportChaptersToCSV(saveFileDialog.FileName);\r
-            }\r
-        }\r
-\r
         /// <summary>\r
         /// Export the Chapter Markers to a CSV file\r
         /// </summary>\r
-        /// <param name="filename">\r
-        /// The filename.\r
-        /// </param>\r
         /// <exception cref="GeneralApplicationException">\r
         /// Thrown when exporting fails.\r
         /// </exception>\r
-        public void ExportChaptersToCSV(string filename)\r
+        public void Export()\r
         {\r
-            try\r
+            string fileName = null;\r
+            using (var saveFileDialog = new SaveFileDialog()\r
+                                        {\r
+                                            Filter = "Csv File|*.csv",\r
+                                            DefaultExt = "csv",\r
+                                            CheckPathExists = true,\r
+                                            OverwritePrompt = true\r
+                                        })\r
             {\r
-                string csv = string.Empty;\r
+                var dialogResult = saveFileDialog.ShowDialog();\r
+                fileName = saveFileDialog.FileName;\r
 \r
-                foreach (ChapterMarker row in this.Task.ChapterNames)\r
+                // Exit early if the user cancelled or the filename is invalid\r
+                if (dialogResult != DialogResult.OK || string.IsNullOrWhiteSpace(fileName))\r
+                    return;\r
+            }\r
+\r
+            try\r
+            {\r
+                using (var csv = new StreamWriter(fileName))\r
                 {\r
-                    csv += row.ChapterNumber.ToString();\r
-                    csv += ",";\r
-                    csv += row.ChapterName.Replace(",", "\\,");\r
-                    csv += Environment.NewLine;\r
+                    foreach (ChapterMarker row in this.Task.ChapterNames)\r
+                    {\r
+                        csv.Write("{0},{1}{2}", row.ChapterNumber, CsvHelper.Escape(row.ChapterName), Environment.NewLine);\r
+                    }\r
                 }\r
-                var file = new StreamWriter(filename);\r
-                file.Write(csv);\r
-                file.Close();\r
-                file.Dispose();\r
             }\r
             catch (Exception exc)\r
             {\r
@@ -152,41 +143,69 @@ namespace HandBrakeWPF.ViewModels
         /// </summary>\r
         public void Import()\r
         {\r
-            var dialog = new OpenFileDialog { Filter = "CSV files (*.csv)|*.csv", CheckFileExists = true };\r
-            bool? dialogResult = dialog.ShowDialog();\r
-            string filename = dialog.FileName;\r
-\r
-            if (!dialogResult.HasValue || !dialogResult.Value || string.IsNullOrEmpty(filename))\r
+            string filename = null;\r
+            using (var dialog = new OpenFileDialog() { Filter = "CSV files (*.csv)|*.csv", CheckFileExists = true })\r
             {\r
-                return;\r
+                var dialogResult = dialog.ShowDialog();\r
+                filename = dialog.FileName;\r
+\r
+                // Exit if the user didn't press the OK button or the file name is invalid\r
+                if (dialogResult != DialogResult.OK || string.IsNullOrWhiteSpace(filename))\r
+                    return;\r
             }\r
 \r
-            IDictionary<int, string> chapterMap = new Dictionary<int, string>();\r
-            try\r
+            var chapterMap = new Dictionary<int, string>();\r
+\r
+            using (TextFieldParser csv = new TextFieldParser(filename)\r
+                    { CommentTokens = new[] { "#" }, // Comment lines\r
+                        Delimiters = new[] { ",", "\t", ";", ":" }, // Support all of these common delimeter types\r
+                        HasFieldsEnclosedInQuotes = true, // Assume that our data will be properly escaped\r
+                        TextFieldType = FieldType.Delimited,\r
+                        TrimWhiteSpace = true // Remove excess whitespace from ends of imported values\r
+                    })\r
             {\r
-                using (CsvReader csv = new CsvReader(new StreamReader(filename), false))\r
+                while (!csv.EndOfData)\r
                 {\r
-                    while (csv.ReadNextRecord())\r
+                    try\r
                     {\r
-                        if (csv.FieldCount == 2)\r
-                        {\r
-                            int chapter;\r
-                            int.TryParse(csv[0], out chapter);\r
-                            chapterMap[chapter] = csv[1];\r
-                        }\r
+                        // Only read the first two columns, anything else will be ignored but will not raise an error\r
+                        var row = csv.ReadFields();\r
+                        if (row == null || row.Length < 2) // null condition happens if the file is somehow corrupt during reading\r
+                            throw new MalformedLineException(Resources.ChaptersViewModel_UnableToImportChaptersLineDoesNotHaveAtLeastTwoColumns, csv.LineNumber);\r
+\r
+                        int chapterNumber;\r
+                        if (!int.TryParse(row[0], out chapterNumber))\r
+                            throw new MalformedLineException(Resources.ChaptersViewModel_UnableToImportChaptersFirstColumnMustContainOnlyIntegerNumber, csv.LineNumber);\r
+\r
+                        // Store the chapter name at the correct index\r
+                        chapterMap[chapterNumber] = row[1]?.Trim();\r
+                    }\r
+                    catch (MalformedLineException mlex)\r
+                    {\r
+                        throw new GeneralApplicationException(\r
+                            Resources.ChaptersViewModel_UnableToImportChaptersWarning,\r
+                            string.Format(Resources.ChaptersViewModel_UnableToImportChaptersMalformedLineMsg, mlex.LineNumber),\r
+                            mlex);\r
                     }\r
                 }\r
             }\r
-            catch (Exception)\r
-            {\r
-                // Do Nothing\r
-            }\r
+\r
+            // Exit early if no chapter information was extracted\r
+            if (chapterMap.Count <= 0)\r
+                return;\r
 \r
             // Now iterate over each chatper we have, and set it's name\r
             foreach (ChapterMarker item in this.Task.ChapterNames)\r
             {\r
                 string chapterName;\r
-                item.ChapterName = chapterMap.TryGetValue(item.ChapterNumber, out chapterName) ? chapterName : string.Empty;\r
+\r
+                // If we don't have a chapter name for this chapter then \r
+                // fallback to the value that is already set for the chapter\r
+                if (!chapterMap.TryGetValue(item.ChapterNumber, out chapterName))\r
+                    chapterName = item.ChapterName;\r
+\r
+                // Assign the chapter name unless the name is not set or only whitespace charaters\r
+                item.ChapterName = !string.IsNullOrWhiteSpace(chapterName) ? chapterName : string.Empty;\r
             }\r
         }\r
 \r