utils.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. from games.constants import (
  2. ENGLISH_PATCHED_KEYWORDS,
  3. HACK_KEYWORDS,
  4. REGION_KEYWORDS,
  5. UNDUB_KEYWORDS,
  6. )
  7. import os
  8. import xml.etree.ElementTree as ET
  9. import subprocess
  10. import re
  11. from dateutil import parser
  12. from django.conf import settings
  13. from .models import (
  14. Developer,
  15. Game,
  16. GameSystem,
  17. Genre,
  18. Publisher,
  19. GameCollection,
  20. )
  21. import logging
  22. logger = logging.Logger(__name__)
  23. def update_media_root_for_import(file_path):
  24. """Given a file path, re-write it for our app MEDIA_ROOT"""
  25. suffix = ""
  26. if file_path:
  27. suffix = file_path.split("/media/")[-1]
  28. return suffix
  29. def import_gamelist_file_to_db_for_system(
  30. game_system_slug, file_path=None, full_scan=False
  31. ):
  32. imported_games = []
  33. not_imported_games = []
  34. if not file_path:
  35. file_path = os.path.join(
  36. settings.ROMS_DIR, game_system_slug, "gamelist.xml"
  37. )
  38. if not os.path.exists(file_path):
  39. logger.info(
  40. "File path for {game_system_slug} had no gamelist.xml file, run a scraper first!"
  41. )
  42. return
  43. gamelist = ET.parse(file_path)
  44. game_system = GameSystem.objects.filter(
  45. retropie_slug=game_system_slug
  46. ).first()
  47. if not game_system:
  48. defaults = settings.GAME_SYSTEM_DEFAULTS.get(game_system_slug, None)
  49. full_system_name = game_system_slug
  50. if defaults:
  51. full_system_name = defaults.get("name", game_system_slug)
  52. game_system = GameSystem.objects.create(
  53. name=full_system_name, retropie_slug=game_system_slug
  54. )
  55. games = gamelist.findall("game")
  56. for game in games:
  57. name = game.find("name").text
  58. game_path = game.find("path").text.lower()
  59. try:
  60. obj, created = Game.objects.get_or_create(
  61. name=name, game_system=game_system
  62. )
  63. except Game.MultipleObjectsReturned:
  64. logger.warning(
  65. f"While importing {name} for {game_system}, duplicate entry found"
  66. )
  67. print(
  68. f"While importing {name} for {game_system}, duplicate entry found"
  69. )
  70. continue
  71. if not created and not full_scan:
  72. not_imported_games.append(obj)
  73. logger.info(f"Not updating {obj}, use full-scan to update")
  74. print(f"Not updating {obj}, use full-scan to update")
  75. continue
  76. elif not created and full_scan:
  77. print(f"Updating {obj}")
  78. else:
  79. print(f"Adding {obj}")
  80. region = Game.Region.X.name
  81. if any(us in game_path for us in REGION_KEYWORDS["US"]):
  82. region = Game.Region.US.name
  83. if any(jp in game_path for jp in REGION_KEYWORDS["JP"]):
  84. region = Game.Region.JP.name
  85. if any(eu in game_path for eu in REGION_KEYWORDS["EU"]):
  86. region = Game.Region.EU.name
  87. english_patched = any(
  88. key in game_path.lower() for key in ENGLISH_PATCHED_KEYWORDS
  89. )
  90. patch_version = None
  91. if english_patched:
  92. version_re = re.search("(?= v)(.*?)(?=\))", game_path.lower())
  93. if version_re:
  94. patch_version = version_re.group(0)[2:]
  95. undub = any(key in game_path.lower() for key in UNDUB_KEYWORDS)
  96. hack = any(key in game_path.lower() for key in HACK_KEYWORDS)
  97. release_date_str = game.find("releasedate").text
  98. developer_str = game.find("developer").text
  99. publisher_str = game.find("publisher").text
  100. genres_str = game.find("genre").text
  101. rating_str = game.find("rating").text
  102. players_str = "1"
  103. if game.find("players"):
  104. players_str = game.find("players").text
  105. try:
  106. kid_game = game.find("kidgame").text == "true"
  107. except AttributeError:
  108. kid_game = False
  109. genre_list = []
  110. if genres_str:
  111. genre_str_list = genres_str.split(", ")
  112. for genre_str in genre_str_list:
  113. genre, _created = Genre.objects.get_or_create(name=genre_str)
  114. genre_list.append(genre)
  115. players = int(players_str) if players_str else 1
  116. rating = float(rating_str) if rating_str else None
  117. publisher = None
  118. if publisher_str:
  119. publisher, _created = Publisher.objects.get_or_create(
  120. name=publisher_str
  121. )
  122. developer = None
  123. if developer_str:
  124. developer, _created = Developer.objects.get_or_create(
  125. name=developer_str
  126. )
  127. release_date = (
  128. parser.parse(release_date_str) if release_date_str else None
  129. )
  130. description = game.find("desc").text
  131. screenshot_path = update_media_root_for_import(game.find("image").text)
  132. rom_path = update_media_root_for_import(game.find("path").text)
  133. video_path_elem = game.find("video")
  134. video_path = ""
  135. if video_path_elem:
  136. video_path = update_media_root_for_import(video_path_elem.text)
  137. marquee_path = update_media_root_for_import(game.find("marquee").text)
  138. obj.game_system = game_system
  139. obj.developer = developer
  140. obj.publisher = publisher
  141. obj.players = players
  142. obj.description = description
  143. obj.release_date = release_date
  144. obj.rating = rating
  145. obj.genre.set(genre_list)
  146. obj.screenshot = screenshot_path
  147. obj.rom_file = rom_path
  148. obj.video = video_path
  149. obj.marquee = marquee_path
  150. obj.kid_game = kid_game
  151. obj.english_patched = english_patched
  152. obj.english_patched_version = patch_version
  153. obj.hack = hack
  154. obj.undub = undub
  155. obj.region = region
  156. obj.save()
  157. imported_games.append(obj)
  158. return {"imported": imported_games, "not_imported": not_imported_games}
  159. def export_gamelist_file_to_path_for_system(game_system_slug, file_path=None):
  160. exported_games = []
  161. game_system = GameSystem.objects.get(retropie_slug=game_system_slug)
  162. if not file_path:
  163. file_path = f"/tmp/{game_system_slug}-gamelist.xml"
  164. # file_path = os.path.join(settings.ROMS_DIR, game_system_slug, "gamelist.xml")
  165. root = ET.Element("gameList")
  166. tree = ET.ElementTree(root)
  167. tree.write(file_path)
  168. games = Game.objects.filter(game_system=game_system)
  169. for game in games:
  170. game_node = ET.SubElement(root, "game")
  171. genre_str = ", ".join(game.genre.all().values_list("name", flat=True))
  172. release_date_str = ""
  173. if game.release_date:
  174. release_date_str = game.release_date.strftime("%Y%m%dT00000")
  175. ET.SubElement(game_node, "path").text = (
  176. game.rom_file.path if game.rom_file else ""
  177. )
  178. ET.SubElement(game_node, "name").text = game.name
  179. ET.SubElement(game_node, "thumbnail").text = ""
  180. ET.SubElement(game_node, "image").text = (
  181. game.screenshot.path if game.screenshot else ""
  182. )
  183. ET.SubElement(game_node, "marquee").text = (
  184. game.marquee.path if game.marquee else ""
  185. )
  186. ET.SubElement(game_node, "video").text = (
  187. game.video.path if game.video else ""
  188. )
  189. ET.SubElement(game_node, "rating").text = str(game.rating)
  190. ET.SubElement(game_node, "desc").text = game.description
  191. ET.SubElement(game_node, "releasedate").text = release_date_str
  192. ET.SubElement(game_node, "developer").text = str(game.developer)
  193. ET.SubElement(game_node, "publisher").text = str(game.publisher)
  194. ET.SubElement(game_node, "genre").text = genre_str
  195. ET.SubElement(game_node, "players").text = str(game.players)
  196. if game.kid_game:
  197. ET.SubElement(game_node, "kidgame").text = "true"
  198. exported_games.append(game)
  199. tree = ET.ElementTree(root)
  200. tree.write(file_path, xml_declaration=True, encoding="utf-8")
  201. return exported_games, file_path
  202. def skyscrape_console(game_system_slug):
  203. scraper_config = settings.SCRAPER_CONFIG_FILE
  204. scraper_binary = settings.SCRAPER_BIN_PATH
  205. scraper_site = settings.SCRAPER_SITE
  206. scraper_frontend = settings.SCRAPER_FRONTEND
  207. # If the config file is relative, append our base dir
  208. if scraper_config[0] != "/":
  209. scraper_config = os.path.join(settings.BASE_DIR, scraper_config)
  210. if not os.path.exists(scraper_config):
  211. logger.info(f"Config file not found at {scraper_config}")
  212. return
  213. logger.info(
  214. f"Scraping game info using configuration file from {scraper_config}"
  215. )
  216. scrape_output = subprocess.run(
  217. [
  218. scraper_binary,
  219. "-c",
  220. f"{scraper_config}",
  221. "-s",
  222. f"{scraper_site}",
  223. "-f",
  224. f"{scraper_frontend}",
  225. "-p",
  226. f"{game_system_slug}",
  227. ],
  228. capture_output=True,
  229. )
  230. load_output = subprocess.run(
  231. [
  232. scraper_binary,
  233. "-c",
  234. f"{scraper_config}",
  235. "-f",
  236. f"{scraper_frontend}",
  237. "-p",
  238. f"{game_system_slug}",
  239. ],
  240. capture_output=True,
  241. )
  242. # TODO We should progressively pipe output to a log file instead of this
  243. # print(scrape_output)
  244. # print(load_output)
  245. print(f"Scraped info for {game_system_slug}")
  246. return scrape_output, load_output
  247. def export_collections(dryrun=True):
  248. exported = []
  249. for collection in GameCollection.objects.all():
  250. collection.export_to_file(dryrun)
  251. exported.append(collection)
  252. return exported