]> Sergey Matveev's repositories - meta4ra.git/blobdiff - internal/hasher.go
*-hashes-detect, *-hash, *-check -stdin/-all-hashes, optional builtins
[meta4ra.git] / internal / hasher.go
diff --git a/internal/hasher.go b/internal/hasher.go
new file mode 100644 (file)
index 0000000..da54b7d
--- /dev/null
@@ -0,0 +1,127 @@
+// meta4ra -- Metalink 4.0 utilities
+// Copyright (C) 2021-2024 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package internal
+
+import (
+       "bytes"
+       "errors"
+       "io"
+       "os/exec"
+       "strings"
+)
+
+type Hasher struct {
+       Names []string
+       Cmds  []*exec.Cmd
+       Ins   []io.WriteCloser
+       Outs  []io.ReadCloser
+}
+
+func NewHasher(hashes string) (*Hasher, error) {
+       h := Hasher{}
+       for _, hc := range strings.Split(hashes, ",") {
+               cols := strings.SplitN(hc, ":", 2)
+               name, cmdline := cols[0], cols[1]
+               if cmdline == BuiltinCmd {
+                       newHash, exists := BuiltinHashes[name]
+                       if !exists {
+                               return nil, errors.New("no builtin hash: " + name)
+                       }
+                       b := &BuiltinHasher{h: newHash()}
+                       h.Names = append(h.Names, name)
+                       h.Ins = append(h.Ins, b)
+                       h.Outs = append(h.Outs, b)
+                       h.Cmds = append(h.Cmds, nil)
+               } else {
+                       cmd := exec.Command("/bin/sh", "-e", "-c", cmdline)
+                       in, err := cmd.StdinPipe()
+                       if err != nil {
+                               return &h, err
+                       }
+                       out, err := cmd.StdoutPipe()
+                       if err != nil {
+                               return &h, err
+                       }
+                       h.Names = append(h.Names, name)
+                       h.Ins = append(h.Ins, in)
+                       h.Outs = append(h.Outs, out)
+                       h.Cmds = append(h.Cmds, cmd)
+               }
+       }
+       return &h, nil
+}
+
+func (h *Hasher) Start() (err error) {
+       for _, cmd := range h.Cmds {
+               if cmd == nil {
+                       continue
+               }
+               err = cmd.Start()
+               if err != nil {
+                       return
+               }
+       }
+       return nil
+}
+
+func (h *Hasher) Stop() {
+       for _, cmd := range h.Cmds {
+               if cmd == nil || cmd.Process == nil {
+                       continue
+               }
+               cmd.Process.Kill()
+               cmd.Process.Release()
+       }
+}
+
+func (h *Hasher) Write(p []byte) (n int, rerr error) {
+       errs := make(chan error)
+       for _, in := range h.Ins {
+               go func(in io.WriteCloser) {
+                       _, err := io.Copy(in, bytes.NewReader(p))
+                       errs <- err
+               }(in)
+       }
+       for i := 0; i < len(h.Names); i++ {
+               if err := <-errs; err != nil {
+                       rerr = err
+               }
+       }
+       n = len(p)
+       return
+}
+
+func (h *Hasher) Sums() (sums []Hash, err error) {
+       sums = make([]Hash, 0, len(h.Names))
+       for i, name := range h.Names {
+               if err = h.Ins[i].Close(); err != nil {
+                       return
+               }
+               var out []byte
+               out, err = io.ReadAll(h.Outs[i])
+               if err != nil {
+                       return
+               }
+               if cmd := h.Cmds[i]; cmd != nil {
+                       if err = h.Cmds[i].Wait(); err != nil {
+                               return
+                       }
+               }
+               cols := strings.Fields(strings.TrimSuffix(string(out), "\n"))
+               sums = append(sums, Hash{Type: name, Hash: cols[0]})
+       }
+       return
+}