-
Notifications
You must be signed in to change notification settings - Fork 9
add extension func #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,244 @@ | ||
| using System; | ||
| using System.IO; | ||
| using System.IO.Compression; | ||
| using System.Linq; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace GeneralUpdate.Tool.Avalonia.Common; | ||
|
|
||
| /// <summary> | ||
| /// Utility class for zip file compression operations | ||
| /// </summary> | ||
| public static class ZipUtility | ||
| { | ||
| /// <summary> | ||
| /// Characters that are invalid in file names across all platforms | ||
| /// Includes platform-specific invalid chars and common problematic characters | ||
| /// </summary> | ||
| private static readonly char[] InvalidFileNameChars = | ||
| Path.GetInvalidFileNameChars() | ||
| .Concat(new[] { '<', '>', ':', '"', '/', '\\', '|', '?', '*' }) | ||
| .Distinct() | ||
| .ToArray(); | ||
|
|
||
| /// <summary> | ||
| /// Sanitizes a string to be used as a filename by replacing invalid characters | ||
| /// </summary> | ||
| /// <param name="fileName">The filename to sanitize</param> | ||
| /// <param name="replacement">The replacement character for invalid characters (default: '_')</param> | ||
| /// <returns>Sanitized filename</returns> | ||
| public static string SanitizeFileName(string fileName, char replacement = '_') | ||
| { | ||
| if (string.IsNullOrWhiteSpace(fileName)) | ||
| return fileName; | ||
|
|
||
| var sanitized = fileName; | ||
| foreach (var invalidChar in InvalidFileNameChars) | ||
| { | ||
| sanitized = sanitized.Replace(invalidChar, replacement); | ||
| } | ||
|
|
||
| return sanitized; | ||
| } | ||
| /// <summary> | ||
| /// Compresses a directory into a zip file | ||
| /// </summary> | ||
| /// <param name="sourceDirectory">Source directory to compress</param> | ||
| /// <param name="destinationZipFile">Destination zip file path</param> | ||
| /// <param name="compressionLevel">Compression level (default: Optimal)</param> | ||
| /// <param name="includeBaseDirectory">Whether to include the base directory in the archive</param> | ||
| /// <exception cref="ArgumentNullException">Thrown when sourceDirectory or destinationZipFile is null or empty</exception> | ||
| /// <exception cref="DirectoryNotFoundException">Thrown when sourceDirectory does not exist</exception> | ||
| public static void CompressDirectory( | ||
| string sourceDirectory, | ||
| string destinationZipFile, | ||
| CompressionLevel compressionLevel = CompressionLevel.Optimal, | ||
| bool includeBaseDirectory = false) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(sourceDirectory)) | ||
| throw new ArgumentNullException(nameof(sourceDirectory)); | ||
|
|
||
| if (string.IsNullOrWhiteSpace(destinationZipFile)) | ||
| throw new ArgumentNullException(nameof(destinationZipFile)); | ||
|
|
||
| if (!Directory.Exists(sourceDirectory)) | ||
| throw new DirectoryNotFoundException($"Source directory not found: {sourceDirectory}"); | ||
|
|
||
| // Ensure the destination directory exists | ||
| var destinationDir = Path.GetDirectoryName(destinationZipFile); | ||
| if (!string.IsNullOrEmpty(destinationDir) && !Directory.Exists(destinationDir)) | ||
| { | ||
| Directory.CreateDirectory(destinationDir); | ||
| } | ||
|
|
||
| // Delete existing zip file if it exists | ||
| if (File.Exists(destinationZipFile)) | ||
| { | ||
| File.Delete(destinationZipFile); | ||
| } | ||
|
|
||
| // Create the zip archive | ||
| ZipFile.CreateFromDirectory(sourceDirectory, destinationZipFile, compressionLevel, includeBaseDirectory); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Compresses a directory into a zip file asynchronously | ||
| /// </summary> | ||
| /// <param name="sourceDirectory">Source directory to compress</param> | ||
| /// <param name="destinationZipFile">Destination zip file path</param> | ||
| /// <param name="compressionLevel">Compression level (default: Optimal)</param> | ||
| /// <param name="includeBaseDirectory">Whether to include the base directory in the archive</param> | ||
| /// <returns>Task representing the asynchronous operation</returns> | ||
| /// <exception cref="ArgumentNullException">Thrown when sourceDirectory or destinationZipFile is null or empty</exception> | ||
| /// <exception cref="DirectoryNotFoundException">Thrown when sourceDirectory does not exist</exception> | ||
| public static Task CompressDirectoryAsync( | ||
| string sourceDirectory, | ||
| string destinationZipFile, | ||
| CompressionLevel compressionLevel = CompressionLevel.Optimal, | ||
| bool includeBaseDirectory = false) | ||
| { | ||
| return Task.Run(() => CompressDirectory(sourceDirectory, destinationZipFile, compressionLevel, includeBaseDirectory)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Extracts a zip file to a directory | ||
| /// </summary> | ||
| /// <param name="sourceZipFile">Source zip file to extract</param> | ||
| /// <param name="destinationDirectory">Destination directory for extraction</param> | ||
| /// <param name="overwriteFiles">Whether to overwrite existing files</param> | ||
| /// <exception cref="ArgumentNullException">Thrown when sourceZipFile or destinationDirectory is null or empty</exception> | ||
| /// <exception cref="FileNotFoundException">Thrown when sourceZipFile does not exist</exception> | ||
| /// <exception cref="InvalidOperationException">Thrown when a zip entry attempts to extract outside the destination directory (zip slip attack)</exception> | ||
| public static void ExtractZipFile( | ||
| string sourceZipFile, | ||
| string destinationDirectory, | ||
| bool overwriteFiles = true) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(sourceZipFile)) | ||
| throw new ArgumentNullException(nameof(sourceZipFile)); | ||
|
|
||
| if (string.IsNullOrWhiteSpace(destinationDirectory)) | ||
| throw new ArgumentNullException(nameof(destinationDirectory)); | ||
|
|
||
| if (!File.Exists(sourceZipFile)) | ||
| throw new FileNotFoundException($"Source zip file not found: {sourceZipFile}"); | ||
|
|
||
| // Ensure the destination directory exists | ||
| if (!Directory.Exists(destinationDirectory)) | ||
| { | ||
| Directory.CreateDirectory(destinationDirectory); | ||
| } | ||
|
|
||
| // Get the normalized full path of the destination directory | ||
| var normalizedDestination = Path.GetFullPath(destinationDirectory); | ||
|
|
||
| // Extract the zip archive with zip slip protection | ||
| using (var archive = System.IO.Compression.ZipFile.OpenRead(sourceZipFile)) | ||
| { | ||
| foreach (var entry in archive.Entries) | ||
| { | ||
| // Get the full path where the entry will be extracted | ||
| var entryPath = Path.Combine(destinationDirectory, entry.FullName); | ||
| var normalizedEntryPath = Path.GetFullPath(entryPath); | ||
|
|
||
| // Validate that the entry path is within the destination directory (zip slip protection) | ||
| if (!normalizedEntryPath.StartsWith(normalizedDestination, StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| throw new InvalidOperationException( | ||
| $"Zip entry '{entry.FullName}' attempts to extract outside the destination directory. " + | ||
| "This may indicate a zip slip attack."); | ||
| } | ||
|
|
||
| // Create directory for the entry if needed | ||
| if (string.IsNullOrEmpty(entry.Name)) | ||
| { | ||
| // This is a directory entry | ||
| Directory.CreateDirectory(normalizedEntryPath); | ||
| } | ||
| else | ||
| { | ||
| // This is a file entry | ||
| var entryDirectory = Path.GetDirectoryName(normalizedEntryPath); | ||
| if (!string.IsNullOrEmpty(entryDirectory) && !Directory.Exists(entryDirectory)) | ||
| { | ||
| Directory.CreateDirectory(entryDirectory); | ||
| } | ||
|
|
||
| // Extract the file | ||
| entry.ExtractToFile(normalizedEntryPath, overwriteFiles); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Extracts a zip file to a directory asynchronously | ||
| /// </summary> | ||
| /// <param name="sourceZipFile">Source zip file to extract</param> | ||
| /// <param name="destinationDirectory">Destination directory for extraction</param> | ||
| /// <param name="overwriteFiles">Whether to overwrite existing files</param> | ||
| /// <returns>Task representing the asynchronous operation</returns> | ||
| /// <exception cref="ArgumentNullException">Thrown when sourceZipFile or destinationDirectory is null or empty</exception> | ||
| /// <exception cref="FileNotFoundException">Thrown when sourceZipFile does not exist</exception> | ||
| /// <exception cref="InvalidOperationException">Thrown when a zip entry attempts to extract outside the destination directory (zip slip attack)</exception> | ||
| public static Task ExtractZipFileAsync( | ||
| string sourceZipFile, | ||
| string destinationDirectory, | ||
| bool overwriteFiles = true) | ||
| { | ||
| return Task.Run(() => ExtractZipFile(sourceZipFile, destinationDirectory, overwriteFiles)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds a file to an existing zip archive | ||
| /// </summary> | ||
| /// <param name="zipFilePath">Path to the zip file</param> | ||
| /// <param name="entryName">Entry name in the archive</param> | ||
| /// <param name="content">Content to add</param> | ||
| /// <exception cref="ArgumentNullException">Thrown when parameters are null or empty</exception> | ||
| /// <exception cref="ArgumentException">Thrown when parameters are empty or whitespace</exception> | ||
| /// <exception cref="FileNotFoundException">Thrown when zipFilePath does not exist</exception> | ||
| public static void AddFileToZip(string zipFilePath, string entryName, string content) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(zipFilePath)) | ||
| throw new ArgumentException("Zip file path cannot be null or empty", nameof(zipFilePath)); | ||
|
|
||
| if (string.IsNullOrWhiteSpace(entryName)) | ||
| throw new ArgumentException("Entry name cannot be null or empty", nameof(entryName)); | ||
|
|
||
| if (content == null) | ||
| throw new ArgumentNullException(nameof(content)); | ||
|
|
||
| if (!File.Exists(zipFilePath)) | ||
| throw new FileNotFoundException($"Zip file not found: {zipFilePath}"); | ||
|
|
||
| using (var archive = System.IO.Compression.ZipFile.Open(zipFilePath, ZipArchiveMode.Update)) | ||
| { | ||
| // Remove existing entry if it exists | ||
| var existingEntry = archive.GetEntry(entryName); | ||
| existingEntry?.Delete(); | ||
|
|
||
| // Create new entry | ||
| var entry = archive.CreateEntry(entryName, CompressionLevel.Optimal); | ||
| using (var writer = new StreamWriter(entry.Open())) | ||
| { | ||
| writer.Write(content); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds a file to an existing zip archive asynchronously | ||
| /// </summary> | ||
| /// <param name="zipFilePath">Path to the zip file</param> | ||
| /// <param name="entryName">Entry name in the archive</param> | ||
| /// <param name="content">Content to add</param> | ||
| /// <returns>Task representing the asynchronous operation</returns> | ||
| /// <exception cref="ArgumentNullException">Thrown when parameters are null</exception> | ||
| /// <exception cref="ArgumentException">Thrown when parameters are empty or whitespace</exception> | ||
| /// <exception cref="FileNotFoundException">Thrown when zipFilePath does not exist</exception> | ||
| public static Task AddFileToZipAsync(string zipFilePath, string entryName, string content) | ||
| { | ||
| return Task.Run(() => AddFileToZip(zipFilePath, entryName, content)); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| using CommunityToolkit.Mvvm.ComponentModel; | ||
|
|
||
| namespace GeneralUpdate.Tool.Avalonia.Models; | ||
|
|
||
| public class CustomPropertyModel : ObservableObject | ||
| { | ||
| private string _key; | ||
| private string _value; | ||
JusterZhu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// Property key | ||
| /// </summary> | ||
| public string Key | ||
| { | ||
| get => _key; | ||
| set => SetProperty(ref _key, value); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Property value | ||
| /// </summary> | ||
| public string Value | ||
| { | ||
| get => _value; | ||
| set => SetProperty(ref _value, value); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.