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
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.expanduser(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
|