#! /usr/bin/env python

## Copyright 2001 by LivingLogic AG, Bayreuth, Germany.
## Copyright 2001 by Walter Dörwald
##
## All Rights Reserved
##
## Permission to use, copy, modify, and distribute this software and its documentation
## for any purpose and without fee is hereby granted, provided that the above copyright
## notice appears in all copies and that both that copyright notice and this permission
## notice appear in supporting documentation, and that the name of LivingLogic AG or
## the author not be used in advertising or publicity pertaining to distribution of the
## software without specific, written prior permission.
##
## LIVINGLOGIC AG AND THE AUTHOR DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
## INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
## LIVING LOGIC AG OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
## DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
## IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
## IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

"""
<doc:par>A module that provides some tools for file i/o related stuff.</doc:par>

<doc:par>The first is a class <pyref class="Filename">Filename</pyref>, that
provides several methods (e.g. a method <pyref class="Filename" method="open">open</pyref>,
that automatically creates any non exiting directories).</doc:par>

<doc:par>The second is a class named <pyref class="SafeFile">SafeFile</pyref>,
that encapsulates a normal file. Nothing will be written to the file with the
specified name. All writes go to a temporary file instead. Only when the
method <pyref class="SafeFile" method="close">close</pyref> is called, will the
temporary file be closed and the file renamed to the original name.</doc:par>

<doc:par>The third tool is a class <pyref class="LogFile">LogFile</pyref>. Writing
to a log file prepends a timestamp to each line.</doc:par>
"""

__version__ = tuple(map(int,"$Revision: 1.22 $"[11:-2].split(".")))
# $Source: /data/cvsroot/LivingLogic/fileutils/fileutils.py,v $

import sys, os, os.path, time, UserString, types

from mx import DateTime

class Filename(UserString.UserString):
	def __init__(self, name):
		"""
		<doc:par>create a new <pyref class="Filename">Filename</pyref> instance.
		<pyref arg="name">name</pyref> may be a <pyref class="Filename">Filename</pyref>
		instance or a string.</doc:par>
		"""
		if isinstance(name, Filename):
			UserString.UserString.__init__(self, name.data)
		else:
			if name.startswith("file:"):
				name = name[5:]
			name = os.path.expanduser(os.path.normpath(name))
			UserString.UserString.__init__(self, name)

	def clone(self):
		"""
		<doc:par>return a (deep) copy of <self/>.</doc:par>
		"""
		return self.__class__(self)

	def __cmp__(self, other):
		if type(other) is type(self):
			othertype = other.__class__
		else:
			othertype = type(other)
		res = cmp(self.__class__.__name__, othertype.__name__)
		if not res:
			res = cmp(self.data, other.data)
		return res

	def __hash__(self):
		return hash(self.data)

	def url(self):
		"""
		<doc:par>return <self/> as an URL (i.e. with a leading <literal>file:</literal>.</doc:par>
		"""
		return "file:" + self.data

	def ext(self):
		"""
		return the file extension.
		"""
		return os.path.splitext(self.data)[1]

	def withExt(self, ext):
		"""
		return a copy of <self/> with the new extension <pyref arg="ext">ext</pyref>.
		"""
		return Filename(os.path.splitext(self.data)[0] + ext)

	def open(self, mode="rb", buffering=-1):
		"""
		Works like the builtin function open, but is able to create
		non existing directories.
		"""
		name = self.data
		try:
			return open(name, mode, buffering)
		except IOError, ex:
			if "w" in mode:
				if ex[0] != 2: # didn't work for some other reason than a non existing directory
					raise
				(splitpath, splitname) = os.path.split(name)
				if splitpath!="": # we really have a parent
					os.makedirs(splitpath)
					return open(name, mode, buffering)
			raise # we don't have a directory to make (or open was for reading), so pass the error on

	def remove(self):
		"""
		delete the file
		"""
		os.remove(self.data)

	def rename(self, other):
		"""
		rename the file. <pyref arg="other">other</pyref> must be a <pyref class="Filename">Filename</pyref> instance.
		"""
		os.rename(self.data, other.data)

	def getmtime(self):
		"""
		return the time of the last modification to the file as a <pyref nolink="1">mx.DateTime.DateTime</pyref>
		object.
		"""
		timestamp = os.path.getmtime(self.data)
		tuple = time.localtime(timestamp)[:6]
		return DateTime.DateTime(*tuple)

	def mkdir(self):
		"""
		create a directory with the name of <self/>.
		"""
		os.makedirs(self.data)

	def mkparentdir(self):
		"""
		create the parent directory of <self/>.
		"""
		name = self.data
		(parent, dummy) = os.path.split(name)
		if parent != name:
			os.makedirs(parent)

	def files(self, withDir=0):
		"""
		return a list of files in this directory (and all subdirectories)
		"""
		files = {}
		name = self.data
		os.path.walk(name, self.__walk, (withDir, files))
		files = files.keys()
		files.sort()
		return [ Filename(file) for file in files ]

	def __walk(self, (withDir, files), dirname, filenames):
		"""
		internal method used by <pyref method="files">files</pyref>
		"""
		try:
			del files[dirname] # don't collect directories
		except KeyError:
			pass
		l = len(self.data) + len(os.sep)
		for filename in filenames:
			filename = os.path.join(dirname, filename)
			if not withDir:
				filename = filename[l:]
			files[filename] = None

	def __div__(self, other):
		"""
		<doc:par>contatenate two filenames. <pyref arg="other">other</pyref> may be a
		<pyref class="Filename">Filename</pyref> or a string.</doc:par>
		"""
		if isinstance(other, Filename):
			other = other.data
		return self.__class__(os.path.join(self.data, other))

	def cd(self, other):
		"""
		<doc:par> synonym for <code>self/other</code>.</doc:par>
		"""
		return self/other

	def __repr__(self):
		return "%s(%r)" % (self.__class__.__name__, self.data)

def cwd():
	"""
	<doc:par>return the current working directory as a <pyref class="Filename">Filename</pyref>.</doc:par>
	"""
	return Filename(os.getcwd())

class SafeFile:
	"""
	encapsulates a normal file.
	When the file will be opened, all writes go to
	a temporary file. After all writes are finished
	the file will be renamed to its final name.
	"""

	def __init__(self, name, mode="w", buffering=0):
		self.name = Filename(name)
		self.tmpname = self.name + ("_%d.tmp" % os.getpid())
		self.mode = mode
		self.buffering = buffering
		self.file = None

	def write(self, *texts):
		self.__open()
		for text in texts:
			self.file.write(text)

	def writelines(self, *alllines):
		self.__open()
		for lines in alllines:
			self.file.writelines(lines)

	def __open(self):
		if self.file is None:
			self.file = self.tmpname.open(self.mode, self.buffering)

	def close(self):
		"""
		This method can be called by the user when all writes to the file
		are done. The file will be closed and moved to the final destination.
		"""
		if self.file is not None:
			self.file.close()
			self.tmpname.rename(self.name)
			self.file = None

class LogFile:
	"""
	A log file. All lines written to the file will be prepended with a time stamp.
	"""
	def __init__(self, name, mode="a", buffering=1, encoding="iso-8859-1"):
		"""
		create a new log file (which will be opened on the first write). Arguments are:
		<ulist>
		<item><pyref arg="name">name</pyref>: the filename (either as a string or a 
		<pyref class="Filename">Filename</pyref> instance).</item>
		<item><pyref arg="mode">mode</pyref>: The mode for opening the file (should be <lit>"w"</lit> or <lit>"a"</lit>)</item>
		<item><pyref arg="buffering">buffering</pyref>: the buffering for the file (<lit>0</lit> is unbuffered, <lit>1</lit> is line buffered, any other integer specifies the buffersize)</item>
		<item><pyref arg="encoding">encoding</pyref>: the encoding to use for the strings written to the file</item>
		</ulist>
		"""
		self.startTime = time.time()
		self.name = name
		self.mode = mode
		self.buffering = buffering
		self.encoding = encoding
		self.file = None

	def _formatTime(self, timestamp):
		"""
		format <pyref arg="timestamp">timestamp</pyref> into a string.
		"""
		return time.strftime("%d/%b/%Y %H:%M:%S", time.localtime(timestamp))

	def __open(self):
		if self.file is None:
			self.file = Filename(self.name).open(self.mode, self.buffering)

	def write(self, *texts):
		"""
		write <pyref arg="texts">texts</pyref> to the log file.
		"""
		now = time.time()
		pid = os.getpid()
		self.__open()
		for text in texts:
			if type(text) is types.UnicodeType:
				text = text.encode(self.encoding, "replace")
			lines = text.split("\n")
			if not len(lines[-1]):
				del lines[-1]
			lines = [ "[pid=%d][%s]=[t+%.2fsec] %s\n" % (pid, self._formatTime(now), now-self.startTime, line) for line in lines ]
			self.file.write("".join(lines))

