Skip to content

archive/zip: add (*Writer).AddFS #54898

Description

@c9845

Zipping a directory is unclear and slightly confusing via the archive/zip package, mostly due to the way paths need to be handled and how directories are created. I suggest adding a func to handle zipping a directory simply to aid in implementation. I have had to implement, or educate others on implementing, similar code for various projects over the years. Furthermore, help via StackOverflow, golang-nuts, etc., isn't very clear either. There is clearly somewhat wide-spread confusion regarding zipping a directory.

Pseudo-code is below.

// Dir creates a zip from sourceDirPath and saves it to zipPath.  If a file at zipPath 
// already exists, an error is returned.
func Dir(sourceDirPath, zipPath string) (err error) {
       // Make sure zip file doesn't already exist at zipPath.
	_, err = os.Stat(zipFileAbs)
	if err == nil {
		return os.ErrExist
	} else if !os.IsNotExist(err) {
		return
	}

	// Create the zip file.
	zipFile, err := os.Create(zipFileAbs)
	if err != nil {
		return
	}
	defer zipFile.Close()

	// Initialize the zip writer.
	z := zip.NewWriter(zipFile)
	defer z.Close()

	// Zip up the sourceDirPath, files and directories, recursively.
	err = filepath.WalkDir(sourceDirPath, func(path string, d fs.DirEntry, err error) error {
		// Error with path.
		if err != nil {
			return err
		}

		// Skip the source directory root. This can be ignored because the source
		// directory root is just the root of the zip file.
		if sourceDir == path {
			return nil
		}

		// Skip directories. Directories will be created automatically from paths to
		// each file to zip up.
		if d.IsDir() {
			return nil
		}

		// Handle formatting path name properly for use in zip file. Paths must be 
                // relative, not start with a slash character, and must use forward slashes,
                // even on Windows.  See: https://pkg.go.dev/archive/zip#Writer.Create
		//
                // Directories are created automatically based on the subdirectories provided
                // in each file's path.
		zipPath := strings.Replace(path, sourceDir, "", 1)
		zipPath = strings.TrimPrefix(zipPath, string(filepath.Separator))
		zipPath = filepath.ToSlash(zipPath)

		// Open the path to read from.
		f, err := os.Open(path)
		if err != nil {
			return err
		}
		defer f.Close()

		// Create the path in the zip.
		w, err := z.Create(zipPath)
		if err != nil {
			return err
		}

		// Write the source file into the zip at path from Create().
		_, err = io.Copy(w, f)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return
	}

	// Zip file created.
	err = z.Close()
	if err != nil {
		return
	}

	return
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions