1import os 2import re 3from .p3 import urlparse, u_maybe 4 5from .content import (check_file, check_directory, read_text_file, write_file, 6 system_path, check_content, copy_content) 7 8from . import content 9 10 11META_EXT = '.yaml' 12BIB_EXT = '.bib' 13 14 15def filter_filename(filename, ext): 16 """ Return the filename without the extension if the extension matches ext. 17 Otherwise return None 18 """ 19 pattern = '.*\{}$'.format(ext) 20 if re.match(pattern, filename) is not None: 21 return u_maybe(filename[:-len(ext)]) 22 23 24class FileBroker(object): 25 """ Handles all access to meta and bib files of the repository. 26 27 * Does *absolutely no* encoding/decoding. 28 * Communicate failure with exceptions. 29 """ 30 31 def __init__(self, directory, create=False): 32 self.directory = os.path.expanduser(directory) 33 self.metadir = os.path.join(self.directory, 'meta') 34 self.bibdir = os.path.join(self.directory, 'bib') 35 self.cachedir = os.path.join(self.directory, '.cache') 36 if create: 37 self._create() 38 check_directory(self.directory) 39 check_directory(self.metadir) 40 check_directory(self.bibdir) 41 # cache directory is created (if absent) if other directories exists. 42 if not check_directory(self.cachedir, fail=False): 43 os.mkdir(system_path(self.cachedir)) 44 45 def _create(self): 46 """Create meta and bib directories if absent""" 47 if not check_directory(self.directory, fail=False): 48 os.mkdir(system_path(self.directory)) 49 if not check_directory(self.metadir, fail=False): 50 os.mkdir(system_path(self.metadir)) 51 if not check_directory(self.bibdir, fail=False): 52 os.mkdir(system_path(self.bibdir)) 53 54 def bib_path(self, citekey): 55 return os.path.join(self.bibdir, citekey + BIB_EXT) 56 57 def meta_path(self, citekey): 58 return os.path.join(self.metadir, citekey + META_EXT) 59 60 def pull_cachefile(self, filename): 61 filepath = os.path.join(self.cachedir, filename) 62 return content.read_binary_file(filepath) 63 64 def push_cachefile(self, filename, data): 65 filepath = os.path.join(self.cachedir, filename) 66 write_file(filepath, data, mode='wb') 67 68 def mtime_metafile(self, citekey): 69 try: 70 filepath = self.meta_path(citekey) 71 return os.path.getmtime(filepath) 72 except OSError: 73 raise IOError("'{}' not found.".format(filepath)) 74 75 def mtime_bibfile(self, citekey): 76 try: 77 filepath = self.bib_path(citekey) 78 return os.path.getmtime(filepath) 79 except OSError: 80 raise IOError("'{}' not found.".format(filepath)) 81 82 def pull_metafile(self, citekey): 83 return read_text_file(self.meta_path(citekey)) 84 85 def pull_bibfile(self, citekey): 86 return read_text_file(self.bib_path(citekey)) 87 88 def push_metafile(self, citekey, metadata): 89 """Put content to disk. Will gladly override anything standing in its way.""" 90 write_file(self.meta_path(citekey), metadata) 91 92 def push_bibfile(self, citekey, bibdata): 93 """Put content to disk. Will gladly override anything standing in its way.""" 94 write_file(self.bib_path(citekey), bibdata) 95 96 def push(self, citekey, metadata, bibdata): 97 """Put content to disk. Will gladly override anything standing in its way.""" 98 self.push_metafile(citekey, metadata) 99 self.push_bibfile(citekey, bibdata) 100 101 def remove(self, citekey): 102 metafilepath = self.meta_path(citekey) 103 if check_file(metafilepath): 104 os.remove(system_path(metafilepath)) 105 bibfilepath = self.bib_path(citekey) 106 if check_file(bibfilepath): 107 os.remove(system_path(bibfilepath)) 108 109 def exists(self, citekey, meta_check=False): 110 """ Checks wether the bibtex of a citekey exists. 111 112 :param meta_check: if True, will return if both the bibtex and the meta file exists. 113 """ 114 does_exists = check_file(self.bib_path(citekey), fail=False) 115 if meta_check: 116 meta_exists = check_file(self.meta_path(citekey), fail=False) 117 does_exists = does_exists and meta_exists 118 return does_exists 119 120 def listing(self, filestats=True): 121 metafiles = [] 122 for filename in os.listdir(system_path(self.metadir)): 123 citekey = filter_filename(filename, META_EXT) 124 if citekey is not None: 125 if filestats: 126 stats = os.stat(system_path(os.path.join(self.metadir, filename))) 127 metafiles.append(citekey, stats) 128 else: 129 metafiles.append(citekey) 130 131 bibfiles = [] 132 for filename in os.listdir(system_path(self.bibdir)): 133 citekey = filter_filename(filename, BIB_EXT) 134 if citekey is not None: 135 if filestats: 136 stats = os.stat(system_path(os.path.join(self.bibdir, filename))) 137 bibfiles.append(citekey, stats) 138 else: 139 bibfiles.append(citekey) 140 141 return {'metafiles': metafiles, 'bibfiles': bibfiles} 142 143 144class DocBroker(object): 145 """ DocBroker manages the document files optionally attached to the papers. 146 147 * only one document can be attached to a paper (might change in the future) 148 * this document can be anything, the content is never processed. 149 * these document have an adress of the type "docsdir://citekey.pdf" 150 * docsdir:// correspond to /path/to/pubsdir/doc (configurable) 151 * document outside of the repository will not be removed. 152 * move_doc only applies from inside to inside the docsdir 153 """ 154 155 def __init__(self, directory, scheme='docsdir', subdir='doc'): 156 self.scheme = scheme 157 self.docdir = os.path.join(directory, subdir) 158 if not check_directory(self.docdir, fail=False): 159 os.mkdir(system_path(self.docdir)) 160 161 def in_docsdir(self, docpath): 162 try: 163 parsed = urlparse(docpath) 164 except Exception: 165 return False 166 return parsed.scheme == self.scheme 167 168 # def doc_exists(self, citekey, ext='.txt'): 169 # return check_file(os.path.join(self.docdir, citekey + ext), fail=False) 170 171 def real_docpath(self, docpath): 172 """ Return the full path 173 Essentially transform pubsdir://doc/{citekey}.{ext} to /path/to/pubsdir/doc/{citekey}.{ext}. 174 Return absoluted paths of regular ones otherwise. 175 """ 176 if self.in_docsdir(docpath): 177 parsed = urlparse(docpath) 178 if parsed.path == '': 179 docpath = os.path.join(self.docdir, parsed.netloc) 180 else: 181 docpath = os.path.join(self.docdir, parsed.netloc, parsed.path[1:]) 182 return docpath 183 184 def add_doc(self, citekey, source_path, overwrite=False): 185 """ Add a document to the docsdir, and return its location. 186 187 The document will be named {citekey}.{ext}. 188 The location will be docsdir://{citekey}.{ext}. 189 :param overwrite: will overwrite existing file. 190 :return: the above location 191 """ 192 full_source_path = self.real_docpath(source_path) 193 check_content(full_source_path) 194 195 target_path = '{}://{}'.format(self.scheme, citekey + os.path.splitext(source_path)[-1]) 196 full_target_path = self.real_docpath(target_path) 197 copy_content(full_source_path, full_target_path, overwrite=overwrite) 198 return target_path 199 200 def remove_doc(self, docpath, silent=True): 201 """ Will remove only file hosted in docsdir:// 202 203 :raise ValueError: for other paths, unless :param silent: is True 204 """ 205 if not self.in_docsdir(docpath): 206 if not silent: 207 raise ValueError(('the file to be removed {} is set as external. ' 208 'you should remove it manually.').format(docpath)) 209 return 210 filepath = self.real_docpath(docpath) 211 if check_file(filepath): 212 os.remove(system_path(filepath)) 213 214 def rename_doc(self, docpath, new_citekey): 215 """ Move a document inside the docsdir 216 217 :raise IOError: if docpath doesn't point to a file 218 if new_citekey doc exists already. 219 :raise ValueError: if docpath is not in docsdir(). 220 221 if an exception is raised, the files on disk haven't changed. 222 """ 223 if not self.in_docsdir(docpath): 224 raise ValueError('cannot rename an external file ({}).'.format(docpath)) 225 226 new_docpath = self.add_doc(new_citekey, docpath) 227 self.remove_doc(docpath) 228 229 return new_docpath 230