Skip to content

Commit c80672b

Browse files
authored
Merge pull request #800 from simulot:simulot/issue792
Enhance asset management and update dependencies
2 parents 3ff83f5 + f604c11 commit c80672b

File tree

96 files changed

+2036
-230
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+2036
-230
lines changed

adapters/fromimmich/fromimmich.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -101,21 +101,17 @@ func (f *FromImmich) getAssetsFromAlbums(ctx context.Context, grpChan chan *asse
101101
}
102102
for _, album := range albums {
103103
for _, albumName := range f.flags.Albums {
104-
if album.Title == albumName {
104+
if album.AlbumName == albumName {
105105
al, err := f.flags.client.Immich.GetAlbumInfo(ctx, album.ID, false)
106106
if err != nil {
107107
return f.logError(err)
108108
}
109109
for _, a := range al.Assets {
110110
if _, ok := assets[a.ID]; !ok {
111-
a.Albums = append(a.Albums, immich.AlbumSimplified{
112-
AlbumName: album.Title,
113-
})
111+
a.Albums = append(a.Albums, album)
114112
assets[a.ID] = a
115113
} else {
116-
assets[a.ID].Albums = append(assets[a.ID].Albums, immich.AlbumSimplified{
117-
AlbumName: album.Title,
118-
})
114+
assets[a.ID].Albums = append(assets[a.ID].Albums, album)
119115
}
120116
}
121117
}
@@ -141,7 +137,7 @@ func (f *FromImmich) filterAsset(ctx context.Context, a *immich.Asset, grpChan c
141137
return nil
142138
}
143139

144-
albums := immich.AlbumsFromAlbumSimplified(a.Albums)
140+
albums := a.Albums
145141

146142
if f.mustFetchAlbums && len(albums) == 0 {
147143
albums, err = f.flags.client.Immich.GetAssetAlbums(ctx, a.ID)
@@ -151,10 +147,10 @@ func (f *FromImmich) filterAsset(ctx context.Context, a *immich.Asset, grpChan c
151147
}
152148
if len(f.flags.Albums) > 0 && len(albums) > 0 {
153149
keepMe := false
154-
newAlbumList := []assets.Album{}
150+
newAlbumList := []immich.AlbumSimplified{}
155151
for _, album := range f.flags.Albums {
156152
for _, aAlbum := range albums {
157-
if album == aAlbum.Title {
153+
if album == aAlbum.AlbumName {
158154
keepMe = true
159155
newAlbumList = append(newAlbumList, aAlbum)
160156
}
@@ -179,6 +175,7 @@ func (f *FromImmich) filterAsset(ctx context.Context, a *immich.Asset, grpChan c
179175

180176
asset.FromApplication = &assets.Metadata{
181177
FileName: a.OriginalFileName,
178+
FileDate: a.FileModifiedAt.Time,
182179
Latitude: a.ExifInfo.Latitude,
183180
Longitude: a.ExifInfo.Longitude,
184181
Description: a.ExifInfo.Description,
@@ -187,7 +184,7 @@ func (f *FromImmich) filterAsset(ctx context.Context, a *immich.Asset, grpChan c
187184
Archived: a.IsArchived,
188185
Favorited: a.IsFavorite,
189186
Rating: byte(a.Rating),
190-
Albums: albums,
187+
Albums: immich.AlbumsFromAlbumSimplified(albums),
191188
Tags: asset.Tags,
192189
}
193190

app/cmd/upload/advice.go

Lines changed: 130 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import (
44
"fmt"
55
"math"
66
"path"
7-
"path/filepath"
7+
"sync"
8+
"sync/atomic"
89
"time"
910

1011
"github.com/simulot/immich-go/immich"
1112
"github.com/simulot/immich-go/internal/assets"
13+
"github.com/simulot/immich-go/internal/gen/syncmap"
14+
"github.com/simulot/immich-go/internal/gen/syncset"
1215
)
1316

1417
// - - go:generate stringer -type=AdviceCode
@@ -40,10 +43,102 @@ const (
4043
NotOnServer
4144
)
4245

46+
type immichIndex struct {
47+
lock sync.Mutex
48+
49+
// map of assetID to asset
50+
immichAssets *syncmap.SyncMap[string, *assets.Asset]
51+
52+
// set of uploaded assets during the current session
53+
uploadedAssets *syncset.Set[string]
54+
55+
// map of base name to assetID
56+
byName *syncmap.SyncMap[string, []string]
57+
58+
// map of deviceID to assetID
59+
byDeviceID *syncmap.SyncMap[string, string]
60+
61+
assetNumber int64
62+
}
63+
64+
func newAssetIndex() *immichIndex {
65+
return &immichIndex{
66+
immichAssets: syncmap.New[string, *assets.Asset](),
67+
byName: syncmap.New[string, []string](),
68+
byDeviceID: syncmap.New[string, string](),
69+
uploadedAssets: syncset.New[string](),
70+
}
71+
}
72+
73+
// Add adds an asset to the index.
74+
// returns true if the asset was added, false if it was already present.
75+
// the returned asset is the existing asset if it was already present.
76+
func (ii *immichIndex) addImmichAsset(ia *immich.Asset) (*assets.Asset, bool) {
77+
ii.lock.Lock()
78+
defer ii.lock.Unlock()
79+
80+
if ia.ID == "" {
81+
panic("asset ID is empty")
82+
}
83+
84+
if existing, ok := ii.immichAssets.Load(ia.ID); ok {
85+
return existing, false
86+
}
87+
a := ia.AsAsset()
88+
return ii.add(a), true
89+
}
90+
91+
func (ii *immichIndex) addLocalAsset(ia *assets.Asset) (*assets.Asset, bool) {
92+
ii.lock.Lock()
93+
defer ii.lock.Unlock()
94+
95+
if ia.ID == "" {
96+
panic("asset ID is empty")
97+
}
98+
if existing, ok := ii.immichAssets.Load(ia.ID); ok {
99+
return existing, false
100+
}
101+
if !ii.uploadedAssets.Add(ia.ID) {
102+
panic("addLocalAsset asset already uploaded")
103+
}
104+
return ii.add(ia), true
105+
}
106+
107+
func (ii *immichIndex) getByID(id string) *assets.Asset {
108+
a, _ := ii.immichAssets.Load(id)
109+
return a
110+
}
111+
112+
func (ii *immichIndex) len() int {
113+
return int(atomic.LoadInt64(&ii.assetNumber))
114+
}
115+
116+
func (ii *immichIndex) add(a *assets.Asset) *assets.Asset {
117+
atomic.AddInt64(&ii.assetNumber, 1)
118+
ii.immichAssets.Store(a.ID, a)
119+
filename := a.OriginalFileName
120+
121+
ii.byDeviceID.Store(a.DeviceAssetID(), a.ID)
122+
l, _ := ii.byName.Load(filename)
123+
l = append(l, a.ID)
124+
ii.byName.Store(filename, l)
125+
return a
126+
}
127+
128+
func (ii *immichIndex) replaceAsset(newA *assets.Asset, oldA *assets.Asset) *assets.Asset {
129+
ii.lock.Lock()
130+
defer ii.lock.Unlock()
131+
132+
ii.byDeviceID.Delete(oldA.DeviceAssetID()) // remove the old AssetID
133+
ii.immichAssets.Store(newA.ID, newA) // Store the new asset
134+
ii.byDeviceID.Store(newA.DeviceAssetID(), newA.ID) // Store the new AssetID
135+
return newA
136+
}
137+
43138
type Advice struct {
44139
Advice AdviceCode
45140
Message string
46-
ServerAsset *immich.Asset
141+
ServerAsset *assets.Asset
47142
LocalAsset *assets.Asset
48143
}
49144

@@ -63,31 +158,31 @@ func formatBytes(s int64) string {
63158
return fmt.Sprintf("%.1f %s", roundedSize, suffixes[exp])
64159
}
65160

66-
func (ai *AssetIndex) adviceSameOnServer(sa *immich.Asset) *Advice {
161+
func (ii *immichIndex) adviceSameOnServer(sa *assets.Asset) *Advice {
67162
return &Advice{
68163
Advice: SameOnServer,
69-
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)),
164+
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.CaptureDate.Format(time.DateTime), formatBytes(int64(sa.FileSize))),
70165
ServerAsset: sa,
71166
}
72167
}
73168

74-
func (ai *AssetIndex) adviceSmallerOnServer(sa *immich.Asset) *Advice {
169+
func (ii *immichIndex) adviceSmallerOnServer(sa *assets.Asset) *Advice {
75170
return &Advice{
76171
Advice: SmallerOnServer,
77-
Message: fmt.Sprintf("An asset with the same name:%q and date:%q but with smaller size:%s exists on the server. Replace it.", sa.OriginalFileName, sa.ExifInfo.DateTimeOriginal.Format(time.DateTime), formatBytes(sa.ExifInfo.FileSizeInByte)),
172+
Message: fmt.Sprintf("An asset with the same name:%q and date:%q but with smaller size:%s exists on the server. Replace it.", sa.OriginalFileName, sa.CaptureDate.Format(time.DateTime), formatBytes(int64(sa.FileSize))),
78173
ServerAsset: sa,
79174
}
80175
}
81176

82-
func (ai *AssetIndex) adviceBetterOnServer(sa *immich.Asset) *Advice {
177+
func (ii *immichIndex) adviceBetterOnServer(sa *assets.Asset) *Advice {
83178
return &Advice{
84179
Advice: BetterOnServer,
85-
Message: fmt.Sprintf("An asset with the same name:%q and date:%q but with bigger size:%s exists on the server. No need to upload.", sa.OriginalFileName, sa.ExifInfo.DateTimeOriginal.Format(time.DateTime), formatBytes(sa.ExifInfo.FileSizeInByte)),
180+
Message: fmt.Sprintf("An asset with the same name:%q and date:%q but with bigger size:%s exists on the server. No need to upload.", sa.OriginalFileName, sa.CaptureDate.Format(time.DateTime), formatBytes(int64(sa.FileSize))),
86181
ServerAsset: sa,
87182
}
88183
}
89184

90-
func (ai *AssetIndex) adviceNotOnServer() *Advice {
185+
func (ii *immichIndex) adviceNotOnServer() *Advice {
91186
return &Advice{
92187
Advice: NotOnServer,
93188
Message: "This a new asset, upload it.",
@@ -103,46 +198,49 @@ func (ai *AssetIndex) adviceNotOnServer() *Advice {
103198
// la.File.Name() is the full path to the file as it is on the source
104199
// la.OriginalFileName is the name of the file as it was on the device before it was uploaded to the server
105200

106-
func (ai *AssetIndex) ShouldUpload(la *assets.Asset) (*Advice, error) {
107-
filename := la.File.Name()
108-
DeviceAssetID := fmt.Sprintf("%s-%d", path.Base(filename), la.FileSize)
201+
func (ii *immichIndex) ShouldUpload(la *assets.Asset) (*Advice, error) {
202+
filename := path.Base(la.File.Name())
203+
DeviceAssetID := fmt.Sprintf("%s-%d", filename, la.FileSize)
109204

110-
sa := ai.byDeviceAssetID[DeviceAssetID]
111-
if sa != nil {
205+
id, ok := ii.byDeviceID.Load(DeviceAssetID)
206+
if ok {
112207
// the same ID exist on the server
113-
return ai.adviceSameOnServer(sa), nil
208+
sa, ok := ii.immichAssets.Load(id)
209+
if ok {
210+
return ii.adviceSameOnServer(sa), nil
211+
}
114212
}
115213

116-
var l []*immich.Asset
117-
118214
// check all files with the same name
215+
ids, ok := ii.byName.Load(filename)
119216

120-
n := filepath.Base(filename)
121-
l = ai.byName[n]
122-
if len(l) == 0 {
123-
// n = strings.TrimSuffix(n, filepath.Ext(n))
124-
l = ai.byName[n]
125-
}
126-
127-
if len(l) > 0 {
217+
if ok && len(ids) > 0 {
128218
dateTaken := la.CaptureDate
219+
if dateTaken.IsZero() {
220+
dateTaken = la.FileDate
221+
}
129222
size := int64(la.FileSize)
130223

131-
for _, sa = range l {
132-
compareDate := compareDate(dateTaken, sa.ExifInfo.DateTimeOriginal.Time)
133-
compareSize := size - sa.ExifInfo.FileSizeInByte
224+
for _, id := range ids {
225+
sa, ok := ii.immichAssets.Load(id)
226+
if !ok {
227+
continue
228+
}
229+
230+
compareDate := compareDate(dateTaken, sa.CaptureDate)
231+
compareSize := size - int64(sa.FileSize)
134232

135233
switch {
136234
case compareDate == 0 && compareSize == 0:
137-
return ai.adviceSameOnServer(sa), nil
235+
return ii.adviceSameOnServer(sa), nil
138236
case compareDate == 0 && compareSize > 0:
139-
return ai.adviceSmallerOnServer(sa), nil
237+
return ii.adviceSmallerOnServer(sa), nil
140238
case compareDate == 0 && compareSize < 0:
141-
return ai.adviceBetterOnServer(sa), nil
239+
return ii.adviceBetterOnServer(sa), nil
142240
}
143241
}
144242
}
145-
return ai.adviceNotOnServer(), nil
243+
return ii.adviceNotOnServer(), nil
146244
}
147245

148246
func compareDate(d1 time.Time, d2 time.Time) int {

app/cmd/upload/assets.go

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)