filesystem.py revision cad810f21b803229eb11403f9209855525a25d57
1# Copyright (C) 2010 Google Inc. All rights reserved. 2# 3# Redistribution and use in source and binary forms, with or without 4# modification, are permitted provided that the following conditions are 5# met: 6# 7# * Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# * Redistributions in binary form must reproduce the above 10# copyright notice, this list of conditions and the following disclaimer 11# in the documentation and/or other materials provided with the 12# distribution. 13# * Neither the name of Google Inc. nor the names of its 14# contributors may be used to endorse or promote products derived from 15# this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29"""Wrapper object for the file system / source tree.""" 30 31from __future__ import with_statement 32 33import codecs 34import errno 35import exceptions 36import os 37import shutil 38import tempfile 39import time 40 41class FileSystem(object): 42 """FileSystem interface for webkitpy. 43 44 Unless otherwise noted, all paths are allowed to be either absolute 45 or relative.""" 46 47 def exists(self, path): 48 """Return whether the path exists in the filesystem.""" 49 return os.path.exists(path) 50 51 def isfile(self, path): 52 """Return whether the path refers to a file.""" 53 return os.path.isfile(path) 54 55 def isdir(self, path): 56 """Return whether the path refers to a directory.""" 57 return os.path.isdir(path) 58 59 def join(self, *comps): 60 """Return the path formed by joining the components.""" 61 return os.path.join(*comps) 62 63 def listdir(self, path): 64 """Return the contents of the directory pointed to by path.""" 65 return os.listdir(path) 66 67 def mkdtemp(self, **kwargs): 68 """Create and return a uniquely named directory. 69 70 This is like tempfile.mkdtemp, but if used in a with statement 71 the directory will self-delete at the end of the block (if the 72 directory is empty; non-empty directories raise errors). The 73 directory can be safely deleted inside the block as well, if so 74 desired.""" 75 class TemporaryDirectory(object): 76 def __init__(self, **kwargs): 77 self._kwargs = kwargs 78 self._directory_path = None 79 80 def __enter__(self): 81 self._directory_path = tempfile.mkdtemp(**self._kwargs) 82 return self._directory_path 83 84 def __exit__(self, type, value, traceback): 85 # Only self-delete if necessary. 86 87 # FIXME: Should we delete non-empty directories? 88 if os.path.exists(self._directory_path): 89 os.rmdir(self._directory_path) 90 91 return TemporaryDirectory(**kwargs) 92 93 def maybe_make_directory(self, *path): 94 """Create the specified directory if it doesn't already exist.""" 95 try: 96 os.makedirs(self.join(*path)) 97 except OSError, e: 98 if e.errno != errno.EEXIST: 99 raise 100 101 class _WindowsError(exceptions.OSError): 102 """Fake exception for Linux and Mac.""" 103 pass 104 105 def remove(self, path, osremove=os.remove): 106 """On Windows, if a process was recently killed and it held on to a 107 file, the OS will hold on to the file for a short while. This makes 108 attempts to delete the file fail. To work around that, this method 109 will retry for a few seconds until Windows is done with the file.""" 110 try: 111 exceptions.WindowsError 112 except AttributeError: 113 exceptions.WindowsError = FileSystem._WindowsError 114 115 retry_timeout_sec = 3.0 116 sleep_interval = 0.1 117 while True: 118 try: 119 osremove(path) 120 return True 121 except exceptions.WindowsError, e: 122 time.sleep(sleep_interval) 123 retry_timeout_sec -= sleep_interval 124 if retry_timeout_sec < 0: 125 raise e 126 127 def read_binary_file(self, path): 128 """Return the contents of the file at the given path as a byte string.""" 129 with file(path, 'rb') as f: 130 return f.read() 131 132 def read_text_file(self, path): 133 """Return the contents of the file at the given path as a Unicode string. 134 135 The file is read assuming it is a UTF-8 encoded file with no BOM.""" 136 with codecs.open(path, 'r', 'utf8') as f: 137 return f.read() 138 139 def write_binary_file(self, path, contents): 140 """Write the contents to the file at the given location.""" 141 with file(path, 'wb') as f: 142 f.write(contents) 143 144 def write_text_file(self, path, contents): 145 """Write the contents to the file at the given location. 146 147 The file is written encoded as UTF-8 with no BOM.""" 148 with codecs.open(path, 'w', 'utf8') as f: 149 f.write(contents) 150 151 def copyfile(self, source, destination): 152 """Copies the contents of the file at the given path to the destination 153 path.""" 154 shutil.copyfile(source, destination) 155 156 def files_under(self, path): 157 """Return the list of all files under the given path.""" 158 return [self.join(path_to_file, filename) 159 for (path_to_file, _, filenames) in os.walk(path) 160 for filename in filenames] 161