@@ -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+
43138type 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
148246func compareDate (d1 time.Time , d2 time.Time ) int {
0 commit comments