From 0c45e7b3019282e98c4f6f0646af3cb66cd2df42 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 13 May 2024 09:42:59 -0700 Subject: [PATCH] latest --- Makefile | 6 ++- README.md | 6 +++ go.mod | 8 +++- go.sum | 8 +++- dcf.go => pkg/dcf/dcf.go | 64 +++++++++++++++++------------- dcf_test.go => pkg/dcf/dcf_test.go | 0 helpers.go => pkg/dcf/helpers.go | 26 ++++++++++-- 7 files changed, 83 insertions(+), 35 deletions(-) rename dcf.go => pkg/dcf/dcf.go (73%) rename dcf_test.go => pkg/dcf/dcf_test.go (100%) rename helpers.go => pkg/dcf/helpers.go (55%) diff --git a/Makefile b/Makefile index 2bc4cc0..1eb937b 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,11 @@ test: go test -v ./... run: build - ./dcfinfo + DEBUG=1 ./dcfinfo build: + go env -w CGO_ENABLED="0" cd cmd/dcfinfo && go build -o ../../dcfinfo + +clean: + rm -f dcfinfo diff --git a/README.md b/README.md index 76ee17e..071d4d6 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,12 @@ The DCF specification is why your digital camera puts images and videos in I wanted to copy images off my memory cards for processing and like overengineering and reusable code. +# bugs + +does not yet handle "dcf file groups" where multiple objects share the same +index number. if you need this, send me a link to a zip or tar of a memory +card that uses it and i'll see what i can do. + # author sneak <[sneak@sneak.berlin](mailto:sneak@sneak.berlin)> diff --git a/go.mod b/go.mod index 0155051..706aef8 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,14 @@ module git.eeqj.de/sneak/dcf go 1.22.1 -require github.com/shirou/gopsutil v3.21.11+incompatible +require ( + github.com/dustin/go-humanize v1.0.1 + github.com/shirou/gopsutil v3.21.11+incompatible +) require ( - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/lmittmann/tint v1.0.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/sys v0.20.0 // indirect ) diff --git a/go.sum b/go.sum index 1753c58..1724945 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,15 @@ -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc= +github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/dcf.go b/pkg/dcf/dcf.go similarity index 73% rename from dcf.go rename to pkg/dcf/dcf.go index 46234de..cac3167 100644 --- a/dcf.go +++ b/pkg/dcf/dcf.go @@ -1,11 +1,14 @@ package dcf import ( + "crypto/sha256" + "fmt" + "io" "os" "path/filepath" "strings" + "github.com/dustin/go-humanize" - "github.com/shirou/gopsutil/disk" ) var imageExtensions = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".raw", ".arw"} @@ -15,16 +18,16 @@ var videoExtensions = []string{".mp4", ".mov", ".avi", ".mkv", ".wmv"} // images and videos in the root of a mounted filesystem. type DCFStore struct { RootDirectory string - Images []Image - Videos []Video + Images []*Image + Videos []*Video } // DCFObject represents a file in a DCF store. It is embedded in Image and Video types. -type DCFObject { +type DCFObject struct { DCFStoreRoot string - Path string - Size int64 - Extension string + Path string + Size int64 + Extension string } // Image represents an image file on a DCF store. @@ -37,7 +40,7 @@ type Video struct { DCFObject } -func (d *DCFObject) FullFilePath() { +func (d *DCFObject) FullFilePath() string { return filepath.Join(d.DCFStoreRoot, d.Path) } @@ -72,7 +75,12 @@ func (d *DCFStore) TotalSize() int64 { } func (d *DCFStore) String() string { - return fmt.Sprintf("DCFStore{RootDirectory: %s, Images: %d, Videos: %d, TotalSize: %s}", d.RootDirectory, d.ImagesCount(), d.VideosCount(), humanize.Bytes(uint64(d.TotalSize())) + return fmt.Sprintf("DCFStore{RootDirectory: %s, Images: %d, Videos: %d, TotalSize: %s}", + d.RootDirectory, + d.ImagesCount(), + d.VideosCount(), + humanize.Bytes(uint64(d.TotalSize())), + ) } func (v *Video) String() string { @@ -85,10 +93,10 @@ func (i *Image) String() string { // Hash() returns the SHA256 hash of the file as a hex string. func (d *DCFObject) Hash() (string, error) { - return pathToSHA256(i.FullFilePath()) + return pathToSHA256(d.FullFilePath()) } -function pathToSHA256 (path string) (string, error) { +func pathToSHA256(path string) (string, error) { file, err := os.Open(path) if err != nil { return "", err @@ -101,12 +109,12 @@ function pathToSHA256 (path string) (string, error) { return fmt.Sprintf("%x", hash.Sum(nil)), nil } - // GetDCFStores returns a list of DCF stores found on the system in the root of any mounted filesystems. +// the integer argument is the number of DCF stores to return. If the argument is 0, all DCF stores are returned. // It does not mount filesystems or search outside of filesystem root directories. It does, however, // walk the filesystems to find images and videos and populate the returned DCFStores. -func GetDCFStores() (*[]DCFStore, error) { - dcfStorePaths, err := findDCFMountPoints() +func GetDCFStores(requestedCount int) (*[]DCFStore, error) { + dcfStorePaths, err := findDCFMountPoints(requestedCount) if err != nil { return nil, err } @@ -114,8 +122,8 @@ func GetDCFStores() (*[]DCFStore, error) { for _, dcfStorePath := range dcfStorePaths { dcfStore := DCFStore{ RootDirectory: dcfStorePath, - Images: []Image{}, - Videos: []Video{}, + Images: make([]*Image, 0), + Videos: make([]*Video, 0), } err := filepath.Walk(dcfStorePath, func(path string, info os.FileInfo, err error) error { @@ -123,20 +131,20 @@ func GetDCFStores() (*[]DCFStore, error) { return err } if !info.IsDir() && isImageFile(path) { - dcfStore.Images = append(dcfStore.Images, Image{ - Path: strings.TrimPrefix(path, dcfStorePath+"/"), - Size: info.Size(), - Extension: filepath.Ext(path), - DCFStoreRoot: dcfStorePath, - }) + image := Image{} + image.Path = strings.TrimPrefix(path, dcfStorePath+"/") + image.Size = info.Size() + image.Extension = filepath.Ext(path) + image.DCFStoreRoot = dcfStorePath + dcfStore.Images = append(dcfStore.Images, &image) } if !info.IsDir() && isVideoFile(path) { - dcfStore.Videos = append(dcfStore.Videos, Video{ - Path: strings.TrimPrefix(path, dcfStorePath+"/") - Size: info.Size(), - Extension: filepath.Ext(path), - DCFStoreRoot: dcfStorePath, - }) + video := Video{} + video.Path = strings.TrimPrefix(path, dcfStorePath+"/") + video.Size = info.Size() + video.Extension = filepath.Ext(path) + video.DCFStoreRoot = dcfStorePath + dcfStore.Videos = append(dcfStore.Videos, &video) } return nil }) diff --git a/dcf_test.go b/pkg/dcf/dcf_test.go similarity index 100% rename from dcf_test.go rename to pkg/dcf/dcf_test.go diff --git a/helpers.go b/pkg/dcf/helpers.go similarity index 55% rename from helpers.go rename to pkg/dcf/helpers.go index fbfa667..1672e3a 100644 --- a/helpers.go +++ b/pkg/dcf/helpers.go @@ -1,8 +1,11 @@ package dcf import ( + "fmt" + "log/slog" "os" "path/filepath" + "runtime" "github.com/shirou/gopsutil/disk" ) @@ -21,25 +24,42 @@ func findAllMountPoints() ([]string, error) { return mountpoints, nil } -func findDCFMountPoints() ([]string, error) { +func dump(x interface{}) { + _, file, line, _ := runtime.Caller(1) + slog.Debug(fmt.Sprintf("%s:%d %#v\n", file, line, x)) +} + +// findDCFMountPoints returns a list of strings of the paths of mountpoints that +// contain a DCIM or PRIVATE directory, which is how we detect DCF stores. Its +// only argument is an integer of how many mountpoints to return. If the +// argument is 0, all mountpoints are returned. +func findDCFMountPoints(requestedCount int) ([]string, error) { filteredMountpoints := []string{} - var err error + var erro error mountpoints, err := findAllMountPoints() - if err != nil { + + if erro != nil { return nil, err } for _, mountpoint := range mountpoints { dcimPath := filepath.Join(mountpoint, "DCIM") privatePath := filepath.Join(mountpoint, "PRIVATE") + privatePath = filepath.Join(mountpoint, "M4ROOT") shouldKeep := false if _, err := os.Stat(dcimPath); err == nil { shouldKeep = true + slog.Debug(fmt.Sprintf("Found DCIM directory at %s\n", dcimPath)) } if _, err := os.Stat(privatePath); err == nil { shouldKeep = true + slog.Debug(fmt.Sprintf("Found M4ROOT directory at %s\n", privatePath)) } if shouldKeep { + slog.Debug(fmt.Sprintf("Keeping mountpoint %s\n", mountpoint)) filteredMountpoints = append(filteredMountpoints, mountpoint) + if (requestedCount > 0) && (len(filteredMountpoints)+1 >= requestedCount) { + break + } } } return filteredMountpoints, nil