11from collections .abc import Iterable
2+ import json
23import os
34import sqlite3
45import aiosqlite
56
67from redfetch import config
78from redfetch .models import DownloadTask , Resource
89from redfetch import meta
10+ from redfetch import utils
911
1012# Unified schema version marker
1113SCHEMA_VERSION = 1
@@ -44,15 +46,19 @@ def get_db_path(db_name: str) -> str:
4446 return os .path .join (_get_cache_dir (), db_name )
4547
4648
47- def initialize_db (db_name : str ) -> None :
49+ def initialize_db (db_name : str , settings_env : str ) :
4850 """Ensure unified schema; reset once to unified schema if version is outdated."""
4951 with get_db_connection (db_name ) as conn :
5052 cursor = conn .cursor ()
51- _ensure_metadata (cursor )
52- _ensure_downloads_table (cursor )
53- _normalize_parent_ids (cursor )
54- _ensure_indexes (cursor )
55- _ensure_navmesh_tables (cursor )
53+ _initialize_schema (cursor )
54+ return _reconcile_install_signature (cursor , settings_env )
55+
56+
57+ def reconcile_install_signature (db_name : str , settings_env : str ):
58+ with get_db_connection (db_name ) as conn :
59+ cursor = conn .cursor ()
60+ _initialize_schema (cursor )
61+ return _reconcile_install_signature (cursor , settings_env )
5662
5763
5864def _ensure_downloads_table (cursor ) -> None :
@@ -99,6 +105,8 @@ def _ensure_metadata(cursor) -> None:
99105 if 'schema_version' not in cols :
100106 cursor .execute ("ALTER TABLE metadata ADD COLUMN schema_version INTEGER" )
101107 cursor .execute ("UPDATE metadata SET schema_version=0 WHERE id=1" )
108+ if 'install_signature' not in cols :
109+ cursor .execute ("ALTER TABLE metadata ADD COLUMN install_signature TEXT" )
102110 # Check current version and reset schema if outdated
103111 cursor .execute ("SELECT schema_version FROM metadata WHERE id=1" )
104112 row = cursor .fetchone ()
@@ -107,6 +115,109 @@ def _ensure_metadata(cursor) -> None:
107115 _reset_to_unified_schema (cursor )
108116
109117
118+ def _initialize_schema (cursor ) -> None :
119+ _ensure_metadata (cursor )
120+ _ensure_downloads_table (cursor )
121+ _normalize_parent_ids (cursor )
122+ _ensure_indexes (cursor )
123+ _ensure_navmesh_tables (cursor )
124+
125+
126+ def _get_vvmq_id_for_env (settings_env : str ) -> str | None :
127+ for resource_id , env in config .VANILLA_MAP .items ():
128+ if env .upper () == settings_env .upper ():
129+ return str (resource_id )
130+ return None
131+
132+
133+ def _compute_install_signature (settings_env : str ) -> dict :
134+ settings_for_env = config .settings .from_env (settings_env )
135+ download_folder = os .path .normpath (settings_for_env .DOWNLOAD_FOLDER )
136+ eqpath = os .path .normpath (settings_for_env .EQPATH ) if settings_for_env .EQPATH else ""
137+
138+ vvmq_id = _get_vvmq_id_for_env (settings_env )
139+ vvmq_path = None
140+ if vvmq_id :
141+ vvmq_resource = settings_for_env .SPECIAL_RESOURCES .get (vvmq_id )
142+ vvmq_path = utils .resolve_special_destination (vvmq_resource , download_folder )
143+
144+ base_path = vvmq_path if vvmq_path else download_folder
145+
146+ special_destinations : dict [str , str ] = {}
147+ for resource_id , resource_info in settings_for_env .SPECIAL_RESOURCES .items ():
148+ destination = utils .resolve_special_destination (resource_info , download_folder )
149+ if destination :
150+ special_destinations [str (resource_id )] = destination
151+
152+ return {
153+ "base_path" : base_path ,
154+ "download_folder" : download_folder ,
155+ "eqpath" : eqpath ,
156+ "special_destinations" : special_destinations ,
157+ }
158+
159+
160+ def _load_install_signature (cursor ) -> dict | None :
161+ cursor .execute ("SELECT install_signature FROM metadata WHERE id = 1" )
162+ row = cursor .fetchone ()
163+ if not row or row [0 ] is None :
164+ return None
165+ return json .loads (row [0 ])
166+
167+
168+ def _save_install_signature (cursor , signature : dict ) -> None :
169+ cursor .execute (
170+ "UPDATE metadata SET install_signature = ? WHERE id = 1" ,
171+ (json .dumps (signature , sort_keys = True ),),
172+ )
173+
174+
175+ def _diff_special_destinations (before : dict | None , after : dict ) -> list [str ]:
176+ before_map = before .get ("special_destinations" , {}) if before else {}
177+ after_map = after .get ("special_destinations" , {})
178+ changed = []
179+ for resource_id in set (before_map ) | set (after_map ):
180+ if before_map .get (resource_id ) != after_map .get (resource_id ):
181+ changed .append (str (resource_id ))
182+ return sorted (changed )
183+
184+
185+ def _reset_download_dates_for_special_resource (cursor , resource_id : str ) -> None :
186+ rid_int = int (resource_id )
187+ cursor .execute (
188+ "UPDATE downloads SET version_local=0 WHERE parent_id = 0 AND resource_id = ?" ,
189+ (rid_int ,),
190+ )
191+ cursor .execute (
192+ "UPDATE downloads SET version_local=0 WHERE parent_id = ?" ,
193+ (rid_int ,),
194+ )
195+
196+
197+ def _reconcile_install_signature (cursor , settings_env : str ) -> dict | None :
198+ before = _load_install_signature (cursor )
199+ after = _compute_install_signature (settings_env )
200+
201+ if before is None :
202+ _save_install_signature (cursor , after )
203+ return None
204+
205+ if before == after :
206+ return None
207+
208+ if before .get ("base_path" ) != after .get ("base_path" ):
209+ reset_download_dates (cursor )
210+ _save_install_signature (cursor , after )
211+ return {"global_reset" : True , "changed_ids" : []}
212+
213+ changed_ids = _diff_special_destinations (before , after )
214+ for resource_id in changed_ids :
215+ _reset_download_dates_for_special_resource (cursor , resource_id )
216+
217+ _save_install_signature (cursor , after )
218+ return {"global_reset" : False , "changed_ids" : changed_ids }
219+
220+
110221def _ensure_indexes (cursor ) -> None :
111222 """Create indexes."""
112223 try :
0 commit comments