• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

brotherlogic / recordcollection / 18500838079

14 Oct 2025 02:58PM UTC coverage: 53.079%. First build
18500838079

Pull #9133

github

brotherlogic
Allow skipping of re-caching
Pull Request #9133: Allow skipping of re-caching

20 of 24 new or added lines in 1 file covered. (83.33%)

681 of 1283 relevant lines covered (53.08%)

0.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

41.81
/recordcollectionutils.go
1
package main
2

3
import (
4
        "fmt"
5
        "strconv"
6
        "strings"
7
        "time"
8

9
        pbgd "github.com/brotherlogic/godiscogs/proto"
10
        "github.com/brotherlogic/goserver/utils"
11
        pb "github.com/brotherlogic/recordcollection/proto"
12
        pbro "github.com/brotherlogic/recordsorganiser/proto"
13
        "github.com/prometheus/client_golang/prometheus"
14
        "github.com/prometheus/client_golang/prometheus/promauto"
15
        "golang.org/x/net/context"
16
        "google.golang.org/grpc/codes"
17
        "google.golang.org/grpc/status"
18

19
        "google.golang.org/protobuf/proto"
20
)
21

22
const (
23
        // RecacheDelay - recache everything every 30 days
24
        RecacheDelay = 60 * 60 * 24 * 30
25
)
26

27
var (
28
        backlogCount = promauto.NewGaugeVec(prometheus.GaugeOpts{
29
                Name: "recordcollection_backlog",
30
                Help: "Push Size",
31
        }, []string{"source"})
32

33
        updateFanout = promauto.NewGauge(prometheus.GaugeOpts{
34
                Name: "recordcollection_updatefanout",
35
                Help: "Push Size",
36
        })
37

38
        updateFanoutFailure = promauto.NewCounterVec(prometheus.CounterOpts{
39
                Name: "recordcollection_updatefanoutfailure",
40
                Help: "Push Size",
41
        }, []string{"error", "server"})
42

43
        loopLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{
44
                Name:    "recordcollection_loop_latency",
45
                Help:    "The latency of server requests",
46
                Buckets: []float64{.005 * 1000, .01 * 1000, .025 * 1000, .05 * 1000, .1 * 1000, .25 * 1000, .5 * 1000, 1 * 1000, 2.5 * 1000, 5 * 1000, 10 * 1000, 30 * 1000, 60 * 1000, 120 * 1000, 240 * 1000},
47
        }, []string{"method"})
48
)
49

50
func (s *Server) runUpdateFanout(ctx context.Context) {
×
51
        for fid := range s.updateFanout {
×
52
                id := fid.iid
×
53
                s.CtxLog(ctx, fmt.Sprintf("Running fanout for %+v", fid))
×
54

×
55
                s.repeatCount[id]++
×
56
                if s.repeatCount[id] > 10 {
×
57
                        //s.RaiseIssue(fmt.Sprintf("%v cannot be updated", id), fmt.Sprintf("Last error was %v", s.repeatError[id]))
×
58
                }
×
59

60
                t := time.Now()
×
61
                loopLatency.With(prometheus.Labels{"method": "elect"}).Observe(float64(time.Now().Sub(t).Nanoseconds() / 1000000))
×
62

×
63
                ctx, cancel := utils.ManualContext("rciu", time.Minute)
×
64

×
65
                t = time.Now()
×
66
                record, err := s.loadRecord(ctx, id, false)
×
67
                loopLatency.With(prometheus.Labels{"method": "load"}).Observe(float64(time.Now().Sub(t).Nanoseconds() / 1000000))
×
68

×
69
                if err != nil {
×
70
                        // Ignore out of range errors - these are deleted records
×
71
                        if status.Convert(err).Code() != codes.OutOfRange {
×
72
                                s.repeatError[id] = err
×
73
                                s.CtxLog(ctx, fmt.Sprintf("Unable to load: %v", err))
×
74
                                updateFanoutFailure.With(prometheus.Labels{"server": "load", "error": fmt.Sprintf("%v", err)}).Inc()
×
75
                                s.updateFanout <- fid
×
76
                        }
×
77

78
                        //We get an Invalid argument when we've failed to save out an added record
79
                        if status.Convert(err).Code() == codes.InvalidArgument {
×
80
                                record = &pb.Record{Release: &pbgd.Release{InstanceId: id}}
×
81
                        } else {
×
82
                                cancel()
×
83
                                time.Sleep(time.Minute)
×
84
                                s.CtxLog(ctx, fmt.Sprintf("Skipping %v because it's %v", id, err))
×
85
                                continue
×
86
                        }
87
                }
88

89
                t = time.Now()
×
90
                err = s.syncWantlist(ctx)
×
91
                if err != nil {
×
92
                        s.CtxLog(ctx, fmt.Sprintf("Error pulling wantlist: %v", err))
×
93
                        updateFanoutFailure.With(prometheus.Labels{"server": "syncwants", "error": fmt.Sprintf("%v", err)}).Inc()
×
94
                }
×
95
                loopLatency.With(prometheus.Labels{"method": "syncwant"}).Observe(float64(time.Now().Sub(t).Nanoseconds() / 1000000))
×
96

×
97
                // Perform a discogs update if needed
×
98
                if time.Now().Sub(time.Unix(record.GetMetadata().GetLastCache(), 0)) > time.Hour*24*30 ||
×
99
                        time.Now().Sub(time.Unix(record.GetMetadata().GetLastInfoUpdate(), 0)) > time.Hour*24*30 ||
×
100
                        record.GetRelease().GetRecordCondition() == "" {
×
101
                        t = time.Now()
×
102
                        s.cacheRecord(ctx, record, true)
×
103
                        loopLatency.With(prometheus.Labels{"method": "cache"}).Observe(float64(time.Now().Sub(t).Nanoseconds() / 1000000))
×
104
                }
×
105

106
                if time.Now().Sub(time.Unix(record.GetMetadata().GetSalePriceUpdate(), 0)) > time.Hour*24*7 {
×
107
                        t = time.Now()
×
108
                        s.updateRecordSalePrice(ctx, record)
×
109
                        loopLatency.With(prometheus.Labels{"method": "saleprice"}).Observe(float64(time.Now().Sub(t).Nanoseconds() / 1000000))
×
110
                }
×
111

112
                // Push the metadata every week
113
                if time.Now().Sub(time.Unix(record.GetMetadata().GetSalePriceUpdate(), 0)) > time.Hour*24 {
×
114
                        t = time.Now()
×
115
                        err = s.pushMetadata(ctx, record)
×
116
                        loopLatency.With(prometheus.Labels{"method": "pushmeta"}).Observe(float64(time.Now().Sub(t).Nanoseconds() / 1000000))
×
117
                        if err != nil {
×
118
                                s.repeatError[id] = err
×
119
                                s.CtxLog(ctx, fmt.Sprintf("Unable to push: %v", err))
×
120
                                updateFanoutFailure.With(prometheus.Labels{"server": "push", "error": fmt.Sprintf("%v", err)}).Inc()
×
121
                                s.updateFanout <- fid
×
122
                                cancel()
×
123
                                time.Sleep(time.Minute)
×
124
                                continue
×
125
                        }
126
                }
127

128
                // Finally push the record if we need to
129
                if record.GetMetadata().GetDirty() {
×
130
                        ctx, cancel2 := utils.ManualContext("rciu", time.Minute)
×
131
                        t = time.Now()
×
132
                        _, err = s.pushRecord(ctx, record)
×
133
                        loopLatency.With(prometheus.Labels{"method": "push"}).Observe(float64(time.Now().Sub(t).Nanoseconds() / 1000000))
×
134
                        if err != nil {
×
135
                                s.repeatError[id] = err
×
136
                                s.CtxLog(ctx, fmt.Sprintf("Unable to push: %v", err))
×
137
                                updateFanoutFailure.With(prometheus.Labels{"server": "push", "error": fmt.Sprintf("%v", err)}).Inc()
×
138
                                s.updateFanout <- fid
×
139
                                cancel2()
×
140
                                cancel()
×
141
                                time.Sleep(time.Minute)
×
142
                                continue
×
143
                        }
144
                }
145
                cancel()
×
146

×
147
                // Update the sale
×
148
                if record.GetMetadata().GetCategory() == pb.ReleaseMetadata_LISTED_TO_SELL || record.GetMetadata().GetCategory() == pb.ReleaseMetadata_STALE_SALE {
×
149
                        ctx, cancel := utils.ManualContext("rcu", time.Minute)
×
150
                        t = time.Now()
×
151
                        err := s.updateSale(ctx, record.GetRelease().GetInstanceId())
×
152
                        loopLatency.With(prometheus.Labels{"method": "updatesale"}).Observe(float64(time.Now().Sub(t).Nanoseconds() / 1000000))
×
153
                        if err == nil {
×
154
                                record, err = s.loadRecord(ctx, id, false)
×
155
                        }
×
156
                        cancel()
×
157

×
158
                        if err != nil {
×
159
                                s.repeatError[id] = err
×
160
                                s.CtxLog(ctx, fmt.Sprintf("Unable to update record for sale: %v", err))
×
161
                                updateFanoutFailure.With(prometheus.Labels{"server": "updateSale", "error": fmt.Sprintf("%v", err)}).Inc()
×
162
                                s.updateFanout <- fid
×
163
                                time.Sleep(time.Minute)
×
164
                                continue
×
165
                        }
166

167
                }
168

169
                // Push the sale (only if we're listed to sell and the record is for sale)
170
                if record.GetMetadata().GetSaleDirty() &&
×
171
                        (record.GetMetadata().GetCategory() == pb.ReleaseMetadata_LISTED_TO_SELL || record.GetMetadata().GetCategory() == pb.ReleaseMetadata_STALE_SALE) &&
×
172
                        record.GetMetadata().GetSaleState() != pbgd.SaleState_SOLD {
×
173
                        ctx, cancel := utils.ManualContext("rciu", time.Minute)
×
174
                        t = time.Now()
×
175
                        _, err = s.pushSale(ctx, record)
×
176
                        loopLatency.With(prometheus.Labels{"method": "cache"}).Observe(float64(time.Now().Sub(t).Nanoseconds() / 1000000))
×
177
                        cancel()
×
178
                        if err != nil {
×
179
                                s.repeatError[id] = err
×
180
                                s.CtxLog(ctx, fmt.Sprintf("Unable to push sale : %v", err))
×
181
                                updateFanoutFailure.With(prometheus.Labels{"server": "pushSale", "error": fmt.Sprintf("%v", err)}).Inc()
×
182
                                s.updateFanout <- fid
×
183
                                time.Sleep(time.Minute)
×
184
                                continue
×
185
                        }
186
                }
187

188
                failed := false
×
189
                for _, server := range s.fanoutServers {
×
190
                        t = time.Now()
×
191
                        ctx, cancel := utils.ManualContext("rcfo", time.Minute*30)
×
192
                        conn, err := s.FDialServer(ctx, server)
×
193

×
194
                        if err != nil {
×
195
                                s.repeatError[id] = err
×
196
                                s.CtxLog(ctx, fmt.Sprintf("Bad dial of %v -> %v", server, err))
×
197
                                updateFanoutFailure.With(prometheus.Labels{"server": server, "error": fmt.Sprintf("%v", err)}).Inc()
×
198
                                s.updateFanout <- fid
×
199
                                failed = true
×
200
                                break
×
201
                        }
202

203
                        client := pb.NewClientUpdateServiceClient(conn)
×
204
                        _, err = client.ClientUpdate(ctx, &pb.ClientUpdateRequest{InstanceId: id})
×
205
                        loopLatency.With(prometheus.Labels{"method": "update-" + server}).Observe(float64(time.Now().Sub(t).Nanoseconds() / 1000000))
×
206
                        if err != nil {
×
207
                                s.repeatError[id] = err
×
208
                                s.CtxLog(ctx, fmt.Sprintf("Bad update of (%v) %v -> %v", id, server, err))
×
209
                                updateFanoutFailure.With(prometheus.Labels{"server": server, "error": fmt.Sprintf("%v", err)}).Inc()
×
210
                                s.updateFanout <- fid
×
211
                                conn.Close()
×
212
                                failed = true
×
213
                                break
×
214
                        }
215

216
                        conn.Close()
×
217
                        cancel()
×
218
                }
219

220
                if !failed {
×
221
                        t = time.Now()
×
222

×
223
                        //Attemp to update the record
×
224
                        ctx, cancel = utils.ManualContext("rc-fw", time.Minute)
×
225
                        record, err = s.loadRecord(ctx, id, false)
×
226
                        if err == nil {
×
227
                                record.GetMetadata().LastUpdateTime = time.Now().Unix()
×
228
                                s.saveRecord(ctx, record)
×
229
                        }
×
230
                        s.CtxLog(ctx, fmt.Sprintf("Ran fanout for %v at %v with %v", id, time.Now(), err))
×
231

×
232
                        updateFanout.Set(float64(len(s.updateFanout)))
×
233
                        updateFanoutFailure.With(prometheus.Labels{"server": "none", "error": "nil"}).Inc()
×
234
                }
235

236
                time.Sleep(time.Second)
×
237
        }
238
}
239

240
func (s *Server) validateSales(ctx context.Context) error {
1✔
241
        sales, err := s.retr.GetInventory(ctx)
1✔
242
        if err != nil {
2✔
243
                return err
1✔
244
        }
1✔
245

246
        s.CtxLog(ctx, fmt.Sprintf("Found %v sales", len(sales)))
1✔
247
        matchCount := 0
1✔
248
        for _, sale := range sales {
2✔
249
                found := false
1✔
250

1✔
251
                // This call will not fail
1✔
252
                recs, _ := s.QueryRecords(ctx, &pb.QueryRecordsRequest{Query: &pb.QueryRecordsRequest_ReleaseId{sale.GetId()}})
1✔
253

1✔
254
                s.CtxLog(ctx, fmt.Sprintf("Found %v results (%v)", len(recs.GetInstanceIds()), sale.GetId()))
1✔
255

1✔
256
                for _, id := range recs.GetInstanceIds() {
2✔
257
                        rec, err := s.getRecord(ctx, id)
1✔
258
                        if err != nil {
1✔
259
                                s.CtxLog(ctx, fmt.Sprintf("Err: %v", err))
×
260
                                return err
×
261
                        }
×
262

263
                        if (rec.GetMetadata().GetCategory() == pb.ReleaseMetadata_LISTED_TO_SELL || rec.GetMetadata().GetCategory() == pb.ReleaseMetadata_STALE_SALE) && rec.GetMetadata().GetSaleId() == sale.GetSaleId() {
2✔
264
                                found = true
1✔
265
                        }
1✔
266
                }
267

268
                if !found {
2✔
269
                        s.CtxLog(ctx, fmt.Sprintf("Sending off problem"))
1✔
270
                        s.RaiseIssue("Sale Error Found", fmt.Sprintf("%v is not found in collection", sale))
1✔
271
                        return fmt.Errorf("Found a sale problem")
1✔
272
                }
1✔
273
                matchCount++
1✔
274
        }
275
        s.CtxLog(ctx, fmt.Sprintf("Matched %v", matchCount))
1✔
276

1✔
277
        // Searching LISTED and STALE
1✔
278
        for _, folder := range []int32{488127, 1708299} {
2✔
279
                recs, _ := s.QueryRecords(ctx, &pb.QueryRecordsRequest{Query: &pb.QueryRecordsRequest_FolderId{folder}})
1✔
280
                for _, id := range recs.GetInstanceIds() {
1✔
281
                        rec, err := s.getRecord(ctx, id)
×
282
                        if err != nil {
×
283
                                return err
×
284
                        }
×
285

286
                        seen := false
×
287
                        for _, sale := range sales {
×
288
                                if sale.GetSaleId() == rec.GetMetadata().GetSaleId() {
×
289
                                        seen = true
×
290
                                }
×
291
                        }
292
                        if !seen {
×
293
                                s.RaiseIssue("Sale Missing", fmt.Sprintf("%v is missing the sale", id))
×
294
                                return fmt.Errorf("Found a sale problem")
×
295
                        }
×
296
                }
297
        }
298

299
        return nil
1✔
300
}
301

302
func (s *Server) pushSale(ctx context.Context, val *pb.Record) (bool, error) {
1✔
303

1✔
304
        if val.GetMetadata().SaleDirty && !val.GetMetadata().GetExpireSale() && val.GetMetadata().NewSalePrice > 0 &&
1✔
305
                (val.GetMetadata().Category == pb.ReleaseMetadata_LISTED_TO_SELL ||
1✔
306
                        val.GetMetadata().Category == pb.ReleaseMetadata_STALE_SALE) {
2✔
307

1✔
308
                if len(val.GetRelease().RecordCondition) == 0 {
1✔
309
                        s.RaiseIssue("Condition Issue", fmt.Sprintf("%v [%v] has no condition info", val.GetRelease().Title, val.GetRelease().Id))
×
310
                        return false, fmt.Errorf("%v [%v/%v] has no condition info", val.GetRelease().Title, val.GetRelease().Id, val.GetRelease().InstanceId)
×
311
                }
×
312

313
                err := s.retr.UpdateSalePrice(ctx, int(val.GetMetadata().SaleId), int(val.GetRelease().Id), val.GetRelease().RecordCondition, val.GetRelease().SleeveCondition, float32(val.GetMetadata().NewSalePrice)/100)
1✔
314
                time.Sleep(time.Second * 5)
1✔
315
                s.CtxLog(ctx, fmt.Sprintf("Updated sale price: %v -> %v", val.GetRelease().GetInstanceId(), err))
1✔
316

1✔
317
                if err == nil {
2✔
318
                        // Only trip the time if the price has actually changed
1✔
319
                        if val.GetMetadata().GetSalePrice() != val.GetMetadata().GetNewSalePrice() {
2✔
320
                                val.GetMetadata().LastSalePriceUpdate = time.Now().Unix()
1✔
321
                        }
1✔
322
                        val.GetMetadata().SaleDirty = false
1✔
323
                        val.GetMetadata().SalePrice = val.GetMetadata().NewSalePrice
1✔
324
                        val.GetMetadata().NewSalePrice = 0
1✔
325
                        err = s.saveRecord(ctx, val)
1✔
326
                } else {
1✔
327
                        // Unavailable is a valid response from a sales push, as is Failed precondition when we try and update a sold item
1✔
328
                        if st, ok := status.FromError(err); !ok || (st.Code() != codes.Unavailable && st.Code() != codes.FailedPrecondition) {
2✔
329
                                // Force a record refresh
1✔
330
                                val.GetMetadata().LastUpdateTime = time.Now().Unix()
1✔
331
                                s.RaiseIssue("Error pushing sale", fmt.Sprintf("Error on sale push for %v: %v", val.GetRelease().Id, err))
1✔
332
                                return true, nil
1✔
333
                        }
1✔
334
                }
335
                return true, err
1✔
336
        }
337

338
        if val.GetMetadata().GetExpireSale() && val.GetMetadata().GetSaleState() == pbgd.SaleState_EXPIRED {
1✔
339
                val.GetMetadata().ExpireSale = false
×
340
                return true, s.saveRecord(ctx, val)
×
341
        }
×
342

343
        if val.GetMetadata().SaleDirty && val.GetMetadata().GetExpireSale() && (val.GetMetadata().GetSaleState() == pbgd.SaleState_FOR_SALE || val.GetMetadata().GetSaleState() < 0) {
2✔
344
                err := s.retr.ExpireSale(ctx, int64(val.GetMetadata().SaleId), int(val.GetRelease().Id), float32(val.GetMetadata().SalePrice+1)/100)
1✔
345
                val.GetMetadata().ExpireSale = err != nil
1✔
346
                if err == nil {
2✔
347
                        val.GetMetadata().SaleState = pbgd.SaleState_EXPIRED
1✔
348
                        val.GetMetadata().SaleDirty = false
1✔
349
                }
1✔
350
                s.CtxLog(ctx, fmt.Sprintf("EXPIRE(%v): %v", val.GetRelease().GetInstanceId(), err))
1✔
351
                return true, err
1✔
352
        }
353

354
        if val.GetMetadata().Category == pb.ReleaseMetadata_SOLD_OFFLINE {
2✔
355
                err := s.retr.RemoveFromSale(ctx, int(val.GetMetadata().SaleId), int(val.GetRelease().Id))
1✔
356

1✔
357
                if err == nil || fmt.Sprintf("%v", err) == "POST ERROR (STATUS CODE): 404, {\"message\": \"Item not found. It may have been deleted.\"}" {
2✔
358
                        val.GetMetadata().SaleState = pbgd.SaleState_SOLD
1✔
359
                        val.GetMetadata().SaleDirty = false
1✔
360
                        val.GetMetadata().LastUpdateTime = time.Now().Unix()
1✔
361
                }
1✔
362
                return true, err
1✔
363
        }
364

365
        //Handle hanging clause
366
        if val.GetMetadata().GetExpireSale() && val.GetMetadata().GetSaleState() == pbgd.SaleState_EXPIRED {
1✔
367
                val.GetMetadata().ExpireSale = false
×
368
        }
×
369

370
        //Nothing to do here
371
        val.GetMetadata().SaleDirty = false
1✔
372
        return false, nil
1✔
373
}
374

375
func (s *Server) updateWant(ctx context.Context, w *pb.Want) bool {
1✔
376
        if w.GetReleaseId() == 0 {
1✔
377
                return false
×
378
        }
×
379
        if w.ClearWant {
1✔
380
                s.CtxLog(ctx, fmt.Sprintf("Removing from the wantlist %v -> %v", w.GetReleaseId(), w.ClearWant))
×
381
                s.retr.RemoveFromWantlist(ctx, int(w.GetReleaseId()))
×
382
                w.ClearWant = false
×
383
                return true
×
384
        }
×
385

386
        return false
1✔
387
}
388

389
func (s *Server) pushRecord(ctx context.Context, r *pb.Record) (bool, error) {
1✔
390
        pushed := (r.GetMetadata().GetSetRating() > 0 && r.GetRelease().Rating != r.GetMetadata().GetSetRating()) || (r.GetMetadata().GetMoveFolder() > 0 && r.GetMetadata().GetMoveFolder() != r.GetRelease().FolderId)
1✔
391

1✔
392
        if r.GetMetadata().GetMoveFolder() > 0 {
2✔
393
                if r.GetMetadata().MoveFolder != r.GetRelease().FolderId {
2✔
394
                        err := s.mover.moveRecord(ctx, r, r.GetRelease().FolderId, r.GetMetadata().GetMoveFolder())
1✔
395
                        if r.GetRelease().FolderId != 1 && err != nil {
2✔
396
                                return false, fmt.Errorf("Move fail %v -> %v: %v (%v)", r.GetRelease().FolderId, r.GetMetadata().GetMoveFolder(), err, ctx)
1✔
397
                        }
1✔
398

399
                        _, err = s.retr.MoveToFolder(ctx, int(r.GetRelease().FolderId), int(r.GetRelease().Id), int(r.GetRelease().InstanceId), int(r.GetMetadata().GetMoveFolder()))
1✔
400
                        if err != nil && status.Code(err) != codes.ResourceExhausted {
1✔
401
                                s.RaiseIssue("Move Failure", fmt.Sprintf("%v -> %v", r.GetRelease().GetInstanceId(), err))
×
402

×
403
                                //We need to clear the move to allow it to change
×
404
                                r.GetMetadata().MoveFolder = 0
×
405
                                r.GetMetadata().Dirty = false
×
406
                                s.saveRecord(ctx, r)
×
407

×
408
                                return false, err
×
409
                        }
×
410
                        r.GetRelease().FolderId = r.GetMetadata().MoveFolder
1✔
411
                        r.GetMetadata().LastMoveTime = time.Now().Unix()
1✔
412
                }
413
        }
414
        r.GetMetadata().MoveFolder = 0
1✔
415

1✔
416
        // Push the score
1✔
417
        if (r.GetMetadata().GetSetRating() > 0 || r.GetMetadata().GetSetRating() == -1) && r.GetRelease().Rating != r.GetMetadata().GetSetRating() {
2✔
418
                err := s.retr.SetRating(ctx, int(r.GetRelease().Id), max(0, int(r.GetMetadata().GetSetRating())))
1✔
419
                s.CtxLog(ctx, fmt.Sprintf("Attempting to set rating on %v: %v", r.GetRelease().InstanceId, err))
1✔
420
                r.GetRelease().Rating = int32(max(0, int(r.GetMetadata().SetRating)))
1✔
421
                if r.GetMetadata().GetSetRating() > 0 {
2✔
422
                        r.GetMetadata().LastListenTime = time.Now().Unix()
1✔
423
                }
1✔
424
        }
425
        r.GetMetadata().SetRating = 0
1✔
426

1✔
427
        // Update the boxness
1✔
428
        if r.GetMetadata().GetNewBoxState() != pb.ReleaseMetadata_BOX_UNKNOWN &&
1✔
429
                r.GetMetadata().GetBoxState() != r.GetMetadata().GetNewBoxState() {
1✔
430
                r.GetMetadata().BoxState = r.GetMetadata().GetNewBoxState()
×
431
                r.GetMetadata().NewBoxState = pb.ReleaseMetadata_BOX_UNKNOWN
×
432

×
433
                r.GetMetadata().SaleId = 0
×
434
                r.GetMetadata().SaleState = pbgd.SaleState_EXPIRED
×
435
                if r.GetMetadata().GetCategory() == pb.ReleaseMetadata_LISTED_TO_SELL ||
×
436
                        r.GetMetadata().GetCategory() == pb.ReleaseMetadata_STALE_SALE ||
×
437
                        r.GetMetadata().GetCategory() == pb.ReleaseMetadata_SOLD_ARCHIVE ||
×
438
                        r.GetMetadata().GetCategory() == pb.ReleaseMetadata_PREPARE_TO_SELL {
×
439
                        r.GetMetadata().Category = pb.ReleaseMetadata_PRE_IN_COLLECTION
×
440
                }
×
441
        }
442

443
        r.GetMetadata().Dirty = false
1✔
444

1✔
445
        //Ensure records get updated
1✔
446
        r.GetMetadata().LastUpdateTime = time.Now().Unix()
1✔
447
        return pushed, s.saveRecord(ctx, r)
1✔
448
}
449

450
func max(a, b int) int {
1✔
451
        if a > b {
1✔
452
                return a
×
453
        }
×
454
        return b
1✔
455
}
456

457
func (s *Server) cacheRecord(ctx context.Context, r *pb.Record, force bool) error {
1✔
458
        s.CtxLog(ctx, fmt.Sprintf("Updating cache for : %v (%v): %v", r.GetRelease().GetTitle(), r.GetRelease().GetRecordCondition(), force))
1✔
459

1✔
460
        //Add the record if it has not instance ID
1✔
461
        if r.GetRelease().InstanceId == 0 {
2✔
462
                inst, err := s.retr.AddToFolder(ctx, r.GetRelease().FolderId, r.GetRelease().Id)
1✔
463
                if err == nil {
2✔
464
                        r.GetRelease().InstanceId = int32(inst)
1✔
465
                } else {
1✔
466
                        return err
×
467
                }
×
468
        }
469

470
        //Force a recache if the record has no title or condition; or if it has the old image format
471
        if !r.GetMetadata().GetSkipRefresh() {
2✔
472
                if time.Since(time.Unix(r.GetMetadata().GetLastCache(), 0)) > time.Hour*24 || r.GetRelease().Title == "" ||
1✔
473
                        len(r.GetRelease().GetTracklist()) == 0 ||
1✔
474
                        (len(r.GetRelease().GetImages()) > 0 && strings.Contains(r.GetRelease().GetImages()[0].GetUri(), "img.discogs")) {
2✔
475
                        release, err := s.retr.GetRelease(ctx, r.GetRelease().Id)
1✔
476
                        s.CtxLog(ctx, fmt.Sprintf("Retreived release for re-cache: %v", err))
1✔
477
                        if err == nil {
2✔
478

1✔
479
                                //Clear repeated fields first
1✔
480
                                r.GetRelease().Images = []*pbgd.Image{}
1✔
481
                                r.GetRelease().Artists = []*pbgd.Artist{}
1✔
482
                                r.GetRelease().Formats = []*pbgd.Format{}
1✔
483
                                r.GetRelease().Labels = []*pbgd.Label{}
1✔
484
                                r.GetRelease().Tracklist = []*pbgd.Track{}
1✔
485
                                r.GetRelease().DigitalVersions = []int32{}
1✔
486
                                r.GetRelease().OtherVersions = []int32{}
1✔
487

1✔
488
                                s.CtxLog(ctx, fmt.Sprintf("Merged %v", release))
1✔
489
                                proto.Merge(r.GetRelease(), release)
1✔
490

1✔
491
                                r.GetMetadata().LastCache = time.Now().Unix()
1✔
492
                                r.GetMetadata().LastUpdateTime = time.Now().Unix()
1✔
493
                        } else {
1✔
NEW
494
                                if strings.Contains(fmt.Sprintf("%v", err), "404") {
×
NEW
495
                                        s.RaiseIssue("404 Error on record get", fmt.Sprintf("%v led to %v", r, err))
×
NEW
496
                                }
×
NEW
497
                                return err
×
498
                        }
499
                }
500
        }
501

502
        // Re pull the date_added
503
        mp, err := s.retr.GetInstanceInfo(ctx, r.GetRelease().GetId())
1✔
504
        s.CtxLog(ctx, fmt.Sprintf("Got %v -> %+v", err, mp))
1✔
505
        if err == nil && mp[r.GetRelease().GetInstanceId()] != nil {
1✔
506

×
507
                // Are we moving out of the 12 Inch collection
×
508
                if r.GetRelease().GetFolderId() == 242017 && mp[r.GetRelease().GetInstanceId()].FolderId != 242017 {
×
509
                        // Run an update - but do it at the end of the request
×
510
                        defer func() {
×
511
                                conn, err := s.FDialServer(ctx, "recordsorganiser")
×
512
                                if err == nil {
×
513
                                        oclient := pbro.NewOrganiserServiceClient(conn)
×
514
                                        oclient.GetOrganisation(ctx, &pbro.GetOrganisationRequest{
×
515
                                                Locations:  []*pbro.Location{&pbro.Location{Name: "12 Inches"}},
×
516
                                                ForceReorg: true,
×
517
                                        })
×
518
                                }
×
519
                        }()
520
                }
521

522
                // Are we moving out of the 7 Inch collection
523
                if r.GetRelease().GetFolderId() == 267116 && mp[r.GetRelease().GetInstanceId()].FolderId != 267116 {
×
524
                        // Run an update - but do it at the end of the request
×
525
                        defer func() {
×
526
                                conn, err := s.FDialServer(ctx, "recordsorganiser")
×
527
                                if err == nil {
×
528
                                        oclient := pbro.NewOrganiserServiceClient(conn)
×
529
                                        oclient.GetOrganisation(ctx, &pbro.GetOrganisationRequest{
×
530
                                                Locations:  []*pbro.Location{&pbro.Location{Name: "7 Inches"}},
×
531
                                                ForceReorg: true,
×
532
                                        })
×
533
                                }
×
534
                        }()
535

536
                }
537

538
                s.CtxLog(ctx, fmt.Sprintf("Updating info (%v): %+v", r.GetRelease().GetInstanceId(), mp[r.GetRelease().GetInstanceId()]))
×
539
                r.GetMetadata().DateAdded = mp[r.GetRelease().GetInstanceId()].DateAdded
×
540
                r.GetRelease().RecordCondition = mp[r.GetRelease().GetInstanceId()].RecordCondition
×
541
                r.GetRelease().SleeveCondition = mp[r.GetRelease().GetInstanceId()].SleeveCondition
×
542
                r.GetMetadata().LastInfoUpdate = time.Now().Unix()
×
543
                r.GetRelease().FolderId = mp[r.GetRelease().GetInstanceId()].FolderId
×
544
                r.GetRelease().Rating = mp[r.GetRelease().GetInstanceId()].Rating
×
545

×
546
                if mp[r.GetRelease().GetInstanceId()].LastListenTime > r.GetMetadata().LastListenTime {
×
547
                        r.GetMetadata().LastListenTime = mp[r.GetRelease().GetInstanceId()].LastListenTime
×
548
                }
×
549

550
                // Don't overwrite an existing clean time
551
                if r.GetMetadata().LastCleanDate == 0 || mp[r.GetRelease().GetInstanceId()].LastCleanDate != "" {
×
552
                        p, err := time.ParseInLocation("2006-01-02", mp[r.GetRelease().GetInstanceId()].LastCleanDate, time.Now().Location())
×
553
                        if err == nil {
×
554
                                r.GetMetadata().LastCleanDate = p.Unix()
×
555
                        } else {
×
556
                                s.CtxLog(ctx, fmt.Sprintf("Cannot parse: %v -> %v", mp[r.GetRelease().GetInstanceId()].LastCleanDate, err))
×
557
                        }
×
558
                }
559

560
                val, err := strconv.ParseFloat(mp[r.GetRelease().GetInstanceId()].Width, 32)
×
561

×
562
                if err == nil && val > 0 {
×
563
                        r.GetMetadata().RecordWidth = float32(val)
×
564
                }
×
565

566
                if r.GetMetadata().GetSleeve() == pb.ReleaseMetadata_SLEEVE_UNKNOWN {
×
567
                        switch mp[r.GetRelease().GetInstanceId()].Sleeve {
×
568
                        case "VinylStorageDoubleFlap":
×
569
                                r.GetMetadata().Sleeve = pb.ReleaseMetadata_VINYL_STORAGE_DOUBLE_FLAP
×
570
                        case "Boxset":
×
571
                                r.GetMetadata().Sleeve = pb.ReleaseMetadata_BOX_SET
×
572
                        }
573
                }
574

575
                if r.GetMetadata().GetPurchaseLocation() == pb.PurchaseLocation_LOCATION_UNKNOWN {
×
576
                        switch mp[r.GetRelease().GetInstanceId()].PurchaseLocation {
×
577
                        case "Amoeba":
×
578
                                r.GetMetadata().PurchaseLocation = pb.PurchaseLocation_AMOEBA
×
579
                        case "Hercules":
×
580
                                r.GetMetadata().PurchaseLocation = pb.PurchaseLocation_HERCULES
×
581
                        case "Lloyds":
×
582
                                r.GetMetadata().PurchaseLocation = pb.PurchaseLocation_LLOYDS
×
583
                        case "Discogs":
×
584
                                r.GetMetadata().PurchaseLocation = pb.PurchaseLocation_DISCOGS
×
585
                        case "Bandcamp":
×
586
                                r.GetMetadata().PurchaseLocation = pb.PurchaseLocation_PBANDCAMP
×
587
                        case "Stranded":
×
588
                                r.GetMetadata().PurchaseLocation = pb.PurchaseLocation_STRANDED
×
589
                        case "Direct":
×
590
                                r.GetMetadata().PurchaseLocation = pb.PurchaseLocation_DIRECT
×
591
                        default:
×
592
                                if mp[r.GetRelease().GetInstanceId()].PurchaseLocation != "" {
×
593
                                        s.RaiseIssue("Unknown Purchase Location", fmt.Sprintf("%v cannot be mapped to a location", mp[r.GetRelease().GetInstanceId()].PurchaseLocation))
×
594
                                }
×
595
                        }
596
                }
597

598
                if r.GetMetadata().GetCost() == 0 {
×
599
                        r.GetMetadata().Cost = mp[r.GetRelease().GetInstanceId()].PurchasePrice
×
600
                }
×
601

602
                switch mp[r.GetRelease().GetInstanceId()].Keep {
×
603
                case "none", "NO_KEEP":
×
604
                        r.GetMetadata().Keep = pb.ReleaseMetadata_NOT_KEEPER
×
605
                case "digital", "DIGITAL_KEEP":
×
606
                        r.GetMetadata().Keep = pb.ReleaseMetadata_DIGITAL_KEEPER
×
607
                case "KEEP":
×
608
                        r.GetMetadata().Keep = pb.ReleaseMetadata_KEEPER
×
609
                case "mintup", "MINT_UP_KEEP":
×
610
                        r.GetMetadata().Keep = pb.ReleaseMetadata_DIGITAL_KEEPER
×
611
                case "":
×
612
                        r.GetMetadata().Keep = pb.ReleaseMetadata_KEEP_UNKNOWN
×
613
                default:
×
614
                        panic(fmt.Sprintf("UNKNOWN KEEP STATE: %v", mp[r.GetRelease().GetInstanceId()].Keep))
×
615
                }
616

617
                if r.GetMetadata().GetDateArrived() == 0 && mp[r.GetRelease().GetInstanceId()].Arrived > 0 {
×
618
                        r.GetMetadata().DateArrived = mp[r.GetRelease().GetInstanceId()].Arrived
×
619

×
620
                }
×
621

622
                val, err = strconv.ParseFloat(mp[r.GetRelease().GetInstanceId()].Weight, 32)
×
623
                if err == nil {
×
624
                        r.GetMetadata().WeightInGrams = int32(val)
×
625
                }
×
626

627
                if mp[r.GetRelease().GetInstanceId()].Arrived > 0 && r.GetMetadata().GetDateArrived() == 0 {
×
628
                        r.GetMetadata().DateArrived = mp[r.GetRelease().GetInstanceId()].Arrived
×
629
                }
×
630

631
        } else {
1✔
632
                return err
1✔
633
        }
1✔
634

635
        return s.saveRecord(ctx, r)
×
636
}
637

638
func (s *Server) syncRecords(ctx context.Context, r *pb.Record, record *pbgd.Release, num int64) {
1✔
639
        //Update record if releases don't match
1✔
640
        hasCondition := len(r.GetRelease().RecordCondition) > 0
1✔
641

1✔
642
        //Clear repeated fields first to prevent growth, but images come from
1✔
643
        //a hard sync so ignore that
1✔
644
        if len(record.GetFormats()) > 0 {
1✔
645
                r.GetRelease().Formats = []*pbgd.Format{}
×
646
                r.GetRelease().Artists = []*pbgd.Artist{}
×
647
                r.GetRelease().Labels = []*pbgd.Label{}
×
648
        }
×
649

650
        if len(record.GetImages()) > 0 {
1✔
651
                r.GetRelease().Images = []*pbgd.Image{}
×
652
        }
×
653
        if len(record.GetTracklist()) > 0 {
2✔
654
                r.GetRelease().Tracklist = []*pbgd.Track{}
1✔
655
        }
1✔
656

657
        proto.Merge(r.Release, record)
1✔
658

1✔
659
        // Set sale dirty if the condition is new
1✔
660
        if !hasCondition && len(r.Release.RecordCondition) > 0 {
2✔
661
                r.Metadata.SaleDirty = true
1✔
662
        }
1✔
663

664
        //Make a goal folder adjustment
665
        if r.GetRelease().GetFolderId() == 1782105 &&
1✔
666
                (r.GetMetadata().GetGoalFolder() == 0 || r.GetMetadata().GetGoalFolder() == 268147) {
1✔
667
                r.GetMetadata().GoalFolder = 1782105
×
668
        }
×
669

670
        s.saveRecord(ctx, r)
1✔
671
}
672

673
func (s *Server) syncCollection(ctx context.Context, colNumber int64) error {
1✔
674
        collection, err := s.readRecordCollection(ctx)
1✔
675
        if err != nil {
1✔
676
                return err
×
677
        }
×
678
        records := s.retr.GetCollection(ctx)
1✔
679
        for _, record := range records {
2✔
680
                foundInList := false
1✔
681
                for iid := range collection.InstanceToFolder {
1✔
682
                        if iid == record.InstanceId {
×
683
                                foundInList = true
×
684
                                r, err := s.loadRecord(ctx, record.InstanceId, false)
×
685
                                if err == nil {
×
686
                                        s.syncRecords(ctx, r, record, colNumber)
×
687
                                } else {
×
688
                                        // If we can't find the record, need to resync
×
689
                                        if status.Convert(err).Code() == codes.NotFound {
×
690
                                                foundInList = false
×
691
                                        } else {
×
692
                                                return err
×
693
                                        }
×
694
                                }
695
                        }
696
                }
697

698
                if !foundInList {
2✔
699
                        nrec := &pb.Record{Release: record, Metadata: &pb.ReleaseMetadata{DateAdded: time.Now().Unix(), GoalFolder: record.FolderId}}
1✔
700
                        s.saveRecord(ctx, nrec)
1✔
701
                }
1✔
702

703
        }
704

705
        return s.saveRecordCollection(ctx, collection)
1✔
706
}
707

708
func (s *Server) updateSale(ctx context.Context, iid int32) error {
1✔
709
        r, err := s.loadRecord(ctx, iid, false)
1✔
710
        if err == nil {
1✔
711
                if r.GetMetadata().GetCategory() == pb.ReleaseMetadata_LISTED_TO_SELL || r.GetMetadata().GetCategory() == pb.ReleaseMetadata_STALE_SALE {
×
712
                        if r.GetMetadata().SaleId > 1 && !r.GetMetadata().SaleDirty {
×
713
                                r.GetMetadata().SalePrice = int32(s.retr.GetCurrentSalePrice(ctx, (r.GetMetadata().SaleId)) * 100)
×
714
                        }
×
715
                        if r.GetMetadata().SaleId > 1 && r.GetMetadata().SaleState != pbgd.SaleState_SOLD {
×
716
                                v, err := s.retr.GetCurrentSaleState(ctx, (r.GetMetadata().SaleId))
×
717
                                if err != nil {
×
718
                                        return err
×
719
                                }
×
720
                                r.GetMetadata().SaleState = v
×
721
                        }
722
                        return s.saveRecord(ctx, r)
×
723
                }
724
        }
725
        return err
1✔
726
}
727

728
func (s *Server) syncWantlist(ctx context.Context) error {
1✔
729
        collection, err := s.readRecordCollection(ctx)
1✔
730
        if err != nil {
1✔
731
                return err
×
732
        }
×
733

734
        wants, err := s.retr.GetWantlist(ctx)
1✔
735
        if err != nil {
1✔
736
                return err
×
737
        }
×
738

739
        for _, want := range wants {
2✔
740

1✔
741
                found := false
1✔
742
                for _, w := range collection.GetNewWants() {
2✔
743
                        if w.GetReleaseId() == want.Id {
2✔
744
                                found = true
1✔
745
                        }
1✔
746
                }
747

748
                if !found {
2✔
749
                        collection.NewWants = append(collection.NewWants, &pb.Want{ReleaseId: want.GetId()})
1✔
750
                }
1✔
751
        }
752

753
        var nw []*pb.Want
1✔
754
        for _, w := range collection.GetNewWants() {
2✔
755
                found := false
1✔
756
                for _, want := range wants {
2✔
757
                        if w.GetReleaseId() == want.Id {
2✔
758
                                found = true
1✔
759
                        }
1✔
760
                }
761

762
                if found {
2✔
763
                        nw = append(nw, w)
1✔
764
                }
1✔
765
        }
766
        collection.NewWants = nw
1✔
767

1✔
768
        return s.saveRecordCollection(ctx, collection)
1✔
769
}
770

771
func (s *Server) runSyncWants(ctx context.Context) error {
1✔
772
        return s.syncWantlist(ctx)
1✔
773
}
1✔
774

775
func (s *Server) runSync(ctx context.Context) error {
1✔
776
        collection, err := s.readRecordCollection(ctx)
1✔
777
        if err != nil {
1✔
778
                return err
×
779
        }
×
780
        err = s.syncCollection(ctx, collection.CollectionNumber+1)
1✔
781
        collection.CollectionNumber++
1✔
782
        s.saveRecordCollection(ctx, collection)
1✔
783
        return err
1✔
784
}
785

786
func (s *Server) pushMetadata(ctx context.Context, record *pb.Record) error {
×
787
        info := &pb.StoredMetadata{
×
788
                Width: int32(record.GetMetadata().GetRecordWidth()),
×
789
        }
×
790
        str, _ := proto.Marshal(info)
×
791
        fmt.Sprintf("%v", str)
×
792
        return nil
×
793
        //return s.retr.AddNotes(ctx, record.GetRelease().GetInstanceId(), string(str))
×
794
}
×
795

796
func (s *Server) recache(ctx context.Context, r *pb.Record) error {
1✔
797
        // Don't recache a record that has a pending score
1✔
798
        if r.GetMetadata().GetSetRating() > 0 || r.GetMetadata().Dirty {
2✔
799
                return fmt.Errorf("%v has pending score or is dirty", r.GetRelease().InstanceId)
1✔
800
        }
1✔
801

802
        // Update the score of the record
803
        sc, err := s.scorer.GetScore(ctx, r.GetRelease().InstanceId)
1✔
804
        if err == nil {
2✔
805
                r.GetMetadata().OverallScore = sc
1✔
806
        }
1✔
807

808
        //Force a recache if the record has no title
809
        release, err := s.retr.GetRelease(ctx, r.GetRelease().Id)
1✔
810
        if status.Code(err) == codes.NotFound {
1✔
811
                s.RaiseIssue(fmt.Sprintf("Cannot read %v from Discogs", r.GetRelease().GetTitle()),
×
812
                        fmt.Sprintf("https://www.discogs.com/release/%v cannot be read -> %v [%v]", r.GetRelease().GetId(), err, r.GetRelease().GetInstanceId()))
×
813
        } else if err == nil {
2✔
814

1✔
815
                //Clear repeated fields first
1✔
816
                r.GetRelease().Images = []*pbgd.Image{}
1✔
817
                r.GetRelease().Artists = []*pbgd.Artist{}
1✔
818
                r.GetRelease().Formats = []*pbgd.Format{}
1✔
819
                r.GetRelease().Labels = []*pbgd.Label{}
1✔
820
                r.GetRelease().Tracklist = []*pbgd.Track{}
1✔
821

1✔
822
                proto.Merge(r.GetRelease(), release)
1✔
823

1✔
824
                r.GetMetadata().LastCache = time.Now().Unix()
1✔
825
                r.GetMetadata().LastUpdateTime = time.Now().Unix()
1✔
826
        }
1✔
827

828
        return nil
1✔
829
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc