@@ -4,11 +4,14 @@ import (
4
4
"fmt"
5
5
"math"
6
6
"path"
7
- "path/filepath"
7
+ "sync"
8
+ "sync/atomic"
8
9
"time"
9
10
10
11
"github.com/simulot/immich-go/immich"
11
12
"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"
12
15
)
13
16
14
17
// - - go:generate stringer -type=AdviceCode
@@ -40,10 +43,102 @@ const (
40
43
NotOnServer
41
44
)
42
45
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
+
43
138
type Advice struct {
44
139
Advice AdviceCode
45
140
Message string
46
- ServerAsset * immich .Asset
141
+ ServerAsset * assets .Asset
47
142
LocalAsset * assets.Asset
48
143
}
49
144
@@ -63,31 +158,31 @@ func formatBytes(s int64) string {
63
158
return fmt .Sprintf ("%.1f %s" , roundedSize , suffixes [exp ])
64
159
}
65
160
66
- func (ai * AssetIndex ) adviceSameOnServer (sa * immich .Asset ) * Advice {
161
+ func (ii * immichIndex ) adviceSameOnServer (sa * assets .Asset ) * Advice {
67
162
return & Advice {
68
163
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 ) )),
70
165
ServerAsset : sa ,
71
166
}
72
167
}
73
168
74
- func (ai * AssetIndex ) adviceSmallerOnServer (sa * immich .Asset ) * Advice {
169
+ func (ii * immichIndex ) adviceSmallerOnServer (sa * assets .Asset ) * Advice {
75
170
return & Advice {
76
171
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 ) )),
78
173
ServerAsset : sa ,
79
174
}
80
175
}
81
176
82
- func (ai * AssetIndex ) adviceBetterOnServer (sa * immich .Asset ) * Advice {
177
+ func (ii * immichIndex ) adviceBetterOnServer (sa * assets .Asset ) * Advice {
83
178
return & Advice {
84
179
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 ) )),
86
181
ServerAsset : sa ,
87
182
}
88
183
}
89
184
90
- func (ai * AssetIndex ) adviceNotOnServer () * Advice {
185
+ func (ii * immichIndex ) adviceNotOnServer () * Advice {
91
186
return & Advice {
92
187
Advice : NotOnServer ,
93
188
Message : "This a new asset, upload it." ,
@@ -103,46 +198,49 @@ func (ai *AssetIndex) adviceNotOnServer() *Advice {
103
198
// la.File.Name() is the full path to the file as it is on the source
104
199
// la.OriginalFileName is the name of the file as it was on the device before it was uploaded to the server
105
200
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 )
109
204
110
- sa := ai . byDeviceAssetID [ DeviceAssetID ]
111
- if sa != nil {
205
+ id , ok := ii . byDeviceID . Load ( DeviceAssetID )
206
+ if ok {
112
207
// 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
+ }
114
212
}
115
213
116
- var l []* immich.Asset
117
-
118
214
// check all files with the same name
215
+ ids , ok := ii .byName .Load (filename )
119
216
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 {
128
218
dateTaken := la .CaptureDate
219
+ if dateTaken .IsZero () {
220
+ dateTaken = la .FileDate
221
+ }
129
222
size := int64 (la .FileSize )
130
223
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 )
134
232
135
233
switch {
136
234
case compareDate == 0 && compareSize == 0 :
137
- return ai .adviceSameOnServer (sa ), nil
235
+ return ii .adviceSameOnServer (sa ), nil
138
236
case compareDate == 0 && compareSize > 0 :
139
- return ai .adviceSmallerOnServer (sa ), nil
237
+ return ii .adviceSmallerOnServer (sa ), nil
140
238
case compareDate == 0 && compareSize < 0 :
141
- return ai .adviceBetterOnServer (sa ), nil
239
+ return ii .adviceBetterOnServer (sa ), nil
142
240
}
143
241
}
144
242
}
145
- return ai .adviceNotOnServer (), nil
243
+ return ii .adviceNotOnServer (), nil
146
244
}
147
245
148
246
func compareDate (d1 time.Time , d2 time.Time ) int {
0 commit comments