/ggpo/common/unsupportedsavestates.py

https://github.com/poliva/pyqtggpo
Python | 152 lines | 127 code | 17 blank | 8 comment | 27 complexity | 8ddfcef8f44e1d749e252b9840e4f8c2 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. import glob
  3. import hashlib
  4. import json
  5. import os
  6. import re
  7. import urllib
  8. import urllib2
  9. import time
  10. from PyQt4 import QtCore
  11. from ggpo.common.util import logdebug, findGamesavesDir, sha256digest
  12. def readLocalJsonDigest():
  13. localJsonDigest = {}
  14. d = findGamesavesDir()
  15. if d:
  16. localjson = os.path.join(d, SyncWorker.JSON_INDEX_FILENAME)
  17. if os.path.isfile(localjson):
  18. # noinspection PyBroadException
  19. try:
  20. localJsonDigest = json.load(file(localjson).read())
  21. except:
  22. pass
  23. if not localJsonDigest:
  24. return writeLocalJsonDigest()
  25. return localJsonDigest
  26. def writeLocalJsonDigest():
  27. localJsonDigest = {}
  28. d = findGamesavesDir()
  29. if d:
  30. localjson = os.path.join(d, SyncWorker.JSON_INDEX_FILENAME)
  31. for filename in glob.glob(os.path.join(d, '*.fs')):
  32. localJsonDigest[os.path.basename(filename)] = sha256digest(filename)
  33. # noinspection PyBroadException
  34. try:
  35. f = open(localjson, 'w')
  36. f.write(json.dumps(localJsonDigest, sort_keys=True, indent=2))
  37. except:
  38. pass
  39. return localJsonDigest
  40. class SyncWorker(QtCore.QObject):
  41. sigFinished = QtCore.pyqtSignal(int, int, int)
  42. sigStatusMessage = QtCore.pyqtSignal(str)
  43. SAVESTATES_GITHUB_BASE_URL = 'https://raw.github.com/poliva/fightcadestates/newfba/'
  44. JSON_INDEX_FILENAME = 'index.json'
  45. SAVESTATES_INDEX_URL = SAVESTATES_GITHUB_BASE_URL + JSON_INDEX_FILENAME
  46. def __init__(self, checkonly=False):
  47. QtCore.QObject.__init__(self)
  48. self.checkonly = checkonly
  49. self.added = 0
  50. self.updated = 0
  51. self.nochange = 0
  52. def download(self):
  53. d = findGamesavesDir()
  54. if not d:
  55. self.sigStatusMessage.emit('Can\'t find Savestates Directory')
  56. self.sigFinished.emit(self.added, self.updated, self.nochange)
  57. return
  58. localJsonDigest = readLocalJsonDigest()
  59. # noinspection PyBroadException
  60. try:
  61. # gotta love CPython's GIL, yield thread
  62. time.sleep(0.05)
  63. response = urllib2.urlopen(self.SAVESTATES_INDEX_URL, timeout=3)
  64. games = json.load(response)
  65. for filename, shahash in games.items():
  66. if re.search(r'[^ .a-zA-Z0-9_-]', filename):
  67. logdebug().error("Filename {} looks suspicious, ignoring".format(filename))
  68. continue
  69. if filename in localJsonDigest:
  70. if localJsonDigest[filename] == shahash:
  71. self.nochange += 1
  72. continue
  73. else:
  74. self.updated += 1
  75. else:
  76. self.added += 1
  77. if not self.checkonly:
  78. time.sleep(0.05)
  79. localfile = os.path.join(d, filename)
  80. fileurl = self.SAVESTATES_GITHUB_BASE_URL + urllib.quote(filename)
  81. urllib.urlretrieve(fileurl, localfile)
  82. self.sigStatusMessage.emit('Downloaded {}'.format(localfile))
  83. # remove config/games/<game>.fs
  84. gamefs=os.path.abspath(os.path.join(d,"..","config","games",filename.replace("_ggpo.fs",".fs")))
  85. if os.path.isfile(gamefs):
  86. try:
  87. os.remove(gamefs)
  88. except:
  89. pass
  90. if not self.checkonly:
  91. if not self.added and not self.updated:
  92. #self.sigStatusMessage.emit('All files are up to date')
  93. pass
  94. else:
  95. writeLocalJsonDigest()
  96. self.sigStatusMessage.emit(
  97. '{} files are current, added {}, updated {}'.format(
  98. self.nochange, self.added, self.updated))
  99. except Exception, ex:
  100. logdebug().error(str(ex))
  101. self.sigFinished.emit(self.added, self.updated, self.nochange)
  102. # noinspection PyClassHasNoInit
  103. class UnsupportedSavestates(QtCore.QObject):
  104. sigRemoteHasUpdates = QtCore.pyqtSignal(int, int, int)
  105. _thread = None
  106. _worker = None
  107. @classmethod
  108. def check(cls, mainWindow, statusMsgCallback=None, finishedCallback=None):
  109. cls.run(True, statusMsgCallback, finishedCallback)
  110. @classmethod
  111. def cleanup(cls, x, y, z):
  112. try:
  113. cls._thread.quit()
  114. cls._thread.wait()
  115. except:
  116. pass
  117. cls._thread = None
  118. cls._worker = None
  119. @classmethod
  120. def run(cls, checkonly, statusMsgCallback=None, finishedCallback=None):
  121. if cls._thread:
  122. logdebug().error('Already has a download thread running')
  123. cls._thread = QtCore.QThread()
  124. cls._worker = SyncWorker(checkonly)
  125. cls._worker.moveToThread(cls._thread)
  126. if finishedCallback:
  127. cls._worker.sigFinished.connect(finishedCallback)
  128. cls._worker.sigFinished.connect(cls.cleanup)
  129. if statusMsgCallback:
  130. cls._worker.sigStatusMessage.connect(statusMsgCallback)
  131. cls._thread.started.connect(cls._worker.download)
  132. cls._thread.start()
  133. @classmethod
  134. def sync(cls, statusMsgCallback=None, finishedCallback=None):
  135. cls.run(False, statusMsgCallback, finishedCallback)