From fdebb502ab7483a0f8fe05f8a03d4448d49c7a16 Mon Sep 17 00:00:00 2001 From: Alex Verner <119082209+Retengart@users.noreply.github.com> Date: Sat, 25 Jan 2025 22:12:14 +0300 Subject: [PATCH 01/10] unpack zip and version in the window title --- src/Picocrypt.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/Picocrypt.go b/src/Picocrypt.go index 89c9c33..7bcbef8 100644 --- a/src/Picocrypt.go +++ b/src/Picocrypt.go @@ -120,6 +120,7 @@ var splitSelected int32 = 1 var recombine bool var compress bool var delete bool +var unpack bool var keep bool var kept bool @@ -552,6 +553,11 @@ func draw() { giu.Checkbox("Delete volume", &delete), giu.Tooltip("Delete the volume after a successful decryption"), ).Build() + + giu.Row( + giu.Checkbox("Unpack .zip", &unpack), + giu.Tooltip("Extract the decrypted .zip (ovewrite files)"), + ).Build() } }), @@ -2112,6 +2118,14 @@ func work() { os.Remove(inputFile) } + if mode == "decrypt" && !kept && unpack { + err := unpackArchive(outputFile) + if err != nil { + mainStatus = "Extraction failed: " + err.Error() + mainStatusColor = RED + giu.Update() + } + } // All done, reset the UI oldKept := kept resetUI() @@ -2330,9 +2344,56 @@ func sizeify(size int64) string { } } +func unpackArchive(zipPath string) error { + reader, err := zip.OpenReader(zipPath) + if err != nil { + return err + } + defer reader.Close() + + // Extract files directly into the folder that contains zipPath + extractDir := filepath.Dir(zipPath) + + for _, f := range reader.File { + outPath := filepath.Join(extractDir, f.Name) + + if f.FileInfo().IsDir() { + // Make sure nested directories exist + if err := os.MkdirAll(outPath, f.Mode()); err != nil { + return err + } + continue + } + + // Create necessary directories for this file + if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil { + return err + } + + // Extract the file + dstFile, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + fileInArchive, err := f.Open() + if err != nil { + dstFile.Close() + return err + } + _, copyErr := io.Copy(dstFile, fileInArchive) + fileInArchive.Close() + dstFile.Close() + if copyErr != nil { + return copyErr + } + } + + return nil +} + func main() { // Create the main window - window = giu.NewMasterWindow("Picocrypt", 318, 507, giu.MasterWindowFlagsNotResizable) + window = giu.NewMasterWindow("Picocrypt "+version[1:], 318, 507, giu.MasterWindowFlagsNotResizable) // Start the dialog module dialog.Init() From fee796845b3b4114003e19ef2aa113e1d53128aa Mon Sep 17 00:00:00 2001 From: Alex Verner <119082209+Retengart@users.noreply.github.com> Date: Sat, 25 Jan 2025 22:59:41 +0300 Subject: [PATCH 02/10] zip unpacking with status bar --- src/Picocrypt.go | 90 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 14 deletions(-) diff --git a/src/Picocrypt.go b/src/Picocrypt.go index 7bcbef8..06896b1 100644 --- a/src/Picocrypt.go +++ b/src/Picocrypt.go @@ -2119,12 +2119,27 @@ func work() { } if mode == "decrypt" && !kept && unpack { + showProgress = true // Turn on the progress popup + canCancel = true // Allow the user to cancel + progress = 0 + progressInfo = "" + popupStatus = "Preparing to unpack..." + giu.Update() + err := unpackArchive(outputFile) if err != nil { mainStatus = "Extraction failed: " + err.Error() mainStatusColor = RED giu.Update() } + + // Turn off the progress UI + showProgress = false + canCancel = false + progress = 0 + progressInfo = "" + popupStatus = "" + giu.Update() } // All done, reset the UI oldKept := kept @@ -2223,6 +2238,7 @@ func resetUI() { recombine = false compress = false delete = false + unpack = false keep = false kept = false @@ -2345,47 +2361,93 @@ func sizeify(size int64) string { } func unpackArchive(zipPath string) error { + // Open the .zip reader, err := zip.OpenReader(zipPath) if err != nil { return err } defer reader.Close() - // Extract files directly into the folder that contains zipPath + // Calculate total unpack size by summing each entry’s UncompressedSize64 + var totalSize int64 + for _, f := range reader.File { + totalSize += int64(f.UncompressedSize64) + } + + // Directory containing the .zip extractDir := filepath.Dir(zipPath) - for _, f := range reader.File { + // Setup progress tracking + var done int64 + startTime := time.Now() + + // Iterate over each file in the archive + for i, f := range reader.File { + // If user clicked "Cancel" elsewhere, stop + if !working { + return errors.New("operation canceled by user") + } + outPath := filepath.Join(extractDir, f.Name) + // Make directory if current entry is a folder if f.FileInfo().IsDir() { - // Make sure nested directories exist if err := os.MkdirAll(outPath, f.Mode()); err != nil { return err } continue } - // Create necessary directories for this file + // Otherwise create necessary parent directories if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil { return err } - // Extract the file + // Open the file inside the archive + fileInArchive, err := f.Open() + if err != nil { + return err + } + defer fileInArchive.Close() + + // Create/overwrite the destination file dstFile, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { return err } - fileInArchive, err := f.Open() - if err != nil { - dstFile.Close() - return err + + // Read from zip in chunks to update progress + buffer := make([]byte, MiB) // e.g., 1 MiB chunk + for { + if !working { + dstFile.Close() + os.Remove(outPath) // remove partial extraction if canceled + return errors.New("operation canceled by user") + } + n, readErr := fileInArchive.Read(buffer) + if n > 0 { + _, writeErr := dstFile.Write(buffer[:n]) + if writeErr != nil { + dstFile.Close() + return writeErr + } + + // Update "done" and recalc progress, speed, eta + done += int64(n) + progress, speed, eta = statify(done, totalSize, startTime) + progressInfo = fmt.Sprintf("%d/%d", i+1, len(reader.File)) + popupStatus = fmt.Sprintf("Unpacking at %.2f MiB/s (ETA: %s)", speed, eta) + giu.Update() + } + if readErr != nil { + if readErr == io.EOF { + break + } + dstFile.Close() + return readErr + } } - _, copyErr := io.Copy(dstFile, fileInArchive) - fileInArchive.Close() dstFile.Close() - if copyErr != nil { - return copyErr - } } return nil From af3e1748fb137398343e53bce226f35600f392e8 Mon Sep 17 00:00:00 2001 From: Alex Verner <119082209+Retengart@users.noreply.github.com> Date: Sun, 26 Jan 2025 00:47:55 +0300 Subject: [PATCH 03/10] Bump to 1.46: zip unpacking and version in title --- Changelog.md | 6 ++++++ VERSION | 2 +- src/Picocrypt.go | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index 045c9ba..99ff0c2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,12 @@
  • Migrate golang.org/x/crypto to standard library imports (https://github.com/golang/go/issues/65269)
  • +# v1.46 (Released 01/26/2025) + + # v1.45 (Released 12/05/2024) -# v1.46 (Released 01/26/2025) +# v1.46 (Released 01/29/2025) # v1.45 (Released 12/05/2024) diff --git a/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml b/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml index cad8990..b304d69 100644 --- a/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml +++ b/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml @@ -44,8 +44,8 @@ - - https://github.com/Picocrypt/Picocrypt/blob/main/Changelog.md#v146-released-01262025 + + https://github.com/Picocrypt/Picocrypt/blob/main/Changelog.md#v146-released-01292025
    • Added Picocrypt version to the window title.
    • From 445cd48e535912030b61ce1ea05acd580a612887 Mon Sep 17 00:00:00 2001 From: Alex Verner <119082209+Retengart@users.noreply.github.com> Date: Tue, 28 Jan 2025 12:22:19 +0300 Subject: [PATCH 10/10] fix: delete a partially extracted file if run out of space --- src/Picocrypt.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Picocrypt.go b/src/Picocrypt.go index 0f953a7..640d320 100644 --- a/src/Picocrypt.go +++ b/src/Picocrypt.go @@ -2438,6 +2438,7 @@ func unpackArchive(zipPath string) error { _, writeErr := dstFile.Write(buffer[:n]) if writeErr != nil { dstFile.Close() + os.Remove(dstFile.Name()) return writeErr }