Source code for apptools.io.file
# (C) Copyright 2005-2024 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
""" A representation of files and folders in a file system. """
# Standard/built-in imports.
import mimetypes
import os
import shutil
import stat
# Enthought library imports.
from traits.api import Bool, HasPrivateTraits, Instance, List, Property
from traits.api import Str
[docs]class File(HasPrivateTraits):
""" A representation of files and folders in a file system. """
#### 'File' interface #####################################################
# The absolute path name of this file/folder.
absolute_path = Property(Str)
# The folder's children (for files this is always None).
children = Property(List("File"))
# The file extension (for folders this is always the empty string).
#
# fixme: Currently the extension includes the '.' (ie. we have '.py' and
# not 'py'). This is because things like 'os.path.splitext' leave the '.'
# on, but I'm not sure that this is a good idea!
ext = Property(Str)
# Does the file/folder exist?
exists = Property(Bool)
# Is this an existing file?
is_file = Property(Bool)
# Is this an existing folder?
is_folder = Property(Bool)
# Is this a Python package (ie. a folder contaning an '__init__.py' file.
is_package = Property(Bool)
# Is the file/folder readonly?
is_readonly = Property(Bool)
# The MIME type of the file (for a folder this will always be
# 'context/unknown' (is that what it should be?)).
mime_type = Property(Str)
# The last component of the path without the extension.
name = Property(Str)
# The parent of this file/folder (None if it has no parent).
parent = Property(Instance("File"))
# The path name of this file/folder.
path = Str
# A URL reference to the file.
url = Property(Str)
###########################################################################
# 'object' interface.
###########################################################################
def __init__(self, path, **traits):
""" Creates a new representation of the specified path. """
super(File, self).__init__(path=path, **traits)
def __str__(self):
""" Returns an 'informal' string representation of the object. """
return "File(%s)" % self.path
###########################################################################
# 'File' interface.
###########################################################################
#### Properties ###########################################################
def _get_absolute_path(self):
""" Returns the absolute path of this file/folder. """
return os.path.abspath(self.path)
def _get_children(self):
"""Returns the folder's children.
Returns None if the path does not exist or is not a folder.
"""
if self.is_folder:
children = []
for name in os.listdir(self.path):
children.append(File(os.path.join(self.path, name)))
else:
children = None
return children
def _get_exists(self):
""" Returns True if the file exists, otherwise False. """
return os.path.exists(self.path)
def _get_ext(self):
""" Returns the file extension. """
name, ext = os.path.splitext(self.path)
return ext
def _get_is_file(self):
""" Returns True if the path exists and is a file. """
return self.exists and os.path.isfile(self.path)
def _get_is_folder(self):
""" Returns True if the path exists and is a folder. """
return self.exists and os.path.isdir(self.path)
def _get_is_package(self):
""" Returns True if the path exists and is a Python package. """
return self.is_folder and "__init__.py" in os.listdir(self.path)
def _get_is_readonly(self):
""" Returns True if the file/folder is readonly, otherwise False. """
# If the File object is a folder, os.access cannot be used because it
# returns True for both read-only and writable folders on Windows
# systems.
if self.is_folder:
# Mask for the write-permission bits on the folder. If these bits
# are set to zero, the folder is read-only.
WRITE_MASK = 0x92
permissions = os.stat(self.path)[0]
if permissions & WRITE_MASK == 0:
readonly = True
else:
readonly = False
elif self.is_file:
readonly = not os.access(self.path, os.W_OK)
else:
readonly = False
return readonly
def _get_mime_type(self):
""" Returns the mime-type of this file/folder. """
mime_type, encoding = mimetypes.guess_type(self.path)
if mime_type is None:
mime_type = "content/unknown"
return mime_type
def _get_name(self):
""" Returns the last component of the path without the extension. """
basename = os.path.basename(self.path)
name, ext = os.path.splitext(basename)
return name
def _get_parent(self):
""" Returns the parent of this file/folder. """
return File(os.path.dirname(self.path))
def _get_url(self):
""" Returns the path as a URL. """
# Strip out the leading slash on POSIX systems.
return "file:///%s" % self.absolute_path.lstrip("/")
#### Methods ##############################################################
[docs] def copy(self, destination):
""" Copies this file/folder. """
# Allow the destination to be a string.
if not isinstance(destination, File):
destination = File(destination)
if self.is_folder:
shutil.copytree(self.path, destination.path)
elif self.is_file:
shutil.copyfile(self.path, destination.path)
[docs] def create_file(self, contents=""):
""" Creates a file at this path. """
if self.exists:
raise ValueError("file %s already exists" % self.path)
f = open(self.path, "w")
f.write(contents)
f.close()
[docs] def create_folder(self):
"""Creates a folder at this path.
All intermediate folders MUST already exist.
"""
if self.exists:
raise ValueError("folder %s already exists" % self.path)
os.mkdir(self.path)
[docs] def create_folders(self):
"""Creates a folder at this path.
This will attempt to create any missing intermediate folders.
"""
if self.exists:
raise ValueError("folder %s already exists" % self.path)
os.makedirs(self.path)
[docs] def create_package(self):
"""Creates a package at this path.
All intermediate folders/packages MUST already exist.
"""
if self.exists:
raise ValueError("package %s already exists" % self.path)
os.mkdir(self.path)
# Create the '__init__.py' file that actually turns the folder into a
# package!
init = File(os.path.join(self.path, "__init__.py"))
init.create_file()
[docs] def delete(self):
"""Deletes this file/folder.
Does nothing if the file/folder does not exist.
"""
if self.is_folder:
# Try to make sure that everything in the folder is writeable.
self.make_writeable()
# Delete it!
shutil.rmtree(self.path)
elif self.is_file:
# Try to make sure that the file is writeable.
self.make_writeable()
# Delete it!
os.remove(self.path)
[docs] def make_writeable(self):
""" Attempt to make the file/folder writeable. """
if self.is_folder:
# Try to make sure that everything in the folder is writeable
# (i.e., can be deleted!). This comes in especially handy when
# deleting '.svn' directories.
for path, dirnames, filenames in os.walk(self.path):
for name in dirnames + filenames:
filename = os.path.join(path, name)
if not os.access(filename, os.W_OK):
os.chmod(filename, stat.S_IWUSR)
elif self.is_file:
# Try to make sure that the file is writeable (i.e., can be
# deleted!).
if not os.access(self.path, os.W_OK):
os.chmod(self.path, stat.S_IWUSR)
[docs] def move(self, destination):
""" Moves this file/folder. """
# Allow the destination to be a string.
if not isinstance(destination, File):
destination = File(destination)
# Try to make sure that everything in the directory is writeable.
self.make_writeable()
# Move it!
shutil.move(self.path, destination.path)