<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://bf2tech.uturista.pt/index.php?action=history&amp;feed=atom&amp;title=Scripts%3AEnhanced_rotate_demo</id>
	<title>Scripts:Enhanced rotate demo - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://bf2tech.uturista.pt/index.php?action=history&amp;feed=atom&amp;title=Scripts%3AEnhanced_rotate_demo"/>
	<link rel="alternate" type="text/html" href="https://bf2tech.uturista.pt/index.php?title=Scripts:Enhanced_rotate_demo&amp;action=history"/>
	<updated>2026-08-06T01:15:03Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.40.1</generator>
	<entry>
		<id>https://bf2tech.uturista.pt/index.php?title=Scripts:Enhanced_rotate_demo&amp;diff=131&amp;oldid=prev</id>
		<title>Pireax: Created page with &quot;__TOC__  == Enhancements ==  &lt;pre&gt;  July 14, 2005 Enhanced rotate_demo.py by: Dave Lawrence  thrash@fragnastika.com Includes updates by: dackz &lt;/pre&gt;  &lt;pre&gt; Changes - Fixed ap...&quot;</title>
		<link rel="alternate" type="text/html" href="https://bf2tech.uturista.pt/index.php?title=Scripts:Enhanced_rotate_demo&amp;diff=131&amp;oldid=prev"/>
		<updated>2018-06-22T15:37:46Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;__TOC__  == Enhancements ==  &amp;lt;pre&amp;gt;  July 14, 2005 Enhanced rotate_demo.py by: Dave Lawrence  thrash@fragnastika.com Includes updates by: dackz &amp;lt;/pre&amp;gt;  &amp;lt;pre&amp;gt; Changes - Fixed ap...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
== Enhancements ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
 July 14, 2005&lt;br /&gt;
Enhanced rotate_demo.py&lt;br /&gt;
by: Dave Lawrence  thrash@fragnastika.com&lt;br /&gt;
Includes updates by: dackz&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Changes&lt;br /&gt;
- Fixed apparent issue where FTP credentials require use of &amp;#039;@&amp;#039; (at least in Win)&lt;br /&gt;
- Added grossly verbose error logging for troubleshooting FTP problems&lt;br /&gt;
- Added cfg option &amp;quot;verbose_log&amp;quot; to enable verbose logging&lt;br /&gt;
- Added verbose log file &amp;quot;rotate_demo_log.txt&amp;quot;&lt;br /&gt;
  * For console debugging, set verbose_log=0  to log to the console&lt;br /&gt;
- Incorporated local file mods by dackz&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Usage:&lt;br /&gt;
- In Windows it is recommended to use the compiled version of this tool&lt;br /&gt;
- In ServerSettings.con you would use something like the following&lt;br /&gt;
-        sv.autoDemoHook &amp;quot;adminutils/demo/rotate_demo.exe&amp;quot;&lt;br /&gt;
- If you did not receive the exe version of this script, you can compile by:&lt;br /&gt;
   * Download ActivePython v2.4       &lt;br /&gt;
   * Install ActivePython&lt;br /&gt;
   * Download py2exe for Python v2.4!&lt;br /&gt;
   * Install py2exe&lt;br /&gt;
   * Place the Enhanced rotate_demo.py in AdminUtils\demo&lt;br /&gt;
   * From the cmd prompt (with python in your PATH), run:&lt;br /&gt;
       python setup.py py2exe&lt;br /&gt;
   * py2exe should execute and create a directory: dist&lt;br /&gt;
   * AdminUtils\demo\dist contains rotate_demo.exe&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Downloads [http://activestate.com/ http://activestate.com] &amp;lt;s&amp;gt;[http://starship.python.net/crew/theller/py2exe http://starship.python.net/crew/theller/py2exe]&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Installation  ==&lt;br /&gt;
&lt;br /&gt;
# Browse to &amp;lt;code&amp;gt;adminutils/demo/&amp;lt;/code&amp;gt; in your bf2 server directory&lt;br /&gt;
# Backup &amp;lt;code&amp;gt;rotate_demo.py&amp;lt;/code&amp;gt;. (&amp;lt;code&amp;gt;cp rotate_demo.py rotate_demo.py.bak&amp;lt;/code&amp;gt;.)&lt;br /&gt;
# Download rotate_demo.py or copy the below text into a file manually.&lt;br /&gt;
# If necessary, download the exe and locate in the same place&lt;br /&gt;
&lt;br /&gt;
== Operation  ==&lt;br /&gt;
&lt;br /&gt;
=== General  ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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 &amp;quot;.bf2demo&amp;quot; for example&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /games/bf2&lt;br /&gt;
touch test1.bf2demo&lt;br /&gt;
python AdminUtils/demo/rotate_demo.py test1.bf2demo&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It is *much* easier to debug this way.&lt;br /&gt;
&lt;br /&gt;
=== Win BF2 and FTP  ===&lt;br /&gt;
&lt;br /&gt;
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 &amp;quot;@&amp;quot;. A popular convention on at least cPanel web hosters is to include the server domain name along with the username, separate with &amp;quot;@&amp;quot;. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Host: files.myhost.com&lt;br /&gt;
User: myhoster@myhost.com&lt;br /&gt;
 Pwd: blahblah&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Code  ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Downloadable  ===&lt;br /&gt;
&lt;br /&gt;
These urls are out of date and no longer available&lt;br /&gt;
&lt;br /&gt;
The source &amp;lt;s&amp;gt;[http://files.fragnastika.net/bf2_rotate_demo/Enhanced/rotate_demo.py http://files.fragnastika.net/bf2_rotate_demo/Enhanced/rotate_demo.py]&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Win exe version &amp;lt;s&amp;gt;[http://files.fragnastika.net/bf2_rotate_demo/Enhanced/rotate_demo.exe http://files.fragnastika.net/bf2_rotate_demo/Enhanced/rotate_demo.exe]&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Readable  ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /usr/bin/python&lt;br /&gt;
#&lt;br /&gt;
# rotate_demo.py: simple file-rotation and index updating script&lt;br /&gt;
#&lt;br /&gt;
# Requires Python 2.3 or newer.&lt;br /&gt;
#&lt;br /&gt;
# Theory of operation:&lt;br /&gt;
# When automatic demo recording is enabled in the BF2 dedicated server it will&lt;br /&gt;
# call a hook program (such as this) when a new demo file is ready for&lt;br /&gt;
# publishing. The server will wait for the hook program to complete before&lt;br /&gt;
# notifying connected clients of the URL the demo can be downloaded from. It is&lt;br /&gt;
# therefore important that all work is done in a blocking manner in this&lt;br /&gt;
# program, or clients might try to download demos that aren&amp;#039;t in place on the&lt;br /&gt;
# web server yet.&lt;br /&gt;
#&lt;br /&gt;
# Copyright (c)2004 Digital Illusions CE AB&lt;br /&gt;
# Author: Andreas Fredriksson&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# July 14, 2005&lt;br /&gt;
# Enhanced rotate_demo.py&lt;br /&gt;
# by: Dave Lawrence  thrash@fragnastika.com&lt;br /&gt;
# Includes updates by: dackz&lt;br /&gt;
#&lt;br /&gt;
# Changes&lt;br /&gt;
# - Fixed apparent issue where FTP credentials require use of &amp;#039;@&amp;#039;&lt;br /&gt;
# - Added grossly verbose error logging for troubleshooting FTP problems&lt;br /&gt;
# - Added cfg option &amp;quot;verbose_log&amp;quot; to enable verbose logging&lt;br /&gt;
# - Added verbose log file &amp;quot;rotate_demo_log.txt&amp;quot;&lt;br /&gt;
#   * For console debugging, set verbose_log=0  to log to the console&lt;br /&gt;
# - Incorporated local file mods by dackz&lt;br /&gt;
#&lt;br /&gt;
# Usage:&lt;br /&gt;
# - In Windows it is recommended to use the compiled version of this tool&lt;br /&gt;
# - In ServerSettings.con you would use something like the following&lt;br /&gt;
#        sv.autoDemoHook &amp;quot;adminutils/demo/rotate_demo.exe&amp;quot;&lt;br /&gt;
# - If you did not receive the exe version of this script, you can compile by:&lt;br /&gt;
#        * Download ActivePython v2.41 from http://activestate.com&lt;br /&gt;
#        * Install ActivePython&lt;br /&gt;
#        * Download py2exe from http://starship.python.net/crew/theller/py2exe  VERSION 2.4!&lt;br /&gt;
#        * Install py2exe&lt;br /&gt;
#         * Place the Enhanced rotate_demo.py in AdminUtils\demo&lt;br /&gt;
#        * From the cmd prompt (with python in your PATH), run:&lt;br /&gt;
#            python setup.py py2exe&lt;br /&gt;
#        * py2exe should execute and create a directory: dist&lt;br /&gt;
#        * AdminUtils\demo\dist contains rotate_demo.exe&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
import os&lt;br /&gt;
import sys&lt;br /&gt;
import shutil&lt;br /&gt;
&lt;br /&gt;
# for debugging a hack like this might be useful since stdout and stderr are&lt;br /&gt;
# discarded when the script is run&lt;br /&gt;
class writer:&lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        self.stream = open(&amp;#039;rotate_demo_log.txt&amp;#039;, &amp;#039;w&amp;#039;)&lt;br /&gt;
    def write(self, str):&lt;br /&gt;
        self.stream.write(str)&lt;br /&gt;
&lt;br /&gt;
# helper function to create directories as needed -- this doesn&amp;#039;t care about&lt;br /&gt;
# umask or permissions in general so consider this a starting point&lt;br /&gt;
def ensure_exists(path):&lt;br /&gt;
    try:&lt;br /&gt;
        os.stat(path)&lt;br /&gt;
    except:&lt;br /&gt;
        try:&lt;br /&gt;
            os.makedirs(path)&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
# set some sane defaults&lt;br /&gt;
options = {&lt;br /&gt;
    &amp;#039;use_ftp&amp;#039;:&amp;#039;0&amp;#039;,&lt;br /&gt;
    &amp;#039;ftp_server&amp;#039;:&amp;#039;&amp;#039;,&lt;br /&gt;
    &amp;#039;ftp_target_dir&amp;#039;:&amp;#039;&amp;#039;,&lt;br /&gt;
    &amp;#039;ftp_user&amp;#039;:None,&lt;br /&gt;
    &amp;#039;ftp_password&amp;#039;:None,&lt;br /&gt;
    &amp;#039;target_root&amp;#039;: &amp;#039;webroot&amp;#039;,&lt;br /&gt;
    &amp;#039;file_limit&amp;#039;: &amp;#039;10&amp;#039;,&lt;br /&gt;
    &amp;#039;verbose_log&amp;#039;: &amp;#039;1&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# parse the config file, if it&amp;#039;s there&lt;br /&gt;
print &amp;quot;Importing cfg\n&amp;quot;&lt;br /&gt;
try:&lt;br /&gt;
    config = open(&amp;#039;rotate_demo.cfg&amp;#039;, &amp;#039;rt&amp;#039;)&lt;br /&gt;
    for line_ in config:&lt;br /&gt;
        line = line_.strip()&lt;br /&gt;
        if len(line) == 0 or line.startswith(&amp;#039;#&amp;#039;): continue&lt;br /&gt;
        try:&lt;br /&gt;
            key, value = line.split(&amp;#039;=&amp;#039;)&lt;br /&gt;
            options[key.strip()] = value.strip()&lt;br /&gt;
        except ValueError, ex:&lt;br /&gt;
            print ex&lt;br /&gt;
except IOError:&lt;br /&gt;
    print &amp;quot;I/O Error on cfg read\n&amp;quot;&lt;br /&gt;
    pass&lt;br /&gt;
&lt;br /&gt;
if options[&amp;#039;verbose_log&amp;#039;] == &amp;#039;1&amp;#039;:&lt;br /&gt;
    sys.stdout = writer()&lt;br /&gt;
&lt;br /&gt;
# our first argument indicates the demo file which is ready to be moved&lt;br /&gt;
path = os.path.normpath(sys.argv[1].replace(&amp;#039;&amp;quot;&amp;#039;, &amp;#039;&amp;#039;))&lt;br /&gt;
&lt;br /&gt;
# handle local file shuffling (web server on same host as bf2 server, or on network share)&lt;br /&gt;
if options[&amp;#039;use_ftp&amp;#039;] == &amp;#039;0&amp;#039;:&lt;br /&gt;
    print &amp;quot;Using local file shuffle\n&amp;quot;&lt;br /&gt;
    # this is our target directory (i.e. the download dir)&lt;br /&gt;
    target_demo_dir = os.path.join(options[&amp;#039;target_root&amp;#039;], &amp;#039;demos&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
    # create the directory structure if it doesn&amp;#039;t exist&lt;br /&gt;
    ensure_exists(options[&amp;#039;target_root&amp;#039;])&lt;br /&gt;
    ensure_exists(os.path.join(options[&amp;#039;target_root&amp;#039;], &amp;#039;demos&amp;#039;))&lt;br /&gt;
&lt;br /&gt;
    if os.path.abspath(os.path.dirname(path)) != os.path.abspath(target_demo_dir):&lt;br /&gt;
        try:&lt;br /&gt;
            # NOTE: this requires atleast Python 2.3&lt;br /&gt;
            print &amp;quot;moving &amp;#039;%s&amp;#039; to &amp;#039;%s&amp;#039;&amp;quot; % (path, target_demo_dir)&lt;br /&gt;
            shutil.move(path, target_demo_dir)&lt;br /&gt;
        except IOError:&lt;br /&gt;
            sys.exit(1)&lt;br /&gt;
&lt;br /&gt;
    timestamped = []&lt;br /&gt;
&lt;br /&gt;
    # get a list of .bf2demo files in the target dir (including our own file)&lt;br /&gt;
    for pf in filter(lambda x: x.endswith(&amp;#039;.bf2demo&amp;#039;), os.listdir(target_demo_dir)):&lt;br /&gt;
        try:&lt;br /&gt;
            ppath = os.path.join(target_demo_dir, pf)&lt;br /&gt;
            os.chmod(ppath, 0644) # make web-readable&lt;br /&gt;
            timestamped.append((os.stat(ppath).st_mtime, ppath))&lt;br /&gt;
        except IOError:&lt;br /&gt;
            pass # don&amp;#039;t let I/O errors stop us&lt;br /&gt;
&lt;br /&gt;
    # sort the timestamped file list according to modification time&lt;br /&gt;
    # NOTE: this sort is reversed so that older files are at the end of the list&lt;br /&gt;
    def compare_times(f1, f2): return cmp(f2[0], f1[0]) # note reverse sort order&lt;br /&gt;
    timestamped.sort(compare_times)&lt;br /&gt;
&lt;br /&gt;
    # delete the oldest files to meet the file limit&lt;br /&gt;
    file_limit = int(options[&amp;#039;file_limit&amp;#039;])&lt;br /&gt;
    for timestamp, deletium in timestamped[file_limit:]:&lt;br /&gt;
        try:&lt;br /&gt;
            os.remove(deletium)&lt;br /&gt;
        except IOError:&lt;br /&gt;
            pass # file in use?&lt;br /&gt;
&lt;br /&gt;
    # create the index file&lt;br /&gt;
    if 0: # dep: I guess this is superfluous&lt;br /&gt;
        idxf = open(os.path.join(options[&amp;#039;target_root&amp;#039;], &amp;#039;index.lst&amp;#039;), &amp;#039;w&amp;#039;)&lt;br /&gt;
        for timestamp, keptfile in timestamped[:file_limit]:&lt;br /&gt;
            fn = keptfile.split(os.sep)[-1]&lt;br /&gt;
            idxf.write(&amp;#039;demos/%s\n&amp;#039; % (fn))&lt;br /&gt;
        idxf.close()&lt;br /&gt;
&lt;br /&gt;
else: # use ftp&lt;br /&gt;
    print &amp;quot;Using FTP\n&amp;quot;&lt;br /&gt;
    try:&lt;br /&gt;
        import ftplib&lt;br /&gt;
        import re&lt;br /&gt;
&lt;br /&gt;
        path.replace(&amp;#039;\\\\&amp;#039;, &amp;#039;\\&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
        path = os.path.normpath(path).replace(&amp;#039;\\&amp;#039;, &amp;#039;/&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
        fn = path&lt;br /&gt;
        idx = fn.rfind(&amp;#039;/&amp;#039;)&lt;br /&gt;
        if idx != -1: fn = fn[idx+1:]&lt;br /&gt;
&lt;br /&gt;
        try:&lt;br /&gt;
            os.stat(path)&lt;br /&gt;
        except:&lt;br /&gt;
            # This shouldn&amp;#039;t happen normally, only when testing from the cmd line&lt;br /&gt;
            print &amp;quot;ERROR- File does not exist: %s\nAborting\n&amp;quot; % (path)&lt;br /&gt;
            raise&lt;br /&gt;
       &lt;br /&gt;
        demof = open(path, &amp;#039;rb&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
        # set up ftp connection&lt;br /&gt;
        print &amp;quot;Connecting to:\n\tHost: %s\n\tUser: %s\n\t Pwd: %s\n&amp;quot; % (options[&amp;#039;ftp_server&amp;#039;], options[&amp;#039;ftp_user&amp;#039;], options[&amp;#039;ftp_password&amp;#039;])&lt;br /&gt;
        try:&lt;br /&gt;
            ftp = ftplib.FTP(options[&amp;#039;ftp_server&amp;#039;], options[&amp;#039;ftp_user&amp;#039;], options[&amp;#039;ftp_password&amp;#039;])&lt;br /&gt;
        except:&lt;br /&gt;
            print &amp;quot;FTP ERROR: Connect failed!&amp;quot;&lt;br /&gt;
            raise&lt;br /&gt;
        print &amp;quot;Connected ... \n&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        # change cwd&lt;br /&gt;
        print &amp;quot;Changing cwd to %s\n&amp;quot; % (options[&amp;#039;ftp_target_dir&amp;#039;])&lt;br /&gt;
        try:&lt;br /&gt;
            ftp.cwd(options[&amp;#039;ftp_target_dir&amp;#039;])&lt;br /&gt;
        except:&lt;br /&gt;
            print &amp;quot;FTP ERROR: failed to cwd =&amp;gt; %s&amp;quot; % (options[&amp;#039;ftp_target_dir&amp;#039;])&lt;br /&gt;
            raise&lt;br /&gt;
&lt;br /&gt;
        file_limit = int(options[&amp;#039;file_limit&amp;#039;])&lt;br /&gt;
&lt;br /&gt;
        print &amp;quot;Retrieving file listing from FTP ...\n&amp;quot;&lt;br /&gt;
        try:&lt;br /&gt;
            files = ftp.nlst()&lt;br /&gt;
        except Exception:&lt;br /&gt;
            files = []&lt;br /&gt;
&lt;br /&gt;
        files = filter(lambda x: x.endswith(&amp;#039;.bf2demo&amp;#039;), files)&lt;br /&gt;
        files.sort()&lt;br /&gt;
&lt;br /&gt;
        for file in files: print &amp;quot;\t&amp;quot; + file&lt;br /&gt;
&lt;br /&gt;
        # store the new file&lt;br /&gt;
        print &amp;quot;\nStoring new demo file: %s&amp;quot; % (fn)&lt;br /&gt;
        try:&lt;br /&gt;
            ftp.storbinary(&amp;#039;STOR &amp;#039;+fn, demof)&lt;br /&gt;
        except:&lt;br /&gt;
            print &amp;quot;FTP ERROR: Upload failed!&amp;quot;&lt;br /&gt;
               &lt;br /&gt;
        print &amp;quot;\tUpload complete!&amp;quot;&lt;br /&gt;
        demof.close()&lt;br /&gt;
&lt;br /&gt;
        # Remove the local file&lt;br /&gt;
        print &amp;quot;Removing local demo file %s&amp;quot; % (path)&lt;br /&gt;
        try:&lt;br /&gt;
            # delete local file&lt;br /&gt;
            os.unlink(path)&lt;br /&gt;
        except OSError:&lt;br /&gt;
            # couldn&amp;#039;t unlink local file, what to do?&lt;br /&gt;
            print &amp;quot;ERROR- %s Could not be removed!&amp;quot; % (path)&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
        # handle remote rotation&lt;br /&gt;
        print &amp;quot;\nHandling rotation and removing old FTP demos: file limit = %s&amp;quot; % (file_limit)&lt;br /&gt;
        while len(files) + 1 &amp;gt; file_limit:&lt;br /&gt;
            # dep: nb: this relies on the data formatting in the bf2demo filenames&lt;br /&gt;
            # if you have other&lt;br /&gt;
            print &amp;#039;\tdeleting %s&amp;#039; % (files[0])&lt;br /&gt;
            try:&lt;br /&gt;
                ftp.delete(files[0])&lt;br /&gt;
            except:&lt;br /&gt;
                print &amp;quot;FTP ERROR: Unable to delete %s&amp;quot; % (files[0])&lt;br /&gt;
                pass&lt;br /&gt;
            del files[0]&lt;br /&gt;
&lt;br /&gt;
        # bye bye&lt;br /&gt;
        ftp.quit()&lt;br /&gt;
        print &amp;quot;\nConnection closed&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    except Exception, detail:&lt;br /&gt;
        print &amp;quot;\n========================\nROTATE_DEMO ERROR!\n&amp;quot;&lt;br /&gt;
        import traceback&lt;br /&gt;
        log = open(&amp;#039;rotate_demo_err.txt&amp;#039;, &amp;#039;w&amp;#039;)&lt;br /&gt;
        ex = sys.exc_info()&lt;br /&gt;
        traceback.print_exception(ex[0], ex[1], ex[2], 16, log)&lt;br /&gt;
        print &amp;quot;%s\n%s\n%s\n%s\n%s&amp;quot; % (ex[0], ex[1], ex[2], 16, log)&lt;br /&gt;
        print &amp;quot;========================\n&amp;quot;&lt;br /&gt;
        log.write(&amp;#039;\n&amp;#039;)&lt;br /&gt;
        log.close()&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Pireax</name></author>
	</entry>
</feed>