| // Copyright 2018 Google Inc. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package main | |
| import ( | |
| "archive/zip" | |
| "flag" | |
| "fmt" | |
| "io" | |
| "io/ioutil" | |
| "log" | |
| "os" | |
| "path/filepath" | |
| "strings" | |
| ) | |
| var ( | |
| outputDir = flag.String("d", "", "output dir") | |
| outputFile = flag.String("l", "", "output list file") | |
| filter = flag.String("f", "", "optional filter pattern") | |
| zipPrefix = flag.String("zip-prefix", "", "optional prefix within the zip file to extract, stripping the prefix") | |
| ) | |
| func must(err error) { | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| } | |
| func writeFile(filename string, in io.Reader, perm os.FileMode) error { | |
| out, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) | |
| if err != nil { | |
| return err | |
| } | |
| _, err = io.Copy(out, in) | |
| if err != nil { | |
| out.Close() | |
| return err | |
| } | |
| return out.Close() | |
| } | |
| func main() { | |
| flag.Usage = func() { | |
| fmt.Fprintln(os.Stderr, "usage: zipsync -d <output dir> [-l <output file>] [-f <pattern>] [zip]...") | |
| flag.PrintDefaults() | |
| } | |
| flag.Parse() | |
| if *outputDir == "" { | |
| flag.Usage() | |
| os.Exit(1) | |
| } | |
| inputs := flag.Args() | |
| // For now, just wipe the output directory and replace its contents with the zip files | |
| // Eventually this could only modify the directory contents as necessary to bring it up | |
| // to date with the zip files. | |
| must(os.RemoveAll(*outputDir)) | |
| must(os.MkdirAll(*outputDir, 0777)) | |
| var files []string | |
| seen := make(map[string]string) | |
| if *zipPrefix != "" { | |
| *zipPrefix = filepath.Clean(*zipPrefix) + "/" | |
| } | |
| for _, input := range inputs { | |
| reader, err := zip.OpenReader(input) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| defer reader.Close() | |
| for _, f := range reader.File { | |
| name := f.Name | |
| if *zipPrefix != "" { | |
| if !strings.HasPrefix(name, *zipPrefix) { | |
| continue | |
| } | |
| name = strings.TrimPrefix(name, *zipPrefix) | |
| } | |
| if *filter != "" { | |
| if match, err := filepath.Match(*filter, filepath.Base(name)); err != nil { | |
| log.Fatal(err) | |
| } else if !match { | |
| continue | |
| } | |
| } | |
| if filepath.IsAbs(name) { | |
| log.Fatalf("%q in %q is an absolute path", name, input) | |
| } | |
| if prev, exists := seen[name]; exists { | |
| log.Fatalf("%q found in both %q and %q", name, prev, input) | |
| } | |
| seen[name] = input | |
| filename := filepath.Join(*outputDir, name) | |
| if f.FileInfo().IsDir() { | |
| must(os.MkdirAll(filename, 0777)) | |
| } else { | |
| must(os.MkdirAll(filepath.Dir(filename), 0777)) | |
| in, err := f.Open() | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| must(writeFile(filename, in, f.FileInfo().Mode())) | |
| in.Close() | |
| files = append(files, filename) | |
| } | |
| } | |
| } | |
| if *outputFile != "" { | |
| data := strings.Join(files, "\n") | |
| if len(files) > 0 { | |
| data += "\n" | |
| } | |
| must(ioutil.WriteFile(*outputFile, []byte(data), 0666)) | |
| } | |
| } |