You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

230 lines
8.6 KiB

import os
import re
from .p3 import urlparse, u_maybe
from .content import (check_file, check_directory, read_text_file, write_file,
system_path, check_content, copy_content)
from . import content
META_EXT = '.yaml'
BIB_EXT = '.bib'
def filter_filename(filename, ext):
""" Return the filename without the extension if the extension matches ext.
Otherwise return None
"""
pattern = '.*\{}$'.format(ext)
if re.match(pattern, filename) is not None:
return u_maybe(filename[:-len(ext)])
class FileBroker(object):
""" Handles all access to meta and bib files of the repository.
* Does *absolutely no* encoding/decoding.
* Communicate failure with exceptions.
"""
def __init__(self, directory, create=False):
self.directory = os.path.expanduser(directory)
self.metadir = os.path.join(self.directory, 'meta')
self.bibdir = os.path.join(self.directory, 'bib')
self.cachedir = os.path.join(self.directory, '.cache')
if create:
self._create()
check_directory(self.directory)
check_directory(self.metadir)
check_directory(self.bibdir)
# cache directory is created (if absent) if other directories exists.
if not check_directory(self.cachedir, fail=False):
os.mkdir(system_path(self.cachedir))
def _create(self):
"""Create meta and bib directories if absent"""
if not check_directory(self.directory, fail=False):
os.mkdir(system_path(self.directory))
if not check_directory(self.metadir, fail=False):
os.mkdir(system_path(self.metadir))
if not check_directory(self.bibdir, fail=False):
os.mkdir(system_path(self.bibdir))
def bib_path(self, citekey):
return os.path.join(self.bibdir, citekey + BIB_EXT)
def meta_path(self, citekey):
return os.path.join(self.metadir, citekey + META_EXT)
def pull_cachefile(self, filename):
filepath = os.path.join(self.cachedir, filename)
return content.read_binary_file(filepath)
def push_cachefile(self, filename, data):
filepath = os.path.join(self.cachedir, filename)
write_file(filepath, data, mode='wb')
def mtime_metafile(self, citekey):
try:
filepath = self.meta_path(citekey)
return os.path.getmtime(filepath)
except OSError:
raise IOError("'{}' not found.".format(filepath))
def mtime_bibfile(self, citekey):
try:
filepath = self.bib_path(citekey)
return os.path.getmtime(filepath)
except OSError:
raise IOError("'{}' not found.".format(filepath))
def pull_metafile(self, citekey):
return read_text_file(self.meta_path(citekey))
def pull_bibfile(self, citekey):
return read_text_file(self.bib_path(citekey))
def push_metafile(self, citekey, metadata):
"""Put content to disk. Will gladly override anything standing in its way."""
write_file(self.meta_path(citekey), metadata)
def push_bibfile(self, citekey, bibdata):
"""Put content to disk. Will gladly override anything standing in its way."""
write_file(self.bib_path(citekey), bibdata)
def push(self, citekey, metadata, bibdata):
"""Put content to disk. Will gladly override anything standing in its way."""
self.push_metafile(citekey, metadata)
self.push_bibfile(citekey, bibdata)
def remove(self, citekey):
metafilepath = self.meta_path(citekey)
if check_file(metafilepath):
os.remove(system_path(metafilepath))
bibfilepath = self.bib_path(citekey)
if check_file(bibfilepath):
os.remove(system_path(bibfilepath))
def exists(self, citekey, meta_check=False):
""" Checks wether the bibtex of a citekey exists.
:param meta_check: if True, will return if both the bibtex and the meta file exists.
"""
does_exists = check_file(self.bib_path(citekey), fail=False)
if meta_check:
meta_exists = check_file(self.meta_path(citekey), fail=False)
does_exists = does_exists and meta_exists
return does_exists
def listing(self, filestats=True):
metafiles = []
for filename in os.listdir(system_path(self.metadir)):
citekey = filter_filename(filename, META_EXT)
if citekey is not None:
if filestats:
stats = os.stat(system_path(os.path.join(self.metadir, filename)))
metafiles.append(citekey, stats)
else:
metafiles.append(citekey)
bibfiles = []
for filename in os.listdir(system_path(self.bibdir)):
citekey = filter_filename(filename, BIB_EXT)
if citekey is not None:
if filestats:
stats = os.stat(system_path(os.path.join(self.bibdir, filename)))
bibfiles.append(citekey, stats)
else:
bibfiles.append(citekey)
return {'metafiles': metafiles, 'bibfiles': bibfiles}
class DocBroker(object):
""" DocBroker manages the document files optionally attached to the papers.
* only one document can be attached to a paper (might change in the future)
* this document can be anything, the content is never processed.
* these document have an adress of the type "docsdir://citekey.pdf"
* docsdir:// correspond to /path/to/pubsdir/doc (configurable)
* document outside of the repository will not be removed.
* move_doc only applies from inside to inside the docsdir
"""
def __init__(self, directory, scheme='docsdir', subdir='doc'):
self.scheme = scheme
self.docdir = os.path.join(directory, subdir)
if not check_directory(self.docdir, fail=False):
os.mkdir(system_path(self.docdir))
def in_docsdir(self, docpath):
try:
parsed = urlparse(docpath)
except Exception:
return False
return parsed.scheme == self.scheme
# def doc_exists(self, citekey, ext='.txt'):
# return check_file(os.path.join(self.docdir, citekey + ext), fail=False)
def real_docpath(self, docpath):
""" Return the full path
Essentially transform pubsdir://doc/{citekey}.{ext} to /path/to/pubsdir/doc/{citekey}.{ext}.
Return absoluted paths of regular ones otherwise.
"""
if self.in_docsdir(docpath):
parsed = urlparse(docpath)
if parsed.path == '':
docpath = os.path.join(self.docdir, parsed.netloc)
else:
docpath = os.path.join(self.docdir, parsed.netloc, parsed.path[1:])
return docpath
def add_doc(self, citekey, source_path, overwrite=False):
""" Add a document to the docsdir, and return its location.
The document will be named {citekey}.{ext}.
The location will be docsdir://{citekey}.{ext}.
:param overwrite: will overwrite existing file.
:return: the above location
"""
full_source_path = self.real_docpath(source_path)
check_content(full_source_path)
target_path = '{}://{}'.format(self.scheme, citekey + os.path.splitext(source_path)[-1])
full_target_path = self.real_docpath(target_path)
copy_content(full_source_path, full_target_path, overwrite=overwrite)
return target_path
def remove_doc(self, docpath, silent=True):
""" Will remove only file hosted in docsdir://
:raise ValueError: for other paths, unless :param silent: is True
"""
if not self.in_docsdir(docpath):
if not silent:
raise ValueError(('the file to be removed {} is set as external. '
'you should remove it manually.').format(docpath))
return
filepath = self.real_docpath(docpath)
if check_file(filepath):
os.remove(system_path(filepath))
def rename_doc(self, docpath, new_citekey):
""" Move a document inside the docsdir
:raise IOError: if docpath doesn't point to a file
if new_citekey doc exists already.
:raise ValueError: if docpath is not in docsdir().
if an exception is raised, the files on disk haven't changed.
"""
if not self.in_docsdir(docpath):
raise ValueError('cannot rename an external file ({}).'.format(docpath))
new_docpath = self.add_doc(new_citekey, docpath)
self.remove_doc(docpath)
return new_docpath