ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/ns_dev/Python/NinoCode/Active_prgs/Gromulus/Gromulus_UI.py
Revision: 978
Committed: Fri Feb 13 22:49:37 2026 UTC (6 weeks ago) by nino.borges
Content type: text/x-python
File size: 15773 byte(s)
Log Message:
System-scoped DAT import refactor and metadata matching improvements

Refactored DAT import workflow so No-Intro and TOSEC imports are tied to a selected local main_app_system instead of global table replacement.
Updated UI import flow to prompt for target system before DAT import.
Added schema support for per-system DAT scoping (app_system_id) and DAT import tracking (dat_import_history with source/version/date/count).
Updated No-Intro/TOSEC import logic to replace rows only for the selected system and upsert last-import metadata.
Switched game metadata matching to use main_app_file_hash.file_md5_hash (via container_file_id) instead of container hash.
Updated game list/detail lookups to use file-level hash matching, including case-insensitive MD5 comparison.
Improved TOSEC/No-Intro lookup accuracy and kept read paths non-destructive (schema writes only during migration/import paths).
Added/updated indexing and normalization hooks to support system-scoped matching performance and backward compatibility.

File Contents

# User Rev Content
1 nino.borges 795 """
2     Created by Emanuel Borges
3     05.19.2023
4    
5     This is the main UI for Gromulus, which is a catalog inventory for roms, games and maybe one day applications.
6     Like most of my GUI programs, this will import a separate library with my methods for doing the actual work.
7    
8     """
9    
10    
11     import sys, os, wx
12     #import wx.lib.buttons as buttons
13     import Gromulus_Lib
14    
15    
16    
17     class MyFrame(wx.Frame):
18     def __init__(self, parent, ID, title, pos=wx.DefaultPosition):
19 nino.borges 976 wx.Frame.__init__(self, parent, ID, title, pos, size = (1450,725))
20 nino.borges 909 self.db = Gromulus_Lib.DatabaseManager()
21 nino.borges 976 self.dbConnection = self.db.get_connection()
22 nino.borges 795 self.panel = wx.Panel(self,-1)
23 nino.borges 976 self.currentSystemKey = "SNES"
24     self.gamesListMatrix = Gromulus_Lib.GetGameListBySystem(self.currentSystemKey, self.dbConnection)
25 nino.borges 795 gamesList = list(self.gamesListMatrix.keys())
26     gamesList.sort()
27 nino.borges 806 self.gamesCount = len(gamesList)
28     unmatchedGamesList=[i for i in gamesList if "." in i]
29     self.unmatchedGamesCount = len(unmatchedGamesList)
30     print(f"{self.gamesCount} total games for this system. {self.gamesCount - self.unmatchedGamesCount} matched and {self.unmatchedGamesCount} unmatched.")
31 nino.borges 795
32     self.CreateSystemButtonSection()
33     self.gameSelectionListBox = wx.ListBox(self.panel, 60, (100, 50), (490, 320), gamesList , wx.LB_SINGLE|wx.LB_OWNERDRAW)
34     self.CreateFieldsFirstRow()
35    
36    
37    
38     mainSizer = wx.BoxSizer(wx.HORIZONTAL)
39     mainSizer.Add(self.buttonSizer, 0, wx.ALIGN_TOP|wx.LEFT|wx.TOP,25)
40     mainSizer.Add(self.gameSelectionListBox, 0, wx.ALIGN_TOP|wx.LEFT|wx.TOP,25)
41     mainSizer.Add(self.fieldsFirstRowSizer, 0, wx.ALIGN_TOP|wx.LEFT|wx.TOP,25)
42     self.panel.SetSizer(mainSizer)
43    
44    
45     self.CreateStatusBar()
46     self.SetStatusText("Ready.")
47     self.CreateMenuBar()
48    
49     self.Bind(wx. EVT_TOGGLEBUTTON, self.OnSystemSelected, self.snesSystemButton)
50     self.Bind(wx. EVT_TOGGLEBUTTON, self.OnSystemSelected, self.nesSystemButton)
51     self.Bind(wx. EVT_TOGGLEBUTTON, self.OnSystemSelected, self.genesisSystemButton)
52     self.Bind(wx. EVT_TOGGLEBUTTON, self.OnSystemSelected, self.fz1SystemButton)
53     self.Bind(wx. EVT_TOGGLEBUTTON, self.OnSystemSelected, self.ps1SystemButton)
54    
55     self.Bind(wx.EVT_LISTBOX, self.OnGameSelected, self.gameSelectionListBox)
56    
57 nino.borges 976 def _set_text(self, text_ctrl, value):
58     text_ctrl.SetValue(value if value else "")
59 nino.borges 795
60 nino.borges 976 def _load_games_for_system(self, system_key):
61     self.currentSystemKey = system_key
62     self.gamesListMatrix = Gromulus_Lib.GetGameListBySystem(system_key, self.dbConnection)
63     gamesList = list(self.gamesListMatrix.keys())
64     gamesList.sort()
65    
66     self.gameSelectionListBox.Clear()
67     self.gameSelectionListBox.AppendItems(gamesList)
68    
69     self._set_text(self.gameNameTextCtrl, "")
70     self._set_text(self.gameHashTextCtrl, "")
71     self._set_text(self.gameFileNameTextCtrl, "")
72     self._set_text(self.gameFilePathTextCtrl, "")
73     self._set_text(self.noIntroGameNameTextCtrl, "")
74     self._set_text(self.noIntroSystemNameTextCtrl, "")
75 nino.borges 978 self._set_text(self.tosecGameNameTextCtrl, "")
76 nino.borges 976
77 nino.borges 978 def _choose_system_for_import(self, prompt_title):
78     systemMatrix = Gromulus_Lib.GetSystemList(self.dbConnection)
79     systemsList = list(systemMatrix.keys())
80     if not systemsList:
81     wx.MessageBox("No systems found. Add systems first.", "Import DAT", wx.OK | wx.ICON_WARNING)
82     return None
83 nino.borges 976
84 nino.borges 978 dlg = wx.SingleChoiceDialog(self, "Select system for this DAT import", prompt_title, systemsList)
85     if dlg.ShowModal() != wx.ID_OK:
86     return None
87     selected_name = dlg.GetStringSelection()
88     return systemMatrix[selected_name][0]
89    
90    
91 nino.borges 795 def CreateSystemButtonSection(self):
92     #systemsList = ['SNES','NES','Genisys']
93     #for system in systemsList:
94     #self.snesSystemButton = buttons.GenToggleButton(self.panel, -1, "SNES")
95     #self.nesSystemButton = buttons.GenToggleButton(self.panel, -1, "NES")
96     #self.genesisSystemButton = buttons.GenToggleButton(self.panel, -1, "Genesis")
97     #self.ps1SystemButton = buttons.GenToggleButton(self.panel, -1, "Playstation")
98    
99     ## Create a dictionary that holds the button instances by label text, so that you can toggle the other ones off in the bind event.
100     self.systemButtonDict = {}
101 nino.borges 976 self.buttonSystemMap = {}
102 nino.borges 795 self.snesSystemButton = wx.ToggleButton(self.panel, -1, "SNES")
103     self.systemButtonDict[self.snesSystemButton.GetLabelText()] = self.snesSystemButton
104 nino.borges 976 self.buttonSystemMap[self.snesSystemButton.GetLabelText()] = "SNES"
105 nino.borges 795
106     self.nesSystemButton = wx.ToggleButton(self.panel, -1, "NES")
107     self.systemButtonDict[self.nesSystemButton.GetLabelText()] = self.nesSystemButton
108 nino.borges 976 self.buttonSystemMap[self.nesSystemButton.GetLabelText()] = "NES"
109 nino.borges 795
110     self.genesisSystemButton = wx.ToggleButton(self.panel, -1, "Genesis")
111     self.systemButtonDict[self.genesisSystemButton.GetLabelText()] = self.genesisSystemButton
112 nino.borges 976 self.buttonSystemMap[self.genesisSystemButton.GetLabelText()] = "Genesis"
113 nino.borges 795
114     self.fz1SystemButton = wx.ToggleButton(self.panel, -1, "3DO")
115     self.systemButtonDict[self.fz1SystemButton.GetLabelText()] = self.fz1SystemButton
116 nino.borges 976 self.buttonSystemMap[self.fz1SystemButton.GetLabelText()] = "3DO"
117 nino.borges 795
118     self.ps1SystemButton = wx.ToggleButton(self.panel, -1, "Playstation")
119     self.systemButtonDict[self.ps1SystemButton.GetLabelText()] = self.ps1SystemButton
120 nino.borges 976 self.buttonSystemMap[self.ps1SystemButton.GetLabelText()] = "Playstation"
121     self.snesSystemButton.SetValue(True)
122 nino.borges 795
123    
124     self.buttonSizer = wx.BoxSizer(wx.VERTICAL)
125     self.buttonSizer.Add(self.snesSystemButton, 0, wx.ALL,10)
126     self.buttonSizer.Add(self.nesSystemButton,0,wx.ALL,10)
127     self.buttonSizer.Add(self.genesisSystemButton,0,wx.ALL,10)
128     self.buttonSizer.Add(self.fz1SystemButton,0,wx.ALL,10)
129     self.buttonSizer.Add(self.ps1SystemButton,0,wx.ALL,10)
130    
131     def CreateFieldsFirstRow(self):
132     self.gameNameStaticText = wx.StaticText(self.panel, -1, "Game Name:")
133     self.gameNameTextCtrl = wx.TextCtrl(self.panel, -1, size=(325, -1))
134    
135     self.gameHashStaticText = wx.StaticText(self.panel, -1, "Game Hash:")
136     self.gameHashTextCtrl = wx.TextCtrl(self.panel, -1, size=(325, -1))
137    
138     self.gameFileNameStaticText = wx.StaticText(self.panel, -1, "Game File Name:")
139     self.gameFileNameTextCtrl = wx.TextCtrl(self.panel, -1, size=(325, -1))
140    
141     self.gameFilePathStaticText = wx.StaticText(self.panel, -1, "Game File Path:")
142     self.gameFilePathTextCtrl = wx.TextCtrl(self.panel, -1, size=(425, -1))
143    
144     self.noIntroGameNameStaticText = wx.StaticText(self.panel, -1, "No Intro Game Name:")
145     self.noIntroGameNameTextCtrl = wx.TextCtrl(self.panel, -1, size=(325, -1))
146    
147     self.noIntroSystemNameStaticText = wx.StaticText(self.panel, -1, "No Intro System:")
148     self.noIntroSystemNameTextCtrl = wx.TextCtrl(self.panel, -1, size=(380, -1))
149    
150 nino.borges 976 self.tosecGameNameStaticText = wx.StaticText(self.panel, -1, "TOSEC Game Name:")
151     self.tosecGameNameTextCtrl = wx.TextCtrl(self.panel, -1, size=(325, -1))
152    
153 nino.borges 795 self.gameNameSizer = wx.BoxSizer(wx.HORIZONTAL)
154     self.gameNameSizer.Add(self.gameNameStaticText,0,wx.ALL, 10)
155     self.gameNameSizer.Add(self.gameNameTextCtrl,0,wx.ALL, 10)
156    
157     self.gameHashSizer = wx.BoxSizer(wx.HORIZONTAL)
158     self.gameHashSizer.Add(self.gameHashStaticText,0,wx.ALL, 10)
159     self.gameHashSizer.Add(self.gameHashTextCtrl,0,wx.ALL, 10)
160    
161     self.gameFileNameSizer = wx.BoxSizer(wx.HORIZONTAL)
162     self.gameFileNameSizer.Add(self.gameFileNameStaticText,0,wx.ALL, 10)
163     self.gameFileNameSizer.Add(self.gameFileNameTextCtrl,0,wx.ALL, 10)
164    
165     self.gameFilePathSizer = wx.BoxSizer(wx.HORIZONTAL)
166     self.gameFilePathSizer.Add(self.gameFilePathStaticText,0,wx.ALL, 10)
167     self.gameFilePathSizer.Add(self.gameFilePathTextCtrl,0,wx.ALL, 10)
168    
169     self.noIntroGameNameSizer = wx.BoxSizer(wx.HORIZONTAL)
170     self.noIntroGameNameSizer.Add(self.noIntroGameNameStaticText,0,wx.ALL, 10)
171     self.noIntroGameNameSizer.Add(self.noIntroGameNameTextCtrl,0,wx.ALL, 10)
172    
173     self.noIntroSystemNameSizer = wx.BoxSizer(wx.HORIZONTAL)
174     self.noIntroSystemNameSizer.Add(self.noIntroSystemNameStaticText,0,wx.ALL, 10)
175     self.noIntroSystemNameSizer.Add(self.noIntroSystemNameTextCtrl,0,wx.ALL, 10)
176    
177 nino.borges 976 self.tosecGameNameSizer = wx.BoxSizer(wx.HORIZONTAL)
178     self.tosecGameNameSizer.Add(self.tosecGameNameStaticText,0,wx.ALL, 10)
179     self.tosecGameNameSizer.Add(self.tosecGameNameTextCtrl,0,wx.ALL, 10)
180    
181 nino.borges 795 self.fieldsFirstRowSizer = wx.BoxSizer(wx.VERTICAL)
182     self.fieldsFirstRowSizer.Add(self.gameNameSizer,0,wx.ALL, 10)
183     self.fieldsFirstRowSizer.Add(self.gameHashSizer,0,wx.ALL, 10)
184     self.fieldsFirstRowSizer.Add(self.gameFileNameSizer,0,wx.ALL, 10)
185     self.fieldsFirstRowSizer.Add(self.gameFilePathSizer,0,wx.ALL, 10)
186     self.fieldsFirstRowSizer.Add(self.noIntroGameNameSizer,0,wx.ALL, 10)
187     self.fieldsFirstRowSizer.Add(self.noIntroSystemNameSizer,0,wx.ALL, 10)
188 nino.borges 976 self.fieldsFirstRowSizer.Add(self.tosecGameNameSizer,0,wx.ALL, 10)
189 nino.borges 795
190    
191     def MenuData(self):
192     return(("&Tools",
193 nino.borges 805 ("&Add Roms","Adds new Roms to a selected system.", self.OnAddNewRoms),
194     ("Import &NoIntro DAT", "Allows for the import of any of the No Intro DAT files.", self.OnImportNewNoIntroDat),
195 nino.borges 806 ("Import &TOSEC DAT", "Allows for the import of any of the TOSEC DAT files.", self.OnImportNewTosecDat)),
196 nino.borges 795 ("&Reports",
197     ("&Duplicates Report","Generates a report detailing duplicates that exist in your collection.", self.NothingYet)),
198     ("&Help",
199     ("&About", "Displays the About Window", self.NothingYet)))
200    
201     def CreateMenuBar(self):
202     menuBar = wx.MenuBar()
203     for eachMenuData in self.MenuData():
204     menuLabel = eachMenuData[0]
205     menuItems = eachMenuData[1:]
206     menuBar.Append(self.CreateMenu(menuItems), menuLabel)
207     self.SetMenuBar(menuBar)
208    
209     def CreateMenu(self, menuData):
210     menu = wx.Menu()
211     for eachLabel, eachStatus, eachHandler in menuData:
212     if not eachLabel:
213     menu.AppendSeparator()
214     continue
215     menuItem = menu.Append(-1, eachLabel, eachStatus)
216     self.Bind(wx.EVT_MENU, eachHandler, menuItem)
217     return menu
218    
219     def OnSystemSelected(self, evt):
220 nino.borges 976 if not evt.GetEventObject().GetValue():
221     evt.GetEventObject().SetValue(True)
222     return
223    
224 nino.borges 795 systemsButtonNamesList = list(self.systemButtonDict.keys())
225     systemsButtonNamesList.remove(evt.GetEventObject().GetLabelText())
226     for i in systemsButtonNamesList:
227     self.systemButtonDict[i].SetValue(False)
228    
229 nino.borges 976 system_key = self.buttonSystemMap.get(evt.GetEventObject().GetLabelText(), "SNES")
230     self._load_games_for_system(system_key)
231 nino.borges 795
232    
233     def OnGameSelected(self, evt):
234     print(self.gameSelectionListBox.GetStringSelection())
235 nino.borges 976 selected = self.gameSelectionListBox.GetStringSelection()
236     if not selected:
237     return
238 nino.borges 795
239 nino.borges 976 gameData = Gromulus_Lib.GetSingleGameById(self.gamesListMatrix[selected], self.dbConnection)
240     if not gameData:
241     return
242 nino.borges 795
243 nino.borges 976 display_name = gameData.get("game_name") or gameData.get("no_intro_game") or gameData.get("tosec_game") or gameData.get("filename")
244     no_intro_name = gameData.get("no_intro_game")
245     no_intro_system = gameData.get("no_intro_system") or gameData.get("tosec_system")
246     tosec_name = gameData.get("tosec_game")
247    
248     self._set_text(self.gameNameTextCtrl, display_name)
249     self._set_text(self.gameHashTextCtrl, gameData.get("hash"))
250     self._set_text(self.gameFileNameTextCtrl, gameData.get("filename"))
251     self._set_text(self.gameFilePathTextCtrl, gameData.get("path"))
252     self._set_text(self.noIntroGameNameTextCtrl, no_intro_name)
253     self._set_text(self.noIntroSystemNameTextCtrl, no_intro_system)
254     self._set_text(self.tosecGameNameTextCtrl, tosec_name)
255    
256 nino.borges 795 def NothingYet(self, event):
257     diag = wx.MessageDialog(self, "Nothing here yet!", "Disabled...", wx.OK | wx.ICON_INFORMATION)
258     diag.ShowModal()
259     diag.Destroy()
260    
261 nino.borges 805 def OnImportNewNoIntroDat(self,event):
262 nino.borges 978 selected_system_id = self._choose_system_for_import("No-Intro DAT System Selection")
263     if not selected_system_id:
264     return
265    
266 nino.borges 805 dlg = wx.FileDialog(
267     self, message="Import New NoIntro DAT ...", defaultDir=os.getcwd(),
268     defaultFile="", wildcard="DAT files (*.dat)|*.dat", style=wx.FD_OPEN | wx.FD_CHANGE_DIR
269     )
270     if dlg.ShowModal() == wx.ID_OK:
271     datPath = dlg.GetPath()
272 nino.borges 978 Gromulus_Lib.ImportNewNoIntroDat(datPath, selected_system_id, self.dbConnection)
273 nino.borges 805 doneDiag = wx.MessageDialog(self, "NoIntro DAT imported","All Done!",wx.OK | wx.ICON_INFORMATION)
274     doneDiag.ShowModal()
275     doneDiag.Destroy()
276 nino.borges 795
277 nino.borges 806 def OnImportNewTosecDat(self,event):
278 nino.borges 978 selected_system_id = self._choose_system_for_import("TOSEC DAT System Selection")
279     if not selected_system_id:
280     return
281    
282 nino.borges 806 dlg = wx.FileDialog(
283     self, message="Import New Tosec DAT ...", defaultDir=os.getcwd(),
284     defaultFile="", wildcard="DAT files (*.dat)|*.dat", style=wx.FD_OPEN | wx.FD_CHANGE_DIR
285     )
286     if dlg.ShowModal() == wx.ID_OK:
287     datPath = dlg.GetPath()
288 nino.borges 978 Gromulus_Lib.ImportNewTosecDat(datPath, selected_system_id, self.dbConnection)
289 nino.borges 806 doneDiag = wx.MessageDialog(self, "Tosec DAT imported","All Done!",wx.OK | wx.ICON_INFORMATION)
290     doneDiag.ShowModal()
291     doneDiag.Destroy()
292    
293    
294 nino.borges 805 def OnAddNewRoms(self,event):
295 nino.borges 908 systemMatrix = Gromulus_Lib.GetSystemList(self.dbConnection)
296 nino.borges 805 systemsList = list(systemMatrix.keys())
297     dlg = wx.SingleChoiceDialog(self,
298     "Select system for new ROMs",
299     "List of systemss", systemsList)
300     if (dlg.ShowModal() == wx.ID_OK):
301     dlg2 = wx.DirDialog(self, "Choose a directory:",
302     style=wx.DD_DEFAULT_STYLE)
303     if (dlg2.ShowModal() == wx.ID_OK):
304     romsToImportPath = dlg2.GetPath()
305     systemID = systemMatrix[dlg.GetStringSelection()][0]
306 nino.borges 908 importedCount, alreadyExistsCount, errImportCount = Gromulus_Lib.AddNewRoms(romsToImportPath, systemID, self.dbConnection)#, testOnly=True)
307 nino.borges 805 #print(systemMatrix[dlg.GetStringSelection()])
308     #print(dlg2.GetPath())
309     doneDiag = wx.MessageDialog(self, f"{str(importedCount)} files imported.\n{str(alreadyExistsCount)} files not imported because you already have them.\n{str(errImportCount)} files imported because of errors.", "Rom import process complete",wx.OK | wx.ICON_INFORMATION)
310     doneDiag.ShowModal()
311     doneDiag.Destroy()
312    
313    
314    
315 nino.borges 795 class MyApp(wx.App):
316     def OnInit(self):
317 nino.borges 909 self.frame = MyFrame(None, -1, "Gromulus v1.2")
318 nino.borges 795 self.frame.Show(True)
319     self.SetTopWindow(self.frame)
320     return True
321 nino.borges 908 def OnExit(self):
322 nino.borges 910 if hasattr(self, "frame") and hasattr(self.frame, "db"):
323     self.frame.db.close()
324 nino.borges 908 print("Closed DB")
325     return 0
326 nino.borges 795
327    
328     if __name__ == '__main__':
329     app = MyApp(0)
330 nino.borges 976 app.MainLoop()