maptransfer/.hg_archival.txt0000644000000000000000000000013611370504716016634 0ustar00usergroup00000000000000repo: 40bc2fa0683628a9e32b41c8e544e5266ee47105 node: 851af4ddcbe34ec97f51470f90a89315f1c32b8c maptransfer/.hgignore0000644000000000000000000000014111370504716015345 0ustar00usergroup00000000000000syntax: glob *~ *.pyc build dist server/server.cfg client/maptransfer.cfg cmd.exe.lnk md5sum.py maptransfer/.hgtags0000644000000000000000000000013411370504716015022 0ustar00usergroup000000000000002bfebede8443019f4634b522b905c5a1c1ae09e8 v0.1 710eae90feb010cf74d259ddff6fc6990a3d4ee5 v0.2 maptransfer/AUTHORS0000644000000000000000000000025011370504716014613 0ustar00usergroup00000000000000MapTransfer has been written by Michael "Svedrin" Ziegler . Others who have contributed: * Daniel Loch maptransfer/CHANGELOG0000644000000000000000000000124211370504716014757 0ustar00usergroup00000000000000MapTransfer changelog ===================== Version 0.3, 6 May 2010: ------------------------------ * Relicensed under MIT as GPL causes problems with OpenSSL. * Removed md5sum.py utility, as Python comes with its own and the licensing is unclear. * Fix NameError when the server config file cannot be found. Version 0.2, 5 May 2010: ------------------------------ * Organize the code a bit and added AUTHORS/CHANGELOG/README files. * Translate the code and GUI files to English instead of an English-German mix. * Use QTs translation mechanism. * Add a German translation. Version 0.1, 4 May 2010: ------------------------------ * Initial release. maptransfer/LICENSE0000644000000000000000000000211411370504716014551 0ustar00usergroup00000000000000The MIT License Copyright © 2009-2010, Michael Ziegler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. maptransfer/README0000644000000000000000000000407111370504716014430 0ustar00usergroup00000000000000MapTransfer -- A tool to help lazy gameserver admins ==================================================== Has this ever happened to YOU? Having to upload yet another map from those crazy clanmates of yours who keep downloading maps like their lives depend on it? Having to open yet another SSH shell, for map in incoming/*; do unzip, mv, cp, bzip2; done. It's boring. It's a total waste of time. It sucks ass. MapTransfer tries to automate this process as much as possible. It handles everything a map upload takes: * Finds the paths on the disk where Steam is installed and where games are located. (Uses the Registry on Windows, and maptransfer.cfg for other OSs) * BZips the Map before upload, and only uploads the BZ2ed file. That way, maps are automatically prepared for fast download, while saving (mega)bytes for the client to upload. * Uploads .nav/.txt/.res files, if any. * Hardlinks files on the server side, if more than one gameserver path is specified in the config; that way a single map is uploaded to multiple servers at once (and only takes up the space once, too). * Saves you and your users a lot of tedious cdery and copypasta. * Highlights the maps you or the server don't have, so you easily see what's missing on either side. Aside from the core functionality, it offers a few niceties as well: * Client allows for simultaneous downloads and uploads. * Uses HTTPS as protocol - that way, you can download maps (or test connectivity) with your favorite web browser, or write your own scripts that interact with the server. * IPv6 ready * Extensible: Any game should work, as long as it uses bsp maps - map directories aren't hard coded but read from the config file. * Does not require *any* installation on Windows clients if using distutils, clients only need to unzip it somewhere and run client.exe. Other notes: * Do use Python >=2.6. It will allow for a cleaner shutdown on the server, and the client will be able to use IPv6. * The server has only been tested on Linux, but I think it should work on other OSs as well. maptransfer/client/Makefile0000644000000000000000000000025611370504716016467 0ustar00usergroup00000000000000 all: client_ui.py client_de.qm client_ui.py: client.ui pyuic4 -o $@ $^ client_%.ts: client.py client.ui pylupdate4 $^ -ts $@ client_%.qm: client_%.ts lrelease-qt4 $^ maptransfer/client/MapList.py0000644000000000000000000000717611370504716016762 0ustar00usergroup00000000000000# -*- coding: utf-8 -*- """ MapList -- ListWidget controller classes that display information Copyright © 2009-2010, Michael "Svedrin" Ziegler """ import os from urllib import urlopen from PyQt4 import Qt from PyQt4 import QtGui class MapListItem( QtGui.QListWidgetItem ): """ Base class for an Item in a map list widget. """ def __init__( self, parent, map_name, map_exts ): QtGui.QListWidgetItem.__init__( self, map_name, parent ) self.map_name = map_name self.map_exts = map_exts self.setFlags( Qt.Qt.ItemIsSelectable | Qt.Qt.ItemIsUserCheckable | Qt.Qt.ItemIsEnabled ) self.uncheck() checked = property( lambda self: self.checkState() == Qt.Qt.Checked, doc="True if this item has been selected by the user." ) def check( self ): """ Set the check state of this item to Checked. """ self.setCheckState( Qt.Qt.Checked ) def uncheck( self ): """ Set the check state of this item to Unchecked. """ self.setCheckState( Qt.Qt.Unchecked ) class BaseMapList( object ): """ Base class for a Map list widget controller. """ def __init__( self, widget ): object.__init__( self ) self.widget = widget self.widget.setSortingEnabled( True ) def get_map_item( self, mapname ): """ Get the map item for the map with the given name. """ items = self.widget.findItems( mapname, Qt.Qt.MatchExactly ) if not items: return None if len(items) == 1: return items[0] else: raise ValueError( "Found multiple maps." ) def mark_missing( self, other_list ): """ Mark entries for maps that are missing in other_list by setting the background color to light gray. """ for i in range( self.widget.count() ): item = self.widget.item(i) if not other_list.has_map( item.text() ): item.setBackgroundColor( Qt.Qt.lightGray ) def has_map( self, map_name ): """ Check if the given map exists in my list. """ for i in range( self.widget.count() ): item = self.widget.item(i) if item and item.map_name == map_name: return True return False def choose_missing( self, other_list ): """ Set CheckState for entries for maps that are missing in other_list to Checked. """ for i in range( self.widget.count() ): item = self.widget.item(i) if not other_list.has_map( item.text() ): item.check() class ClientMapList( BaseMapList ): """ Map list widget controller that operates on the local file system. """ def fetch( self, basedir ): """ Populate the list by finding map files in the given directory. """ while self.widget.takeItem( 0 ): pass fileslist = dict() for thisfile in os.listdir( basedir ): if not os.path.isfile( "%s/%s" % ( basedir, thisfile ) ): continue base, ext = thisfile.split( '.', 1 ) if base not in fileslist: fileslist[base] = [ext] else: fileslist[base].append( ext ) for mapname in fileslist: if "bsp" in fileslist[mapname]: self.widget.addItem( MapListItem( self.widget, mapname, fileslist[mapname] ) ) class ServerMapList( BaseMapList ): """ Map list widget controller that queries the server. """ def fetch( self, baseurl ): """ Populate the list by querying the server for existing maps. """ while self.widget.takeItem( 0 ): pass # Fetch the baseURL - it returns somthing like this: # dm_lockdown: bsp # dm_underpass: bsp bsp.bz2 # dm_resistance: bsp index = urlopen( baseurl ).fp while True: line = index.readline() if not line: break map_name, map_exts_string = line.strip().split( ':', 1 ) map_exts = map_exts_string.strip().split( ' ' ) self.widget.addItem( MapListItem( self.widget, map_name, map_exts ) ) maptransfer/client/TransferHandler.py0000644000000000000000000001262011370504716020461 0ustar00usergroup00000000000000# -*- coding: utf-8 -*- """ TransferHandler -- class to handle uploading and downloading files Copyright © 2009-2010, Michael "Svedrin" Ziegler """ from PyQt4 import QtCore from urllib import urlopen import httplib import urlparse import bz2 import base64 import threading class TransferHandler( QtCore.QObject ): """ Manages up- and downloading files. """ sig_download_start = QtCore.SIGNAL( 'downloadStart(const QString)' ) sig_download_success = QtCore.SIGNAL( 'downloadSuccess(const QString)' ) sig_download_fail = QtCore.SIGNAL( 'downloadFail(const QString, const QString)' ) sig_download_finished = QtCore.SIGNAL( 'downloadFinished()' ) sig_upload_start = QtCore.SIGNAL( 'uploadStart(const QString)' ) sig_upload_success = QtCore.SIGNAL( 'uploadSuccess(const QString)' ) sig_upload_fail = QtCore.SIGNAL( 'uploadFail(const QString, const int, const QString, const QString)' ) sig_upload_finished = QtCore.SIGNAL( 'uploadFinished()' ) def __init__( self, username=None, password=None ): QtCore.QObject.__init__( self ) self.downloader = None self.uploader = None self.mapsdir = None self.mapsurl = None self.username = username self.password = password def perform_transfers( self, map_dir, map_url, download, upload ): """ Start the transfers listed in download and upload in seperate threads. download and upload need to be lists of dictionaries, containing: - mapname: the base name of the map to download, e.g. de_dust - mapexts: list of file extensions to be downloaded, e.g. [bsp.bz2, txt, nav] """ self.mapsdir = map_dir self.mapsurl = map_url if self.downloader is None or not self.downloader.isAlive(): self.downloader = threading.Thread( target=self.perform_downloads, args=(download,) ) self.downloader.start() if self.uploader is None or not self.uploader.isAlive(): self.uploader = threading.Thread( target=self.perform_uploads, args=(upload,) ) self.uploader.start() def perform_downloads( self, download ): """ The thread worker that handles downloading files. For documentation on the download parameter, see perform_transfers. """ for job in download: mapname = job['mapname'] mapexts = job['mapexts'] self.emit( TransferHandler.sig_download_start, mapname ) for cur_ext in mapexts: if cur_ext == "bsp" and "bsp.bz2" in mapexts: # We won't download a bsp now because a bz2'ed file exists continue mapfile = urlopen( "%s/%s.%s" % ( self.mapsurl, mapname, cur_ext ) ).fp.read() if cur_ext == "bsp.bz2": mapfile = bz2.decompress( mapfile ) cur_ext = "bsp" mapfile_fd = open( "%s/%s.%s" % ( self.mapsdir, mapname, cur_ext ), 'wb' ) mapfile_fd.write( mapfile ) mapfile_fd.close() self.emit( TransferHandler.sig_download_success, mapname ) self.emit( TransferHandler.sig_download_finished ) def perform_uploads( self, upload ): """ The thread worker that handles uploading files. For documentation on the upload parameter, see perform_transfers. """ for job in upload: mapname = job['mapname'] mapexts = job['mapexts'] self.emit( TransferHandler.sig_upload_start, mapname ) bspath = "%s/%s.bsp" % ( self.mapsdir, mapname ) bspurl = "%s/%s.bsp.bz2" % ( self.mapsurl, mapname ) if not self.upload_file( mapname, bspath, bspurl ): continue for cur_ext in mapexts: if cur_ext in ( "bsp", "bsp.bz2" ): continue filepath = "%s/%s.%s" % ( self.mapsdir, mapname, cur_ext ) fileurl = "%s/%s.%s" % ( self.mapsurl, mapname, cur_ext ) self.upload_file( mapname, filepath, fileurl ) self.emit( TransferHandler.sig_upload_success, mapname ) self.emit( TransferHandler.sig_upload_finished ) def upload_file( self, mapname, filepath, fileurl ): """ See if the server will accept the file, and if so, upload it. """ if self.upload_file_real( mapname, filepath, fileurl, True ): return self.upload_file_real( mapname, filepath, fileurl, False ) return False def upload_file_real( self, mapname, filepath, fileurl, dry = False ): """ Upload a file. """ diskfd = open( filepath, 'rb' ) try: plaincontent = diskfd.read() finally: diskfd.close() if fileurl[-3:] == 'bz2': fd_content = bz2.compress( plaincontent ) else: fd_content = plaincontent parsedurl = urlparse.urlparse( fileurl ) # Send HTTP Put request httpreq = httplib.HTTPS( parsedurl.netloc ) httpreq.putrequest( 'PUT', parsedurl.path ) httpreq.putheader( 'Accept', '*/*' ) httpreq.putheader( 'Allow', 'PUT' ) httpreq.putheader( 'Content-Type', 'application/octet-stream' ) httpreq.putheader( 'Content-Length', str( len( fd_content ) ) ) if self.username and self.password: httpreq.putheader( 'Authorization', base64.b64encode( '%s:%s' % ( self.username, self.password ) ) ) if dry: httpreq.putheader( 'x-dry-run', 'true' ) httpreq.endheaders() if not dry: print "Sending file with %s bytes (original: %d)" % ( str( len( fd_content ) ), len( plaincontent ) ) httpreq.send( fd_content ) # check reply errcode, errmsg, _ = httpreq.getreply() hfd = httpreq.getfile() if hfd: replybody = hfd.read() else: replybody = "" if errcode != 201: self.emit( TransferHandler.sig_upload_fail, mapname, errcode, errmsg, replybody ) return False httpreq.close() return True maptransfer/client/client.py0000755000000000000000000003052611370504716016665 0ustar00usergroup00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ MapTransfer (Client) -- upload/download maps to/from a VALVe game server Copyright © 2009-2010, Michael "Svedrin" Ziegler """ import sys from urllib import urlopen from os import listdir from os.path import join, isdir, exists from ConfigParser import ConfigParser from PyQt4 import Qt from PyQt4.Qt import QLocale, QTranslator from PyQt4 import QtCore from PyQt4 import QtGui from client_ui import Ui_MainWindow from MapList import ServerMapList, ClientMapList from TransferHandler import TransferHandler class Gui(Ui_MainWindow, QtCore.QObject): """ The main class that handles the GUI and controls background processes. """ def __init__( self, conf, steampath ): QtCore.QObject.__init__( self ) Ui_MainWindow.__init__( self ) self.conf = conf self.steampath = steampath self.baseurl = conf.get( "server", "baseurl" ) self.main_window = QtGui.QMainWindow() self.setupUi( self.main_window ) self.client_maplist = ClientMapList( self.lstClientList ) self.server_maplist = ServerMapList( self.lstServerList ) self.transfer_handler = TransferHandler( conf.get( "auth", "username" ), conf.get( "auth", "password" ) ) self.lblDownloadStatus = QtGui.QLabel( self.tr( "No downloads are running." ) ) self.statusbar.addPermanentWidget( self.lblDownloadStatus ) self.lblUploadStatus = QtGui.QLabel( self.tr( "No uploads are running." ) ) self.statusbar.addPermanentWidget( self.lblUploadStatus ) # Scan for maps directories and store them in a dict. # account_paths = { # account_name: { # game_name: game maps directory # } # } self.account_paths = {} for accountdir in listdir( join( self.steampath, "steamapps" ) ): if not isdir( join( self.steampath, "steamapps", accountdir ) ) or \ accountdir in ( "common", "SourceMods" ): continue appspath = join( self.steampath, "steamapps", accountdir ) print "Found Steam account: '%s'" % appspath for gamedir in listdir( appspath ): if self.conf.has_option( "gametags", gamedir ) and \ self.conf.has_option( "mapdirs", gamedir ): mapsdir = join( appspath, gamedir, self.conf.get( "mapdirs", gamedir ) ) if exists( mapsdir ): if accountdir not in self.account_paths: self.account_paths[accountdir] = { None: appspath } print "Map directory for '%s' is:\n %s" % ( gamedir, mapsdir ) self.account_paths[accountdir][ self.conf.get( "gametags", gamedir ) ] = mapsdir else: print "Map directory for '%s' does not exist" % gamedir else: print "Map directory for '%s' is unknown" % gamedir self.setup_accounts() self.account_changed(0) self.main_window.connect( self.cmbAccount, QtCore.SIGNAL("currentIndexChanged (int)"), self.account_changed ) self.main_window.connect( self.cmbGameServer, QtCore.SIGNAL("currentIndexChanged (int)"), self.server_changed ) self.main_window.connect( self.btnStart, QtCore.SIGNAL("clicked(bool)"), self.perform_transfers ) self.main_window.connect( self.btnSelectAllClient, QtCore.SIGNAL("clicked(bool)"), self.choose_all_client ) self.main_window.connect( self.btnSelectAllServer, QtCore.SIGNAL("clicked(bool)"), self.choose_all_server ) self.main_window.connect( self.transfer_handler, TransferHandler.sig_download_start, self.status_download_start ) self.main_window.connect( self.transfer_handler, TransferHandler.sig_download_success, self.status_download_success ) self.main_window.connect( self.transfer_handler, TransferHandler.sig_download_fail, self.status_download_fail ) self.main_window.connect( self.transfer_handler, TransferHandler.sig_download_finished, self.status_download_finished) self.main_window.connect( self.transfer_handler, TransferHandler.sig_upload_start, self.status_upload_start ) self.main_window.connect( self.transfer_handler, TransferHandler.sig_upload_success, self.status_upload_success ) self.main_window.connect( self.transfer_handler, TransferHandler.sig_upload_fail, self.status_upload_fail ) self.main_window.connect( self.transfer_handler, TransferHandler.sig_upload_finished, self.status_upload_finished ) def show( self ): """ Show the main Window. """ self.main_window.show() def setup_accounts( self ): """ Search the steamapps directory for usable Steam accounts. """ self.cmbAccount.clear() for accountdir in self.account_paths: self.cmbAccount.addItem( accountdir, QtCore.QVariant( self.account_paths[accountdir][None] ) ) current_account = property( lambda self: str( self.cmbAccount.itemText( self.cmbAccount.currentIndex() ) ), doc="The name of the currently selected Steam account." ) apps_path = property( lambda self: self.account_paths[self.current_account][None], doc="The SteamApps subdirectory of the currently selected Steam account." ) def account_changed( self, accid ): """ A different account has been selected, reload the server list. """ print "Selected Steam account: '%s'" % self.apps_path self.setup_servers() self.server_changed(0) def setup_servers( self ): """ Retrieve a server list and check for which game servers we have games in the selected account. """ self.cmbGameServer.clear() # Fetch the BaseURL - it returns something like this: # pub:cssource:Publicserver # war:cssource:Warserver # emp:Empires:EmpiresMod v3.1.3.3.7 index = urlopen( self.baseurl ).fp while True: line = index.readline().strip() if not line: break srv, game, desc = line.split( ':', 2 ) if game in self.account_paths[self.current_account]: self.cmbGameServer.addItem( "%s (%s)" % ( desc, game ), QtCore.QVariant( "%s:%s" % ( srv, game ) ) ) server_info = property( lambda self: str( self.cmbGameServer.itemData( self.cmbGameServer.currentIndex() ).toString() ).split(':'), doc="The server ID and game name for the currently selected Game server." ) server_id = property( lambda self: self.server_info[0], doc="The Server ID for the currently selected Game server." ) server_game = property( lambda self: self.server_info[1], doc="The game directory name for the currently selected Game server." ) def server_changed( self, srvid ): """ A different game server has been selected, reload map lists. """ self.server_maplist.fetch( self.baseurl + self.server_id ) maps_dir = self.account_paths[self.current_account][ self.server_game ] self.lblPath.setText( self.tr("Maps are being saved to:\n%1").arg(maps_dir) ) self.client_maplist.fetch( maps_dir ) # now compare the maps and highlight those that should be transferred self.client_maplist.mark_missing( self.server_maplist ) self.server_maplist.mark_missing( self.client_maplist ) def choose_all_client( self, checked ): """ Select all maps in the client view that don't exist on the server. """ self.client_maplist.choose_missing( self.server_maplist ) def choose_all_server( self, checked ): """ Select all maps in the server view that don't exist on the client. """ self.server_maplist.choose_missing( self.client_maplist ) def status_download_start( self, mapname ): """ Slot to react on a starting download. """ self.lblDownloadStatus.setText( self.tr("Downloading %1").arg(mapname) ) def status_download_success( self, mapname ): """ Slot to react on a successfully finished download. """ item = self.server_maplist.get_map_item( mapname ) if item: item.setBackgroundColor( Qt.Qt.green ) def status_download_fail( self, mapname, errcode, errmsg, errdesc ): """ Slot to react on a failed download. """ item = self.server_maplist.get_map_item( mapname ) if item: item.setBackgroundColor( Qt.Qt.red ) QtGui.QMessageBox.information( self.main_window, self.main_window.windowTitle(), self.tr( "Map %1 cannot be downloaded. The server said:\n%2: %3\n\n%4" ).arg(mapname).arg(errcode).arg(errmsg).arg(errdesc) ) def status_download_finished( self ): """ Slot to react when all downloads have finished. """ self.lblDownloadStatus.setText( self.tr("All downloads finished") ) self.server_changed(0) def status_upload_start( self, mapname ): """ Slot to react on a starting upload. """ self.lblUploadStatus.setText( self.tr("Uploading %1").arg(mapname) ) def status_upload_success( self, mapname ): """ Slot to react on a successfully finished upload. """ item = self.server_maplist.get_map_item( mapname ) if item: item.setBackgroundColor( Qt.Qt.green ) def status_upload_fail( self, mapname, errcode, errmsg, errdesc ): """ Slot to react on a failed upload. """ item = self.server_maplist.get_map_item( mapname ) if item: item.setBackgroundColor( Qt.Qt.red ) QtGui.QMessageBox.information( self.main_window, self.main_window.windowTitle(), self.tr( "Map %1 cannot be uploaded. The server said:\n%2: %3\n\n%4" ).arg(mapname).arg(errcode).arg(errmsg).arg(errdesc) ) def status_upload_finished( self ): """ Slot to react when all uploads have finished. """ self.lblUploadStatus.setText( self.tr("All uploads finished") ) self.server_changed(0) def perform_transfers( self, checked ): """ Start the background thread to transfer the selected files. Before doing so, check if the user ordered to download files we already have, and ask them what to do with these downloads. """ yes_to_all = False no_to_all = False download = [] upload = [] for i in range( self.server_maplist.widget.count() ): listitem = self.server_maplist.widget.item(i) if not listitem or not listitem.checked: continue mapname = listitem.map_name bspath = "%s/%s.bsp" % ( self.account_paths[self.current_account][self.server_game], mapname ) if exists( bspath ): if yes_to_all == no_to_all == False: choice = QtGui.QMessageBox.question( self.main_window, self.main_window.windowTitle(), self.tr("Map %1 exists - overwrite it?").arg(mapname), ( QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | QtGui.QMessageBox.YesToAll | QtGui.QMessageBox.NoToAll ), QtGui.QMessageBox.No ) elif yes_to_all: choice = QtGui.QMessageBox.Yes else: choice = QtGui.QMessageBox.No if choice == QtGui.QMessageBox.YesToAll: yes_to_all = True elif choice == QtGui.QMessageBox.NoToAll: no_to_all = True if choice in ( QtGui.QMessageBox.No, QtGui.QMessageBox.NoToAll ): continue download.append({ 'mapname': mapname, 'mapexts': listitem.map_exts }) for i in range( self.client_maplist.widget.count() ): listitem = self.client_maplist.widget.item(i) if not listitem or not listitem.checked: continue mapname = listitem.map_name if self.server_maplist.has_map( mapname ): QtGui.QMessageBox.information( self.main_window, self.main_window.windowTitle(), self.tr("Map %1 already exists on the server and will not be uploaded.").arg(mapname) ) continue upload.append({ 'mapname': mapname, 'mapexts': listitem.map_exts }) self.transfer_handler.perform_transfers( self.account_paths[self.current_account][ self.server_game ], self.baseurl + self.server_id, download=download, upload=upload ) if __name__ == '__main__': from sys import argv if len( argv ) > 1: cfg = argv[1] else: cfg = "maptransfer.cfg" if not exists( cfg ): print "The configuration file '%s' does not exist!" % cfg print "Hit enter to exit." raw_input() sys.exit(1) config = ConfigParser() config.read( cfg ) try: from _winreg import OpenKey, CloseKey, QueryValueEx, HKEY_CURRENT_USER except ImportError: steam = config.get( "steam", "path" ) else: print "Auto-Detecting Gamedirs..." regkey = OpenKey( HKEY_CURRENT_USER, config.get( "registry", "keypath" ) ) steam = QueryValueEx( regkey, config.get( "registry", "keyname" ) )[0] CloseKey( regkey ) try: app = QtGui.QApplication( sys.argv ) locale = QLocale.system().name() print "loading locale", locale translator = QTranslator() translator.load("client_" + locale) app.installTranslator(translator) g = Gui( config, steam ) g.show() app.exec_() finally: if config.getboolean( "general", "hitEnterToExit" ): print print print "Hit enter to exit." raw_input() maptransfer/client/client.ui0000644000000000000000000001162211370504716016643 0ustar00usergroup00000000000000 MainWindow 0 0 500 600 MapTransfer The base path where Steam is installed. Found path to Steam: D:\Yadda\Yadda\Steam Steam Account The Steam account to choose games and maps from. Game Server The game server to upload the maps to. Local maps in your game folder. Remote maps on the server. Maps on the Client: Maps on the Server: Qt::Horizontal 40 20 Select all maps that are not available on the server. Select all Qt::Horizontal 40 20 Start transferring the selected maps. Start Qt::Horizontal 40 20 Select all maps that are not available on the client. Select all Qt::Horizontal 40 20 maptransfer/client/client_de.qm0000644000000000000000000000704411370504716017316 0ustar00usergroup00000000000000 Gui No downloads are running. Keine Downloads gestartet. No uploads are running. Keine Uploads gestartet. Maps are being saved to: {0} Maps werden gespeichert in: {0} Downloading {0} Lade {0} herunter Map {0} cannot be downloaded. The server said: {1}: {2} {3} Map {0} kann nicht heruntergeladen werden. Der Server sagte: {1}: {2} {3} All downloads finished Alle Downloads fertig Uploading {0} Lade {0} hoch Map {0} cannot be uploaded. The server said: {1}: {2} {3} Map {0} kann nicht hochgeladen werden. Der Server sagte: {1}: {2} {3} All uploads finished Alle Uploads fertig Map {0} exists - overwrite it? Map {0} existiert - überschreiben? Map {0} already exists on the server and will not be uploaded. Map {0} existiert bereits auf dem Server und wird daher nicht hochgeladen. Maps are being saved to: %1 Maps werden gespeichert in: %1 Downloading %1 Lade %1 herunter Map %1 cannot be downloaded. The server said: %2: %3 %4 Map %1 kann nicht heruntergeladen werden. Der Server sagte: %2: %3 %4 Uploading %1 Lade %1 hoch Map %1 cannot be uploaded. The server said: %2: %3 %4 Map %1 kann nicht hochgeladen werden. Der Server sagte: %2: %3 %4 Map %1 exists - overwrite it? Map %1 existiert - überschreiben? Map %1 already exists on the server and will not be uploaded. Map %1 existiert bereits auf dem Server und wird daher nicht hochgeladen. MainWindow Start Start Found path to Steam: D:\Yadda\Yadda\Steam Pfad zu Steam: D:\Yadda\Yadda\Steam Steam Account Steam-Account Game Server Game-Server Maps on the Client: Maps auf dem Client: Maps on the Server: Maps auf dem Server: Select all Alle auswählen MapTransfer MapTransfer The base path where Steam is installed. Das Verzeichnis in dem Steam installiert ist. The Steam account to choose games and maps from. Der Steam-Account aus dem Spiele und Maps ausgewählt werden sollen. The game server to upload the maps to. Der Game-Server auf den die Maps hochgeladen werden sollen. Local maps in your game folder. Maps in deinem lokalen Spielverzeichnis. Remote maps on the server. Maps auf dem Server. Select all maps that are not available on the server. Markiert alle Maps, die nicht auf dem Server vorhanden sind. Start transferring the selected maps. Beginne die Übertragung der ausgewählten Maps. Select all maps that are not available on the client. Markiert alle Maps, die nicht lokal vorhanden sind. maptransfer/client/client_ui.py0000644000000000000000000001503311370504716017353 0ustar00usergroup00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'client.ui' # # Created: Tue May 4 22:00:49 2010 # by: PyQt4 UI code generator 4.7.3 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(500, 600) self.centralwidget = QtGui.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.verticalLayout = QtGui.QVBoxLayout(self.centralwidget) self.verticalLayout.setObjectName("verticalLayout") self.lblPath = QtGui.QLabel(self.centralwidget) self.lblPath.setObjectName("lblPath") self.verticalLayout.addWidget(self.lblPath) self.lblAccount = QtGui.QLabel(self.centralwidget) self.lblAccount.setObjectName("lblAccount") self.verticalLayout.addWidget(self.lblAccount) self.cmbAccount = QtGui.QComboBox(self.centralwidget) self.cmbAccount.setObjectName("cmbAccount") self.verticalLayout.addWidget(self.cmbAccount) self.lblGameServer = QtGui.QLabel(self.centralwidget) self.lblGameServer.setObjectName("lblGameServer") self.verticalLayout.addWidget(self.lblGameServer) self.cmbGameServer = QtGui.QComboBox(self.centralwidget) self.cmbGameServer.setObjectName("cmbGameServer") self.verticalLayout.addWidget(self.cmbGameServer) self.gridLayout = QtGui.QGridLayout() self.gridLayout.setObjectName("gridLayout") self.lstClientList = QtGui.QListWidget(self.centralwidget) self.lstClientList.setObjectName("lstClientList") self.gridLayout.addWidget(self.lstClientList, 1, 1, 1, 1) self.lstServerList = QtGui.QListWidget(self.centralwidget) self.lstServerList.setObjectName("lstServerList") self.gridLayout.addWidget(self.lstServerList, 1, 2, 1, 1) self.lblClientList = QtGui.QLabel(self.centralwidget) self.lblClientList.setObjectName("lblClientList") self.gridLayout.addWidget(self.lblClientList, 0, 1, 1, 1) self.lblServerList = QtGui.QLabel(self.centralwidget) self.lblServerList.setObjectName("lblServerList") self.gridLayout.addWidget(self.lblServerList, 0, 2, 1, 1) self.verticalLayout.addLayout(self.gridLayout) self.horizontalLayout_2 = QtGui.QHBoxLayout() self.horizontalLayout_2.setObjectName("horizontalLayout_2") spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) self.btnSelectAllClient = QtGui.QPushButton(self.centralwidget) self.btnSelectAllClient.setObjectName("btnSelectAllClient") self.horizontalLayout_2.addWidget(self.btnSelectAllClient) spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.btnStart = QtGui.QPushButton(self.centralwidget) self.btnStart.setObjectName("btnStart") self.horizontalLayout_2.addWidget(self.btnStart) spacerItem2 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem2) self.btnSelectAllServer = QtGui.QPushButton(self.centralwidget) self.btnSelectAllServer.setObjectName("btnSelectAllServer") self.horizontalLayout_2.addWidget(self.btnSelectAllServer) spacerItem3 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem3) self.verticalLayout.addLayout(self.horizontalLayout_2) MainWindow.setCentralWidget(self.centralwidget) self.statusbar = QtGui.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MapTransfer", None, QtGui.QApplication.UnicodeUTF8)) self.lblPath.setToolTip(QtGui.QApplication.translate("MainWindow", "The base path where Steam is installed.", None, QtGui.QApplication.UnicodeUTF8)) self.lblPath.setText(QtGui.QApplication.translate("MainWindow", "Found path to Steam: D:\\Yadda\\Yadda\\Steam", None, QtGui.QApplication.UnicodeUTF8)) self.lblAccount.setText(QtGui.QApplication.translate("MainWindow", "Steam Account", None, QtGui.QApplication.UnicodeUTF8)) self.cmbAccount.setToolTip(QtGui.QApplication.translate("MainWindow", "The Steam account to choose games and maps from.", None, QtGui.QApplication.UnicodeUTF8)) self.lblGameServer.setText(QtGui.QApplication.translate("MainWindow", "Game Server", None, QtGui.QApplication.UnicodeUTF8)) self.cmbGameServer.setToolTip(QtGui.QApplication.translate("MainWindow", "The game server to upload the maps to.", None, QtGui.QApplication.UnicodeUTF8)) self.lstClientList.setToolTip(QtGui.QApplication.translate("MainWindow", "Local maps in your game folder.", None, QtGui.QApplication.UnicodeUTF8)) self.lstServerList.setToolTip(QtGui.QApplication.translate("MainWindow", "Remote maps on the server.", None, QtGui.QApplication.UnicodeUTF8)) self.lblClientList.setText(QtGui.QApplication.translate("MainWindow", "Maps on the Client:", None, QtGui.QApplication.UnicodeUTF8)) self.lblServerList.setText(QtGui.QApplication.translate("MainWindow", "Maps on the Server:", None, QtGui.QApplication.UnicodeUTF8)) self.btnSelectAllClient.setToolTip(QtGui.QApplication.translate("MainWindow", "Select all maps that are not available on the server.", None, QtGui.QApplication.UnicodeUTF8)) self.btnSelectAllClient.setText(QtGui.QApplication.translate("MainWindow", "Select all", None, QtGui.QApplication.UnicodeUTF8)) self.btnStart.setToolTip(QtGui.QApplication.translate("MainWindow", "Start transferring the selected maps.", None, QtGui.QApplication.UnicodeUTF8)) self.btnStart.setText(QtGui.QApplication.translate("MainWindow", "Start", None, QtGui.QApplication.UnicodeUTF8)) self.btnSelectAllServer.setToolTip(QtGui.QApplication.translate("MainWindow", "Select all maps that are not available on the client.", None, QtGui.QApplication.UnicodeUTF8)) self.btnSelectAllServer.setText(QtGui.QApplication.translate("MainWindow", "Select all", None, QtGui.QApplication.UnicodeUTF8)) maptransfer/client/maptransfer.example.cfg0000644000000000000000000000240711370504716021464 0ustar00usergroup00000000000000# kate: hl INI Files [general] hitEnterToExit = no # [gametags] # game directory name under steamapps = gametag # the gametag is used to determine which game is running on which servers, # in order to choose the correct maps folder where to put maps. [gametags] counter-strike = cstrike counter-strike source = cssource day of defeat source = dodsource half-life 2 deathmatch = hl2mp team fortress 2 = tf2 Empires = empires FortressForever = f4ever hidden = hidden # [mapdirs] # game directory name under steamapps = mapdir # these list the map directories under the game folder. [mapdirs] counter-strike = cstrike/maps counter-strike source = cstrike/maps day of defeat source = dod/maps half-life 2 deathmatch = hl2mp/maps team fortress 2 = tf/maps Empires = maps FortressForever = maps hidden = maps [server] # the URL to connect to. baseurl = https://funzt-halt.net:51968/ [registry] # where to get the Steam installation path from keypath = Software\Valve\Steam keyname = SteamPath [steam] # this path is used if the Windows Registry cannot be accessed and on other OSs path = /media/daten/Spiele/Steam [auth] # the authentication data used to login on the server. username = ukah1eiXOox9paku password = raaW1ahwFaesie0O maptransfer/client/setup_py2exe.py0000644000000000000000000000121711370504716020033 0ustar00usergroup00000000000000# -*- coding: utf-8 -*- """ Distutils script for use with py2exe in order to build a distributable ZIP package containing everything needed to run the MapTransfer client. Copyright © 2009-2010, Michael "Svedrin" Ziegler """ from distutils.core import setup import py2exe setup( version = "0.3", description = "Uploads maps from local Steam accounts to a gameserver. (Especially useful for lazy admins.)", name = "maptransfer", console = [ { "script": "client.py" } ], data_files = [ 'maptransfer.example.cfg', 'client_de.qm' ], options = { "py2exe": { "includes":["sip"] } } ) maptransfer/server/server.example.cfg0000644000000000000000000000202311370504716020472 0ustar00usergroup00000000000000# kate: hl INI Files # [general] # address = space-separated list of addresses to bind to (both IPv4 or IPv6) # port = the port number to bind to # sslcert = the path of the certificate file # sslkey = the path of the private key file [general] address = port = 51968 sslcert = /cert/funzthalt_net.cert sslkey = /cert/funzthalt_net.key # [:servertag] # game = gametag # name = name to show in the client # mapdir = space-separated list of map dirs to upload maps to. can be enclosed in "" or '' to contain spaces. [:war] game = cssource name = CS:S Warserver mapdir = /home/svedrin/srcds/war/cstrike/maps /home/svedrin/srcds/pub/cstrike/maps [:emp] game = empires name = EmpiresMod mapdir = /home/svedrin/srcds/empires/empires/maps [:tf2] game = tf2 name = Team Fortress 2 mapdir = /home/svedrin/srcds/tf2/orangebox/tf/maps [:dod] game = dod name = Day of Defeat: Source mapdir = /home/svedrin/srcds/dod/dod/maps [users] # username = password ukah1eiXOox9paku = raaW1ahwFaesie0O maptransfer/server/server.py0000755000000000000000000002545111370504716016746 0ustar00usergroup00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ MapTransfer (Server) -- upload/download maps to/from a VALVe game server Copyright © 2009-2010, Michael "Svedrin" Ziegler """ import os import re import base64 import bz2 import shutil import socket import threading import time from SimpleHTTPServer import SimpleHTTPRequestHandler from BaseHTTPServer import HTTPServer from ConfigParser import ConfigParser from SocketServer import ThreadingMixIn from os.path import exists from OpenSSL import SSL from shlox import shlox def link_file( src, dest ): """ Link a file, after checking the file exists and the destination does not. """ if exists( src ) and not exists( dest ): os.link( src, dest ) class Conf( object, ConfigParser ): """ Context file processor that eases access to certain fields. """ def __init__( self, filename ): ConfigParser.__init__( self ) self.filename = filename self.read( filename ) def get_maps_dir( self, server, num = 0 ): """ Get a map directory entry for the given server. """ mdirs = list( shlox( self.gs( server, "mapdir" ) ) ) return mdirs[num] def get_maps_dir_list( self, server, num = 0 ): """ Get a list of map directories for the given server. """ mdirs = list( shlox( self.gs( server, "mapdir" ) ) ) if num == 0: return mdirs else: return mdirs[num:] def has_server( self, server ): """ Check if a section for this game server can be found. """ return self.has_section( ':'+server ) def gs( self, server, field ): """ Get a field's value for the given game server. """ return self.get( ':'+server, field ) known_servers = property( lambda self: [ ident[1:] for ident in self.sections() if ident[0] == ':' ], doc="A list of all known server identifiers." ) class SecureHTTPRequestHandler( SimpleHTTPRequestHandler ): """ Extends the SimpleHTTPRequestHandler to be used with a SecureHTTPServer. """ def setup(self): self.connection = self.request self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) class MapHTTPHandler( SecureHTTPRequestHandler ): """ Handles incoming HTTP requests. """ pathregex = re.compile( '/[\w\d]+/[\w\d_-]+\.[\w\d]+' ) def check_auth( self ): """ Check if the client is authenticated and authorized for access. """ if "authorization" not in self.headers: self.send_response( 401, "Unauthorized." ) self.send_header( "WWW-Authenticate", 'Basic realm="MapTransfer"' ) self.end_headers() self.wfile.write( "Please authenticate yourself." ) return False else: authname, authpass = base64.b64decode( self.headers["authorization"] ).split(':') # check for unknown user or wrong password if not MapHTTPHandler.conf.has_option( "users", authname ) or \ MapHTTPHandler.conf.get( "users", authname ) != authpass: self.send_response( 401, "Unauthorized." ) self.send_header( "WWW-Authenticate", 'Basic realm="MapTransfer"' ) self.end_headers() self.wfile.write( "Please authenticate yourself." ) return False else: return True def do_GET( self ): """ Handle a GET request: a client wants to download a file from us. """ if self.path == "/": # send server list self.send_response( 200, "OK" ) self.end_headers() for serv in MapHTTPHandler.conf.known_servers: self.wfile.write( "%s:%s:%s\r\n" % ( serv, # server identifier MapHTTPHandler.conf.gs( serv, "game" ), # gametag MapHTTPHandler.conf.gs( serv, "name" ) # human-readable name ) ) # path: /ident - check if ident is a known identifier, if so send map list elif self.path[0] == '/' and MapHTTPHandler.conf.has_server( self.path[1:] ): basedir = MapHTTPHandler.conf.get_maps_dir( self.path[1:] ) files = os.listdir( basedir ) fileslist = dict() for thisfile in files: if not os.path.isfile( "%s/%s" % ( basedir, thisfile ) ): continue base, ext = thisfile.split( '.', 1 ) if base not in fileslist: fileslist[base] = [ext] else: fileslist[base].append( ext ) self.send_response( 200, "OK" ) self.end_headers() for mapname in fileslist: if "bsp" in fileslist[mapname]: self.wfile.write( "%s:%s\n" % ( mapname, ' '.join( fileslist[mapname] ) ) ) # This is probably a path /srv/mapname.something, and probably one that we know elif MapHTTPHandler.pathregex.match( self.path ): _, srvpath, filename = self.path.split( "/", 2 ) if MapHTTPHandler.conf.has_server( srvpath ) and \ os.path.isfile( "%s/%s" % ( MapHTTPHandler.conf.get_maps_dir( srvpath ), filename ) ): # path is valid, server is known and file exists: send the file self.send_response( 200, "OK" ) self.send_header( "Content-Type", "application/octet-stream" ) self.end_headers() diskfd = open( "%s/%s" % ( MapHTTPHandler.conf.get_maps_dir( srvpath ), filename ), 'rb' ) try: shutil.copyfileobj( diskfd, self.wfile ) finally: diskfd.close() else: self.send_response( 404, "File not found." ) self.end_headers() self.wfile.write( "File not found. Searched for Server '%s' and Map '%s'." % ( srvpath, filename ) ) # Else: dunno. else: self.send_response( 404, "File not found." ) self.end_headers() self.wfile.write( "File not found." ) def do_PUT( self ): """ Handle a PUT request: a client wants to upload a file to us. """ if not MapHTTPHandler.pathregex.match( self.path ): self.send_response( 400, "Bad Request." ) self.end_headers() self.wfile.write( "You tried to PUT the main path of a server or a server itself, but you " "can only upload map files as everything else is configured statically." ) return _, srvpath, filename = self.path.split( "/", 2 ) if not MapHTTPHandler.conf.has_server( srvpath ): self.send_response( 404, "No such server: '%s'." % srvpath ) self.end_headers() self.wfile.write( "The specified server '%s' does not exist." % srvpath ) return mdir = MapHTTPHandler.conf.get_maps_dir( srvpath ) fullpath = "%s/%s" % ( mdir, filename ) if os.path.commonprefix( [ fullpath, mdir ] ) != mdir: # due to the regex above, this sort of can't possibly fail - but you never know. self.send_response( 403, "Forbidden." ) self.end_headers() self.wfile.write( "What are you doing?" ) return if os.path.isfile( fullpath ): self.send_response( 409, "Map exists: '%s'." % filename ) self.end_headers() self.wfile.write("The specified map '%s' already exists on the server and therefore won't be accepted." % filename) return if not self.check_auth(): return # check the ending - we only accept nav or txt or res files if a bsp exists, and never accept anything else base, ext = filename.split( '.', 1 ) if ext not in ( 'bsp', 'bsp.bz2' ): if not ( ext in ( 'nav', 'res', 'txt' ) and \ os.path.isfile( "%s/%s.bsp" % ( MapHTTPHandler.conf.get_maps_dir( srvpath ), base ) ) ): self.send_response( 403, "Forbidden." % filename ) self.end_headers() self.wfile.write( "Either this is not a map file, or you are trying to upload resources before the actual map." ) return print "File upload accepted." # Request seems fine, let's upload the map! if not "x-dry-run" in self.headers: basepath = "%s/%s" % ( mdir, base ) rfile_content = self.rfile.read( int( self.headers['content-length'] ) ) print "File upload completed (read %d bytes), processing..." % len( rfile_content ) if not rfile_content: self.send_response( 400, "Bad Request." ) self.end_headers() self.wfile.write( "Where's the data?" ) return diskfd = open( fullpath, 'wb' ) try: diskfd.write( rfile_content ) finally: diskfd.close() # We were served one file, but we want to have both a bsp and a bsp.bz2. if ext == "bsp.bz2": bsp_fd = open( "%s.bsp" % basepath, 'wb' ) bsp_fd.write( bz2.decompress( rfile_content ) ) bsp_fd.close() elif ext == "bsp": bz2_fd = open( "%s.bsp.bz2" % basepath, 'wb' ) bz2_fd.write( bz2.compress( rfile_content ) ) bz2_fd.close() # If there's more than one dir in the tuple, hardlink the Map to all other dirs for thedir in MapHTTPHandler.conf.get_maps_dir_list( srvpath, 1 ): link_file( fullpath, "%s/%s" % ( thedir, filename ) ) if ext == "bsp.bz2": link_file( "%s.bsp" % basepath, "%s/%s.bsp" % ( thedir, base ) ) elif ext == "bsp": link_file( "%s.bsp.bz2" % basepath, "%s/%s.bsp.bz2" % ( thedir, base ) ) self.send_response( 201, "Created." ) self.send_header( "Location", "/%s/%s.bsp.bz2" % ( srvpath, base ) ) self.end_headers() self.wfile.write( "Map uploaded successfully." ) class SecureHTTPServer( HTTPServer ): """ HTTPServer extension that provides SSL security. See . """ def __init__( self, server_address, handler_class ): HTTPServer.__init__( self, server_address, handler_class ) ctx = SSL.Context( SSL.SSLv23_METHOD ) ctx.use_privatekey_file( handler_class.conf.get( "general", "sslkey" ) ) ctx.use_certificate_file( handler_class.conf.get( "general", "sslcert" ) ) self.socket = SSL.Connection( ctx, socket.socket(self.address_family, self.socket_type) ) self.server_bind() self.server_activate() class ThreadedHTTPServer( ThreadingMixIn, SecureHTTPServer ): """ Process each query in a separate thread, to allow serving multiple requests at once. """ pass class IPv6ThreadedHTTPServer( ThreadedHTTPServer ): """ Bind on an IPv6 socket. """ address_family = socket.AF_INET6 if __name__ == '__main__': from sys import argv if len( argv ) > 1: cfg = argv[1] else: cfg = "server.cfg" if not exists( cfg ): print "The configuration file '%s' does not exist!" % cfg import sys sys.exit(1) MapHTTPHandler.conf = Conf( cfg ) address = MapHTTPHandler.conf.get( "general", "address" ) port = MapHTTPHandler.conf.getint( "general", "port" ) if not address: address = ':: 0.0.0.0' servers = { socket.AF_INET: ThreadedHTTPServer, socket.AF_INET6: IPv6ThreadedHTTPServer, } threads = [] for addrstr in address.split(' '): (family, socktype, proto, canonname, sockaddr) = socket.getaddrinfo( addrstr, port, 0, socket.SOCK_STREAM )[0] httpd = servers[family]( sockaddr, MapHTTPHandler ) thr = threading.Thread( target=httpd.serve_forever, name=("Servant for %s" % sockaddr[0]) ) thr.servant = httpd threads.append(thr) thr.start() try: while(True): time.sleep(999999) except KeyboardInterrupt: print "Shutting down." for thr in threads: if hasattr( thr.servant, "shutdown" ): thr.servant.shutdown() else: print "Can't shutdown the servant. You might want to use Python >=2.6. Hit ^\ to kill me." thr.join() maptransfer/server/shlox.py0000644000000000000000000000274011370504716016566 0ustar00usergroup00000000000000# -*- coding: utf-8 -*- """ Re-Implementation of Python's shlex.split(), because shlex can't cope with the input being Unicode. Copyright (C) 2009, Michael "Svedrin" Ziegler """ def shlox( line, escape='\\', comment='#', sep=(' ', '\t', '\r', '\n' ) ): ST_NORMAL, ST_ESCAPE, ST_SINGLE_QUOTED, ST_DOUBLE_QUOTED, ST_DOUBLE_ESCAPE = range(5) state = ST_NORMAL word = '' empty = True for char in line: if state == ST_NORMAL: if char == escape: state = ST_ESCAPE elif char == '"': empty = False state = ST_DOUBLE_QUOTED elif char == "'": empty = False state = ST_SINGLE_QUOTED elif char == comment: if empty: raise StopIteration else: word += char elif char in sep: if not empty: yield word empty = True word = '' else: empty = False word += char elif state == ST_ESCAPE: word += char state = ST_NORMAL elif state == ST_SINGLE_QUOTED: if char == "'": state = ST_NORMAL else: word += char elif state == ST_DOUBLE_QUOTED: if char == escape: state = ST_DOUBLE_ESCAPE elif char == '"': state = ST_NORMAL else: word += char elif state == ST_DOUBLE_ESCAPE: if char in ( escape, comment, '"', "'" ) + sep: word += char else: word += '\\' + char state = ST_DOUBLE_QUOTED if state != ST_NORMAL: raise ValueError( "Unclosed quote or \\ at end of line." ) elif not empty: yield word