From c3907ecca1bfdc19b80bfbc074a23ec9d75d6f42 Mon Sep 17 00:00:00 2001 From: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com> Date: Sat, 3 Feb 2024 22:12:08 +0300 Subject: [PATCH] Add CI with Linting and Testing (#142) * Refactor codebase for improved functionality, error handling, and data manipulation Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * Add Dependabot configuration file - Added a new file `.github/dependbot.yml` to the codebase. - The file includes configuration for Dependabot. - Added settings for two package ecosystems: `github-actions` and `gomod`. - Both ecosystems are set to be checked on a weekly basis. Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * Add CI pipeline configuration for continuous integration A new file named "ci.yml" has been added to the GitHub repository. This file contains a CI pipeline configuration that enables continuous integration. The pipeline is triggered by pull requests and pushes to the main branch. It runs on an Ubuntu environment and includes several steps such as checking out code, setting up Go, running golangci-lint, and building binaries for different operating systems and architectures. Additionally, the configuration includes build commands for different operating systems and architectures, followed by a test command for running tests with coverage analysis. Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * Refactor CI workflow to remove unnecessary build steps for darwin/arm platforms Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * Rename .github/dependbot.yml to .github/dependabot.yml. Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * Refactor linter settings in .golangci.yml Removed the errcheck and stylecheck linters from the list of enabled linters in .golangci.yml. Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --------- Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- .github/dependabot.yml | 11 +++++++ .github/workflows/ci.yml | 53 +++++++++++++++++++++++++++++++ .golangci.yml | 49 ++++++++++++++++++++++++++++ browser/files/localassets.go | 7 +--- browser/files/localassets_test.go | 7 ++-- browser/gp/googlephotos.go | 3 -- browser/gp/json_test.go | 2 -- browser/gp/testgp_samples_test.go | 11 +++---- browser/gp/testgp_test.go | 8 +---- browser/readersearch.go | 1 - cmdduplicate/duplicate.go | 13 +++----- cmdtool/cmdalbum/cmdalbum.go | 3 +- cmdtool/cmdtool.go | 3 +- cmdupload/assets.go | 4 +-- cmdupload/configuration.go | 1 - cmdupload/stringlist_test.go | 2 -- cmdupload/upload.go | 15 ++------- cmdupload/upload_test.go | 9 +++--- helpers/docker/docker.go | 14 +++++--- helpers/fshelper/parseArgs.go | 3 +- helpers/fshelper/readjson.go | 4 +-- helpers/fshelper/removefs.go | 1 - helpers/gen/slices.go | 4 +-- helpers/myflag/boolfn.go | 2 -- helpers/stacking/stack.go | 19 ++++------- helpers/stacking/statck_test.go | 1 - helpers/tzone/timezone.go | 3 +- immich/albums.go | 3 -- immich/asset.go | 16 ++++------ immich/call.go | 3 +- immich/call_test.go | 1 - immich/daterange.go | 18 ++++++----- immich/daterange_test.go | 1 - immich/metadata/direct.go | 2 -- immich/metadata/exif.go | 3 +- immich/metadata/namesdate.go | 1 - immich/metadata/quicktime.go | 4 +-- immich/metadata/search.go | 1 - immich/metadata/sidecar.go | 2 -- immich/trace.go | 1 - logger/journal.go | 5 ++- logger/log.go | 4 +-- main.go | 12 +++---- 43 files changed, 188 insertions(+), 142 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .golangci.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..603f653 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cb9ec05 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,53 @@ +name: CI Pipeline + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + ci: + name: Continuous Integration + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + cache-dependency-path: "go.sum" + + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest + + - name: Build binary + run: | + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o immich-linux-amd64.exe -ldflags="-s -w -extldflags=-static" main.go + CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o immich-linux-arm64.exe -ldflags="-s -w -extldflags=-static" main.go + CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -o immich-linux-arm6.exe -ldflags="-s -w -extldflags=-static" main.go + CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -o immich-linux-arm7.exe -ldflags="-s -w -extldflags=-static" main.go + + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o immich-windows-amd64.exe -ldflags="-s -w -extldflags=-static" main.go + CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -o immich-windows-arm64.exe -ldflags="-s -w -extldflags=-static" main.go + CGO_ENABLED=0 GOOS=windows GOARCH=arm GOARM=6 go build -o immich-windows-arm6.exe -ldflags="-s -w -extldflags=-static" main.go + CGO_ENABLED=0 GOOS=windows GOARCH=arm GOARM=7 go build -o immich-windows-arm7.exe -ldflags="-s -w -extldflags=-static" main.go + + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o immich-darwin-amd64.exe -ldflags="-s -w -extldflags=-static" main.go + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o immich-darwin-arm64.exe -ldflags="-s -w -extldflags=-static" main.go + + CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -o immich-freebsd-amd64.exe -ldflags="-s -w -extldflags=-static" main.go + CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build -o immich-freebsd-arm64.exe -ldflags="-s -w -extldflags=-static" main.go + CGO_ENABLED=0 GOOS=freebsd GOARCH=arm GOARM=6 go build -o immich-freebsd-arm6.exe -ldflags="-s -w -extldflags=-static" main.go + CGO_ENABLED=0 GOOS=freebsd GOARCH=arm GOARM=7 go build -o immich-freebsd-arm7.exe -ldflags="-s -w -extldflags=-static" main.go + + - name: Run tests + run: | + go test --race -v -count=1 -coverprofile=coverage.out ./... diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..3b5d9c0 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,49 @@ +run: + timeout: 3m + +issues: + max-issues-per-linter: 100 + max-same-issues: 100 + +linters-settings: + gocritic: + enabled-checks: + - captLocal + - singleCaseSwitch + - switchTrue + - httpNoBody + - emptyStringTest + - builtinShadow + - exposedSyncMutex + enabled-tags: + - diagnostic + disabled-tags: + - performance + - style + - experimental + - opinionated + +linters: + disable-all: true + enable: + - gocritic + - gosimple + - govet + - ineffassign + - misspell + - whitespace + - gci + - gofmt + - goimports + - loggercheck + - asasalint + - contextcheck + - decorder + - dogsled + - errchkjson + - exportloopref + - ginkgolinter + - gocheckcompilerdirectives + - goprintffuncname + - mirror + - nakedret diff --git a/browser/files/localassets.go b/browser/files/localassets.go index 3d8ce5a..09cdd13 100644 --- a/browser/files/localassets.go +++ b/browser/files/localassets.go @@ -54,7 +54,6 @@ func (la *LocalAssetBrowser) Browse(ctx context.Context) chan *browser.LocalAsse } } return nil - }) if err != nil { // Check if the context has been cancelled before sending the error @@ -68,7 +67,6 @@ func (la *LocalAssetBrowser) Browse(ctx context.Context) chan *browser.LocalAsse } } } - }(ctx) return fileChan @@ -151,7 +149,6 @@ func (la *LocalAssetBrowser) handleFolder(ctx context.Context, fsys fs.FS, fileC default: fileChan <- &f } - } return nil } @@ -174,7 +171,6 @@ func (la *LocalAssetBrowser) checkSidecar(fsys fs.FS, f *browser.LocalAssetFile, la.log.AddEntry(name, logger.ASSOCIATED_META, "") return true } - } } return false @@ -193,8 +189,7 @@ func baseNames(n string) []string { return names } n = strings.TrimSuffix(n, ext) - names = append(names, n) - names = append(names, n+".*") + names = append(names, n, n+".*") ext = path.Ext(n) } } diff --git a/browser/files/localassets_test.go b/browser/files/localassets_test.go index 8f1d4a5..622409f 100644 --- a/browser/files/localassets_test.go +++ b/browser/files/localassets_test.go @@ -8,11 +8,10 @@ import ( "sort" "testing" - "github.com/simulot/immich-go/browser/files" - "github.com/simulot/immich-go/logger" - "github.com/kr/pretty" "github.com/psanford/memfs" + "github.com/simulot/immich-go/browser/files" + "github.com/simulot/immich-go/logger" ) type inMemFS struct { @@ -91,8 +90,6 @@ func TestLocalAssets(t *testing.T) { t.Errorf("difference\n") pretty.Ldiff(t, c.expected, results) } - }) - } } diff --git a/browser/gp/googlephotos.go b/browser/gp/googlephotos.go index b133046..740870f 100644 --- a/browser/gp/googlephotos.go +++ b/browser/gp/googlephotos.go @@ -11,7 +11,6 @@ import ( "unicode/utf8" "github.com/simulot/immich-go/browser" - "github.com/simulot/immich-go/helpers/fshelper" "github.com/simulot/immich-go/helpers/gen" "github.com/simulot/immich-go/logger" @@ -94,7 +93,6 @@ func (to *Takeout) passOne(ctx context.Context) error { func (to *Takeout) passOneFsWalk(ctx context.Context, w fs.FS) error { err := fs.WalkDir(w, ".", func(name string, d fs.DirEntry, err error) error { - if err != nil { return err } @@ -474,7 +472,6 @@ func (to *Takeout) passTwoWalk(ctx context.Context, w fs.FS, assetChan chan *bro } return nil }) - } // googleMDToAsset makes a localAssetFile based on the google metadata diff --git a/browser/gp/json_test.go b/browser/gp/json_test.go index c8f18ce..c5f10eb 100644 --- a/browser/gp/json_test.go +++ b/browser/gp/json_test.go @@ -7,7 +7,6 @@ import ( ) func TestPresentFields(t *testing.T) { - tcs := []struct { name string json string @@ -132,5 +131,4 @@ func TestPresentFields(t *testing.T) { } }) } - } diff --git a/browser/gp/testgp_samples_test.go b/browser/gp/testgp_samples_test.go index 119e982..0c57ac9 100644 --- a/browser/gp/testgp_samples_test.go +++ b/browser/gp/testgp_samples_test.go @@ -10,9 +10,8 @@ import ( "strings" "time" - "github.com/simulot/immich-go/immich/metadata" - "github.com/psanford/memfs" + "github.com/simulot/immich-go/immich/metadata" ) type inMemFS struct { @@ -36,9 +35,9 @@ func (mfs *inMemFS) addFile(name string, content []byte) *inMemFS { return mfs } -func (mfs *inMemFS) addImage(name string, len int) *inMemFS { - b := make([]byte, len) - for i := 0; i < len; i++ { +func (mfs *inMemFS) addImage(name string, length int) *inMemFS { + b := make([]byte, length) + for i := 0; i < length; i++ { b[i] = byte(i % 256) } mfs.addFile(name, b) @@ -124,7 +123,6 @@ func simpleYear() *inMemFS { addImage("Takeout/Google Photos/Photos from 2023/PXL_20230922_144936660.jpg", 10). addJSONImage("Takeout/Google Photos/Photos from 2023/PXL_20230922_144956000.jpg.json", "PXL_20230922_144956000.jpg"). addImage("Takeout/Google Photos/Photos from 2023/PXL_20230922_144956000.jpg", 20) - } func simpleAlbum() *inMemFS { @@ -190,7 +188,6 @@ func titlesWithForbiddenChars() *inMemFS { addImage("Takeout/Google Photos/Photos from 2012/27_06_12 - 1.mov", 52). addJSONImage("Takeout/Google Photos/Photos from 2012/27_06_12 - 1.json", "27/06/12 - 1"). addImage("Takeout/Google Photos/Photos from 2012/27_06_12 - 1.jpg", 24) - } func namesIssue39() *inMemFS { diff --git a/browser/gp/testgp_test.go b/browser/gp/testgp_test.go index a1ca321..f051d9f 100644 --- a/browser/gp/testgp_test.go +++ b/browser/gp/testgp_test.go @@ -6,9 +6,8 @@ import ( "reflect" "testing" - "github.com/simulot/immich-go/logger" - "github.com/kr/pretty" + "github.com/simulot/immich-go/logger" ) func TestBrowse(t *testing.T) { @@ -97,7 +96,6 @@ func TestBrowse(t *testing.T) { } for _, c := range tc { t.Run(c.name, func(t *testing.T) { - fsys := c.gen() if fsys.err != nil { t.Error(fsys.err) @@ -122,11 +120,9 @@ func TestBrowse(t *testing.T) { } }) } - } func TestAlbums(t *testing.T) { - type album map[string][]fileResult tc := []struct { name string @@ -173,7 +169,6 @@ func TestAlbums(t *testing.T) { for _, c := range tc { t.Run(c.name, func(t *testing.T) { - ctx := context.Background() fsys := c.gen() if fsys.err != nil { @@ -203,7 +198,6 @@ func TestAlbums(t *testing.T) { t.Errorf("difference\n") pretty.Ldiff(t, c.albums, albums) } - }) } } diff --git a/browser/readersearch.go b/browser/readersearch.go index c984b05..0a2b820 100644 --- a/browser/readersearch.go +++ b/browser/readersearch.go @@ -47,7 +47,6 @@ func searchPattern(r io.Reader, pattern []byte, maxDataLen int) ([]byte, error) } func seekReaderAtPattern(r io.Reader, pattern []byte) (io.Reader, error) { - var err error pos := 0 // Create a buffer to hold the chunk of dataZ diff --git a/cmdduplicate/duplicate.go b/cmdduplicate/duplicate.go index 3910d37..2cad238 100644 --- a/cmdduplicate/duplicate.go +++ b/cmdduplicate/duplicate.go @@ -103,11 +103,8 @@ func DuplicateCommand(ctx context.Context, ic *immich.ImmichClient, log *logger. return false } c = strings.Compare(keys[i].Name, keys[j].Name) - switch c { - case -1: - return true - } - return false + + return c == -1 }) for _, k := range keys { @@ -118,12 +115,12 @@ func DuplicateCommand(ctx context.Context, ic *immich.ImmichClient, log *logger. l := app.assetsByBaseAndDate[k] app.logger.OK("There are %d copies of the asset %s, taken on %s ", len(l), k.Name, l[0].ExifInfo.DateTimeOriginal.Format(time.RFC3339)) albums := []immich.AlbumSimplified{} - delete := []string{} + assetsToDelete := []string{} sort.Slice(l, func(i, j int) bool { return l[i].ExifInfo.FileSizeInByte < l[j].ExifInfo.FileSizeInByte }) for p, a := range l { if p < len(l)-1 { log.OK(" delete %s %dx%d, %s, %s", a.OriginalFileName, a.ExifInfo.ExifImageWidth, a.ExifInfo.ExifImageHeight, ui.FormatBytes(a.ExifInfo.FileSizeInByte), a.OriginalPath) - delete = append(delete, a.ID) + assetsToDelete = append(assetsToDelete, a.ID) r, err := app.Immich.GetAssetAlbums(ctx, a.ID) if err != nil { log.Error("Can't get asset's albums: %s", err.Error()) @@ -143,7 +140,7 @@ func DuplicateCommand(ctx context.Context, ic *immich.ImmichClient, log *logger. } } if yes { - err = app.Immich.DeleteAssets(ctx, delete, false) + err = app.Immich.DeleteAssets(ctx, assetsToDelete, false) if err != nil { log.Error("Can't delete asset: %s", err.Error()) } else { diff --git a/cmdtool/cmdalbum/cmdalbum.go b/cmdtool/cmdalbum/cmdalbum.go index 0ba6e9a..5be2a87 100644 --- a/cmdtool/cmdalbum/cmdalbum.go +++ b/cmdtool/cmdalbum/cmdalbum.go @@ -18,8 +18,7 @@ func AlbumCommand(ctx context.Context, ic *immich.ImmichClient, log *logger.Log, cmd := args[0] args = args[1:] - switch cmd { - case "delete": + if cmd == "delete" { return deleteAlbum(ctx, ic, log, args) } } diff --git a/cmdtool/cmdtool.go b/cmdtool/cmdtool.go index 0edffe4..34a6e0e 100644 --- a/cmdtool/cmdtool.go +++ b/cmdtool/cmdtool.go @@ -14,8 +14,7 @@ func CommandTool(ctx context.Context, ic *immich.ImmichClient, logger *logger.Lo cmd := args[0] args = args[1:] - switch cmd { - case "album": + if cmd == "album" { return cmdalbum.AlbumCommand(ctx, ic, logger, args) } } diff --git a/cmdupload/assets.go b/cmdupload/assets.go index 61a4f28..a407336 100644 --- a/cmdupload/assets.go +++ b/cmdupload/assets.go @@ -41,9 +41,9 @@ func (ai *AssetIndex) Len() int { return len(ai.assets) } -func (ai *AssetIndex) AddLocalAsset(la *browser.LocalAssetFile, ImmichID string) { +func (ai *AssetIndex) AddLocalAsset(la *browser.LocalAssetFile, immichID string) { sa := &immich.Asset{ - ID: ImmichID, + ID: immichID, DeviceAssetID: la.DeviceAssetID(), OriginalFileName: strings.TrimSuffix(path.Base(la.Title), path.Ext(la.Title)), ExifInfo: immich.ExifInfo{ diff --git a/cmdupload/configuration.go b/cmdupload/configuration.go index fca95d2..958a7b6 100644 --- a/cmdupload/configuration.go +++ b/cmdupload/configuration.go @@ -28,7 +28,6 @@ func (c *Configuration) IsValid() error { c.ExcludeExtensions, _ = checkExtensions(c.ExcludeExtensions) return jerr - } func checkExtensions(l StringList) (StringList, error) { diff --git a/cmdupload/stringlist_test.go b/cmdupload/stringlist_test.go index 210aae7..d6a7c6e 100644 --- a/cmdupload/stringlist_test.go +++ b/cmdupload/stringlist_test.go @@ -3,7 +3,6 @@ package cmdupload import "testing" func TestStringList_Include(t *testing.T) { - tests := []struct { name string sl StringList @@ -51,7 +50,6 @@ func TestStringList_Include(t *testing.T) { } func TestStringList_Exclude(t *testing.T) { - tests := []struct { name string sl StringList diff --git a/cmdupload/upload.go b/cmdupload/upload.go index f68f412..b89967e 100644 --- a/cmdupload/upload.go +++ b/cmdupload/upload.go @@ -23,7 +23,6 @@ import ( "github.com/simulot/immich-go/helpers/stacking" "github.com/simulot/immich-go/immich" "github.com/simulot/immich-go/immich/metadata" - "github.com/simulot/immich-go/logger" ) @@ -196,7 +195,6 @@ func NewUpCmd(ctx context.Context, ic iClient, log logger.Logger, args []string) app.AssetIndex.ReIndex() return &app, err - } func UploadCommand(ctx context.Context, ic iClient, log logger.Logger, args []string) error { @@ -205,7 +203,6 @@ func UploadCommand(ctx context.Context, ic iClient, log logger.Logger, args []st return err } return app.Run(ctx, app.fsys) - } func (app *UpCmd) journalAsset(a *browser.LocalAssetFile, action logger.Action, comment ...string) { @@ -213,7 +210,6 @@ func (app *UpCmd) journalAsset(a *browser.LocalAssetFile, action logger.Action, } func (app *UpCmd) Run(ctx context.Context, fsyss []fs.FS) error { - var browser browser.Browser var err error @@ -498,7 +494,6 @@ func (app *UpCmd) handleAsset(ctx context.Context, a *browser.LocalAssetFile) er } return nil - } func (app *UpCmd) isInAlbum(a *browser.LocalAssetFile, album string) bool { @@ -527,7 +522,6 @@ func (app *UpCmd) UploadAsset(ctx context.Context, a *browser.LocalAssetFile) (s var resp immich.AssetResponse var err error if !app.DryRun { - if app.ForceSidecar { sc := metadata.SideCar{} sc.DateTaken = a.DateTaken @@ -553,7 +547,6 @@ func (app *UpCmd) UploadAsset(ctx context.Context, a *browser.LocalAssetFile) (s if app.CreateStacks { app.stacks.ProcessAsset(resp.ID, a.FileName, a.DateTaken) } - } else { app.journalAsset(a, logger.SERVER_DUPLICATE, "already on the server") } @@ -574,12 +567,12 @@ func (app *UpCmd) albumName(al browser.LocalAlbum) string { return Name } -func (app *UpCmd) AddToAlbum(ID string, album string) { +func (app *UpCmd) AddToAlbum(id string, album string) { l := app.updateAlbums[album] if l == nil { l = map[string]any{} } - l[ID] = nil + l[id] = nil app.updateAlbums[album] = l } @@ -596,7 +589,6 @@ func (app *UpCmd) DeleteLocalAssets() error { } else { app.Journal.Warning("file %q not deleted, dry run mode", a.Title) } - } return nil } @@ -619,7 +611,6 @@ func (app *UpCmd) ManageAlbums(ctx context.Context) error { return fmt.Errorf("can't get the album list from the server: %w", err) } for album, list := range app.updateAlbums { - found := false for _, sal := range serverAlbums { if sal.AlbumName == album { @@ -728,7 +719,6 @@ func (ai *AssetIndex) adviceIDontKnow(la *browser.LocalAssetFile) *Advice { } func (ai *AssetIndex) adviceSameOnServer(sa *immich.Asset) *Advice { - return &Advice{ Advice: SameOnServer, Message: fmt.Sprintf("An asset with the same name:%q, date:%q and size:%s exists on the server. No need to upload.", sa.OriginalFileName, sa.ExifInfo.DateTimeOriginal.Format(time.DateTime), formatBytes(sa.ExifInfo.FileSizeInByte)), @@ -791,7 +781,6 @@ func (ai *AssetIndex) ShouldUpload(la *browser.LocalAssetFile) (*Advice, error) size := int(la.Size()) if err != nil { return ai.adviceIDontKnow(la), nil - } for _, sa = range l { compareDate := compareDate(dateTaken, sa.ExifInfo.DateTimeOriginal.Time) diff --git a/cmdupload/upload_test.go b/cmdupload/upload_test.go index 8982d09..2291257 100644 --- a/cmdupload/upload_test.go +++ b/cmdupload/upload_test.go @@ -9,12 +9,11 @@ import ( "slices" "testing" + "github.com/kr/pretty" "github.com/simulot/immich-go/browser" "github.com/simulot/immich-go/helpers/gen" "github.com/simulot/immich-go/immich" "github.com/simulot/immich-go/logger" - - "github.com/kr/pretty" ) type stubIC struct { @@ -38,15 +37,15 @@ func (c *stubIC) AddAssetToAlbum(context.Context, string, []string) ([]immich.Up func (c *stubIC) CreateAlbum(context.Context, string, []string) (immich.AlbumSimplified, error) { return immich.AlbumSimplified{}, nil } -func (c *stubIC) UpdateAssets(ctx context.Context, IDs []string, isArchived bool, isFavorite bool, latitude float64, longitude float64, removeParent bool, stackParentId string) error { +func (c *stubIC) UpdateAssets(ctx context.Context, ids []string, isArchived bool, isFavorite bool, latitude float64, longitude float64, removeParent bool, stackParentId string) error { return nil } -func (c *stubIC) StackAssets(ctx context.Context, cover string, IDs []string) error { +func (c *stubIC) StackAssets(ctx context.Context, cover string, ids []string) error { return nil } -func (c *stubIC) UpdateAsset(ctx context.Context, ID string, a *browser.LocalAssetFile) (*immich.Asset, error) { +func (c *stubIC) UpdateAsset(ctx context.Context, id string, a *browser.LocalAssetFile) (*immich.Asset, error) { return nil, nil } diff --git a/helpers/docker/docker.go b/helpers/docker/docker.go index eb96acc..5b8f798 100644 --- a/helpers/docker/docker.go +++ b/helpers/docker/docker.go @@ -96,14 +96,12 @@ func (d *DockerConnect) connect(ctx context.Context, host string, container stri if l[:len(l)-1] == d.Container { return nil } - } return fmt.Errorf("container 'immich_server' not found: %w", err) } // Download a file from the docker container func (d *DockerConnect) Download(ctx context.Context, hostFile string) (io.Reader, error) { - cmd, err := d.proxy.docker(ctx, "cp", d.Container+":"+hostFile, "-") if err != nil { return nil, err @@ -173,6 +171,9 @@ func (d *DockerConnect) Upload(ctx context.Context, file string, size int64, r i return } _, err = io.Copy(tw, r) + if err != nil { + return + } }() err = cmd.Start() @@ -194,6 +195,9 @@ func (d *DockerConnect) BatchUpload(ctx context.Context, dir string) (*batchUplo } out, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } go func() { io.Copy(os.Stdout, out) }() @@ -212,13 +216,12 @@ func (d *DockerConnect) BatchUpload(ctx context.Context, dir string) (*batchUplo var err error tw := tar.NewWriter(mw) defer func() { - //f.Close() + // f.Close() tw.Close() in.Close() cmd.Wait() }() for { - select { case <-ctx.Done(): return @@ -286,6 +289,9 @@ func (d *DockerConnect) Command(ctx context.Context, args ...string) (string, er buffOut := bytes.NewBuffer(nil) out, err := cmd.StdoutPipe() + if err != nil { + return "", err + } go func() { io.Copy(buffOut, out) }() diff --git a/helpers/fshelper/parseArgs.go b/helpers/fshelper/parseArgs.go index c42fc47..22374af 100644 --- a/helpers/fshelper/parseArgs.go +++ b/helpers/fshelper/parseArgs.go @@ -57,8 +57,7 @@ func ParsePath(args []string, googlePhoto bool) ([]fs.FS, error) { for _, f := range p.files { d, b := filepath.Split(f) d = filepath.Clean(d) - l := append(p.paths[d], b) - p.paths[d] = l + p.paths[d] = append(p.paths[d], b) } for pa, l := range p.paths { diff --git a/helpers/fshelper/readjson.go b/helpers/fshelper/readjson.go index 59d75c2..52c1397 100644 --- a/helpers/fshelper/readjson.go +++ b/helpers/fshelper/readjson.go @@ -8,9 +8,9 @@ import ( // readJSON reads a JSON file from the provided file system (fs.FS) // with the given name and unmarshals it into the provided type T. -func ReadJSON[T any](FSys fs.FS, name string) (*T, error) { +func ReadJSON[T any](fsys fs.FS, name string) (*T, error) { var object T - b, err := fs.ReadFile(FSys, name) + b, err := fs.ReadFile(fsys, name) if err != nil { return nil, err } diff --git a/helpers/fshelper/removefs.go b/helpers/fshelper/removefs.go index db53ac0..e230844 100644 --- a/helpers/fshelper/removefs.go +++ b/helpers/fshelper/removefs.go @@ -28,7 +28,6 @@ type dirRemoveFS struct { } func DirRemoveFS(name string) fs.FS { - fsys := &dirRemoveFS{ FS: os.DirFS(name), dir: name, diff --git a/helpers/gen/slices.go b/helpers/gen/slices.go index f221b47..c9aa466 100644 --- a/helpers/gen/slices.go +++ b/helpers/gen/slices.go @@ -1,9 +1,9 @@ package gen -func DeleteItem[T comparable](s []T, delete T) []T { +func DeleteItem[T comparable](s []T, item T) []T { r := make([]T, 0, len(s)) for i := range s { - if s[i] != delete { + if s[i] != item { r = append(r, s[i]) } } diff --git a/helpers/myflag/boolfn.go b/helpers/myflag/boolfn.go index 5dc0322..1bdf47c 100644 --- a/helpers/myflag/boolfn.go +++ b/helpers/myflag/boolfn.go @@ -12,7 +12,6 @@ import ( func BoolFlagFn(b *bool, defaultValue bool) func(string) error { *b = defaultValue return func(v string) error { - switch strings.ToLower(v) { case "": *b = true @@ -26,5 +25,4 @@ func BoolFlagFn(b *bool, defaultValue bool) func(string) error { return err } } - } diff --git a/helpers/stacking/stack.go b/helpers/stacking/stack.go index 87fcfb5..6f0e506 100644 --- a/helpers/stacking/stack.go +++ b/helpers/stacking/stack.go @@ -45,10 +45,9 @@ func NewStackBuilder() *StackBuilder { sb.dateRange.Set("1850-01-04,2030-01-01") return &sb - } -func (sb *StackBuilder) ProcessAsset(ID string, fileName string, captureDate time.Time) { +func (sb *StackBuilder) ProcessAsset(id string, fileName string, captureDate time.Time) { if !sb.dateRange.InRange(captureDate) { return } @@ -82,18 +81,18 @@ func (sb *StackBuilder) ProcessAsset(ID string, fileName string, captureDate tim } s, ok := sb.stacks[k] if !ok { - s.CoverID = ID + s.CoverID = id s.Date = captureDate } - s.IDs = append(s.IDs, ID) + s.IDs = append(s.IDs, id) s.Names = append(s.Names, path.Base(fileName)) if burst { s.StackType = StackBurst } if cover { - s.CoverID = ID + s.CoverID = id } else if !burst && slices.Contains([]string{".jpeg", ".jpg", ".jpe"}, ext) { - s.CoverID = ID + s.CoverID = id } sb.stacks[k] = s } @@ -183,7 +182,6 @@ func (sb *StackBuilder) Stacks() []Stack { }) s.IDs = ids stacks = append(stacks, s) - } sort.Slice(stacks, func(i, j int) bool { c := stacks[i].Date.Compare(stacks[j].Date) @@ -194,11 +192,8 @@ func (sb *StackBuilder) Stacks() []Stack { return false } c = strings.Compare(stacks[i].Names[0], stacks[j].Names[0]) - switch c { - case -1: - return true - } - return false + + return c == -1 }) return stacks } diff --git a/helpers/stacking/statck_test.go b/helpers/stacking/statck_test.go index 0603b3b..e28a0ce 100644 --- a/helpers/stacking/statck_test.go +++ b/helpers/stacking/statck_test.go @@ -216,6 +216,5 @@ func Test_Stack(t *testing.T) { pretty.Ldiff(t, tt.want, got) } }) - } } diff --git a/helpers/tzone/timezone.go b/helpers/tzone/timezone.go index 8fdff76..b94adb1 100644 --- a/helpers/tzone/timezone.go +++ b/helpers/tzone/timezone.go @@ -22,7 +22,7 @@ var ( func SetLocal(tz string) (*time.Location, error) { onceSetLocal.Do(func() { - if len(tz) == 0 { + if tz == "" { tz, _err = tzlocal.RuntimeTZ() if _err != nil { return @@ -31,7 +31,6 @@ func SetLocal(tz string) (*time.Location, error) { _local, _err = time.LoadLocation(strings.TrimSuffix(tz, "\n")) }) return _local, _err - } func Local() (*time.Location, error) { diff --git a/immich/albums.go b/immich/albums.go index 5fbbd0c..ce7fa0a 100644 --- a/immich/albums.go +++ b/immich/albums.go @@ -27,7 +27,6 @@ func (ic *ImmichClient) GetAllAlbums(ctx context.Context) ([]AlbumSimplified, er return nil, err } return albums, nil - } type AlbumContent struct { @@ -82,7 +81,6 @@ func (ic *ImmichClient) GetAssetsAlbums(ctx context.Context, id string) ([]Album return nil, err } return albums, nil - } type UpdateAlbum struct { @@ -96,7 +94,6 @@ type UpdateAlbumResult struct { } func (ic *ImmichClient) AddAssetToAlbum(ctx context.Context, albumID string, assets []string) ([]UpdateAlbumResult, error) { - var r []UpdateAlbumResult body := UpdateAlbum{ IDS: assets, diff --git a/immich/asset.go b/immich/asset.go index 2f8519c..d702dc7 100644 --- a/immich/asset.go +++ b/immich/asset.go @@ -132,14 +132,12 @@ func (ic *ImmichClient) AssetUpload(ctx context.Context, la *browser.LocalAssetF return } } - }() err = ic.newServerCall(ctx, "AssetUpload"). do(post("/asset/upload", m.FormDataContentType(), setAcceptJSON(), setBody(body)), responseJSON(&ar)) return ar, err - } var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") @@ -200,7 +198,6 @@ func (ic *ImmichClient) GetAllAssets(ctx context.Context, opt *GetAssetOptions) // // It calls the server for IMAGE, VIDEO, normal item, trashed Items func (ic *ImmichClient) GetAllAssetsWithFilter(ctx context.Context, opt *GetAssetOptions, filter func(*Asset)) error { - for _, t := range []string{"IMAGE", "VIDEO", "AUDIO", "OTHER"} { values := opt.Values() values.Set("type", t) @@ -239,7 +236,7 @@ func (ic *ImmichClient) GetAssetByID(ctx context.Context, id string) (*Asset, er return &r, err } -func (ic *ImmichClient) UpdateAssets(ctx context.Context, IDs []string, +func (ic *ImmichClient) UpdateAssets(ctx context.Context, ids []string, isArchived bool, isFavorite bool, latitude float64, longitude float64, removeParent bool, stackParentId string) error { @@ -254,7 +251,7 @@ func (ic *ImmichClient) UpdateAssets(ctx context.Context, IDs []string, } param := updAssets{ - IDs: IDs, + IDs: ids, IsArchived: isArchived, IsFavorite: isFavorite, Latitude: latitude, @@ -265,8 +262,7 @@ func (ic *ImmichClient) UpdateAssets(ctx context.Context, IDs []string, return ic.newServerCall(ctx, "updateAssets").do(put("/asset", setJSONBody(param))) } -func (ic *ImmichClient) UpdateAsset(ctx context.Context, ID string, a *browser.LocalAssetFile) (*Asset, error) { - +func (ic *ImmichClient) UpdateAsset(ctx context.Context, id string, a *browser.LocalAssetFile) (*Asset, error) { type updAsset struct { IsArchived bool `json:"isArchived"` IsFavorite bool `json:"isFavorite"` @@ -282,15 +278,15 @@ func (ic *ImmichClient) UpdateAsset(ctx context.Context, ID string, a *browser.L Longitude: a.Longitude, } r := Asset{} - err := ic.newServerCall(ctx, "updateAsset").do(put("/asset/"+ID, setJSONBody(param)), responseJSON(&r)) + err := ic.newServerCall(ctx, "updateAsset").do(put("/asset/"+id, setJSONBody(param)), responseJSON(&r)) return &r, err } -func (ic *ImmichClient) StackAssets(ctx context.Context, coverID string, IDs []string) error { +func (ic *ImmichClient) StackAssets(ctx context.Context, coverID string, ids []string) error { cover, err := ic.GetAssetByID(ctx, coverID) if err != nil { return err } - return ic.UpdateAssets(ctx, IDs, cover.IsArchived, cover.IsFavorite, cover.ExifInfo.Latitude, cover.ExifInfo.Longitude, false, coverID) + return ic.UpdateAssets(ctx, ids, cover.IsArchived, cover.IsFavorite, cover.ExifInfo.Latitude, cover.ExifInfo.Longitude, false, coverID) } diff --git a/immich/call.go b/immich/call.go index 29b8cb3..9942844 100644 --- a/immich/call.go +++ b/immich/call.go @@ -148,8 +148,7 @@ func setPaginator(pageParameter string, startPage int) serverCallOption { type requestFunction func(sc *serverCall) *http.Request func (sc *serverCall) request(method string, url string, opts ...serverRequestOption) *http.Request { - - req, err := http.NewRequestWithContext(sc.ctx, method, url, nil) + req, err := http.NewRequestWithContext(sc.ctx, method, url, http.NoBody) if sc.joinError(err) != nil { return nil } diff --git a/immich/call_test.go b/immich/call_test.go index 323774c..1dd1b4a 100644 --- a/immich/call_test.go +++ b/immich/call_test.go @@ -88,5 +88,4 @@ func TestCall(t *testing.T) { t.Logf("response received: %#v", r) }) } - } diff --git a/immich/daterange.go b/immich/daterange.go index d81147c..004f67d 100644 --- a/immich/daterange.go +++ b/immich/daterange.go @@ -12,14 +12,16 @@ type DateRange struct { } func (dr DateRange) String() string { - if dr.day { + switch { + case dr.day: return dr.After.Format("2006-01-02") - } else if dr.month { + case dr.month: return dr.After.Format("2006-01") - } else if dr.year { + case dr.year: return dr.After.Format("2006") + default: + return dr.After.Format("2006-01-02") + "," + dr.Before.AddDate(0, 0, -1).Format("2006-01-02") } - return dr.After.Format("2006-01-02") + "," + dr.Before.AddDate(0, 0, -1).Format("2006-01-02") } func (dr *DateRange) Set(s string) (err error) { @@ -32,21 +34,21 @@ func (dr *DateRange) Set(s string) (err error) { dr.After, err = time.ParseInLocation("2006", s, time.UTC) if err == nil { dr.Before = dr.After.AddDate(1, 0, 0) - return + return nil } case 7: dr.month = true dr.After, err = time.ParseInLocation("2006-01", s, time.UTC) if err == nil { dr.Before = dr.After.AddDate(0, 1, 0) - return + return nil } case 10: dr.day = true dr.After, err = time.ParseInLocation("2006-01-02", s, time.UTC) if err == nil { dr.Before = dr.After.AddDate(0, 0, 1) - return + return nil } case 21: dr.After, err = time.ParseInLocation("2006-01-02", s[:10], time.UTC) @@ -54,7 +56,7 @@ func (dr *DateRange) Set(s string) (err error) { dr.Before, err = time.ParseInLocation("2006-01-02", s[11:], time.UTC) if err == nil { dr.Before = dr.Before.AddDate(0, 0, 1) - return + return nil } } } diff --git a/immich/daterange_test.go b/immich/daterange_test.go index a659445..c82cbfd 100644 --- a/immich/daterange_test.go +++ b/immich/daterange_test.go @@ -6,7 +6,6 @@ import ( ) func TestDateRange_InRange(t *testing.T) { - tests := []struct { name string check []struct { diff --git a/immich/metadata/direct.go b/immich/metadata/direct.go index 9d4a30a..220a7cf 100644 --- a/immich/metadata/direct.go +++ b/immich/metadata/direct.go @@ -54,7 +54,6 @@ func GetFromReader(rd io.Reader, ext string) (MetaData, error) { // readExifDateTaken pase the file for Exif DateTaken func readExifDateTaken(r io.Reader) (time.Time, error) { - md, err := getExifFromReader(r) return md.DateTaken, err } @@ -104,5 +103,4 @@ func readCR3DateTaken(r *sliceReader) (time.Time, error) { md, err := getExifFromReader(r) return md.DateTaken, err - } diff --git a/immich/metadata/exif.go b/immich/metadata/exif.go index 1c6bf2a..08db722 100644 --- a/immich/metadata/exif.go +++ b/immich/metadata/exif.go @@ -7,9 +7,8 @@ import ( "strings" "time" - "github.com/simulot/immich-go/helpers/tzone" - "github.com/rwcarlsen/goexif/exif" + "github.com/simulot/immich-go/helpers/tzone" ) func getExifFromReader(r io.Reader) (MetaData, error) { diff --git a/immich/metadata/namesdate.go b/immich/metadata/namesdate.go index f5be48a..034a34d 100644 --- a/immich/metadata/namesdate.go +++ b/immich/metadata/namesdate.go @@ -37,7 +37,6 @@ func TakeTimeFromName(name string) time.Time { if i > 0 { m[i-1], _ = strconv.Atoi(mm[i]) } - } t := time.Date(m[0], time.Month(m[1]), m[2], m[3], m[4], m[5], 0, time.UTC) if t.Year() != m[0] || t.Month() != time.Month(m[1]) || t.Day() != m[2] || diff --git a/immich/metadata/quicktime.go b/immich/metadata/quicktime.go index 2eb9cff..8bd040f 100644 --- a/immich/metadata/quicktime.go +++ b/immich/metadata/quicktime.go @@ -33,7 +33,7 @@ If any of the optional fields are present, the size of the atom would increase a */ type MvhdAtom struct { - Marker []byte //4 bytes + Marker []byte // 4 bytes Version uint8 Flags []byte // 3 bytes CreationTime time.Time @@ -48,7 +48,6 @@ type MvhdAtom struct { } func decodeMvhdAtom(r *sliceReader) (*MvhdAtom, error) { - a := &MvhdAtom{} // Read the mvhd marker (4 bytes) @@ -66,7 +65,6 @@ func decodeMvhdAtom(r *sliceReader) (*MvhdAtom, error) { a.ModificationTime = convertTime32(binary.BigEndian.Uint32(b)) b, _ = r.ReadSlice(4) a.CreationTime = convertTime32(binary.BigEndian.Uint32(b)) - } else { // Read the creation time (4 bytes) b, _ := r.ReadSlice(8) diff --git a/immich/metadata/search.go b/immich/metadata/search.go index 31df76c..ab049d9 100644 --- a/immich/metadata/search.go +++ b/immich/metadata/search.go @@ -23,7 +23,6 @@ func (r *sliceReader) ReadSlice(l int) ([]byte, error) { } func searchPattern(r io.Reader, pattern []byte, buffer []byte) (*sliceReader, error) { - var err error pos := 0 ofs := 0 diff --git a/immich/metadata/sidecar.go b/immich/metadata/sidecar.go index f1091ef..495c94b 100644 --- a/immich/metadata/sidecar.go +++ b/immich/metadata/sidecar.go @@ -44,11 +44,9 @@ func (sc *SideCar) Open(fsys fs.FS, name string) (io.ReadCloser, error) { } return io.NopCloser(b), nil - } func (sc *SideCar) Bytes() ([]byte, error) { - b := bytes.NewBuffer(nil) err := sidecarTemplate.Execute(b, sc) if err != nil { diff --git a/immich/trace.go b/immich/trace.go index bcf6b42..ac41bc7 100644 --- a/immich/trace.go +++ b/immich/trace.go @@ -70,5 +70,4 @@ func traceRequest(req *http.Request) { tr := io.TeeReader(req.Body, os.Stdout) req.Body = &smartBodyCloser{body: req.Body, r: tr} } - } diff --git a/logger/journal.go b/logger/journal.go index 4ea14fc..60b4432 100644 --- a/logger/journal.go +++ b/logger/journal.go @@ -62,14 +62,14 @@ func (j *Journal) AddEntry(file string, action Action, comment ...string) { } } j.mut.Lock() - j.counts[action] = j.counts[action] + 1 + j.counts[action]++ if action == UPGRADED { j.counts[UPLOADED]-- } j.mut.Unlock() } -func (j *Journal) Report() { +func (j *Journal) Report() { checkFiles := j.counts[SCANNED_IMAGE] + j.counts[SCANNED_VIDEO] + j.counts[METADATA] + j.counts[UNSUPPORTED] + j.counts[FAILED_VIDEO] + j.counts[DISCARDED] handledFiles := j.counts[NOT_SELECTED] + j.counts[LOCAL_DUPLICATE] + j.counts[SERVER_DUPLICATE] + j.counts[SERVER_BETTER] + j.counts[UPLOADED] + j.counts[UPGRADED] + j.counts[SERVER_ERROR] j.Logger.OK("Scan of the sources:") @@ -95,5 +95,4 @@ func (j *Journal) Report() { j.Logger.OK("%6d errors when uploading", j.counts[SERVER_ERROR]) j.Logger.OK("%6d handled total (difference %d)", handledFiles, j.counts[SCANNED_IMAGE]+j.counts[SCANNED_VIDEO]-handledFiles) - } diff --git a/logger/log.go b/logger/log.go index 6d13a4a..1e6e80c 100644 --- a/logger/log.go +++ b/logger/log.go @@ -70,9 +70,9 @@ type Log struct { out io.WriteCloser } -func NewLogger(DisplayLevel Level, noColors bool, debug bool) *Log { +func NewLogger(displayLevel Level, noColors bool, debug bool) *Log { l := Log{ - displayLevel: DisplayLevel, + displayLevel: displayLevel, noColors: noColors, colorStrings: map[Level]string{}, debug: debug, diff --git a/main.go b/main.go index d37acef..3fba387 100644 --- a/main.go +++ b/main.go @@ -54,7 +54,8 @@ func main() { } if err != nil { log.Error(err.Error()) - os.Exit(1) + log.Close() + os.Exit(1) //nolint:gocritic } log.OK("Done.") } @@ -73,12 +74,11 @@ type Application struct { Immich *immich.ImmichClient // Immich client Logger *logger.Log // Program's logger - LogFile string //Log file + LogFile string // Log file } func Run(ctx context.Context, log *logger.Log) (*logger.Log, error) { - var err error app := Application{} @@ -112,12 +112,12 @@ func Run(ctx context.Context, log *logger.Log) (*logger.Log, error) { } switch { - case len(app.Server) == 0 && len(app.API) == 0: + case app.Server == "" && app.API == "": err = errors.Join(err, errors.New("missing -server, Immich server address (http://:2283 or https://)")) case len(app.Server) > 0 && len(app.API) > 0: err = errors.Join(err, errors.New("give either the -server or the -api option")) } - if len(app.Key) == 0 { + if app.Key == "" { err = errors.Join(err, errors.New("missing -key")) } @@ -179,7 +179,7 @@ func Run(ctx context.Context, log *logger.Log) (*logger.Log, error) { case "tool": err = cmdtool.CommandTool(ctx, app.Immich, app.Logger, flag.Args()[1:]) default: - err = fmt.Errorf("unknwon command: %q", cmd) + err = fmt.Errorf("unknown command: %q", cmd) } return app.Logger, err }