@@ -164,17 +164,75 @@ func buildDiscoveryItems(uncovered map[int]cfw.LocalRomFile, savesByRom map[int]
164164 "romID" , romID , "romName" , rom .RomName , "saveID" , best .ID ,
165165 "file" , best .FileName , "fetched" , len (saves ))
166166
167- items = append ( items , SyncItem {
167+ item := SyncItem {
168168 LocalSave : ls ,
169169 RemoteSave : best ,
170170 TargetSlot : preferredSlot ,
171171 Action : ActionDownload ,
172- })
172+ }
173+ // First-time multi-slot pull: offer the slot choice to the UI. Discovery only
174+ // runs for ROMs with no local save, so every multi-slot case is first-time.
175+ if slots := distinctSaveSlots (saves ); len (slots ) > 1 {
176+ item .AvailableSlots = slots
177+ item .AllRemoteSaves = saves
178+ }
179+
180+ items = append (items , item )
173181 }
174182
175183 return items
176184}
177185
186+ // distinctSaveSlots returns the sorted distinct slot names across saves (nil/empty slot
187+ // counts as "autosave").
188+ func distinctSaveSlots (saves []romm.Save ) []string {
189+ set := make (map [string ]bool )
190+ for _ , s := range saves {
191+ slot := "autosave"
192+ if s .Slot != nil && * s .Slot != "" {
193+ slot = * s .Slot
194+ }
195+ set [slot ] = true
196+ }
197+ out := make ([]string , 0 , len (set ))
198+ for s := range set {
199+ out = append (out , s )
200+ }
201+ sort .Strings (out )
202+ return out
203+ }
204+
205+ // distinctOpSlots returns the sorted distinct slot names across download ops (nil/empty
206+ // slot counts as "autosave").
207+ func distinctOpSlots (ops []romm.SyncOperationSchema ) []string {
208+ set := make (map [string ]bool )
209+ for _ , op := range ops {
210+ slot := "autosave"
211+ if op .Slot != nil && * op .Slot != "" {
212+ slot = * op .Slot
213+ }
214+ set [slot ] = true
215+ }
216+ out := make ([]string , 0 , len (set ))
217+ for s := range set {
218+ out = append (out , s )
219+ }
220+ sort .Strings (out )
221+ return out
222+ }
223+
224+ // opStubsToSaves builds romm.Save stubs from download ops for slot re-selection by the
225+ // multi-slot picker.
226+ func opStubsToSaves (ops []romm.SyncOperationSchema ) []romm.Save {
227+ saves := make ([]romm.Save , 0 , len (ops ))
228+ for _ , op := range ops {
229+ if stub := buildRemoteSaveStub (op ); stub != nil {
230+ saves = append (saves , * stub )
231+ }
232+ }
233+ return saves
234+ }
235+
178236// discoverRemoteOnlySaves finds locally-present ROMs that have no local save and were
179237// not covered by a negotiate operation, fetches their server saves, and builds download
180238// items for any save this device has never synced.
@@ -378,12 +436,25 @@ func mapOperationsToItems(
378436 if ! ok {
379437 ls = resolveLocalSaveForDownload (op , resolvedRoms , cm )
380438 }
381- items = append ( items , SyncItem {
439+ item := SyncItem {
382440 LocalSave : ls ,
383441 RemoteSave : buildRemoteSaveStub (op ),
384442 TargetSlot : preferred ,
385443 Action : ActionDownload ,
386- })
444+ }
445+
446+ // First-time multi-slot pull: if this ROM has no local save yet and the server
447+ // offers it in more than one slot, surface the choice to the UI instead of
448+ // silently picking. (A ROM that already has a local save was filtered to its
449+ // managed slot above, so it never reaches here multi-slot.)
450+ if _ , hasLocal := localByRom [romID ]; ! hasLocal {
451+ if slots := distinctOpSlots (dops ); len (slots ) > 1 {
452+ item .AvailableSlots = slots
453+ item .AllRemoteSaves = opStubsToSaves (dops )
454+ }
455+ }
456+
457+ items = append (items , item )
387458 }
388459
389460 return items
@@ -526,6 +597,25 @@ func RegisterDevice(client *romm.Client, name string) (romm.Device, error) {
526597 return dev , nil
527598}
528599
600+ // RefreshDeviceVersion updates the server's record of this device's client_version when
601+ // the running grout version differs from lastReported (i.e. the app was upgraded since
602+ // the version was last sent). Returns the version now reported and whether an update was
603+ // sent. Best-effort: a failure is logged and leaves lastReported unchanged.
604+ func RefreshDeviceVersion (client * romm.Client , deviceID , lastReported string ) (string , bool ) {
605+ current := version .Get ().Version
606+ if deviceID == "" || current == "" || current == lastReported {
607+ return lastReported , false
608+ }
609+ if _ , err := client .UpdateDevice (deviceID , romm.UpdateDeviceRequest {ClientVersion : current }); err != nil {
610+ gaba .GetLogger ().Warn ("Failed to refresh device client_version on upgrade" ,
611+ "deviceID" , deviceID , "from" , lastReported , "to" , current , "error" , err )
612+ return lastReported , false
613+ }
614+ gaba .GetLogger ().Debug ("Refreshed device client_version after upgrade" ,
615+ "deviceID" , deviceID , "from" , lastReported , "to" , current )
616+ return current , true
617+ }
618+
529619func ScanSaves (config * internal.Config ) []LocalSave {
530620 logger := gaba .GetLogger ()
531621 currentCFW := cfw .GetCFW ()
0 commit comments