From: <Blu...@us...> - 2010-08-30 22:22:23
|
Revision: 378 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=378&view=rev Author: BlueWolf_ Date: 2010-08-30 22:22:16 +0000 (Mon, 30 Aug 2010) Log Message: ----------- Added the ability to download files and fixed some other stuff, including how the playground gets notified about the current status (changed changestatus to a more general 'call'). I also changed rsa_received to connection_ready in the client-core as that's a more appropriate name for what it is used for Modified Paths: -------------- trunk/client/VP.py trunk/client/core/callback.py trunk/client/core/parser.py trunk/client/functions.py trunk/client/layout.py trunk/client/playground.py trunk/client/windows.py Added Paths: ----------- trunk/client/downloader.py trunk/client/downloads/ trunk/client/downloads/img/ trunk/client/downloads/snd/ Modified: trunk/client/VP.py =================================================================== --- trunk/client/VP.py 2010-08-28 13:36:11 UTC (rev 377) +++ trunk/client/VP.py 2010-08-30 22:22:16 UTC (rev 378) @@ -24,6 +24,7 @@ from playground import Playground from layout import Layout from windows import Windows +from downloader import Downloader class Main(): @@ -32,6 +33,7 @@ sh['has_hover'] = True sh['filetable'] = {} sh['timer'] = Timer() + sh['downloader'] = Downloader() # Fire up pygame os.environ['SDL_VIDEO_CENTERED']='1' @@ -66,7 +68,10 @@ 'app_version': VERSION}, Callback()) - self.changestatus("connecting") + self.call("status", { + "status": "login", + "where": "connecting" + }) sh['client'].connect(*SERVER) @@ -151,10 +156,10 @@ pygame.display.update(rect) - def changestatus(self, status): - sh['playground'].changestatus(status) - sh['layout'].changestatus(status) - sh['windows'].changestatus(status) + def call(self, name, data): + sh['playground'].call(name, data) + sh['layout'].call(name, data) + sh['windows'].call(name, data) class Timer(): """ @@ -206,16 +211,33 @@ def disconnect(self, reason): print "Server disconnected: " + reason - def received_rsa(self, public): + def connection_ready(self, public_rsa, reconnecting): # We are connected - sh['main'].changestatus("login") + + if reconnecting: + sh['main'].call("status", { + "status": "login", + "where": "logging in" + }) + else: + sh['main'].call("status", { + "status": "login", + "where": "waiting" + }) def logged_in(self, username, cid, owner): - sh['main'].changestatus("playground") + sh['main'].call("status", { + "status": "login", + "where": "downloading" + }) def failed_logging_in(self, reason): # [TODO] Send the reason in some way - sh['main'].changestatus("login") + + sh['main'].call("status", { + "status": "login", + "where": "waiting" + }) def disconnected(self, reason): if reason == "manual": return @@ -223,22 +245,35 @@ # [TODO] Send the reason in some way if reason in ["closed", "gone offline"]: # Reconnect while logging in - sh['main'].changestatus("connecting") + sh['main'].call("status", { + "status": "login", + "where": "connecting" + }) return True elif reason in ["duplicate", "crash"]: # Reconnecting while not logging in - sh['main'].changestatus("connecting") + sh['main'].call("status", { + "status": "login", + "where": "connecting" + }) sh['client'].connect(*SERVER) else: # Not doing anything at all # [TODO] Show popup or something, without reconnecting? - sh['main'].changestatus("connecting") + sh['main'].call("status", { + "status": "login", + "where": "connecting" + }) def custom_received(self, header, body): if header == "filetable": # List of all the downloadable objects sh['filetable'] = body + + sh['main'].call("filetable", { + "action": "update" + }) Modified: trunk/client/core/callback.py =================================================================== --- trunk/client/core/callback.py 2010-08-28 13:36:11 UTC (rev 377) +++ trunk/client/core/callback.py 2010-08-30 22:22:16 UTC (rev 378) @@ -28,15 +28,19 @@ """ pass - def received_rsa(self, public): + def connection_ready(self, public_rsa, reconnecting): """ When a connection is made, the server will generate a rsa-key. The client has to wait for this, before logging in. After this event, you - can use client.login (if you haven't specified a login at client.connect) + can use client.login (if you haven't specified a login at + client.connect) public: The generated public rsa-key. It is a dict with 2 values: e and n. It's used for encoding the password + reconnecting: + Boolean which is True when the core automatically tries to log in + again This is a placeholder. If you want to catch this event, overwrite this Modified: trunk/client/core/parser.py =================================================================== --- trunk/client/core/parser.py 2010-08-28 13:36:11 UTC (rev 377) +++ trunk/client/core/parser.py 2010-08-30 22:22:16 UTC (rev 378) @@ -56,9 +56,11 @@ self.sh['rsakey'] = msg['public'] - self.call.received_rsa(msg['public']) + reconnect = (self.core.cid == None and self.sh['auto_login']) - if self.core.cid == None and self.sh['auto_login'] != (): + self.call.connection_ready(msg['public'], reconnect) + + if reconnect: login = self.sh['auto_login'] login[0](*login[1]) Added: trunk/client/downloader.py =================================================================== --- trunk/client/downloader.py (rev 0) +++ trunk/client/downloader.py 2010-08-30 22:22:16 UTC (rev 378) @@ -0,0 +1,221 @@ +## This file is part of Virtual Playground +## Copyright (c) 2009 Jos Ratsma + Koen Koning + +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation; either +## version 2 of the License, or (at your option) any later version. + +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. + +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software + +from functions import * +from Queue import Queue +import urllib2, shutil + +DOWNLOAD_THREADS = 2 + +class Downloader(): + """ + This class will download and check all the stuff that is needed + """ + def __init__(self): + self.notify = threading.Condition() + + self.tasks = [] # Dict within a list with the following things: + # filename, filetype, callbacks, usages + + self.loaded_files = {} # Dict with the following things: + # filetype, cache, usages + + self.workers = [] + for i in range(DOWNLOAD_THREADS): + self.workers.append(Worker(self.tasks, self.notify, self.error, \ + self.loaded_files)) + + + def get_file(self, filename, filetype, callback, *arg): + # First check if we already have this file loaded + + if filename in self.loaded_files: + # File is already loaded. Easy! + load = self.loaded_files[filename] + load['usages'] += 1 + callback(filename, load['cache'], *arg) + + + if filename in sh['filetable']: + # We know where it lives. Do we already have a task for this? + task = self.find_task(filename) + if task: + # Append our callback + task['usages'] += 1 + task['callbacks'].append((callback, arg)) + + else: + # Create a new task + self.tasks.append({ + "filename": filename, + "filetype": filetype, + "callbacks": [(callback, arg),], + "usages": 1, + }) + self.notify.acquire() + self.notify.notify() + self.notify.release() + + else: + print "File '" + filename + "' is not in the repository!" + callback(filename, self.error(filetype, "not in repos"), *arg) + + + def remove_usage(self, filename): + try: load = self.loaded_files[filename] + except: pass + + load['usages'] -= 1 + if load['usages'] == 0: + # Remove the file + self.remove_loaded_file(filename) + + + def remove_loaded_file(self, filename): + try: del self.loaded_files[filename] + except: pass + + + def find_task(self, filename): + for task in self.tasks: + if task['filename'] == filename: + return task + + return None + + def error(self, filetype, error): + if filetype == "img": + # [TODO] Maybe show a little error-image? + return pygame.Surface((1,1)) + + +class Worker(threading.Thread): + def __init__(self, tasks, notify, error, loaded_files): + self.tasks = tasks + self.notify = notify + self.error = error + self.loaded_files = loaded_files + + threading.Thread.__init__(self) + self.start() + + def run(self): + self.notify.acquire() + + while 1: + try: + task = self.tasks.pop(0) + except IndexError: + self.notify.wait() + continue + + + # Setting some variables + destination = real_path("downloads", task['filetype'], \ + task['filename']) + + if not task['filename'] in sh['filetable']: + print "File '" + filename + "' is not in the repository!" + self.callbacks(task, self.error(task['filetype'], \ + "not in repos")) + return + + filetable = sh['filetable'][task['filename']] + url = filetable['url'] + checksum = filetable['checksum'] + + + need_download = not os.path.exists(destination) + + # Is this file already downloaded? + if os.path.exists(destination): + # Check if we need to download the file + filehash = file_checksum("downloads", task['filetype'], \ + task['filename']) + + if checksum != filehash: + need_download = True + + # Remove existing file + os.remove(destination) + + + # Do we need to (re)download it? + if need_download: + # Download the file! + request = urllib2.Request(url) + request.add_header('User-agent', \ + "VirtualPlayground-downloader/3.0") + + try: f = urllib2.urlopen(request) + except urllib2.URLError, e: + print "Url '" + url + "' could not be found!" + self.callbacks(task, self.error(task['filetype'], \ + "not found")) + return + + target = file(destination + ".tmp", "wb") + + while 1: + bytes = f.read(10240) #Read 10240 bytes each time + + #If its zero, we've reached the end of the file! + if len(bytes) == 0: break + + target.write(bytes) + + target.close() + + # Check the checksum + filehash = file_checksum("downloads", task['filetype'], \ + task['filename'] + ".tmp") + + if checksum != filehash: + print "Checksum mismatch for '" + task['filename'] + "! " + \ + filehash + " > " + checksum + self.callbacks(task, self.error(task['filetype'], "checksum")) + + # Remove tmp file + os.remove(destination + ".tmp") + return + + # Move the file + shutil.move(destination + ".tmp", destination) + + + # Everything went good! Now load the file + load = { + "filename": task['filename'], + "filetype": task['filetype'], + "cache": None, + "usages": task['usages'] + } + + if task['filetype'] == "img": + # Load image + load['cache'] = load_image(True, "downloads", task['filetype'],\ + task['filename']) + + self.loaded_files[task['filename']] = load + self.callbacks(task, load['cache']) + + + self.notify.release() + + + def callbacks(self, task, cache): + for callback in task['callbacks']: + callback[0](task['filename'], cache, *callback[1]) Property changes on: trunk/client/downloads/img ___________________________________________________________________ Added: svn:ignore + * Property changes on: trunk/client/downloads/snd ___________________________________________________________________ Added: svn:ignore + * Modified: trunk/client/functions.py =================================================================== --- trunk/client/functions.py 2010-08-28 13:36:11 UTC (rev 377) +++ trunk/client/functions.py 2010-08-30 22:22:16 UTC (rev 378) @@ -17,7 +17,7 @@ import pygame, os, sys, threading, time, math from pygame.locals import * -from hashlib import sha1 +from hashlib import sha1, md5 import core VERSION = "0.0.1" @@ -51,6 +51,19 @@ print 'Cannot load image:', fullname raise SystemExit, message return image + +def file_checksum(*dir): + # Read the file in chunks of 32KB + f = file(real_path(*dir)) + checksum = md5() + while 1: + data = f.read(32768) + if not data: break + checksum.update(data) + + return checksum.hexdigest() + + # GUI needs the functions again. That's why it has to be imported after all Modified: trunk/client/layout.py =================================================================== --- trunk/client/layout.py 2010-08-28 13:36:11 UTC (rev 377) +++ trunk/client/layout.py 2010-08-30 22:22:16 UTC (rev 378) @@ -75,30 +75,34 @@ return True - def changestatus(self, status): - sh['timer'].stop("layout-topslide") # Just in case + def call(self, name, data): + if name == "status": + if data['status'] == "login": + if self.status == "playground": + # Unload all the from playground + + # Slide the topbar out + sh['timer'].start("layout-topslide", self.timer, \ + time.time(), 0) - login_list = ['connecting', 'login', 'logging in'] - if status in login_list and self.status not in login_list: - # (Un)load all the data - if self.topbar: - # Slide the topbar out - sh['timer'].start("layout-topslide", self.timer, time.time(), 0) + elif data['status'] == "playground": + if self.status != "playground": + # load al the data for the playground + self.topbar = load_image(True, "images", "topbar.png") + + self.selected = None + self.hover = self.topbarcursor(pygame.mouse.get_pos()) + + self.toppos = 0 + + # Slide the topbar in + sh['timer'].start("layout-topslide", self.timer, \ + time.time(), 1) - elif status == "playground": - self.topbar = load_image(True, "images", "topbar.png") - - self.selected = None - self.hover = self.topbarcursor(pygame.mouse.get_pos()) - - self.toppos = 0 - - # Slide the topbar in - sh['timer'].start("layout-topslide", self.timer, time.time(), 1) - - self.status = status + self.status = data['status'] + def updatetop(self, wholebar = False): if wholebar: self.surf.fill([0,0,0,0]) Modified: trunk/client/playground.py =================================================================== --- trunk/client/playground.py 2010-08-28 13:36:11 UTC (rev 377) +++ trunk/client/playground.py 2010-08-30 22:22:16 UTC (rev 378) @@ -24,6 +24,7 @@ self.surf.fill([0,0,0]) self.status = None + self.statuswhere = None # Login stuff self.loginbg = None @@ -32,7 +33,7 @@ self.logingui = None # Playground stuff - # ... + self.backgrounds = {} def event(self, ev): if self.status == "login": @@ -51,36 +52,60 @@ - def changestatus(self, status): - login_list = ['connecting', 'login', 'logging in'] - if status in login_list and self.status not in login_list: - # (Un)load all the data for the login - self.loginbg = load_image(False, "images", "loginbg.png") - self.loginbox = load_image(True, "images", "loginbox.png") - self.loginfont = pygame.font.Font(real_path("fonts", \ - "DejaVuSans.ttf"), 35) - self.loginfeedback = LoginFeedback(self) - - elif status == "playground": - # (Un)load all the data for the playground - self.loginbg = None - self.loginbox = None - self.loginfont = None - self.logingui = None + def call(self, name, data): + if name == "status": # A status update + if data['status'] == "login": + if data['status'] != self.status: + # Unload data from the playground + if self.status != None: + self.backgrounds = {} + + # Load the data for the login + self.loginbg = load_image(False, "images", "loginbg.png") + self.loginbox = load_image(True, "images", "loginbox.png") + self.loginfont = pygame.font.Font(real_path("fonts", \ + "DejaVuSans.ttf"), 35) + self.loginfeedback = LoginFeedback(self) + + + if data['where'] == "connecting": + self.drawlogin("connecting") + + elif data['where'] == "waiting": + self.drawlogin("login") + + elif data['where'] == "logging in": + self.drawlogin("logging in") + + elif data['where'] == "downloading": + self.drawlogin("downloading") - self.surf.fill([0,0,0]) - sh['update']() + elif data['status'] == "playground": + if data['status'] != self.status: + # Unload data from the login + self.loginbg = None + self.loginbox = None + self.loginfont = None + self.logingui = None + + # Load the data for the playground + self.surf.fill([0,0,0]) + sh['update']() + + self.status = data['status'] + self.statuswhere = data['where'] - if status == "connecting": - self.drawlogin("connecting") - elif status == "login": - self.drawlogin("login") - elif status == "logging in": - self.drawlogin("logging in") + elif name == "filetable": + if data['action'] == "update" and self.status == "login" and \ + self.statuswhere == "downloading": + + # Pre-load the necessarily files for the playground + counter = {"counter": 1} + sh['downloader'].get_file("bg-grass.png", "img", self.download,\ + "background", counter) + - self.status = status - def drawlogin(self, status): self.loginstatus = status self.surf.blit(self.loginbg, (0,0)) @@ -141,6 +166,12 @@ posleft = (1000/2)-(text.get_size()[0]/2) self.surf.blit(text, (posleft, 330)) + + elif status == "downloading": + text = self.loginfont.render("Downloading...", True, (132,125,114)) + posleft = (1000/2)-(text.get_size()[0]/2) + self.surf.blit(text, (posleft, 330)) + sh['update']() def logingui_draw(self, rect): @@ -153,8 +184,26 @@ self.surf.blit(self.logingui.surf, rect, rect) sh['update'](rect) + + + + def download(self, filename, cache, name, *arg): + if name == "background": + self.backgrounds[filename] = cache + + counter = arg[0] + counter['counter'] -= 1 + + if counter['counter'] == 0: + # We're finished downloading the necessarily stuff + sh['main'].call("status", { + "status": "playground", + "where": "playground" + }) - + + + class LoginFeedback(gui.Feedback): def __init__(self, parent): self.parent = parent @@ -182,7 +231,10 @@ pwd = sha1(self.parent.logingui['pwd'].input).hexdigest() self.parent.logingui = None - sh['main'].changestatus("logging in") + sh['main'].call("status", { + "status": "login", + "where": "logging in" + }) sh['client'].login(usr, pwd) Modified: trunk/client/windows.py =================================================================== --- trunk/client/windows.py 2010-08-28 13:36:11 UTC (rev 377) +++ trunk/client/windows.py 2010-08-30 22:22:16 UTC (rev 378) @@ -24,5 +24,5 @@ def event(self, ev): pass - def changestatus(self, status): + def call(self, name, data): pass This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |