@@ -33,6 +33,7 @@ def print_info():
3333 -ac [lid] Add a level card record without info and walkthrough
3434 -acr [lid lid] Add a range of level card records
3535 -rm [lid] Remove one level record
36+ -sc Sync cards
3637 -u [lid] Update a level record
3738
3839 -ld [lid] List download files records
@@ -196,93 +197,141 @@ def list_downloads(lid):
196197 print (row )
197198
198199
199- def sync_cards ():
200- """Lazy tail sync of cards."""
201- index = match_tails ()
202- print (f"{ index } " )
200+ class TailSync :
201+ """Match sync 5 records tails and add new records."""
203202
203+ def __init__ (self ):
204+ """Set default member variables."""
205+ self .local = []
206+ self .trle = []
204207
205- def match_tails (match_size = 5 , max_pages = 100 ):
206- """Find a tail match between local and trle databases based ids."""
207- local = []
208- trle = []
208+ self .local_offset = 0
209+ self .trle_offset = 0
209210
210- local_offset = 0
211- trle_offset = 0
211+ def match_record (self , a , b ):
212+ """trle.net record data matching."""
213+ keys = ['trle_id' , 'author' , 'title' , 'difficulty' , 'duration' , 'class' , 'type' , 'release' ]
214+ return all (a .get (k ) == b .get (k ) for k in keys )
212215
213- def get_local_page_and_extend (offset ):
214- con = database_make_connection ()
215- page = get_local_page (offset , con )['levels' ]
216- con .close ()
217- local .extend (page )
218- return len (page )
219-
220- def get_trle_page_and_extend (offset ):
221- page = get_trle_page (offset )['levels' ]
222- trle .extend (page )
223- return len (page )
224-
225- def ensure_data (index_local , index_trle ):
226- """Ensure enough items are available from each database."""
227- nonlocal local_offset , trle_offset
228-
229- # Load local pages if needed
230- while index_local + match_size > len (local ):
231- local_offset += 1
232- print (f"Loading local page at offset { local_offset } " )
233- if local_offset >= max_pages or get_local_page_and_extend (local_offset ) == 0 :
234- return False
216+ def match_tails (self , match_size = 5 , max_pages = 100 ):
217+ """Find a tail match between local and trle databases based ids."""
235218
236- # Load trle pages if needed
237- while index_trle + match_size > len (trle ):
238- trle_offset += 1
239- print (f"Loading trle page at offset { trle_offset } " )
240- if trle_offset >= max_pages or get_trle_page_and_extend (trle_offset ) == 0 :
241- return False
219+ def get_local_page_and_extend (offset ):
220+ con = database_make_connection ()
221+ page = get_local_page (offset , con )['levels' ]
222+ con .close ()
223+ self .local .extend (page )
224+ return len (page )
225+
226+ def get_trle_page_and_extend (offset ):
227+ page = get_trle_page (offset )['levels' ]
228+ self .trle .extend (page )
229+ return len (page )
230+
231+ def ensure_data (index_local , index_trle ):
232+ """Ensure enough items are available from each database."""
233+ # Load local pages if needed
234+ while index_local + match_size > len (self .local ):
235+ self .local_offset += 1
236+ print (f"Loading local page at offset { self .local_offset } " )
237+ if self .local_offset >= max_pages or \
238+ get_local_page_and_extend (self .local_offset ) == 0 :
239+ return False
242240
243- return True
241+ # Load trle pages if needed
242+ while index_trle + match_size > len (self .trle ):
243+ self .trle_offset += 1
244+ print (f"Loading trle page at offset { self .trle_offset } " )
245+ if self .trle_offset >= max_pages or get_trle_page_and_extend (self .trle_offset ) == 0 :
246+ return False
244247
245- def ids_match (offset_local , offset_trle ):
246- """Compare sequences of trle_ids from both lists."""
247- if not ensure_data (offset_local , offset_trle ):
248- return False
248+ return True
249249
250- for i in range (match_size ):
251- try :
252- local_id = int (local [offset_local + i ]['trle_id' ])
253- trle_id = int (trle [offset_trle + i ]['trle_id' ])
254- if local_id != trle_id :
250+ def ids_match (offset_local , offset_trle ):
251+ """Compare sequences of trle_ids from both lists."""
252+ if not ensure_data (offset_local , offset_trle ):
253+ return False
254+
255+ for i in range (match_size ):
256+ try :
257+ local_id = int (self .local [offset_local + i ]['trle_id' ])
258+ trle_id = int (self .trle [offset_trle + i ]['trle_id' ])
259+ if local_id != trle_id :
260+ return False
261+ except IndexError :
255262 return False
263+ return True
264+
265+ # Load initial data
266+ get_local_page_and_extend (self .local_offset )
267+ get_trle_page_and_extend (self .trle_offset )
268+
269+ i = 0
270+ while True :
271+ if i + match_size > len (self .local ):
272+ # Try to load more local data
273+ if not ensure_data (i , 0 ):
274+ break
275+
276+ for j in range (len (self .trle ) - match_size + 1 ):
277+ if ids_match (i , j ):
278+ print (f"Match found at local[{ i } ] and trle[{ j } ]" )
279+ return (i , j )
280+ i += 1
281+
282+ # If we run out of trle data, load more
283+ if len (self .trle ) - match_size < 1 :
284+ added = get_trle_page_and_extend (self .trle_offset + 1 )
285+ self .trle_offset += 1
286+ if added == 0 or self .trle_offset >= max_pages :
287+ break
288+
289+ print ("❌ No match found after paging." )
290+ return None
291+
292+ def run (self ):
293+ """
294+ Tail-sync in 3 simple stages.
295+
296+ 1. Update the last 5 matching records if their attributes differ.
297+ 2. Add new records from remote page after the match.
298+ 3. Update or delete unmatched local records after the match.
299+ """
300+ local_start , remote_start = self .match_tails ()
301+ tail_count = 5
302+
303+ # Stage 1: Check and update mismatched records
304+ for i in range (tail_count ):
305+ try :
306+ local = self .local [local_start + i ]
307+ remote = self .trle [remote_start + i ]
256308 except IndexError :
257- return False
258- return True
309+ print ( f"IndexError self.local[ { local_start + i } ] self.trle[ { remote_start + i } ]" )
310+ sys . exit ( 1 )
259311
260- # Load initial data
261- get_local_page_and_extend (local_offset )
262- get_trle_page_and_extend (trle_offset )
312+ if not self .match_record (local , remote ):
313+ update_level (local ['trle_id' ])
263314
264- i = 0
265- while True :
266- if i + match_size > len (local ):
267- # Try to load more local data
268- if not ensure_data (i , 0 ):
269- break
315+ # Stage 2: Add new remote records
316+ existing_ids = {ids ['trle_id' ] for ids in self .local }
317+ for remote in self .trle [:remote_start ]:
318+ if remote ['trle_id' ] not in existing_ids :
319+ add_level_card (remote ['trle_id' ])
270320
271- for j in range (len (trle ) - match_size + 1 ):
272- if ids_match (i , j ):
273- print (f"Match found at local[{ i } ] and trle[{ j } ]" )
274- return (i , j )
275- i += 1
276-
277- # If we run out of trle data, load more
278- if len (trle ) - match_size < 1 :
279- added = get_trle_page_and_extend (trle_offset + 1 )
280- trle_offset += 1
281- if added == 0 or trle_offset >= max_pages :
282- break
321+ # Stage 3: Check unmatched local records
322+ for local in self .local [:local_start ]:
323+ exists = any (r ['trle_id' ] == local ['trle_id' ] for r in self .trle )
324+ if exists :
325+ update_level (local ['trle_id' ])
326+ else :
327+ remove_level (local ['trle_id' ])
283328
284- print ("❌ No match found after paging." )
285- return None
329+
330+
331+ def sync_cards ():
332+ """Lazy tail sync of cards."""
333+ tailsync = TailSync ()
334+ tailsync .run ()
286335
287336
288337def get_local_page (offset , con ):
0 commit comments