Scripts:Enhanced rotate demo

From BF2 Technical Information Wiki
Jump to navigation Jump to search

Enhancements

 July 14, 2005
Enhanced rotate_demo.py
by: Dave Lawrence  [email protected]
Includes updates by: dackz
Changes
- Fixed apparent issue where FTP credentials require use of '@' (at least in Win)
- Added grossly verbose error logging for troubleshooting FTP problems
- Added cfg option "verbose_log" to enable verbose logging
- Added verbose log file "rotate_demo_log.txt"
  * For console debugging, set verbose_log=0  to log to the console
- Incorporated local file mods by dackz
Usage:
- In Windows it is recommended to use the compiled version of this tool
- In ServerSettings.con you would use something like the following
-        sv.autoDemoHook "adminutils/demo/rotate_demo.exe"
- If you did not receive the exe version of this script, you can compile by:
   * Download ActivePython v2.4       
   * Install ActivePython
   * Download py2exe for Python v2.4!
   * Install py2exe
   * Place the Enhanced rotate_demo.py in AdminUtils\demo
   * From the cmd prompt (with python in your PATH), run:
       python setup.py py2exe
   * py2exe should execute and create a directory: dist
   * AdminUtils\demo\dist contains rotate_demo.exe

Downloads http://activestate.com http://starship.python.net/crew/theller/py2exe

Installation

  1. Browse to adminutils/demo/ in your bf2 server directory
  2. Backup rotate_demo.py. (cp rotate_demo.py rotate_demo.py.bak.)
  3. Download rotate_demo.py or copy the below text into a file manually.
  4. If necessary, download the exe and locate in the same place

Operation

General

By default, a log file called rotate_demo_log.txt will created that logs the actions from the prior execution. When there is an error, you can view each operation that completed prio to the error as well as the eeor itself in this file. In addition, rotate_demo_err.txt still clumsily records the same info it did before.

If you are having problems getting the demo moved the appropriate place, I strongly recommend that you run the script manualy from the command line, and outside of the game. The script takes one parameter which is the filename of the demo file. This filename can be of any file. IF you want to check the rotation features, just make sure the test files end with ".bf2demo" for example

cd /games/bf2
touch test1.bf2demo
python AdminUtils/demo/rotate_demo.py test1.bf2demo

It is *much* easier to debug this way.

Win BF2 and FTP

For some reason I cannot get the rotate_demo.py script to execute properly under a Windows BF2 server using FTP with an FTP username that use a "@". A popular convention on at least cPanel web hosters is to include the server domain name along with the username, separate with "@". For example:

Host: files.myhost.com
User: [email protected]
 Pwd: blahblah

With a username as shown above, under Windows, the BF2 server is just not parsing the username correctly and the FTP fails. I have no idea why, although it must have something to do with the BF2 version Python. In any case, if you are using Windows, and you have an FTP username like this, I recommend using the compiled version of this script, rotate_demo.exe. This will work.

Code

Downloadable

These urls are out of date and no longer available

The source http://files.fragnastika.net/bf2_rotate_demo/Enhanced/rotate_demo.py

The Win exe version http://files.fragnastika.net/bf2_rotate_demo/Enhanced/rotate_demo.exe

Readable

#! /usr/bin/python
#
# rotate_demo.py: simple file-rotation and index updating script
#
# Requires Python 2.3 or newer.
#
# Theory of operation:
# When automatic demo recording is enabled in the BF2 dedicated server it will
# call a hook program (such as this) when a new demo file is ready for
# publishing. The server will wait for the hook program to complete before
# notifying connected clients of the URL the demo can be downloaded from. It is
# therefore important that all work is done in a blocking manner in this
# program, or clients might try to download demos that aren't in place on the
# web server yet.
#
# Copyright (c)2004 Digital Illusions CE AB
# Author: Andreas Fredriksson

#
# July 14, 2005
# Enhanced rotate_demo.py
# by: Dave Lawrence  [email protected]
# Includes updates by: dackz
#
# Changes
# - Fixed apparent issue where FTP credentials require use of '@'
# - Added grossly verbose error logging for troubleshooting FTP problems
# - Added cfg option "verbose_log" to enable verbose logging
# - Added verbose log file "rotate_demo_log.txt"
#   * For console debugging, set verbose_log=0  to log to the console
# - Incorporated local file mods by dackz
#
# Usage:
# - In Windows it is recommended to use the compiled version of this tool
# - In ServerSettings.con you would use something like the following
#        sv.autoDemoHook "adminutils/demo/rotate_demo.exe"
# - If you did not receive the exe version of this script, you can compile by:
#        * Download ActivePython v2.41 from http://activestate.com
#        * Install ActivePython
#        * Download py2exe from http://starship.python.net/crew/theller/py2exe  VERSION 2.4!
#        * Install py2exe
#         * Place the Enhanced rotate_demo.py in AdminUtils\demo
#        * From the cmd prompt (with python in your PATH), run:
#            python setup.py py2exe
#        * py2exe should execute and create a directory: dist
#        * AdminUtils\demo\dist contains rotate_demo.exe
#

import os
import sys
import shutil

# for debugging a hack like this might be useful since stdout and stderr are
# discarded when the script is run
class writer:
    def __init__(self):
        self.stream = open('rotate_demo_log.txt', 'w')
    def write(self, str):
        self.stream.write(str)

# helper function to create directories as needed -- this doesn't care about
# umask or permissions in general so consider this a starting point
def ensure_exists(path):
    try:
        os.stat(path)
    except:
        try:
            os.makedirs(path)
        except:
            pass

# set some sane defaults
options = {
    'use_ftp':'0',
    'ftp_server':'',
    'ftp_target_dir':'',
    'ftp_user':None,
    'ftp_password':None,
    'target_root': 'webroot',
    'file_limit': '10',
    'verbose_log': '1',
}

# parse the config file, if it's there
print "Importing cfg\n"
try:
    config = open('rotate_demo.cfg', 'rt')
    for line_ in config:
        line = line_.strip()
        if len(line) == 0 or line.startswith('#'): continue
        try:
            key, value = line.split('=')
            options[key.strip()] = value.strip()
        except ValueError, ex:
            print ex
except IOError:
    print "I/O Error on cfg read\n"
    pass

if options['verbose_log'] == '1':
    sys.stdout = writer()

# our first argument indicates the demo file which is ready to be moved
path = os.path.normpath(sys.argv[1].replace('"', ''))

# handle local file shuffling (web server on same host as bf2 server, or on network share)
if options['use_ftp'] == '0':
    print "Using local file shuffle\n"
    # this is our target directory (i.e. the download dir)
    target_demo_dir = os.path.join(options['target_root'], 'demos')

    # create the directory structure if it doesn't exist
    ensure_exists(options['target_root'])
    ensure_exists(os.path.join(options['target_root'], 'demos'))

    if os.path.abspath(os.path.dirname(path)) != os.path.abspath(target_demo_dir):
        try:
            # NOTE: this requires atleast Python 2.3
            print "moving '%s' to '%s'" % (path, target_demo_dir)
            shutil.move(path, target_demo_dir)
        except IOError:
            sys.exit(1)

    timestamped = []

    # get a list of .bf2demo files in the target dir (including our own file)
    for pf in filter(lambda x: x.endswith('.bf2demo'), os.listdir(target_demo_dir)):
        try:
            ppath = os.path.join(target_demo_dir, pf)
            os.chmod(ppath, 0644) # make web-readable
            timestamped.append((os.stat(ppath).st_mtime, ppath))
        except IOError:
            pass # don't let I/O errors stop us

    # sort the timestamped file list according to modification time
    # NOTE: this sort is reversed so that older files are at the end of the list
    def compare_times(f1, f2): return cmp(f2[0], f1[0]) # note reverse sort order
    timestamped.sort(compare_times)

    # delete the oldest files to meet the file limit
    file_limit = int(options['file_limit'])
    for timestamp, deletium in timestamped[file_limit:]:
        try:
            os.remove(deletium)
        except IOError:
            pass # file in use?

    # create the index file
    if 0: # dep: I guess this is superfluous
        idxf = open(os.path.join(options['target_root'], 'index.lst'), 'w')
        for timestamp, keptfile in timestamped[:file_limit]:
            fn = keptfile.split(os.sep)[-1]
            idxf.write('demos/%s\n' % (fn))
        idxf.close()

else: # use ftp
    print "Using FTP\n"
    try:
        import ftplib
        import re

        path.replace('\\\\', '\\')

        path = os.path.normpath(path).replace('\\', '/')

        fn = path
        idx = fn.rfind('/')
        if idx != -1: fn = fn[idx+1:]

        try:
            os.stat(path)
        except:
            # This shouldn't happen normally, only when testing from the cmd line
            print "ERROR- File does not exist: %s\nAborting\n" % (path)
            raise
       
        demof = open(path, 'rb')

        # set up ftp connection
        print "Connecting to:\n\tHost: %s\n\tUser: %s\n\t Pwd: %s\n" % (options['ftp_server'], options['ftp_user'], options['ftp_password'])
        try:
            ftp = ftplib.FTP(options['ftp_server'], options['ftp_user'], options['ftp_password'])
        except:
            print "FTP ERROR: Connect failed!"
            raise
        print "Connected ... \n"

        # change cwd
        print "Changing cwd to %s\n" % (options['ftp_target_dir'])
        try:
            ftp.cwd(options['ftp_target_dir'])
        except:
            print "FTP ERROR: failed to cwd => %s" % (options['ftp_target_dir'])
            raise

        file_limit = int(options['file_limit'])

        print "Retrieving file listing from FTP ...\n"
        try:
            files = ftp.nlst()
        except Exception:
            files = []

        files = filter(lambda x: x.endswith('.bf2demo'), files)
        files.sort()

        for file in files: print "\t" + file

        # store the new file
        print "\nStoring new demo file: %s" % (fn)
        try:
            ftp.storbinary('STOR '+fn, demof)
        except:
            print "FTP ERROR: Upload failed!"
               
        print "\tUpload complete!"
        demof.close()

        # Remove the local file
        print "Removing local demo file %s" % (path)
        try:
            # delete local file
            os.unlink(path)
        except OSError:
            # couldn't unlink local file, what to do?
            print "ERROR- %s Could not be removed!" % (path)
            pass

        # handle remote rotation
        print "\nHandling rotation and removing old FTP demos: file limit = %s" % (file_limit)
        while len(files) + 1 > file_limit:
            # dep: nb: this relies on the data formatting in the bf2demo filenames
            # if you have other
            print '\tdeleting %s' % (files[0])
            try:
                ftp.delete(files[0])
            except:
                print "FTP ERROR: Unable to delete %s" % (files[0])
                pass
            del files[0]

        # bye bye
        ftp.quit()
        print "\nConnection closed"

    except Exception, detail:
        print "\n========================\nROTATE_DEMO ERROR!\n"
        import traceback
        log = open('rotate_demo_err.txt', 'w')
        ex = sys.exc_info()
        traceback.print_exception(ex[0], ex[1], ex[2], 16, log)
        print "%s\n%s\n%s\n%s\n%s" % (ex[0], ex[1], ex[2], 16, log)
        print "========================\n"
        log.write('\n')
        log.close()
        sys.exit(1)