# RookChat
#
# Copyright (c) 2002 by Samuel Stoddard <rookchat@rinkworks.com>
#
# This file is part of RookChat.
#
# RookChat 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.
#
# RookChat 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
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
##############################################################################
# Turn this on if you're experiencing long locking times and want to output
# timing information to a debug file. Set log_lock_threshold to nonzero to
# suppress locking lock times less than the threshold.
#
log_lock_times = 0
log_lock_threshold = 0.1
##############################################################################
from rc_install_config import rookchat_data_directory, file_permissions
# Directories. (Must end in a slash.)
#
lang_directory = "language/"
config_directory = "config/"
smileys_directory = "smileys/"
botdata_directory = "botdata/"
botimages_directory = "botimages/"
botimages_temp_directory = "botimages/temp/"
rooms_directory = rookchat_data_directory + "rooms/"
# Straight filenames.
#
script_filename = "index.cgi"
streamer_filename = "stream.cgi"
accounts_filename = rookchat_data_directory + "users/accounts"
banips_filename = rookchat_data_directory + "users/banips"
cbanips_filename = rookchat_data_directory + "users/cbanips"
cban_filename = rookchat_data_directory + "users/cban"
lobby_filename = rookchat_data_directory + "users/lobby"
memolists_filename = rookchat_data_directory + "users/memolists"
expired_users_filename = rookchat_data_directory + "logs/expired.log"
# Filenames that require % username.
#
options_filename = rookchat_data_directory + "users/options/%s"
memos_filename = rookchat_data_directory + "users/memos/%s"
aliases_filename = rookchat_data_directory + "users/aliases/%s"
macros_filename = rookchat_data_directory + "users/macros/%s"
# Filenames that require % room number.
#
roommode_filename = rookchat_data_directory + "rooms/%d/roomsettings"
topic_filename = rookchat_data_directory + "rooms/%d/topic"
invited_filename = rookchat_data_directory + "rooms/%d/invited"
evicted_filename = rookchat_data_directory + "rooms/%d/evicted"
evictedips_filename = rookchat_data_directory + "rooms/%d/evictedips"
voiced_filename = rookchat_data_directory + "rooms/%d/voiced"
userlist_filename = rookchat_data_directory + "rooms/%d/userlist"
messages_filename = rookchat_data_directory + "rooms/%d/messages"
bots_filename = rookchat_data_directory + "rooms/%d/bots"
transcripts_filename = rookchat_data_directory + "rooms/%d/transcripts"
log_filename = rookchat_data_directory + "logs/rclog%d.html"
# Filenames that require % username, transcript name.
# Note: All transcripts must be located in the same directory,
# or TransDirMethods will not work.
#
transcript_filename = rookchat_data_directory + "users/transcripts/%s.%s"
# Filenames that require % memolist.
#
subscribers_filename = rookchat_data_directory + "users/subscribers/%s"
# Filenames that require % bot name.
#
botscript_filename = "bots/%s.cgi"
botlogin_filename = "bots/%s.login"
# Filenames that require % bot name, tag.
#
botpersistentdata_filename = rookchat_data_directory + "users/bots/%s/%s"
# Filenames that require % room number, bot name.
#
botstate_filename = rookchat_data_directory + "rooms/%d/botstates/%s"
botlog_filename = rookchat_data_directory + "rooms/%d/botlogs/%s"
# Method for opening files that don't exist that might be in directories that
# don't exist. Creates the requested file (and directories) if they don't
# exist, then returns an open file handle.
#
def rcopen(name,mode):
try:
f = open(name,mode)
set_file_permissions(name)
return f
except:
pass
import os
dirname = os.path.dirname(name)
# Sometimes os.path.exists is weird and throws an exception when the
# file doesn't exist instead of simply returning 0.
#
make_directories(dirname)
# Do the same dance when checking for the existence of the file.
#
try:
no_file = not os.path.exists(name)
except:
no_file = 1
if no_file:
if mode != 'w':
open(name,"w").close()
# If it fails this time, let the exception fly.
#
f = open(name,mode)
set_file_permissions(name)
return f
# Creates the given file if it doesn't exist, then makes it empty, then
# returns a locked file handle -- it MUST be unlocked and closed after
# use.
#
# Note: file is opened in 'r+' mode and truncated, rather than being opened
# in 'w' mode, because opening in 'w' mode bypasses the file locking.
#
def rcopen_empty(filename):
file = rcopen(filename,'r+')
set_file_permissions(filename)
lock(file)
file.truncate()
return file
# Returns the list of files in a given directory. Creates the directory
# if it doesn't exist.
#
def rclistdir(dirname):
import os
try:
return os.listdir(dirname)
except:
make_directories(dirname)
return []
# Construct a hierarchy of directories with the appropriate permissions.
#
def make_directories(dirname):
import os
try:
no_dir = not os.path.exists(dirname)
except:
no_dir = 1
if no_dir:
from rc_install_config import file_permissions
if file_permissions == 0:
perms = 0700
else:
perms = 0777
old_umask = os.umask(0)
os.makedirs(dirname,perms)
os.umask(old_umask)
# Sets the permissions on a new file.
#
def set_file_permissions(filename):
import os
from rc_install_config import file_permissions
if file_permissions == 0:
os.chmod(filename,0600)
elif file_permissions == 1:
os.chmod(filename,0644)
elif file_permissions == 2:
os.chmod(filename,0666)
# Unlock a file.
#
def unlock(file):
# Locking a file is different, depending on what operation system
# we are running on. Try it the UNIX way first, and if an error
# occurs, try it the Windows way. One of them should work, because
# hopefully we already ran the lock() function successfully.
#
try:
from fcntl import lockf, flock, LOCK_UN
except:
from win32file import UnlockFileEx, _get_osfhandle
from pywintypes import OVERLAPPED
os_file_handle = _get_osfhandle(file.fileno())
UnlockFileEx(os_file_handle,-1,-1,OVERLAPPED())
else:
# Try it a couple different UNIX ways, just to be sure.
#
try:
lockf(file.fileno(),LOCK_UN)
except:
flock(file,LOCK_UN)
# Lock a file.
#
def lock(file):
FileLockData(file)
# Locking a file is different, depending on what operating system
# we are running on. Try it the UNIX way first, and if an error
# occurs, try it the Windows way. If both fail, print an error
# message and raise an exception.
#
try:
from signal import signal, alarm, SIGALRM
from fcntl import lockf, flock, LOCK_EX, LOCK_SH
except:
import os
if os.name == 'posix':
import display
display.plain_header()
display.html_begin(None)
print "<em>RookChat</em> should be supported by this"
print "operating system, but it encountered an error"
print "trying to import the signal library.<p>"
display.html_foot()
raise
else:
try:
from threading import Timer
from win32file import LockFileEx, _get_osfhandle
from win32con import LOCKFILE_EXCLUSIVE_LOCK as LOCK_EX
from pywintypes import OVERLAPPED
except:
import display
display.plain_header()
display.html_begin(None)
if os.name == 'nt':
print "You need the win32all Python package installed"
print "for <em>RookChat</em> to work on a Windows"
print "machine. You can download the win32all"
print "package from"
print "<a href='http://www.python.org/windows/win32all/' target='_blank'>http://www.python.org/windows/win32all/</a>."
else:
print "<em>RookChat</em> is not supported on this"
print "operating system."
display.html_foot()
raise
else:
flock_timer = Timer(20.0,__flock_timeout)
flock_timer.start()
os_file_handle = _get_osfhandle(file.fileno())
LockFileEx(os_file_handle,LOCK_EX,-1,-1,OVERLAPPED())
flock_timer.cancel()
else:
signal(SIGALRM,__flock_timeout)
alarm(20)
if log_lock_times:
from time import time
lock_time = time()
# Try it a couple different UNIX ways, just to be sure.
#
try:
if file.mode == 'r':
lockf(file.fileno(),LOCK_SH)
else:
lockf(file.fileno(),LOCK_EX)
except:
flock(file,LOCK_EX)
if log_lock_times:
end_lock_time = time()
if end_lock_time - lock_time >= log_lock_threshold:
lock_log = open('lock.log','a')
lock_log.write("Waited %.1fs on lock for %s, mode '%s'.\n" % ((end_lock_time - lock_time), file.name, file.mode))
lock_log.close()
alarm(0)
# This class is used to remember what file has been locked, so that,
# if a timeout occurs, we know where it happened.
#
class FileLockData:
file = None
def __init__(self,file = None):
if file:
FileLockData.file = file
# If there's a timeout on locking a particular file, choke.
#
def __flock_timeout(sig,frame):
from time import time
from query import Query
q = Query()
from rc_install_config import rookchat_data_directory
exceptions_directory = rookchat_data_directory + "exceptions/"
file = open(exceptions_directory + "lock.log","a")
file.write(str(long(time())) + " timeout on " \
+ str(FileLockData().file.name) \
+ " with request: " + q.url([]) + "\n")
file.close()
class FlockTimeout(Exception):
pass
raise FlockTimeout