From 0b6864f4f17707cfe9fac6e63ce84be673815f34 Mon Sep 17 00:00:00 2001 From: eltos Date: Wed, 31 Jan 2024 22:09:20 +0100 Subject: [PATCH] Implement append mode (text-like and zip files) --- PasteIntoFile/ClipboardContents.cs | 67 ++++++++++++------- PasteIntoFile/Dialog.Designer.cs | 16 +++++ PasteIntoFile/Dialog.cs | 40 +++++++++-- PasteIntoFile/Main.cs | 7 +- .../Properties/Resources.Designer.cs | 28 ++++++++ PasteIntoFile/Properties/Resources.de.resx | 10 +++ PasteIntoFile/Properties/Resources.resx | 10 +++ 7 files changed, 146 insertions(+), 32 deletions(-) diff --git a/PasteIntoFile/ClipboardContents.cs b/PasteIntoFile/ClipboardContents.cs index fa42fb1..0045ce0 100644 --- a/PasteIntoFile/ClipboardContents.cs +++ b/PasteIntoFile/ClipboardContents.cs @@ -18,6 +18,8 @@ namespace PasteIntoFile { + public class AppendNotSupportedException : ArgumentException { } + /// /// This is the base class to hold clipboard contents, metadata, and perform actions with it /// @@ -54,7 +56,8 @@ public string DefaultExtension { /// /// Full path where to save (incl. filename and extension) /// format to use for saving - public abstract void SaveAs(string path, string extension); + /// If true, append content + public abstract void SaveAs(string path, string extension, bool append = false); /// /// Add the content to the data object to be placed in the clipboard @@ -86,7 +89,9 @@ public ImageContent(Image image) { public override string[] Extensions => new[] { "png", "bmp", "gif", "jpg", "pdf", "tif" }; public override string Description => string.Format(Resources.str_preview_image, Image.Width, Image.Height); - public override void SaveAs(string path, string extension) { + public override void SaveAs(string path, string extension, bool append = false) { + if (append) + throw new AppendNotSupportedException(); Image image = ImagePreview(extension); if (image == null) throw new FormatException(string.Format(Resources.str_error_cliboard_format_missmatch, extension)); @@ -174,7 +179,9 @@ public VectorImageContent(Metafile metafile) { public override string[] Extensions => new[] { "emf" }; public override string Description => Resources.str_preview_image_vector; - public override void SaveAs(string path, string extension) { + public override void SaveAs(string path, string extension, bool append = false) { + if (append) + throw new AppendNotSupportedException(); switch (extension) { case "emf": IntPtr h = Metafile.GetHenhmetafile(); @@ -241,7 +248,9 @@ public string XmlString { public override string[] Extensions => new[] { "svg" }; public override string Description => Resources.str_preview_svg; - public override void SaveAs(string path, string extension) { + public override void SaveAs(string path, string extension, bool append = false) { + if (append) + throw new AppendNotSupportedException(); switch (extension) { case "svg": using (FileStream w = File.Create(path)) { @@ -265,8 +274,17 @@ public TextLikeContent(string text) { } public string Text => Data as string; public static readonly Encoding Encoding = new UTF8Encoding(false); // omit unnecessary BOM bytes - public override void SaveAs(string path, string extension) { - File.WriteAllText(path, Text, Encoding); + public override void SaveAs(string path, string extension, bool append = false) { + Save(path, Text, append); + } + + public static void Save(string path, string text, bool append = false) { + using (StreamWriter streamWriter = new StreamWriter(path, append)) + streamWriter.Write(EnsureNewline(text), Encoding); + } + + public static string EnsureNewline(string text) { + return text.TrimEnd('\n') + '\n'; } /// @@ -295,11 +313,11 @@ public class HtmlContent : TextLikeContent { public HtmlContent(string text) : base(text) { } public override string[] Extensions => new[] { "html", "htm", "xhtml" }; public override string Description => Resources.str_preview_html; - public override void SaveAs(string path, string extension) { + public override void SaveAs(string path, string extension, bool append = false) { var html = Text; - if (!html.StartsWith("")) + if (!append && !html.StartsWith("")) html = "\n" + html; - File.WriteAllText(path, html, Encoding); + Save(path, html, append); } public override void AddTo(IDataObject data) { // prepare header @@ -389,8 +407,8 @@ public override string TextPreview(string extension) { } } - public override void SaveAs(string path, string extension) { - File.WriteAllText(path, TextPreview(extension), Encoding); + public override void SaveAs(string path, string extension, bool append = false) { + Save(path, TextPreview(extension), append); } } @@ -438,11 +456,10 @@ public class UrlContent : TextLikeContent { public UrlContent(string text) : base(text) { } public override string[] Extensions => new[] { "url" }; public override string Description => Resources.str_preview_url; - public override void SaveAs(string path, string extension) { - File.WriteAllLines(path, new[] { - @"[InternetShortcut]", - @"URL=" + Text - }, Encoding); + public override void SaveAs(string path, string extension, bool append = false) { + if (append) + throw new AppendNotSupportedException(); + Save(path, "[InternetShortcut]\nURL=" + Text); } public override void AddTo(IDataObject data) { data.SetData(DataFormats.Text, Text); @@ -469,22 +486,24 @@ public List FileList { public override string[] Extensions => new[] { "zip", "m3u", "files", "txt" }; public override string Description => string.Format(Resources.str_preview_files, Files.Count); - public override void SaveAs(string path, string extension) { + public override void SaveAs(string path, string extension, bool append = false) { switch (extension) { case "zip": // TODO: since zipping can take a while depending on file size, this should show a progress to the user - var archive = ZipFile.Open(path, ZipArchiveMode.Create); - foreach (var file in Files) { - if ((File.GetAttributes(file) & FileAttributes.Directory) == FileAttributes.Directory) { - AddToZipArchive(archive, Path.GetFileName(file), new DirectoryInfo(file)); - } else { - archive.CreateEntryFromFile(file, Path.GetFileName(file)); + using (var archive = ZipFile.Open(path, append ? ZipArchiveMode.Update : ZipArchiveMode.Create)) { + foreach (var file in Files) { + if ((File.GetAttributes(file) & FileAttributes.Directory) == FileAttributes.Directory) { + AddToZipArchive(archive, Path.GetFileName(file), new DirectoryInfo(file)); + } else { + archive.CreateEntryFromFile(file, Path.GetFileName(file)); + } } } + break; default: - File.WriteAllText(path, FileListString, TextLikeContent.Encoding); + TextLikeContent.Save(path, FileListString, append); break; } } diff --git a/PasteIntoFile/Dialog.Designer.cs b/PasteIntoFile/Dialog.Designer.cs index b22ba0a..0c0c9e8 100644 --- a/PasteIntoFile/Dialog.Designer.cs +++ b/PasteIntoFile/Dialog.Designer.cs @@ -41,6 +41,7 @@ private void InitializeComponent() { this.btnBrowseForFolder = new System.Windows.Forms.Button(); this.versionInfoLabel = new System.Windows.Forms.LinkLabel(); this.infoLinkLabel = new System.Windows.Forms.LinkLabel(); + this.chkAppend = new System.Windows.Forms.CheckBox(); this.chkAutoSave = new System.Windows.Forms.CheckBox(); this.box = new System.Windows.Forms.GroupBox(); this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); @@ -344,6 +345,7 @@ private void InitializeComponent() { this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); this.tableLayoutPanel2.Controls.Add(this.btnSave, 1, 0); + this.tableLayoutPanel2.Controls.Add(this.chkAppend, 0, 0); this.tableLayoutPanel2.Controls.Add(this.chkContinuousMode, 0, 1); this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel2.Location = new System.Drawing.Point(15, 125); @@ -355,6 +357,19 @@ private void InitializeComponent() { this.tableLayoutPanel2.Size = new System.Drawing.Size(546, 54); this.tableLayoutPanel2.TabIndex = 12; // + // chkAppend + // + this.chkAppend.AutoSize = true; + this.chkAppend.Location = new System.Drawing.Point(4, 0); + this.chkAppend.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.chkAppend.Name = "chkAppend"; + this.chkAppend.Size = new System.Drawing.Size(169, 24); + this.chkAppend.TabIndex = 5; + this.chkAppend.Text = Resources.str_append; + this.chkAppend.UseVisualStyleBackColor = true; + this.chkAppend.CheckedChanged += new System.EventHandler(this.chkAppend_CheckedChanged); + this.toolTip.SetToolTip(this.chkAppend, Resources.str_append_tooltip); + // // chkContinuousMode // this.chkContinuousMode.AutoSize = true; @@ -447,6 +462,7 @@ private void InitializeComponent() { private System.Windows.Forms.Button btnBrowseForFolder; private System.Windows.Forms.LinkLabel versionInfoLabel; private System.Windows.Forms.LinkLabel infoLinkLabel; + private System.Windows.Forms.CheckBox chkAppend; private System.Windows.Forms.CheckBox chkAutoSave; private System.Windows.Forms.GroupBox box; private System.Windows.Forms.ToolTip toolTip; diff --git a/PasteIntoFile/Dialog.cs b/PasteIntoFile/Dialog.cs index cd44605..946ee97 100644 --- a/PasteIntoFile/Dialog.cs +++ b/PasteIntoFile/Dialog.cs @@ -34,7 +34,14 @@ protected override void OnFormClosed(FormClosedEventArgs e) { - public Dialog(string location = null, string filename = null, bool? showDialogOverwrite = null, bool? clearClipboardOverwrite = null, bool overwriteIfExists = false) { + public Dialog( + string location = null, + string filename = null, + bool? showDialogOverwrite = null, + bool? clearClipboardOverwrite = null, + bool overwriteIfExists = false, + bool append = false + ) { Settings.Default.Reload(); // load modifications made from other instance Settings.Default.continuousMode = false; // always start in normal mode Settings.Default.Save(); @@ -91,6 +98,8 @@ public Dialog(string location = null, string filename = null, bool? showDialogOv if (saveIntoSubdir) location += @"\" + formatFilenameTemplate(Settings.Default.subdirTemplate); txtCurrentLocation.Text = location; + + chkAppend.Checked = append; // non-persistent setting updateUiFromSettings(); Settings.Default.PropertyChanged += (sender, args) => updateUiFromSettings(); @@ -241,7 +250,7 @@ private void ClipboardChanged(Object sender, SharpClipboard.ClipboardChangedEven ignore |= Clipboard.ContainsData(Program.PATCHED_CLIPBOARD_MAGIC); if (!ignore) { - updateFilename(); + if (!chkAppend.Checked) updateFilename(); save(); updateSavebutton(); } @@ -328,7 +337,8 @@ private void updateContentPreview() { private void updateSavebutton() { - btnSave.Enabled = txtFilename.Enabled = !chkContinuousMode.Checked; + txtFilename.Enabled = !chkContinuousMode.Checked || chkAppend.Checked; + btnSave.Enabled = !chkContinuousMode.Checked; btnSave.Text = chkContinuousMode.Checked ? string.Format(Resources.str_n_saved, saveCount) : Resources.str_save; } @@ -354,7 +364,7 @@ string save(bool overwriteIfExists = false, bool? clearClipboardOverwrite = fals if (overwriteIfExists) { // Move old file to recycle bin and proceed ExplorerUtil.MoveToRecycleBin(file); - } else { + } else if (!chkAppend.Checked) { // Ask user for confirmation var result = MessageBox.Show(string.Format(Resources.str_file_exists, file), Resources.app_title, MessageBoxButtons.YesNo, MessageBoxIcon.Warning); @@ -375,7 +385,20 @@ string save(bool overwriteIfExists = false, bool? clearClipboardOverwrite = fals BaseContent contentToSave = clipData.ForExtension(comExt.Text); if (contentToSave != null) { - contentToSave.SaveAs(file, ext); + try { + contentToSave.SaveAs(file, ext, chkAppend.Checked); + } catch (AppendNotSupportedException) { + // So ask user if we should replace instead + var msg = string.Format(Resources.str_append_not_supported, comExt.Text) + "\n\n" + + string.Format(Resources.str_file_exists, file); + var result = MessageBox.Show(msg, Resources.app_title, MessageBoxButtons.YesNo, MessageBoxIcon.Warning); + if (result == DialogResult.Yes) { + contentToSave.SaveAs(file, ext); + } else { + return null; + } + } + } else { return null; } @@ -429,13 +452,18 @@ private void Main_KeyPress(object sender, KeyPressEventArgs e) { } } + private void chkAppend_CheckedChanged(object sender, EventArgs e) { + if (disableUiEvents) return; + updateSavebutton(); + } + private void chkContinuousMode_CheckedChanged(object sender, EventArgs e) { if (disableUiEvents) return; if (chkContinuousMode.Checked) { var saveNow = MessageBox.Show(Resources.str_continuous_mode_enabled_ask_savenow, Resources.str_continuous_mode, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question); if (saveNow == DialogResult.Yes) // save current clipboard now { - updateFilename(); + // for first time save, keep current filename save(); } else if (saveNow != DialogResult.No) chkContinuousMode.Checked = false; diff --git a/PasteIntoFile/Main.cs b/PasteIntoFile/Main.cs index 73768fd..8166a62 100644 --- a/PasteIntoFile/Main.cs +++ b/PasteIntoFile/Main.cs @@ -44,7 +44,10 @@ class ArgsPaste : ArgsCommon { [Value(0, Hidden = true)] public string DirectoryFallback { get; set; } // alternative: directory as first value argument - [Option("overwrite", Default = false, HelpText = "Overwrite existing file without prompt. Requires --autosave=true.")] + [Option("append", Default = false, HelpText = "Append to file if it exists (and extension supports appending).")] + public bool Append { get; set; } + + [Option("overwrite", Default = false, HelpText = "Overwrite existing file without prompt. Requires --autosave=true. If append is also given, this only applies in case appending fails.")] public bool Overwrite { get; set; } } @@ -187,7 +190,7 @@ static int RunPaste(ArgsPaste args) { showDialogOverwrite = !args.Autosave; // launch it - Application.Run(new Dialog(directory, filename, showDialogOverwrite, args.ClearClipboard, args.Overwrite)); + Application.Run(new Dialog(directory, filename, showDialogOverwrite, args.ClearClipboard, args.Overwrite, args.Append)); return Environment.ExitCode; } diff --git a/PasteIntoFile/Properties/Resources.Designer.cs b/PasteIntoFile/Properties/Resources.Designer.cs index 4084909..170ba3c 100644 --- a/PasteIntoFile/Properties/Resources.Designer.cs +++ b/PasteIntoFile/Properties/Resources.Designer.cs @@ -96,6 +96,34 @@ internal static string str_always_on_top { } } + /// + /// Looks up a localized string similar to Append into file. + /// + internal static string str_append { + get { + return ResourceManager.GetString("str_append", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Appending is not supported for extension "{0}".. + /// + internal static string str_append_not_supported { + get { + return ResourceManager.GetString("str_append_not_supported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Appends the contents to the selected file, if it already exists. + ///(Only for supported file types). + /// + internal static string str_append_tooltip { + get { + return ResourceManager.GetString("str_append_tooltip", resourceCulture); + } + } + /// /// Looks up a localized string similar to Hold shift key during save to show option window.. /// diff --git a/PasteIntoFile/Properties/Resources.de.resx b/PasteIntoFile/Properties/Resources.de.resx index 5dc3f4d..734522c 100644 --- a/PasteIntoFile/Properties/Resources.de.resx +++ b/PasteIntoFile/Properties/Resources.de.resx @@ -294,4 +294,14 @@ Die Umschalttaste gedrückt halten, um diese Einstellung temporär zu invertiere Allows to keep application window always on top (in foreground of other windows), even if not focussed. + + Zur Datei hinzufügen + + + Hinzufügen zu bestehender Datei wird für "{0}" nicht unterstützt. + + + Falls die Datei bereits existiert wird der Inhalt zur Datei hinzugefügt. +(Nur für unterstützte Dateitypen) + diff --git a/PasteIntoFile/Properties/Resources.resx b/PasteIntoFile/Properties/Resources.resx index b249eb6..2140b1f 100644 --- a/PasteIntoFile/Properties/Resources.resx +++ b/PasteIntoFile/Properties/Resources.resx @@ -303,4 +303,14 @@ Hold SHIFT to temporarly invert this setting. Allows to keep application window always on top (in foreground of other windows), even if not focussed. + + Append into file + + + Appends the contents to the selected file, if it already exists. +(Only for supported file types) + + + Appending is not supported for extension "{0}". +