1 #!/usr/bin/env python 2 3 """ 4 Filesystem utilities. 5 6 Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 import errno 23 from imiptools.config import DEFAULT_PERMISSIONS, DEFAULT_DIR_PERMISSIONS 24 from os.path import abspath, commonprefix, exists, join, split 25 from os import chmod, makedirs, mkdir, rename, rmdir 26 from time import sleep, time 27 28 def check_dir(base, filename): 29 30 "Return whether 'base' contains 'filename'." 31 32 return commonprefix([base, abspath(filename)]) == base 33 34 def remaining_parts(base, filename): 35 36 "Return the remaining parts from 'base' provided by 'filename'." 37 38 if not check_dir(base, filename): 39 return None 40 41 filename = abspath(filename) 42 43 parts = [] 44 while True: 45 filename, part = split(filename) 46 if check_dir(base, filename): 47 parts.insert(0, part) 48 else: 49 break 50 51 return parts 52 53 def fix_permissions(filename, is_dir=False): 54 55 """ 56 Fix permissions for 'filename', with 'is_dir' indicating whether the object 57 should be a directory or not. 58 """ 59 60 try: 61 chmod(filename, is_dir and DEFAULT_DIR_PERMISSIONS or DEFAULT_PERMISSIONS) 62 except OSError: 63 pass 64 65 def make_path(base, parts): 66 67 """ 68 Make the path within 'base' having components defined by the given 'parts'. 69 Note that this function does not check the parts for suitability. To do so, 70 use the FileBase methods instead. 71 """ 72 73 for part in parts: 74 pathname = join(base, part) 75 if not exists(pathname): 76 mkdir(pathname) 77 fix_permissions(pathname, True) 78 base = pathname 79 80 class FileBase: 81 82 "Basic filesystem operations." 83 84 lock_name = "__lock__" 85 86 def __init__(self, store_dir): 87 self.store_dir = store_dir 88 if not exists(self.store_dir): 89 makedirs(self.store_dir) 90 fix_permissions(self.store_dir, True) 91 92 def get_file_object(self, base, *parts): 93 94 """ 95 Within the given 'base' location, return a path corresponding to the 96 given 'parts'. 97 """ 98 99 # Handle "empty" components. 100 101 pathname = join(base, *parts) 102 return check_dir(base, pathname) and pathname or None 103 104 def get_object_in_store(self, *parts): 105 106 """ 107 Return the name of any valid object stored within a hierarchy specified 108 by the given 'parts'. 109 """ 110 111 parent = expected = self.store_dir 112 113 # Handle "empty" components. 114 115 parts = [p for p in parts if p] 116 117 for part in parts: 118 filename = self.get_file_object(expected, part) 119 if not filename: 120 return False 121 parent = expected 122 expected = filename 123 124 if not exists(parent): 125 make_path(self.store_dir, parts[:-1]) 126 127 return filename 128 129 def move_object(self, source, target): 130 131 "Move 'source' to 'target'." 132 133 if not self.ensure_parent(target): 134 return False 135 rename(source, target) 136 137 def ensure_parent(self, target): 138 139 "Ensure that the parent of 'target' exists." 140 141 parts = remaining_parts(self.store_dir, target) 142 if not parts or not self.get_file_object(self.store_dir, *parts[:-1]): 143 return False 144 145 make_path(self.store_dir, parts[:-1]) 146 return True 147 148 # Locking methods. 149 # This uses the directory creation method exploited by MoinMoin.util.lock. 150 # However, a simple single lock type mechanism is employed here. 151 152 def get_lock_dir(self, *parts): 153 154 "Return the lock directory defined by the given 'parts'." 155 156 parts = parts and list(parts) or [] 157 parts.append(self.lock_name) 158 return self.get_object_in_store(*parts) 159 160 def acquire_lock(self, timeout=None, *parts): 161 162 """ 163 Acquire an exclusive lock on the directory or a path within it described 164 by 'parts'. 165 """ 166 167 start = now = time() 168 169 while not timeout or now - start < timeout: 170 try: 171 mkdir(self.get_lock_dir(*parts)) 172 break 173 except OSError, exc: 174 if exc.errno != errno.EEXIST: 175 raise 176 sleep(1) 177 now = time() 178 179 def release_lock(self, *parts): 180 181 """ 182 Release an acquired lock on the directory or a path within it described 183 by 'parts'. 184 """ 185 186 try: 187 rmdir(self.get_lock_dir(*parts)) 188 except OSError, exc: 189 if exc.errno != errno.ENOENT: 190 raise 191 192 # vim: tabstop=4 expandtab shiftwidth=4