From 1733ffbf2aff9a888bbfa44fae248e38257bb803 Mon Sep 17 00:00:00 2001 From: RuneImp Date: Thu, 16 Apr 2020 02:36:55 -0700 Subject: [PATCH] initial commit --- README.md | 22 ++++++ go.mod | 3 + main.go | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 README.md create mode 100644 go.mod create mode 100644 main.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..8b3fb91 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +GzipDate +======== + +Simple command line app for the specific creation and handling of Gzip archives. + + +Features +-------- + +* Create gzip archives for each of a list of files, adding a file system safe ISO-601 datatime value to the filename when creating the archive +* Extract the archived file from Gzip archives restoring their original names if available. The original name is always available for archives created by GzipDate. +* Does not delete source files by default. Though they can be automatically deleted with a command line switch. +* Always uses maximum compression when creating archives +* 100% compatible with gzip and gunzip tools + + +Rational +-------- + +I like making archives with the date in the name. Especially for game saves that get auto deleted like in Roguelike type games. It's also supper annoying to me that `gunzip` doesn't use the stored filename within the archive by default. I also like my tools to be smart about if a file is already compressed or not. Gzip will give an error if you try to recompress a file that already ends in `.gz`. GzipDate just uncompresses the file instead. + + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..53a5ad7 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/runeimp/gzipdate + +go 1.14 diff --git a/main.go b/main.go new file mode 100644 index 0000000..cc419ce --- /dev/null +++ b/main.go @@ -0,0 +1,206 @@ +package main + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path" + "time" +) + +/* + * CONSTANTS + */ +const ( + AppName = "GzipDate" + AppVersion = "1.0.0" + argDeleteUsage = "Delete the source file after successful processing" + argHelpUsage = "Display this help info" + argVersionUsage = "Display this apps version number" +) + +/* + * DERIVED CONSTANTS + */ +var ( + AppLabel = fmt.Sprintf("%s v%s", AppName, AppVersion) +) + +/* + * VARIABLES + */ +var ( + deleteSource = false + files []string + showHelp = false +) + +func main() { + + // Bespoke CLI Handler because flags is lame + for _, arg := range os.Args[1:] { + switch arg { + case "-d", "-del", "-delete": + deleteSource = true + case "-h", "-help": + helpOutput() + os.Exit(0) + case "-v", "-ver", "-version": + fmt.Println(AppLabel) + os.Exit(0) + default: + files = append(files, arg) + } + } + + if len(os.Args[1:]) == 0 { + helpOutput() + os.Exit(1) + } + + // fmt.Printf("Delete Source File? %t\n", deleteSource) + + var err error + + if len(files) > 0 { + for _, filename := range files { + // j := i + 1 + // fmt.Printf("%-3d %s\n", j, filename) + if path.Ext(filename) == ".gz" { + // fmt.Printf("File #%-3d gunzip %q\n", j, filename) + content, err := ioutil.ReadFile(filename) + if err != nil { + log.Fatal(err) + } + err = gzipDecode(content) + if err == nil && deleteSource { + fmt.Printf(" Deleting source: %q\n", filename) + os.Remove(filename) + } + } else { + // fmt.Printf("File #%-3d gzip %q\n", j, filename) + err = gzipEncode(filename) + if err == nil && deleteSource { + fmt.Printf(" Deleting source: %q\n", filename) + os.Remove(filename) + } + } + } + } +} + +func gzipDecode(content []byte) error { + newFilename := "filename.unknown" + + zr, err := gzip.NewReader(bytes.NewReader(content)) + if err != nil { + log.Fatal(err) + } + + if err := zr.Close(); err != nil { + log.Fatal(err) + } + + // fmt.Printf("Name: %s\nComment: %s\nModTime: %s\n\n", zr.Name, zr.Comment, zr.ModTime.UTC()) + // if _, err := io.Copy(os.Stdout, zr); err != nil { + // log.Fatal(err) + // } + + if len(zr.Name) > 0 { + newFilename = zr.Name + } + destination, err := os.Create(newFilename) + if err != nil { + log.Fatal(err) + } + defer destination.Close() + + nBytes, err := io.Copy(destination, zr) + if err != nil { + log.Fatal(err) + } + + mtime := zr.ModTime.UTC() + atime := zr.ModTime.UTC() + if err := os.Chtimes(newFilename, atime, mtime); err != nil { + log.Fatal(err) + } + + fmt.Printf("%d B written to %q\n", nBytes, zr.Name) + + return err +} + +func gzipEncode(filename string) error { + var ( + destination *os.File + err error + file *os.File + fileinfo os.FileInfo + nBytes int64 + ) + // content, err := ioutil.ReadFile(filename) + file, err = os.Open(filename) + if err != nil { + log.Fatal(err) + } + fileinfo, err = file.Stat() + content := make([]byte, fileinfo.Size()) + _, err = file.Read(content) + if err != nil { + log.Fatal(err) + } + + now := time.Now() + datetime := now.Format("2006-01-02_150405") + newFilename := fmt.Sprintf("%s_%s.gz", filename, datetime) + // fmt.Printf("gzipEncode() | filename = %q | content length = %d B | newFilename = %q\n", filename, len(content), newFilename) + + var buf bytes.Buffer + zw, zwError := gzip.NewWriterLevel(&buf, gzip.BestCompression) + if zwError != nil { + log.Fatal(zwError) + } + + // Setting the Header fields is optional. + zw.Name = filename + zw.Comment = fmt.Sprintf("Compressed with %s", AppLabel) + zw.ModTime = fileinfo.ModTime() + + if _, err := zw.Write(content); err != nil { + log.Fatal(err) + } + + if err := zw.Close(); err != nil { + log.Fatal(err) + } + + destination, err = os.Create(newFilename) + if err != nil { + log.Fatal(err) + } + defer destination.Close() + + nBytes, err = buf.WriteTo(destination) + fmt.Printf("Saving %d bytes from %q to %q\n", nBytes, filename, newFilename) + + return err +} + +func helpOutput() { + // flag.Usage() + // fmt.Println("----") + fmt.Printf("USAGE: gzipdate [OPTIONS] FILENAMES\n\nOPTIONS:\n") + // flag.PrintDefaults() + // fmt.Println() + + fmt.Printf(" -d | -del | -delete %s\n", argDeleteUsage) + fmt.Printf(" -h | -help %s\n", argHelpUsage) + fmt.Printf(" -v | -ver | -version %s\n", argVersionUsage) + fmt.Println() + fmt.Printf("Options may be interspersed with file names if so desired.\nThey are not position dependent.\n\n") +}