Poodletooth-iLand/otp/launcher/LauncherBase.py

1752 lines
79 KiB
Python
Raw Normal View History

2015-03-03 22:10:12 +00:00
import sys
import os
import time
import string
import __builtin__
from panda3d.core import *
from direct.showbase.MessengerGlobal import *
from direct.showbase.DirectObject import DirectObject
from direct.showbase.EventManagerGlobal import *
from direct.task.MiniTask import MiniTask, MiniTaskManager
from direct.directnotify.DirectNotifyGlobal import *
class LogAndOutput:
def __init__(self, orig, log):
self.orig = orig
self.log = log
self.console = False
def write(self, str):
self.log.write(str)
self.log.flush()
if self.console:
self.orig.write(str)
self.orig.flush()
def flush(self):
self.log.flush()
self.orig.flush()
class LauncherBase(DirectObject):
GameName = 'game'
ArgCount = 6
LauncherPhases = [1,
2,
3,
4]
TmpOverallMap = [0.25,
0.25,
0.25,
0.25]
BANDWIDTH_ARRAY = [1800,
3600,
4200,
6600,
8000,
12000,
16000,
24000,
32000,
48000,
72000,
96000,
128000,
192000,
250000,
500000,
750000,
1000000,
1250000,
1500000,
1750000,
2000000,
3000000,
4000000,
6000000,
8000000,
10000000,
12000000,
14000000,
16000000,
24000000,
32000000,
48000000,
64000000,
96000000,
128000000,
256000000,
512000000,
1024000000]
win32con_FILE_PERSISTENT_ACLS = 8
InstallDirKey = 'INSTALL_DIR'
GameLogFilenameKey = 'GAMELOG_FILENAME'
PandaWindowOpenKey = 'PANDA_WINDOW_OPEN'
PandaErrorCodeKey = 'PANDA_ERROR_CODE'
NewInstallationKey = 'IS_NEW_INSTALLATION'
LastLoginKey = 'LAST_LOGIN'
UserLoggedInKey = 'USER_LOGGED_IN'
PaidUserLoggedInKey = 'PAID_USER_LOGGED_IN'
ReferrerKey = 'REFERRER_CODE'
PeriodNameKey = 'PERIOD_NAME'
SwidKey = 'SWID'
PatchCDKey = 'FROM_CD'
ProxyServerKey = 'PROXY_SERVER'
ProxyDirectHostsKey = 'PROXY_DIRECT_HOSTS'
launcherFileDbFilename = 'launcherFileDb'
webLauncherFlag = False
def __init__(self):
self.started = False
self.taskMgrStarted = False
self._downloadComplete = False
self.pandaErrorCode = 0
self.WIN32 = os.name == 'nt'
if self.WIN32:
self.VISTA = sys.getwindowsversion()[3] == 2 and sys.getwindowsversion()[0] == 6
else:
self.VISTA = 0
ltime = time.localtime()
logSuffix = '%02d%02d%02d_%02d%02d%02d' % (ltime[0] - 2000,
ltime[1],
ltime[2],
ltime[3],
ltime[4],
ltime[5])
logPrefix = ''
if not self.WIN32:
logPrefix = os.environ.get('LOGFILE_PREFIX', '')
logfile = logPrefix + self.getLogFileName() + '-' + logSuffix + '.log'
self.errorfile = 'errorCode'
log = open(logfile, 'a')
logOut = LogAndOutput(sys.__stdout__, log)
logErr = LogAndOutput(sys.__stderr__, log)
sys.stdout = logOut
sys.stderr = logErr
if sys.platform == 'darwin':
os.system('/usr/sbin/system_profiler >>' + logfile)
elif sys.platform == 'linux2':
os.system('cat /proc/meminfo >>' + logfile)
os.system('/sbin/ifconfig -a >>' + logfile)
print '\n\nStarting %s...' % self.GameName
print 'Current time: ' + time.asctime(time.localtime(time.time())) + ' ' + time.tzname[0]
print 'sys.path = ', sys.path
print 'sys.argv = ', sys.argv
if len(sys.argv) >= self.ArgCount:
Configrc_args = sys.argv[self.ArgCount - 1]
print "generating configrc using: '" + Configrc_args + "'"
else:
Configrc_args = ''
print 'generating standard configrc'
os.environ['CONFIG_CONFIG'] = ':_:configdir_.:configpath_:configname_Configrc.exe:configexe_1:configargs_-stdout ' + Configrc_args
cpMgr = ConfigPageManager.getGlobalPtr()
cpMgr.reloadImplicitPages()
launcherConfig = getConfigExpress()
__builtin__.config = launcherConfig
if config.GetBool('log-private-info', 0):
print 'os.environ = ', os.environ
elif '__COMPAT_LAYER' in os.environ:
print '__COMPAT_LAYER = %s' % (os.environ['__COMPAT_LAYER'],)
self.miniTaskMgr = MiniTaskManager()
self.VerifyFiles = self.getVerifyFiles()
self.setServerVersion(launcherConfig.GetString('server-version', 'no_version_set'))
self.ServerVersionSuffix = launcherConfig.GetString('server-version-suffix', '')
self.UserUpdateDelay = launcherConfig.GetFloat('launcher-user-update-delay', 0.5)
self.TELEMETRY_BANDWIDTH = launcherConfig.GetInt('launcher-telemetry-bandwidth', 2000)
self.INCREASE_THRESHOLD = launcherConfig.GetFloat('launcher-increase-threshold', 0.75)
self.DECREASE_THRESHOLD = launcherConfig.GetFloat('launcher-decrease-threshold', 0.5)
self.BPS_WINDOW = launcherConfig.GetFloat('launcher-bps-window', 8.0)
self.DECREASE_BANDWIDTH = launcherConfig.GetBool('launcher-decrease-bandwidth', 1)
self.MAX_BANDWIDTH = launcherConfig.GetInt('launcher-max-bandwidth', 0)
self.nout = MultiplexStream()
Notify.ptr().setOstreamPtr(self.nout, 0)
self.nout.addFile(Filename(logfile))
if launcherConfig.GetBool('console-output', 0):
self.nout.addStandardOutput()
sys.stdout.console = True
sys.stderr.console = True
self.notify = directNotify.newCategory('Launcher')
self.clock = TrueClock.getGlobalPtr()
self.logPrefix = logPrefix
downloadServerString = launcherConfig.GetString('download-server', '')
if downloadServerString:
self.notify.info('Overriding downloadServer to %s.' % downloadServerString)
else:
downloadServerString = self.getValue('DOWNLOAD_SERVER', '')
self.notify.info('Download Server List %s' % downloadServerString)
self.downloadServerList = []
for name in downloadServerString.split(';'):
url = URLSpec(name, 1)
self.downloadServerList.append(url)
self.nextDownloadServerIndex = 0
self.getNextDownloadServer()
self.gameServer = self.getGameServer()
self.notify.info('Game Server %s' % self.gameServer)
self.downloadServerRetries = 3
self.multifileRetries = 1
self.curMultifileRetry = 0
self.downloadServerRetryPause = 1
self.bandwidthIndex = len(self.BANDWIDTH_ARRAY) - 1
self.everIncreasedBandwidth = 0
self.goUserName = ''
self.downloadPercentage = 90
self.decompressPercentage = 5
self.extractPercentage = 4
self.lastLauncherMsg = None
self.topDir = Filename.fromOsSpecific(self.getValue(self.InstallDirKey, '.'))
tmpVal = self.getValue(self.PatchCDKey)
if tmpVal == None:
self.fromCD = 0
else:
self.fromCD = tmpVal
self.notify.info('patch directory is ' + `(self.fromCD)`)
self.dbDir = self.topDir
self.patchDir = self.topDir
self.mfDir = self.topDir
self.contentDir = 'content/'
self.clientDbFilename = 'client.ddb'
self.compClientDbFilename = self.clientDbFilename + '.pz'
self.serverDbFilename = 'server.ddb'
self.compServerDbFilename = self.serverDbFilename + '.pz'
self.serverDbFilePath = self.contentDir + self.compServerDbFilename
self.clientStarterDbFilePath = self.contentDir + self.compClientDbFilename
self.progressFilename = 'progress'
self.overallComplete = 0
self.progressSoFar = 0
self.patchExtension = 'pch'
self.firstPhase = self.LauncherPhases[0]
self.finalPhase = self.LauncherPhases[-1]
self.showPhase = 3.5
self.numPhases = len(self.LauncherPhases)
self.phaseComplete = {}
self.phaseNewDownload = {}
self.phaseOverallMap = {}
tmpOverallMap = self.TmpOverallMap
tmpPhase3Map = [0.001,
0.996,
0.0,
0.0,
0.003]
phaseIdx = 0
for phase in self.LauncherPhases:
self.phaseComplete[phase] = 0
self.phaseNewDownload[phase] = 0
self.phaseOverallMap[phase] = tmpOverallMap[phaseIdx]
phaseIdx += 1
self.patchList = []
self.reextractList = []
self.byteRate = 0
self.byteRateRequested = 0
self.resetBytesPerSecond()
self.dldb = None
self.currentMfname = None
self.currentPhaseIndex = 0
self.currentPhase = self.LauncherPhases[self.currentPhaseIndex]
self.currentPhaseName = self.Localizer.LauncherPhaseNames[self.currentPhaseIndex]
if self.getServerVersion() == 'no_version_set':
self.setPandaErrorCode(10)
self.notify.info('Aborting, Configrc did not run!')
sys.exit()
self.launcherMessage(self.Localizer.LauncherStartingMessage)
self.http = HTTPClient()
if self.http.getProxySpec() == '':
self.http.setProxySpec(self.getValue(self.ProxyServerKey, ''))
self.http.setDirectHostSpec(self.getValue(self.ProxyDirectHostsKey, ''))
self.notify.info('Proxy spec is: %s' % self.http.getProxySpec())
if self.http.getDirectHostSpec() != '':
self.notify.info('Direct hosts list is: %s' % self.http.getDirectHostSpec())
self.httpChannel = self.http.makeChannel(0)
self.httpChannel.setDownloadThrottle(1)
connOk = 0
while not connOk:
proxies = self.http.getProxiesForUrl(self.downloadServer)
if proxies == 'DIRECT':
self.notify.info('No proxy for download.')
else:
self.notify.info('Download proxy: %s' % proxies)
testurl = self.addDownloadVersion(self.launcherFileDbFilename)
connOk = self.httpChannel.getHeader(DocumentSpec(testurl))
statusCode = self.httpChannel.getStatusCode()
statusString = self.httpChannel.getStatusString()
if not connOk:
self.notify.warning('Could not contact download server at %s' % testurl.cStr())
self.notify.warning('Status code = %s %s' % (statusCode, statusString))
if statusCode == 407 or statusCode == 1407 or statusCode == HTTPChannel.SCSocksNoAcceptableLoginMethod:
self.setPandaErrorCode(3)
elif statusCode == 404:
self.setPandaErrorCode(13)
elif statusCode < 100:
self.setPandaErrorCode(4)
elif statusCode > 1000:
self.setPandaErrorCode(9)
else:
self.setPandaErrorCode(6)
if not self.getNextDownloadServer():
sys.exit()
self.notify.info('Download server: %s' % self.downloadServer.cStr())
if self.notify.getDebug():
self.accept('page_up', self.increaseBandwidth)
self.accept('page_down', self.decreaseBandwidth)
self.httpChannel.setPersistentConnection(1)
self.foreground()
self.prepareClient()
self.setBandwidth()
self.downloadLauncherFileDb()
return
def getTime(self):
return self.clock.getShortTime()
def isDummy(self):
return 0
def getNextDownloadServer(self):
if self.nextDownloadServerIndex >= len(self.downloadServerList):
self.downloadServer = None
return 0
self.downloadServer = self.downloadServerList[self.nextDownloadServerIndex]
self.notify.info('Using download server %s.' % self.downloadServer.cStr())
self.nextDownloadServerIndex += 1
return 1
def background(self):
self.notify.info('background: Launcher now operating in background')
self.backgrounded = 1
def foreground(self):
self.notify.info('foreground: Launcher now operating in foreground')
self.backgrounded = 0
def handleInitiateFatalError(self, errorCode):
self.notify.warning('handleInitiateFatalError: ' + errorToText(errorCode))
sys.exit()
def handleDecompressFatalError(self, task, errorCode):
self.notify.warning('handleDecompressFatalError: ' + errorToText(errorCode))
self.miniTaskMgr.remove(task)
self.handleGenericMultifileError()
def handleDecompressWriteError(self, task, errorCode):
self.notify.warning('handleDecompressWriteError: ' + errorToText(errorCode))
self.miniTaskMgr.remove(task)
self.handleGenericMultifileError()
def handleDecompressZlibError(self, task, errorCode):
self.notify.warning('handleDecompressZlibError: ' + errorToText(errorCode))
self.miniTaskMgr.remove(task)
self.handleGenericMultifileError()
def handleExtractFatalError(self, task, errorCode):
self.notify.warning('handleExtractFatalError: ' + errorToText(errorCode))
self.miniTaskMgr.remove(task)
self.handleGenericMultifileError()
def handleExtractWriteError(self, task, errorCode):
self.notify.warning('handleExtractWriteError: ' + errorToText(errorCode))
self.miniTaskMgr.remove(task)
self.handleGenericMultifileError()
def handlePatchFatalError(self, task, errorCode):
self.notify.warning('handlePatchFatalError: ' + errorToText(errorCode))
self.miniTaskMgr.remove(task)
self.handleGenericMultifileError()
def handlePatchWriteError(self, task, errorCode):
self.notify.warning('handlePatchWriteError: ' + errorToText(errorCode))
self.miniTaskMgr.remove(task)
self.handleGenericMultifileError()
def handleDownloadFatalError(self, task):
self.notify.warning('handleDownloadFatalError: status code = %s %s' % (self.httpChannel.getStatusCode(), self.httpChannel.getStatusString()))
self.miniTaskMgr.remove(task)
statusCode = self.httpChannel.getStatusCode()
if statusCode == 404:
self.setPandaErrorCode(5)
elif statusCode < 100:
self.setPandaErrorCode(4)
else:
self.setPandaErrorCode(6)
if not self.getNextDownloadServer():
sys.exit()
def handleDownloadWriteError(self, task):
self.notify.warning('handleDownloadWriteError.')
self.miniTaskMgr.remove(task)
self.setPandaErrorCode(2)
sys.exit()
def handleGenericMultifileError(self):
if not self.currentMfname:
sys.exit()
if self.curMultifileRetry < self.multifileRetries:
self.notify.info('recover attempt: %s / %s' % (self.curMultifileRetry, self.multifileRetries))
self.curMultifileRetry += 1
self.notify.info('downloadPatchDone: Recovering from error.' + ' Deleting files in: ' + self.currentMfname)
self.dldb.setClientMultifileIncomplete(self.currentMfname)
self.dldb.setClientMultifileSize(self.currentMfname, 0)
self.notify.info('downloadPatchDone: Recovering from error.' + ' redownloading: ' + self.currentMfname)
self.httpChannel.reset()
self.getMultifile(self.currentMfname)
else:
self.setPandaErrorCode(6)
self.notify.info('handleGenericMultifileError: Failed to download multifile')
sys.exit()
def foregroundSleep(self):
if not self.backgrounded:
time.sleep(self.ForegroundSleepTime)
def forceSleep(self):
if not self.backgrounded:
time.sleep(3.0)
def addDownloadVersion(self, serverFilePath):
url = URLSpec(self.downloadServer)
origPath = url.getPath()
if origPath and origPath[-1] == '/':
origPath = origPath[:-1]
if self.fromCD:
url.setPath(self.getCDDownloadPath(origPath, serverFilePath))
else:
url.setPath(self.getDownloadPath(origPath, serverFilePath))
self.notify.info('***' + url.cStr())
return url
def download(self, serverFilePath, localFilename, callback, callbackProgress):
self.launcherMessage(self.Localizer.LauncherDownloadFile % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases})
task = MiniTask(self.downloadTask)
task.downloadRam = 0
task.serverFilePath = serverFilePath
task.serverFileURL = self.addDownloadVersion(serverFilePath)
self.notify.info('Download request: %s' % task.serverFileURL.cStr())
task.callback = callback
task.callbackProgress = callbackProgress
task.lastUpdate = 0
self.resetBytesPerSecond()
task.localFilename = localFilename
self.httpChannel.beginGetDocument(DocumentSpec(task.serverFileURL))
self.httpChannel.downloadToFile(task.localFilename)
self.miniTaskMgr.add(task, 'launcher-download')
def downloadRam(self, serverFilePath, callback):
self.ramfile = Ramfile()
task = MiniTask(self.downloadTask)
task.downloadRam = 1
task.serverFilePath = serverFilePath
task.serverFileURL = self.addDownloadVersion(serverFilePath)
self.notify.info('Download request: %s' % task.serverFileURL.cStr())
task.callback = callback
task.callbackProgress = None
task.lastUpdate = 0
self.resetBytesPerSecond()
self.httpChannel.beginGetDocument(DocumentSpec(task.serverFileURL))
self.httpChannel.downloadToRam(self.ramfile)
self.miniTaskMgr.add(task, 'launcher-download')
return
def downloadTask(self, task):
self.maybeStartGame()
if self.httpChannel.run():
now = self.getTime()
if now - task.lastUpdate >= self.UserUpdateDelay:
task.lastUpdate = now
self.testBandwidth()
if task.callbackProgress:
task.callbackProgress(task)
bytesWritten = self.httpChannel.getBytesDownloaded()
totalBytes = self.httpChannel.getFileSize()
if totalBytes:
pct = int(round(bytesWritten / float(totalBytes) * 100))
self.launcherMessage(self.Localizer.LauncherDownloadFilePercent % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'percent': pct})
else:
self.launcherMessage(self.Localizer.LauncherDownloadFileBytes % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'bytes': bytesWritten})
self.foregroundSleep()
return task.cont
statusCode = self.httpChannel.getStatusCode()
statusString = self.httpChannel.getStatusString()
self.notify.info('HTTP status %s: %s' % (statusCode, statusString))
if self.httpChannel.isValid() and self.httpChannel.isDownloadComplete():
bytesWritten = self.httpChannel.getBytesDownloaded()
totalBytes = self.httpChannel.getFileSize()
if totalBytes:
pct = int(round(bytesWritten / float(totalBytes) * 100))
self.launcherMessage(self.Localizer.LauncherDownloadFilePercent % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'percent': pct})
else:
self.launcherMessage(self.Localizer.LauncherDownloadFileBytes % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'bytes': bytesWritten})
self.notify.info('downloadTask: Download done: %s' % task.serverFileURL.cStr())
task.callback()
del task.callback
return task.done
else:
if statusCode == HTTPChannel.SCDownloadOpenError or statusCode == HTTPChannel.SCDownloadWriteError:
self.handleDownloadWriteError(task)
elif statusCode == HTTPChannel.SCLostConnection:
gotBytes = self.httpChannel.getBytesDownloaded()
self.notify.info('Connection lost while downloading; got %s bytes. Reconnecting.' % gotBytes)
if task.downloadRam:
self.downloadRam(task.serverFilePath, task.callback)
else:
self.download(task.serverFilePath, task.localFilename, task.callback, None)
else:
if self.httpChannel.isValid():
self.notify.info('Unexpected situation: no error status, but %s incompletely downloaded.' % task.serverFileURL.cStr())
self.handleDownloadFatalError(task)
if task.downloadRam:
self.downloadRam(task.serverFilePath, task.callback)
else:
self.download(task.serverFilePath, task.localFilename, task.callback, None)
return task.done
return
def downloadMultifile(self, serverFilename, localFilename, mfname, callback, totalSize, currentSize, callbackProgress):
if currentSize != 0 and currentSize == totalSize:
callback()
return
self.launcherMessage(self.Localizer.LauncherDownloadFile % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases})
task = MiniTask(self.downloadMultifileTask)
mfURL = self.addDownloadVersion(serverFilename)
task.mfURL = mfURL
self.notify.info('downloadMultifile: %s ' % task.mfURL.cStr())
task.callback = callback
task.callbackProgress = callbackProgress
task.lastUpdate = 0
self.httpChannel.getHeader(DocumentSpec(task.mfURL))
if self.httpChannel.isFileSizeKnown():
task.totalSize = self.httpChannel.getFileSize()
else:
task.totalSize = totalSize
self.resetBytesPerSecond()
task.serverFilename = serverFilename
task.localFilename = localFilename
task.mfname = mfname
if currentSize != 0:
if task.totalSize == currentSize:
self.notify.info('already have full file! Skipping download.')
callback()
return
self.httpChannel.beginGetSubdocument(DocumentSpec(task.mfURL), currentSize, task.totalSize)
self.httpChannel.downloadToFile(task.localFilename, True)
else:
self.httpChannel.beginGetDocument(DocumentSpec(task.mfURL))
self.httpChannel.downloadToFile(task.localFilename)
self._addMiniTask(task, 'launcher-download-multifile')
def downloadPatchSimpleProgress(self, task):
startingByte = self.httpChannel.getFirstByteDelivered()
bytesDownloaded = self.httpChannel.getBytesDownloaded()
bytesWritten = startingByte + bytesDownloaded
totalBytes = self.httpChannel.getFileSize()
percentPatchComplete = int(round(bytesWritten / float(totalBytes) * self.downloadPercentage))
self.setPercentPhaseComplete(self.currentPhase, percentPatchComplete)
def getPercentPatchComplete(self, bytesWritten):
return int(round((self.patchDownloadSoFar + bytesWritten) / float(self.totalPatchDownload) * self.downloadPercentage))
def downloadPatchOverallProgress(self, task):
startingByte = self.httpChannel.getFirstByteDelivered()
bytesDownloaded = self.httpChannel.getBytesDownloaded()
bytesWritten = startingByte + bytesDownloaded
percentPatchComplete = self.getPercentPatchComplete(bytesWritten)
self.setPercentPhaseComplete(self.currentPhase, percentPatchComplete)
def downloadMultifileWriteToDisk(self, task):
self.maybeStartGame()
startingByte = self.httpChannel.getFirstByteDelivered()
bytesDownloaded = self.httpChannel.getBytesDownloaded()
bytesWritten = startingByte + bytesDownloaded
if self.dldb:
self.dldb.setClientMultifileSize(task.mfname, bytesWritten)
percentComplete = 0
if task.totalSize != 0:
percentComplete = int(round(bytesWritten / float(task.totalSize) * self.downloadPercentage))
self.setPercentPhaseComplete(self.currentPhase, percentComplete)
def downloadMultifileTask(self, task):
task.totalSize = self.httpChannel.getFileSize()
if self.httpChannel.run():
now = self.getTime()
if now - task.lastUpdate >= self.UserUpdateDelay:
task.lastUpdate = now
self.testBandwidth()
if task.callbackProgress:
task.callbackProgress(task)
startingByte = self.httpChannel.getFirstByteDelivered()
bytesDownloaded = self.httpChannel.getBytesDownloaded()
bytesWritten = startingByte + bytesDownloaded
percentComplete = 0
if task.totalSize != 0:
percentComplete = int(round(100.0 * bytesWritten / float(task.totalSize)))
self.launcherMessage(self.Localizer.LauncherDownloadFilePercent % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'percent': percentComplete})
self.foregroundSleep()
return task.cont
statusCode = self.httpChannel.getStatusCode()
statusString = self.httpChannel.getStatusString()
self.notify.info('HTTP status %s: %s' % (statusCode, statusString))
if self.httpChannel.isValid() and self.httpChannel.isDownloadComplete():
if task.callbackProgress:
task.callbackProgress(task)
self.notify.info('done: %s' % task.mfname)
if self.dldb:
self.dldb.setClientMultifileComplete(task.mfname)
task.callback()
del task.callback
return task.done
else:
if statusCode == HTTPChannel.SCDownloadOpenError or statusCode == HTTPChannel.SCDownloadWriteError:
self.handleDownloadWriteError(task)
elif statusCode == HTTPChannel.SCLostConnection:
startingByte = self.httpChannel.getFirstByteDelivered()
bytesDownloaded = self.httpChannel.getBytesDownloaded()
bytesWritten = startingByte + bytesDownloaded
self.notify.info('Connection lost while downloading; got %s bytes. Reconnecting.' % bytesDownloaded)
self.downloadMultifile(task.serverFilename, task.localFilename, task.mfname, task.callback, task.totalSize, bytesWritten, task.callbackProgress)
elif (statusCode == 416 or statusCode == HTTPChannel.SCDownloadInvalidRange) and self.httpChannel.getFirstByteRequested() != 0:
self.notify.info('Invalid subrange; redownloading entire file.')
self.downloadMultifile(task.serverFilename, task.localFilename, task.mfname, task.callback, task.totalSize, 0, task.callbackProgress)
else:
if self.httpChannel.isValid():
self.notify.info('Unexpected situation: no error status, but %s incompletely downloaded.' % task.mfname)
self.handleDownloadFatalError(task)
self.downloadMultifile(task.serverFilename, task.localFilename, task.mfname, task.callback, task.totalSize, 0, task.callbackProgress)
return task.done
def decompressFile(self, localFilename, callback):
self.notify.info('decompress: request: ' + localFilename.cStr())
self.launcherMessage(self.Localizer.LauncherDecompressingFile % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases})
task = MiniTask(self.decompressFileTask)
task.localFilename = localFilename
task.callback = callback
task.lastUpdate = 0
task.decompressor = Decompressor()
errorCode = task.decompressor.initiate(task.localFilename)
if errorCode > 0:
self._addMiniTask(task, 'launcher-decompressFile')
else:
self.handleInitiateFatalError(errorCode)
def decompressFileTask(self, task):
errorCode = task.decompressor.run()
if errorCode == EUOk:
now = self.getTime()
if now - task.lastUpdate >= self.UserUpdateDelay:
task.lastUpdate = now
progress = task.decompressor.getProgress()
self.launcherMessage(self.Localizer.LauncherDecompressingPercent % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'percent': int(round(progress * 100))})
self.foregroundSleep()
return task.cont
elif errorCode == EUSuccess:
self.launcherMessage(self.Localizer.LauncherDecompressingPercent % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'percent': 100})
self.notify.info('decompressTask: Decompress done: ' + task.localFilename.cStr())
del task.decompressor
task.callback()
del task.callback
return task.done
elif errorCode == EUErrorAbort:
self.handleDecompressFatalError(task, errorCode)
return task.done
elif errorCode == EUErrorWriteOutOfFiles or errorCode == EUErrorWriteDiskFull or errorCode == EUErrorWriteDiskSectorNotFound or errorCode == EUErrorWriteOutOfMemory or errorCode == EUErrorWriteSharingViolation or errorCode == EUErrorWriteDiskFault or errorCode == EUErrorWriteDiskNotFound:
self.handleDecompressWriteError(task, errorCode)
return task.done
elif errorCode == EUErrorZlib:
self.handleDecompressZlibError(task, errorCode)
return task.done
elif errorCode > 0:
self.notify.warning('decompressMultifileTask: Unknown success return code: ' + errorToText(errorCode))
return task.cont
else:
self.notify.warning('decompressMultifileTask: Unknown return code: ' + errorToText(errorCode))
self.handleDecompressFatalError(task, errorCode)
return task.done
def decompressMultifile(self, mfname, localFilename, callback):
self.notify.info('decompressMultifile: request: ' + localFilename.cStr())
self.launcherMessage(self.Localizer.LauncherDecompressingFile % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases})
task = MiniTask(self.decompressMultifileTask)
task.mfname = mfname
task.localFilename = localFilename
task.callback = callback
task.lastUpdate = 0
task.decompressor = Decompressor()
errorCode = task.decompressor.initiate(task.localFilename)
if errorCode > 0:
self._addMiniTask(task, 'launcher-decompressMultifile')
else:
self.handleInitiateFatalError(errorCode)
def decompressMultifileTask(self, task):
errorCode = task.decompressor.run()
if errorCode == EUOk:
now = self.getTime()
if now - task.lastUpdate >= self.UserUpdateDelay:
task.lastUpdate = now
progress = task.decompressor.getProgress()
self.launcherMessage(self.Localizer.LauncherDecompressingPercent % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'percent': int(round(progress * 100))})
percentProgress = int(round(progress * self.decompressPercentage))
totalPercent = self.downloadPercentage + percentProgress
self.setPercentPhaseComplete(self.currentPhase, totalPercent)
self.foregroundSleep()
return task.cont
elif errorCode == EUSuccess:
self.launcherMessage(self.Localizer.LauncherDecompressingPercent % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'percent': 100})
totalPercent = self.downloadPercentage + self.decompressPercentage
self.setPercentPhaseComplete(self.currentPhase, totalPercent)
self.notify.info('decompressMultifileTask: Decompress multifile done: ' + task.localFilename.cStr())
self.dldb.setClientMultifileDecompressed(task.mfname)
del task.decompressor
task.callback()
del task.callback
return task.done
elif errorCode == EUErrorAbort:
self.handleDecompressFatalError(task, errorCode)
return task.done
elif errorCode == EUErrorWriteOutOfFiles or errorCode == EUErrorWriteDiskFull or errorCode == EUErrorWriteDiskSectorNotFound or errorCode == EUErrorWriteOutOfMemory or errorCode == EUErrorWriteSharingViolation or errorCode == EUErrorWriteDiskFault or errorCode == EUErrorWriteDiskNotFound:
self.handleDecompressWriteError(task, errorCode)
return task.done
elif errorCode == EUErrorZlib:
self.handleDecompressZlibError(task, errorCode)
return task.done
elif errorCode > 0:
self.notify.warning('decompressMultifileTask: Unknown success return code: ' + errorToText(errorCode))
return task.cont
else:
self.notify.warning('decompressMultifileTask: Unknown return code: ' + errorToText(errorCode))
self.handleDecompressFatalError(task, errorCode)
return task.done
def extract(self, mfname, localFilename, destDir, callback):
self.notify.info('extract: request: ' + localFilename.cStr() + ' destDir: ' + destDir.cStr())
self.launcherMessage(self.Localizer.LauncherExtractingFile % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases})
task = MiniTask(self.extractTask)
task.mfname = mfname
task.localFilename = localFilename
task.destDir = destDir
task.callback = callback
task.lastUpdate = 0
task.extractor = Extractor()
task.extractor.setExtractDir(task.destDir)
if not task.extractor.setMultifile(task.localFilename):
self.setPandaErrorCode(6)
self.notify.info('extract: Unable to open multifile %s' % task.localFilename.cStr())
sys.exit()
numFiles = self.dldb.getServerNumFiles(mfname)
for i in xrange(numFiles):
subfile = self.dldb.getServerFileName(mfname, i)
if not task.extractor.requestSubfile(Filename(subfile)):
self.setPandaErrorCode(6)
self.notify.info('extract: Unable to find subfile %s in multifile %s' % (subfile, mfname))
sys.exit()
self.notify.info('Extracting %d subfiles from multifile %s.' % (numFiles, mfname))
self._addMiniTask(task, 'launcher-extract')
def extractTask(self, task):
errorCode = task.extractor.step()
if errorCode == EUOk:
now = self.getTime()
if now - task.lastUpdate >= self.UserUpdateDelay:
task.lastUpdate = now
progress = task.extractor.getProgress()
self.launcherMessage(self.Localizer.LauncherExtractingPercent % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'percent': int(round(progress * 100.0))})
percentProgress = int(round(progress * self.extractPercentage))
totalPercent = self.downloadPercentage + self.decompressPercentage + percentProgress
self.setPercentPhaseComplete(self.currentPhase, totalPercent)
self.foregroundSleep()
return task.cont
elif errorCode == EUSuccess:
self.launcherMessage(self.Localizer.LauncherExtractingPercent % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'percent': 100})
totalPercent = self.downloadPercentage + self.decompressPercentage + self.extractPercentage
self.setPercentPhaseComplete(self.currentPhase, totalPercent)
self.notify.info('extractTask: Extract multifile done: ' + task.localFilename.cStr())
self.dldb.setClientMultifileExtracted(task.mfname)
del task.extractor
task.callback()
del task.callback
return task.done
elif errorCode == EUErrorAbort:
self.handleExtractFatalError(task, errorCode)
return task.done
elif errorCode == EUErrorFileEmpty:
self.handleExtractFatalError(task, errorCode)
return task.done
elif errorCode == EUErrorWriteOutOfFiles or errorCode == EUErrorWriteDiskFull or errorCode == EUErrorWriteDiskSectorNotFound or errorCode == EUErrorWriteOutOfMemory or errorCode == EUErrorWriteSharingViolation or errorCode == EUErrorWriteDiskFault or errorCode == EUErrorWriteDiskNotFound:
self.handleExtractWriteError(task, errorCode)
return task.done
elif errorCode > 0:
self.notify.warning('extractTask: Unknown success return code: ' + errorToText(errorCode))
return task.cont
else:
self.notify.warning('extractTask: Unknown error return code: ' + errorToText(errorCode))
self.handleExtractFatalError(task, errorCode)
return task.done
def patch(self, patchFile, patcheeFile, callback):
self.notify.info('patch: request: ' + patchFile.cStr() + ' patchee: ' + patcheeFile.cStr())
self.launcherMessage(self.Localizer.LauncherPatchingFile % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases})
task = MiniTask(self.patchTask)
task.patchFile = patchFile
task.patcheeFile = patcheeFile
task.callback = callback
task.lastUpdate = 0
task.patcher = Patcher()
errorCode = task.patcher.initiate(task.patchFile, task.patcheeFile)
if errorCode > 0:
self._addMiniTask(task, 'launcher-patch')
else:
self.handleInitiateFatalError(errorCode)
def patchTask(self, task):
errorCode = task.patcher.run()
if errorCode == EUOk:
now = self.getTime()
if now - task.lastUpdate >= self.UserUpdateDelay:
task.lastUpdate = now
progress = task.patcher.getProgress()
self.launcherMessage(self.Localizer.LauncherPatchingPercent % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'percent': int(round(progress * 100.0))})
self.foregroundSleep()
return task.cont
elif errorCode == EUSuccess:
self.launcherMessage(self.Localizer.LauncherPatchingPercent % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases,
'percent': 100})
self.notify.info('patchTask: Patch done: ' + task.patcheeFile.cStr())
del task.patcher
task.callback()
del task.callback
return task.done
elif errorCode == EUErrorAbort:
self.handlePatchFatalError(task, errorCode)
return task.done
elif errorCode == EUErrorFileEmpty:
self.handlePatchFatalError(task, errorCode)
return task.done
elif errorCode == EUErrorWriteOutOfFiles or errorCode == EUErrorWriteDiskFull or errorCode == EUErrorWriteDiskSectorNotFound or errorCode == EUErrorWriteOutOfMemory or errorCode == EUErrorWriteSharingViolation or errorCode == EUErrorWriteDiskFault or errorCode == EUErrorWriteDiskNotFound:
self.handlePatchWriteError(task, errorCode)
return task.done
elif errorCode > 0:
self.notify.warning('patchTask: Unknown success return code: ' + errorToText(errorCode))
return task.cont
else:
self.notify.warning('patchTask: Unknown error return code: ' + errorToText(errorCode))
self.handlePatchFatalError(task, errorCode)
return task.done
def getProgressSum(self, phase):
sum = 0
for i in xrange(0, len(self.linesInProgress)):
if self.linesInProgress[i].find(phase) > -1:
nameSizeTuple = self.linesInProgress[i].split()
numSize = nameSizeTuple[1].split('L')
sum += numSize[0].atoi()
return sum
def readProgressFile(self):
localFilename = Filename(self.dbDir, Filename(self.progressFilename))
if not localFilename.exists():
self.notify.warning('File does not exist: %s' % localFilename.cStr())
self.linesInProgress = []
else:
f = open(localFilename.toOsSpecific())
self.linesInProgress = f.readlines()
f.close()
localFilename.unlink()
self.progressSum = 0
token = 'phase_'
self.progressSum = self.getProgressSum(token)
self.progressSum -= self.getProgressSum(token + '2')
self.notify.info('total phases to be downloaded = ' + `(self.progressSum)`)
self.checkClientDbExists()
def prepareClient(self):
self.notify.info('prepareClient: Preparing client for install')
if not self.topDir.exists():
self.notify.info('prepareClient: Creating top directory: ' + self.topDir.cStr())
os.makedirs(self.topDir.toOsSpecific())
if not self.dbDir.exists():
self.notify.info('prepareClient: Creating db directory: ' + self.dbDir.cStr())
os.makedirs(self.dbDir.toOsSpecific())
if not self.patchDir.exists():
self.notify.info('prepareClient: Creating patch directory: ' + self.patchDir.cStr())
os.makedirs(self.patchDir.toOsSpecific())
if not self.mfDir.exists():
self.notify.info('prepareClient: Creating mf directory: ' + self.mfDir.cStr())
os.makedirs(self.mfDir.toOsSpecific())
def downloadLauncherFileDb(self):
self.notify.info('Downloading launcherFileDb')
self.downloadRam(self.launcherFileDbFilename, self.downloadLauncherFileDbDone)
def downloadLauncherFileDbDone(self):
self.launcherFileDbHash = HashVal()
self.launcherFileDbHash.hashRamfile(self.ramfile)
if self.VerifyFiles:
self.notify.info('Validating Launcher files')
for fileDesc in self.ramfile.readlines():
try:
filename, hashStr = fileDesc.split(' ', 1)
except:
self.notify.info('Invalid line: "%s"' % fileDesc)
self.failLauncherFileDb('No hash in launcherFileDb')
serverHash = HashVal()
if not self.hashIsValid(serverHash, hashStr):
self.notify.info('Not a valid hash string: "%s"' % hashStr)
self.failLauncherFileDb('Invalid hash in launcherFileDb')
localHash = HashVal()
localFilename = Filename(self.topDir, Filename(filename))
localHash.hashFile(localFilename)
if localHash != serverHash:
self.failLauncherFileDb('%s does not match expected version.' % filename)
self.downloadServerDbFile()
def failLauncherFileDb(self, string):
self.notify.info(string)
self.setPandaErrorCode(15)
sys.exit()
def downloadServerDbFile(self):
self.notify.info('Downloading server db file')
self.launcherMessage(self.Localizer.LauncherDownloadServerFileList)
self.downloadRam(self.serverDbFilePath, self.downloadServerDbFileDone)
def downloadServerDbFileDone(self):
self.serverDbFileHash = HashVal()
self.serverDbFileHash.hashRamfile(self.ramfile)
self.readProgressFile()
def checkClientDbExists(self):
clientFilename = Filename(self.dbDir, Filename(self.clientDbFilename))
if clientFilename.exists():
self.notify.info('Client Db exists')
self.createDownloadDb()
else:
self.notify.info('Client Db does not exist')
self.downloadClientDbStarterFile()
def downloadClientDbStarterFile(self):
self.notify.info('Downloading Client Db starter file')
localFilename = Filename(self.dbDir, Filename(self.compClientDbFilename))
self.download(self.clientStarterDbFilePath, localFilename, self.downloadClientDbStarterFileDone, None)
return
def downloadClientDbStarterFileDone(self):
localFilename = Filename(self.dbDir, Filename(self.compClientDbFilename))
decompressor = Decompressor()
decompressor.decompress(localFilename)
self.createDownloadDb()
def createDownloadDb(self):
self.notify.info('Creating downloadDb')
self.launcherMessage(self.Localizer.LauncherCreatingDownloadDb)
clientFilename = Filename(self.dbDir, Filename(self.clientDbFilename))
self.notify.info('Client file name: ' + clientFilename.cStr())
self.launcherMessage(self.Localizer.LauncherDownloadClientFileList)
serverFile = self.ramfile
decompressor = Decompressor()
decompressor.decompress(serverFile)
self.notify.info('Finished decompress')
self.dldb = DownloadDb(serverFile, clientFilename)
self.notify.info('created download db')
self.launcherMessage(self.Localizer.LauncherFinishedDownloadDb)
self.currentPhase = self.LauncherPhases[0]
self.currentPhaseIndex = 1
self.currentPhaseName = self.Localizer.LauncherPhaseNames[self.currentPhase]
self.updatePhase(self.currentPhase)
def maybeStartGame(self):
if not self.started and self.currentPhase >= self.showPhase:
self.started = True
self.notify.info('maybeStartGame: starting game')
self.launcherMessage(self.Localizer.LauncherStartingGame)
self.background()
__builtin__.launcher = self
self.startGame()
def _runTaskManager(self):
if not self.taskMgrStarted:
self.miniTaskMgr.run()
self.notify.info('Switching task managers.')
taskMgr.run()
def _stepMiniTaskManager(self, task):
self.miniTaskMgr.step()
if self.miniTaskMgr.taskList:
return task.cont
self.notify.info('Stopping mini task manager.')
self.miniTaskMgr = None
return task.done
def _addMiniTask(self, task, name):
if not self.miniTaskMgr:
self.notify.info('Restarting mini task manager.')
self.miniTaskMgr = MiniTaskManager()
from direct.task.TaskManagerGlobal import taskMgr
taskMgr.remove('miniTaskManager')
taskMgr.add(self._stepMiniTaskManager, 'miniTaskManager')
self.miniTaskMgr.add(task, name)
def newTaskManager(self):
self.taskMgrStarted = True
if self.miniTaskMgr.running:
self.miniTaskMgr.stop()
from direct.task.TaskManagerGlobal import taskMgr
taskMgr.remove('miniTaskManager')
taskMgr.add(self._stepMiniTaskManager, 'miniTaskManager')
def mainLoop(self):
try:
self._runTaskManager()
except SystemExit:
if hasattr(__builtin__, 'base'):
base.destroy()
self.notify.info('Normal exit.')
raise
except:
self.setPandaErrorCode(12)
self.notify.warning('Handling Python exception.')
if hasattr(__builtin__, 'base') and getattr(base, 'cr', None):
if base.cr.timeManager:
from otp.otpbase import OTPGlobals
base.cr.timeManager.setDisconnectReason(OTPGlobals.DisconnectPythonError)
base.cr.timeManager.setExceptionInfo()
base.cr.sendDisconnect()
if hasattr(__builtin__, 'base'):
base.destroy()
self.notify.info('Exception exit.\n')
import traceback
traceback.print_exc()
sys.exit()
return
def updatePhase(self, phase):
self.notify.info('Updating multifiles in phase: ' + `phase`)
self.setPercentPhaseComplete(self.currentPhase, 0)
self.phaseMultifileNames = []
numfiles = self.dldb.getServerNumMultifiles()
for i in xrange(self.dldb.getServerNumMultifiles()):
mfname = self.dldb.getServerMultifileName(i)
if self.dldb.getServerMultifilePhase(mfname) == phase:
self.phaseMultifileNames.append(mfname)
self.updateNextMultifile()
def updateNextMultifile(self):
if len(self.phaseMultifileNames) > 0:
self.currentMfname = self.phaseMultifileNames.pop()
self.curMultifileRetry = 0
self.getMultifile(self.currentMfname)
else:
if self.currentMfname is None:
self.notify.warning('no multifile found! See below for debug info:')
for i in xrange(self.dldb.getServerNumMultifiles()):
mfname = self.dldb.getServerMultifileName(i)
phase = self.dldb.getServerMultifilePhase(mfname)
print i, mfname, phase
self.handleGenericMultifileError()
decompressedMfname = os.path.splitext(self.currentMfname)[0]
localFilename = Filename(self.mfDir, Filename(decompressedMfname))
nextIndex = self.LauncherPhases.index(self.currentPhase) + 1
if nextIndex < len(self.LauncherPhases):
self.MakeNTFSFilesGlobalWriteable(localFilename)
else:
self.MakeNTFSFilesGlobalWriteable()
vfs = VirtualFileSystem.getGlobalPtr()
vfs.mount(localFilename, '.', VirtualFileSystem.MFReadOnly)
self.setPercentPhaseComplete(self.currentPhase, 100)
self.notify.info('Done updating multifiles in phase: ' + `(self.currentPhase)`)
self.progressSoFar += int(round(self.phaseOverallMap[self.currentPhase] * 100))
self.notify.info('progress so far ' + `(self.progressSoFar)`)
messenger.send('phaseComplete-' + `(self.currentPhase)`)
if nextIndex < len(self.LauncherPhases):
self.currentPhase = self.LauncherPhases[nextIndex]
self.currentPhaseIndex = nextIndex + 1
self.currentPhaseName = self.Localizer.LauncherPhaseNames[self.currentPhase]
self.updatePhase(self.currentPhase)
else:
self.notify.info('ALL PHASES COMPLETE')
self.maybeStartGame()
messenger.send('launcherAllPhasesComplete')
self.cleanup()
return
def isDownloadComplete(self):
return self._downloadComplete
def updateMultifileDone(self):
self.updateNextMultifile()
def downloadMultifileDone(self):
self.getDecompressMultifile(self.currentMfname)
def getMultifile(self, mfname):
self.notify.info('Downloading multifile: ' + mfname)
if not self.dldb.clientMultifileExists(mfname):
self.maybeStartGame()
self.notify.info('Multifile does not exist in client db,' + 'creating new record: ' + mfname)
self.dldb.addClientMultifile(mfname)
curHash = self.dldb.getServerMultifileHash(mfname)
self.dldb.setClientMultifileHash(mfname, curHash)
localFilename = Filename(self.mfDir, Filename(mfname))
if localFilename.exists():
curSize = localFilename.getFileSize()
self.dldb.setClientMultifileSize(mfname, curSize)
if curSize == self.dldb.getServerMultifileSize(mfname):
self.dldb.setClientMultifileComplete(mfname)
decompressedMfname = os.path.splitext(mfname)[0]
decompressedFilename = Filename(self.mfDir, Filename(decompressedMfname))
if (not self.dldb.clientMultifileComplete(mfname) or not self.dldb.clientMultifileDecompressed(mfname)) and decompressedFilename.exists():
clientMd5 = HashVal()
clientMd5.hashFile(decompressedFilename)
clientVer = self.dldb.getVersion(Filename(decompressedMfname), clientMd5)
if clientVer != -1:
self.notify.info('Decompressed multifile is already on disk and correct: %s (version %s)' % (mfname, clientVer))
self.dldb.setClientMultifileComplete(mfname)
self.dldb.setClientMultifileDecompressed(mfname)
compressedFilename = Filename(self.mfDir, Filename(mfname))
compressedFilename.unlink()
extractedOk = True
numFiles = self.dldb.getServerNumFiles(mfname)
for i in xrange(numFiles):
subfile = self.dldb.getServerFileName(mfname, i)
fn = Filename(self.mfDir, Filename(subfile))
if fn.compareTimestamps(decompressedFilename) <= 0:
extractedOk = False
break
if extractedOk:
self.notify.info('Multifile appears to have been extracted already.')
self.dldb.setClientMultifileExtracted(mfname)
if not self.dldb.clientMultifileComplete(mfname) or not decompressedFilename.exists():
self.maybeStartGame()
currentSize = self.dldb.getClientMultifileSize(mfname)
totalSize = self.dldb.getServerMultifileSize(mfname)
localFilename = Filename(self.mfDir, Filename(mfname))
if not localFilename.exists():
currentSize = 0
else:
currentSize = min(currentSize, localFilename.getFileSize())
if currentSize == 0:
self.notify.info('Multifile has not been started, ' + 'downloading new file: ' + mfname)
curHash = self.dldb.getServerMultifileHash(mfname)
self.dldb.setClientMultifileHash(mfname, curHash)
self.phaseNewDownload[self.currentPhase] = 1
self.downloadMultifile(self.contentDir + mfname, localFilename, mfname, self.downloadMultifileDone, totalSize, 0, self.downloadMultifileWriteToDisk)
else:
clientHash = self.dldb.getClientMultifileHash(mfname)
serverHash = self.dldb.getServerMultifileHash(mfname)
if clientHash.eq(serverHash):
self.notify.info('Multifile is not complete, finishing download for %s, size = %s / %s' % (mfname, currentSize, totalSize))
self.downloadMultifile(self.contentDir + mfname, localFilename, mfname, self.downloadMultifileDone, totalSize, currentSize, self.downloadMultifileWriteToDisk)
elif self.curMultifileRetry < self.multifileRetries:
self.notify.info('recover attempt: %s / %s' % (self.curMultifileRetry, self.multifileRetries))
self.curMultifileRetry += 1
self.notify.info('Multifile is not complete, and is out of date. ' + 'Restarting download with newest multifile')
self.dldb.setClientMultifileIncomplete(self.currentMfname)
self.dldb.setClientMultifileSize(self.currentMfname, 0)
self.dldb.setClientMultifileHash(self.currentMfname, serverHash)
self.getMultifile(self.currentMfname)
else:
self.setPandaErrorCode(6)
self.notify.info('getMultifile: Failed to download multifile')
sys.exit()
else:
self.notify.info('Multifile already complete: ' + mfname)
self.downloadMultifileDone()
def updateMultifileDone(self):
self.updateNextMultifile()
def downloadMultifileDone(self):
self.getDecompressMultifile(self.currentMfname)
def getMultifile(self, mfname):
self.notify.info('Downloading multifile: ' + mfname)
if not self.dldb.clientMultifileExists(mfname):
self.maybeStartGame()
self.notify.info('Multifile does not exist in client db,' + 'creating new record: ' + mfname)
self.dldb.addClientMultifile(mfname)
if self.DecompressMultifiles:
curHash = self.dldb.getServerMultifileHash(mfname)
self.dldb.setClientMultifileHash(mfname, curHash)
localFilename = Filename(self.mfDir, Filename(mfname))
if localFilename.exists():
curSize = localFilename.getFileSize()
self.dldb.setClientMultifileSize(mfname, curSize)
if curSize == self.dldb.getServerMultifileSize(mfname):
self.dldb.setClientMultifileComplete(mfname)
decompressedMfname = os.path.splitext(mfname)[0]
decompressedFilename = Filename(self.mfDir, Filename(decompressedMfname))
if self.DecompressMultifiles:
if (not self.dldb.clientMultifileComplete(mfname) or not self.dldb.clientMultifileDecompressed(mfname)) and decompressedFilename.exists():
clientMd5 = HashVal()
clientMd5.hashFile(decompressedFilename)
clientVer = self.dldb.getVersion(Filename(decompressedMfname), clientMd5)
if clientVer != -1:
self.notify.info('Decompressed multifile is already on disk and correct: %s (version %s)' % (mfname, clientVer))
self.dldb.setClientMultifileComplete(mfname)
self.dldb.setClientMultifileDecompressed(mfname)
compressedFilename = Filename(self.mfDir, Filename(mfname))
compressedFilename.unlink()
extractedOk = True
numFiles = self.dldb.getServerNumFiles(mfname)
for i in xrange(numFiles):
subfile = self.dldb.getServerFileName(mfname, i)
fn = Filename(self.mfDir, Filename(subfile))
if fn.compareTimestamps(decompressedFilename) <= 0:
extractedOk = False
break
if extractedOk:
self.notify.info('Multifile appears to have been extracted already.')
self.dldb.setClientMultifileExtracted(mfname)
if not self.dldb.clientMultifileComplete(mfname) or not decompressedFilename.exists():
self.maybeStartGame()
currentSize = self.dldb.getClientMultifileSize(mfname)
totalSize = self.dldb.getServerMultifileSize(mfname)
localFilename = Filename(self.mfDir, Filename(mfname))
if not localFilename.exists():
currentSize = 0
if currentSize == 0:
self.notify.info('Multifile has not been started, ' + 'downloading new file: ' + mfname)
curHash = self.dldb.getServerMultifileHash(mfname)
self.dldb.setClientMultifileHash(mfname, curHash)
self.phaseNewDownload[self.currentPhase] = 1
self.downloadMultifile(self.contentDir + mfname, localFilename, mfname, self.downloadMultifileDone, totalSize, 0, self.downloadMultifileWriteToDisk)
else:
clientHash = self.dldb.getClientMultifileHash(mfname)
serverHash = self.dldb.getServerMultifileHash(mfname)
if clientHash.eq(serverHash):
self.notify.info('Multifile is not complete, finishing download for %s, size = %s / %s' % (mfname, currentSize, totalSize))
self.downloadMultifile(self.contentDir + mfname, localFilename, mfname, self.downloadMultifileDone, totalSize, currentSize, self.downloadMultifileWriteToDisk)
elif self.curMultifileRetry < self.multifileRetries:
self.notify.info('recover attempt: %s / %s' % (self.curMultifileRetry, self.multifileRetries))
self.curMultifileRetry += 1
self.notify.info('Multifile is not complete, and is out of date. ' + 'Restarting download with newest multifile')
self.dldb.setClientMultifileIncomplete(self.currentMfname)
self.dldb.setClientMultifileSize(self.currentMfname, 0)
if self.DecompressMultifiles:
self.dldb.setClientMultifileHash(self.currentMfname, serverHash)
self.getMultifile(self.currentMfname)
else:
self.setPandaErrorCode(6)
self.notify.info('getMultifile: Failed to download multifile')
sys.exit()
else:
self.notify.info('Multifile already complete: ' + mfname)
self.downloadMultifileDone()
def getDecompressMultifile(self, mfname):
if not self.DecompressMultifiles:
self.decompressMultifileDone()
elif not self.dldb.clientMultifileDecompressed(mfname):
self.maybeStartGame()
self.notify.info('decompressMultifile: Decompressing multifile: ' + mfname)
localFilename = Filename(self.mfDir, Filename(mfname))
self.decompressMultifile(mfname, localFilename, self.decompressMultifileDone)
else:
self.notify.info('decompressMultifile: Multifile already decompressed: ' + mfname)
self.decompressMultifileDone()
def decompressMultifileDone(self):
if self.phaseNewDownload[self.currentPhase]:
self.setPercentPhaseComplete(self.currentPhase, 95)
self.extractMultifile(self.currentMfname)
def extractMultifile(self, mfname):
if not self.dldb.clientMultifileExtracted(mfname):
self.maybeStartGame()
self.notify.info('extractMultifile: Extracting multifile: ' + mfname)
decompressedMfname = os.path.splitext(mfname)[0]
localFilename = Filename(self.mfDir, Filename(decompressedMfname))
destDir = Filename(self.topDir)
self.notify.info('extractMultifile: Extracting: ' + localFilename.cStr() + ' to: ' + destDir.cStr())
self.extract(mfname, localFilename, destDir, self.extractMultifileDone)
else:
self.notify.info('extractMultifile: Multifile already extracted: ' + mfname)
self.extractMultifileDone()
def extractMultifileDone(self):
if self.phaseNewDownload[self.currentPhase]:
self.setPercentPhaseComplete(self.currentPhase, 99)
self.notify.info('extractMultifileDone: Finished updating multifile: ' + self.currentMfname)
self.patchMultifile()
def getPatchFilename(self, fname, currentVersion):
return fname + '.v' + `currentVersion` + '.' + self.patchExtension
def downloadPatches(self):
if len(self.patchList) > 0:
self.currentPatch, self.currentPatchee, self.currentPatchVersion = self.patchList.pop()
self.notify.info(self.contentDir)
self.notify.info(self.currentPatch)
patchFile = self.currentPatch + '.pz'
serverPatchFilePath = self.contentDir + patchFile
self.notify.info(serverPatchFilePath)
localPatchFilename = Filename(self.patchDir, Filename(patchFile))
if self.currentPhase > 3:
self.download(serverPatchFilePath, localPatchFilename, self.downloadPatchDone, self.downloadPatchSimpleProgress)
else:
self.download(serverPatchFilePath, localPatchFilename, self.downloadPatchDone, self.downloadPatchOverallProgress)
else:
self.notify.info('applyNextPatch: Done patching multifile: ' + `(self.currentPhase)`)
self.patchDone()
def downloadPatchDone(self):
self.patchDownloadSoFar += self.httpChannel.getBytesDownloaded()
self.notify.info('downloadPatchDone: Decompressing patch file: ' + self.currentPatch + '.pz')
self.decompressFile(Filename(self.patchDir, Filename(self.currentPatch + '.pz')), self.decompressPatchDone)
def decompressPatchDone(self):
self.notify.info('decompressPatchDone: Patching file: ' + self.currentPatchee + ' from ver: ' + `(self.currentPatchVersion)`)
patchFile = Filename(self.patchDir, Filename(self.currentPatch))
patchFile.setBinary()
patchee = Filename(self.mfDir, Filename(self.currentPatchee))
patchee.setBinary()
self.patch(patchFile, patchee, self.downloadPatches)
def patchDone(self):
self.notify.info('patchDone: Patch successful')
del self.currentPatch
del self.currentPatchee
del self.currentPatchVersion
decompressedMfname = os.path.splitext(self.currentMfname)[0]
localFilename = Filename(self.mfDir, Filename(decompressedMfname))
destDir = Filename(self.topDir)
self.extract(self.currentMfname, localFilename, destDir, self.updateMultifileDone)
def startReextractingFiles(self):
self.notify.info('startReextractingFiles: Reextracting ' + `(len(self.reextractList))` + ' files for multifile: ' + self.currentMfname)
self.launcherMessage(self.Localizer.LauncherRecoverFiles)
self.currentMfile = Multifile()
decompressedMfname = os.path.splitext(self.currentMfname)[0]
self.currentMfile.openRead(Filename(self.mfDir, Filename(decompressedMfname)))
self.reextractNextFile()
def reextractNextFile(self):
failure = 0
while not failure and len(self.reextractList) > 0:
currentReextractFile = self.reextractList.pop()
subfileIndex = self.currentMfile.findSubfile(currentReextractFile)
if subfileIndex >= 0:
destFilename = Filename(self.topDir, Filename(currentReextractFile))
result = self.currentMfile.extractSubfile(subfileIndex, destFilename)
if not result:
self.notify.warning('reextractNextFile: Failure on reextract.')
failure = 1
else:
self.notify.warning('reextractNextFile: File not found in multifile: ' + `currentReextractFile`)
failure = 1
if failure:
sys.exit()
self.notify.info('reextractNextFile: Done reextracting files for multifile: ' + `(self.currentPhase)`)
del self.currentMfile
self.updateMultifileDone()
def patchMultifile(self):
self.launcherMessage(self.Localizer.LauncherCheckUpdates % {'name': self.currentPhaseName,
'current': self.currentPhaseIndex,
'total': self.numPhases})
self.notify.info('patchMultifile: Checking for patches on multifile: ' + self.currentMfname)
self.patchList = []
clientMd5 = HashVal()
decompressedMfname = os.path.splitext(self.currentMfname)[0]
localFilename = Filename(self.mfDir, Filename(decompressedMfname))
clientMd5.hashFile(localFilename)
clientVer = self.dldb.getVersion(Filename(decompressedMfname), clientMd5)
if clientVer == 1:
self.patchAndHash()
return
elif clientVer == -1:
self.notify.info('patchMultifile: Invalid hash for file: ' + self.currentMfname)
self.maybeStartGame()
if self.curMultifileRetry < self.multifileRetries:
self.notify.info('recover attempt: %s / %s' % (self.curMultifileRetry, self.multifileRetries))
self.curMultifileRetry += 1
self.notify.info('patchMultifile: Restarting download with newest multifile')
self.dldb.setClientMultifileIncomplete(self.currentMfname)
self.dldb.setClientMultifileSize(self.currentMfname, 0)
self.getMultifile(self.currentMfname)
else:
self.setPandaErrorCode(6)
self.notify.info('patchMultifile: Failed to download multifile')
sys.exit()
return
elif clientVer > 1:
self.notify.info('patchMultifile: Old version for multifile: ' + self.currentMfname + ' Client ver: ' + `clientVer`)
self.maybeStartGame()
self.totalPatchDownload = 0
self.patchDownloadSoFar = 0
for ver in xrange(1, clientVer):
patch = self.getPatchFilename(decompressedMfname, ver + 1)
patchee = decompressedMfname
patchVersion = ver + 1
self.patchList.append((patch, patchee, patchVersion))
if self.currentPhase == 3:
self.totalPatchDownload += self.getProgressSum(patch)
self.notify.info('total patch to be downloaded = ' + `(self.totalPatchDownload)`)
self.downloadPatches()
return
def patchAndHash(self):
self.reextractList = []
self.PAHClean = 1
self.PAHNumFiles = self.dldb.getServerNumFiles(self.currentMfname)
self.PAHFileCounter = 0
if self.PAHNumFiles > 0:
task = MiniTask(self.patchAndHashTask)
task.cleanCallback = self.updateMultifileDone
task.uncleanCallback = self.startReextractingFiles
self._addMiniTask(task, 'patchAndHash')
else:
self.updateMultifileDone()
def patchAndHashTask(self, task):
self.launcherMessage(self.Localizer.LauncherVerifyPhase)
if self.PAHFileCounter == self.PAHNumFiles:
if self.PAHClean:
task.cleanCallback()
else:
task.uncleanCallback()
return task.done
else:
i = self.PAHFileCounter
self.PAHFileCounter += 1
fname = self.dldb.getServerFileName(self.currentMfname, i)
fnameFilename = Filename(self.topDir, Filename(fname))
if not os.path.exists(fnameFilename.toOsSpecific()):
self.notify.info('patchAndHash: File not found: ' + fname)
self.reextractList.append(fname)
self.PAHClean = 0
return task.cont
if self.VerifyFiles and self.dldb.hasVersion(Filename(fname)):
clientMd5 = HashVal()
clientMd5.hashFile(fnameFilename)
clientVer = self.dldb.getVersion(Filename(fname), clientMd5)
if clientVer == 1:
return task.cont
else:
self.notify.info('patchAndHash: Invalid hash for file: ' + fname)
self.reextractList.append(fname)
self.PAHClean = 0
return task.cont
def launcherMessage(self, msg):
if msg != self.lastLauncherMsg:
self.lastLauncherMsg = msg
self.notify.info(msg)
def recordPeriodName(self, periodName):
self.setValue(self.PeriodNameKey, periodName)
def recordSwid(self, swid):
self.setValue(self.SwidKey, swid)
def getGoUserName(self):
return self.goUserName
def setGoUserName(self, userName):
self.goUserName = userName
def getInstallDir(self):
return self.topDir.cStr()
def setPandaWindowOpen(self):
self.setValue(self.PandaWindowOpenKey, 1)
def setPandaErrorCode(self, code):
self.notify.info('setting panda error code to %s' % code)
self.pandaErrorCode = code
errorLog = open(self.errorfile, 'w')
errorLog.write(str(code) + '\n')
errorLog.flush()
errorLog.close()
def getPandaErrorCode(self):
return self.pandaErrorCode
def setDisconnectDetailsNormal(self):
self.notify.info('Setting Disconnect Details normal')
self.disconnectCode = 0
self.disconnectMsg = 'normal'
def setDisconnectDetails(self, newCode, newMsg):
self.notify.info('New Disconnect Details: %s - %s ' % (newCode, newMsg))
self.disconnectCode = newCode
self.disconnectMsg = newMsg
def setServerVersion(self, version):
self.ServerVersion = version
def getServerVersion(self):
return self.ServerVersion
def getIsNewInstallation(self):
result = self.getValue(self.NewInstallationKey, 1)
result = base.config.GetBool('new-installation', result)
return result
def setIsNotNewInstallation(self):
self.setValue(self.NewInstallationKey, 0)
def getLastLogin(self):
return self.getValue(self.LastLoginKey, '')
def setLastLogin(self, login):
self.setValue(self.LastLoginKey, login)
def setUserLoggedIn(self):
self.setValue(self.UserLoggedInKey, '1')
def setPaidUserLoggedIn(self):
self.setValue(self.PaidUserLoggedInKey, '1')
def getReferrerCode(self):
return self.getValue(self.ReferrerKey, None)
def getPhaseComplete(self, phase):
percentDone = self.phaseComplete[phase]
return percentDone == 100
def setPercentPhaseComplete(self, phase, percent):
self.notify.info('phase updating %s, %s' % (phase, percent))
oldPercent = self.phaseComplete[phase]
if oldPercent != percent:
self.phaseComplete[phase] = percent
messenger.send('launcherPercentPhaseComplete', [phase,
percent,
self.getBandwidth(),
self.byteRate])
self.overallComplete = int(round(percent * self.phaseOverallMap[phase])) + self.progressSoFar
def getPercentPhaseComplete(self, phase):
return self.phaseComplete[phase]
dr = finalRequested - startRequested
if dt <= 0.0:
return -1
self.byteRate = db / dt
self.byteRateRequested = dr / dt
return self.byteRate
def addPhasePostProcess(self, phase, func, taskChain = 'default'):
if self.getPhaseComplete(phase):
func()
return
self.acceptOnce('phaseComplete-%s' % phase, func)
def testBandwidth(self):
self.recordBytesPerSecond()
byteRate = self.getBytesPerSecond()
if byteRate < 0:
return
if byteRate >= self.getBandwidth() * self.INCREASE_THRESHOLD:
self.increaseBandwidth(byteRate)
elif byteRate < self.byteRateRequested * self.DECREASE_THRESHOLD:
self.decreaseBandwidth(byteRate)
def getBandwidth(self):
if self.backgrounded:
bandwidth = self.BANDWIDTH_ARRAY[self.bandwidthIndex] - self.TELEMETRY_BANDWIDTH
else:
bandwidth = self.BANDWIDTH_ARRAY[self.bandwidthIndex]
if self.MAX_BANDWIDTH > 0:
bandwidth = min(bandwidth, self.MAX_BANDWIDTH)
return bandwidth
def increaseBandwidth(self, targetBandwidth = None):
maxBandwidthIndex = len(self.BANDWIDTH_ARRAY) - 1
if self.bandwidthIndex == maxBandwidthIndex:
self.notify.debug('increaseBandwidth: Already at maximum bandwidth')
return 0
self.bandwidthIndex += 1
self.everIncreasedBandwidth = 1
self.setBandwidth()
return 1
def decreaseBandwidth(self, targetBandwidth = None):
if not self.DECREASE_BANDWIDTH:
return 0
if self.backgrounded and self.everIncreasedBandwidth:
return 0
if self.bandwidthIndex == 0:
return 0
else:
self.bandwidthIndex -= 1
if targetBandwidth:
while self.bandwidthIndex > 0 and self.BANDWIDTH_ARRAY[self.bandwidthIndex] > targetBandwidth:
self.bandwidthIndex -= 1
self.setBandwidth()
return 1
def setBandwidth(self):
self.resetBytesPerSecond()
self.httpChannel.setMaxBytesPerSecond(self.getBandwidth())
def resetBytesPerSecond(self):
self.bpsList = []
def recordBytesPerSecond(self):
bytesDownloaded = self.httpChannel.getBytesDownloaded()
bytesRequested = self.httpChannel.getBytesRequested()
t = self.getTime()
self.bpsList.append((t, bytesDownloaded, bytesRequested))
while 1:
if len(self.bpsList) == 0:
break
ft, fb, fr = self.bpsList[0]
if ft < t-self.BPS_WINDOW:
self.bpsList.pop(0)
else:
break
def getBytesPerSecond(self):
if len(self.bpsList) < 2:
return -1
startTime, startBytes, startRequested = self.bpsList[0]
finalTime, finalBytes, finalRequested = self.bpsList[-1]
dt = finalTime - startTime
db = finalBytes - startBytes
dr = finalRequested - startRequested
if dt <= 0.0:
return -1
self.byteRate = db / dt
self.byteRateRequested = dr / dt
return self.byteRate
def testBandwidth(self):
self.recordBytesPerSecond()
byteRate = self.getBytesPerSecond()
if byteRate < 0:
return
if byteRate >= self.getBandwidth() * self.INCREASE_THRESHOLD:
self.increaseBandwidth(byteRate)
elif byteRate < self.byteRateRequested * self.DECREASE_THRESHOLD:
self.decreaseBandwidth(byteRate)
def getBandwidth(self):
if self.backgrounded:
bandwidth = self.BANDWIDTH_ARRAY[self.bandwidthIndex] - self.TELEMETRY_BANDWIDTH
else:
bandwidth = self.BANDWIDTH_ARRAY[self.bandwidthIndex]
if self.MAX_BANDWIDTH > 0:
bandwidth = min(bandwidth, self.MAX_BANDWIDTH)
return bandwidth
def increaseBandwidth(self, targetBandwidth = None):
maxBandwidthIndex = len(self.BANDWIDTH_ARRAY) - 1
if self.bandwidthIndex == maxBandwidthIndex:
return 0
self.bandwidthIndex += 1
self.everIncreasedBandwidth = 1
self.setBandwidth()
return 1
def decreaseBandwidth(self, targetBandwidth = None):
if not self.DECREASE_BANDWIDTH:
return 0
if self.backgrounded and self.everIncreasedBandwidth:
return 0
if self.bandwidthIndex == 0:
return 0
else:
self.bandwidthIndex -= 1
if targetBandwidth:
while self.bandwidthIndex > 0 and self.BANDWIDTH_ARRAY[self.bandwidthIndex] > targetBandwidth:
self.bandwidthIndex -= 1
self.setBandwidth()
return 1
def setBandwidth(self):
self.resetBytesPerSecond()
self.httpChannel.setMaxBytesPerSecond(self.getBandwidth())
def MakeNTFSFilesGlobalWriteable(self, pathToSet = None):
if not self.WIN32:
return
import win32api
if pathToSet == None:
pathToSet = self.getInstallDir()
else:
pathToSet = pathToSet.cStr() + '*'
DrivePath = pathToSet[0:3]
try:
volname, volsernum, maxfilenamlen, sysflags, filesystemtype = win32api.GetVolumeInformation(DrivePath)
except:
return
if self.win32con_FILE_PERSISTENT_ACLS & sysflags:
self.notify.info('NTFS detected, making files global writeable\n')
win32dir = win32api.GetWindowsDirectory()
cmdLine = win32dir + '\\system32\\cacls.exe "' + pathToSet + '" /T /E /C /G Everyone:F > nul'
os.system(cmdLine)
return
def cleanup(self):
self.notify.info('cleanup: cleaning up Launcher')
self.ignoreAll()
del self.clock
del self.dldb
del self.httpChannel
del self.http
def getPlayToken(self):
2015-04-06 09:32:54 +00:00
return None