Skip to content

Commit 1390fbe

Browse files
committed
feat: add support for tracking already processed assets by checksum
1 parent d7c4609 commit 1390fbe

File tree

1 file changed

+83
-32
lines changed

1 file changed

+83
-32
lines changed

app/cmd/upload/advice.go

Lines changed: 83 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ func (a AdviceCode) String() string {
3131
return "SameOnServer"
3232
case NotOnServer:
3333
return "NotOnServer"
34+
case AlreadyProcessed:
35+
return "AlreadyProcessed"
3436
}
3537
return fmt.Sprintf("advice(%d)", a)
3638
}
@@ -41,32 +43,33 @@ const (
4143
BetterOnServer
4244
SameOnServer
4345
NotOnServer
46+
AlreadyProcessed
4447
)
4548

4649
type immichIndex struct {
4750
lock sync.Mutex
4851

49-
// map of assetID to asset
52+
// map of assetID to asset, local and server ones
5053
immichAssets *syncmap.SyncMap[string, *assets.Asset]
5154

52-
// set of uploaded assets during the current session
53-
uploadedAssets *syncset.Set[string]
55+
// set of Uploaded Checksums
56+
uploadsChecksum *syncset.Set[string]
5457

5558
// map of base name to assetID
5659
byName *syncmap.SyncMap[string, []string]
5760

58-
// map of deviceID to assetID
59-
byDeviceID *syncmap.SyncMap[string, string]
61+
// map of SHA1 to assetID
62+
byChecksum *syncmap.SyncMap[string, *assets.Asset]
6063

6164
assetNumber int64
6265
}
6366

6467
func newAssetIndex() *immichIndex {
6568
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](),
69+
immichAssets: syncmap.New[string, *assets.Asset](),
70+
byChecksum: syncmap.New[string, *assets.Asset](),
71+
byName: syncmap.New[string, []string](),
72+
uploadsChecksum: syncset.New[string](),
7073
}
7174
}
7275

@@ -85,23 +88,27 @@ func (ii *immichIndex) addImmichAsset(ia *immich.Asset) (*assets.Asset, bool) {
8588
return existing, false
8689
}
8790
a := ia.AsAsset()
88-
return ii.add(a), true
91+
return ii.add(a, false), true
8992
}
9093

9194
func (ii *immichIndex) addLocalAsset(ia *assets.Asset) (*assets.Asset, bool) {
9295
ii.lock.Lock()
9396
defer ii.lock.Unlock()
9497

95-
if ia.ID == "" {
96-
panic("asset ID is empty")
97-
}
9898
if existing, ok := ii.immichAssets.Load(ia.ID); ok {
9999
return existing, false
100100
}
101-
if !ii.uploadedAssets.Add(ia.ID) {
102-
panic("addLocalAsset asset already uploaded")
101+
if existing, ok := ii.byChecksum.Load(ia.Checksum); ok {
102+
return existing, false
103103
}
104-
return ii.add(ia), true
104+
return ii.add(ia, true), true
105+
}
106+
107+
func (ii *immichIndex) getByChecksum(checksum string) *assets.Asset {
108+
if a, ok := ii.byChecksum.Load(checksum); ok {
109+
return a
110+
}
111+
return nil
105112
}
106113

107114
func (ii *immichIndex) getByID(id string) *assets.Asset {
@@ -113,28 +120,62 @@ func (ii *immichIndex) len() int {
113120
return int(atomic.LoadInt64(&ii.assetNumber))
114121
}
115122

116-
func (ii *immichIndex) add(a *assets.Asset) *assets.Asset {
123+
func (ii *immichIndex) add(a *assets.Asset, local bool) *assets.Asset {
124+
if a.ID == "" {
125+
panic("asset ID is empty")
126+
}
127+
if a.Checksum == "" {
128+
panic("asset checksum is empty")
129+
}
130+
131+
if _, ok := ii.byChecksum.Load(a.Checksum); ok {
132+
panic("asset checksum already exists")
133+
}
134+
135+
if ii.uploadsChecksum.Contains(a.Checksum) {
136+
panic("asset checksum already exists in uploads")
137+
}
138+
117139
atomic.AddInt64(&ii.assetNumber, 1)
118140
ii.immichAssets.Store(a.ID, a)
141+
ii.byChecksum.Store(a.Checksum, a)
119142
filename := a.OriginalFileName
120143

121-
ii.byDeviceID.Store(a.DeviceAssetID(), a.ID)
144+
if local {
145+
ii.uploadsChecksum.Add(a.Checksum)
146+
}
147+
122148
l, _ := ii.byName.Load(filename)
123149
l = append(l, a.ID)
124150
ii.byName.Store(filename, l)
125151
return a
126152
}
127153

128154
func (ii *immichIndex) replaceAsset(newA *assets.Asset, oldA *assets.Asset) *assets.Asset {
155+
if newA.ID == "" {
156+
panic("asset ID is empty")
157+
}
158+
if newA.Checksum == "" {
159+
panic("asset checksum is empty")
160+
}
129161
ii.lock.Lock()
130162
defer ii.lock.Unlock()
163+
oldA.Trashed = true
164+
ii.immichAssets.Store(newA.ID, newA) // Store the new asset
165+
ii.byChecksum.Store(newA.Checksum, newA) // Store the new SHA1
166+
ii.uploadsChecksum.Add(newA.Checksum)
131167

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
168+
filename := newA.OriginalFileName
169+
l, _ := ii.byName.Load(filename)
170+
l = append(l, newA.ID)
171+
ii.byName.Store(filename, l)
135172
return newA
136173
}
137174

175+
func (ii *immichIndex) isAlreadyProcessed(checksum string) bool {
176+
return ii.uploadsChecksum.Contains(checksum)
177+
}
178+
138179
type Advice struct {
139180
Advice AdviceCode
140181
Message string
@@ -182,6 +223,14 @@ func (ii *immichIndex) adviceBetterOnServer(sa *assets.Asset) *Advice {
182223
}
183224
}
184225

226+
func (ii *immichIndex) adviceAlreadyProcessed(sa *assets.Asset) *Advice {
227+
return &Advice{
228+
Advice: AlreadyProcessed,
229+
Message: fmt.Sprintf("An asset with the same checksum:%q has been already processed. No need to upload.", sa.Checksum),
230+
ServerAsset: sa,
231+
}
232+
}
233+
185234
func (ii *immichIndex) adviceNotOnServer() *Advice {
186235
return &Advice{
187236
Advice: NotOnServer,
@@ -199,18 +248,20 @@ func (ii *immichIndex) adviceNotOnServer() *Advice {
199248
// la.OriginalFileName is the name of the file as it was on the device before it was uploaded to the server
200249

201250
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)
204-
205-
id, ok := ii.byDeviceID.Load(DeviceAssetID)
206-
if ok {
207-
// the same ID exist on the server
208-
sa, ok := ii.immichAssets.Load(id)
209-
if ok {
210-
return ii.adviceSameOnServer(sa), nil
251+
checksum, err := la.GetChecksum()
252+
if err != nil {
253+
return nil, err
254+
}
255+
256+
if sa, ok := ii.byChecksum.Load(checksum); ok {
257+
if ii.isAlreadyProcessed(checksum) {
258+
return ii.adviceAlreadyProcessed(sa), nil
211259
}
260+
return ii.adviceSameOnServer(sa), nil
212261
}
213262

263+
filename := path.Base(la.File.Name())
264+
214265
// check all files with the same name
215266
ids, ok := ii.byName.Load(filename)
216267

@@ -247,9 +298,9 @@ func compareDate(d1 time.Time, d2 time.Time) int {
247298
diff := d1.Sub(d2)
248299

249300
switch {
250-
case diff < -5*time.Minute:
301+
case diff < -5*time.Second:
251302
return -1
252-
case diff >= 5*time.Minute:
303+
case diff >= 5*time.Second:
253304
return +1
254305
}
255306
return 0

0 commit comments

Comments
 (0)