Make code PEP8-compliant

This commit is contained in:
Johannes Baiter
2013-05-27 14:47:31 +02:00
parent 04b75f6cf7
commit 39a9821a07
+230 -189
View File
@@ -1,4 +1,4 @@
''' """
beetFs beetFs
Copyright 2010 Martin Eve Copyright 2010 Martin Eve
@@ -17,31 +17,32 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with beetFs. If not, see <http://www.gnu.org/licenses/>. along with beetFs. If not, see <http://www.gnu.org/licenses/>.
''' """
from beets.plugins import BeetsPlugin import calendar
from beets.ui import Subcommand import datetime
import beets import errno
import fuse
import logging import logging
from string import Template
import operator import operator
import os import os
import tempfile
import re import re
import fuse
import errno
import stat import stat
import datetime import struct
import time from errno import EINVAL
import calendar
import mutagen
from mutagen import flac, id3
from mutagen.flac import FLAC, Padding, MetadataBlock, VCFLACDict, CueSheet, SeekTable, FLACNoHeaderError
from mutagen.id3 import ID3
import io
from io import BytesIO from io import BytesIO
from string import Template
path_format = "$artist/$album ($year) [$format_upper]/$track - $artist - $title.$format" import beets
from beets.plugins import BeetsPlugin
from beets.ui import Subcommand
from mutagen.flac import (FLAC, Padding, MetadataBlock, VCFLACDict, CueSheet,
SeekTable, FLACNoHeaderError, FLACVorbisError)
from mutagen.id3 import ID3, BitPaddedInt, MakeID3v1
from mutagen._util import insert_bytes
PATH_FORMAT = ("$artist/$album ($year) [$format_upper]/"
"$track - $artist - $title.$format")
beetFs_command = Subcommand('mount', help='Mount a beets filesystem') beetFs_command = Subcommand('mount', help='Mount a beets filesystem')
log = logging.getLogger('beets') log = logging.getLogger('beets')
@@ -50,10 +51,8 @@ log = logging.getLogger('beets')
# FUSE version at the time of writing. Be compatible with this version. # FUSE version at the time of writing. Be compatible with this version.
fuse.fuse_python_api = (0, 2) fuse.fuse_python_api = (0, 2)
""" This is duplicated from Library. Ideally should be exposed from there. """ This is duplicated from Library. Ideally should be exposed from there."""
""" METADATA_RW_FIELDS = [
metadata_rw_fields = [
('title', 'text'), ('title', 'text'),
('artist', 'text'), ('artist', 'text'),
('album', 'text'), ('album', 'text'),
@@ -72,19 +71,18 @@ metadata_rw_fields = [
('bpm', 'int'), ('bpm', 'int'),
('comp', 'bool'), ('comp', 'bool'),
] ]
metadata_fields = [ METADATA_FIELDS = [
('length', 'real'), ('length', 'real'),
('bitrate', 'int'), ('bitrate', 'int'),
] + metadata_rw_fields ] + METADATA_RW_FIELDS
metadata_keys = map(operator.itemgetter(0), metadata_fields) METADATA_KEYS = map(operator.itemgetter(0), METADATA_FIELDS)
def template_mapping(lib, item): def template_mapping(lib, item):
""" Builds a template substitution map. Taken from library.py. """ Builds a template substitution map. Taken from library.py."""
"""
mapping = {} mapping = {}
for key in metadata_keys: for key in METADATA_KEYS:
value = getattr(item, key) value = getattr(item, key)
# sanitize the value for inclusion in a path: # sanitize the value for inclusion in a path:
# replace / and leading . with _ # replace / and leading . with _
@@ -98,9 +96,9 @@ def template_mapping(lib, item):
value = str(value) value = str(value)
mapping[key] = value mapping[key] = value
format = os.path.splitext(item.path)[1][1:] format_ = os.path.splitext(item.path)[1][1:]
mapping['format'] = re.sub(r'[\\/:]|^\.', '_', format) mapping['format'] = re.sub(r'[\\/:]|^\.', '_', format_)
mapping['format_upper'] = re.sub(r'[\\/:]|^\.', '_', format).upper() mapping['format_upper'] = re.sub(r'[\\/:]|^\.', '_', format_).upper()
# fix dud entries # fix dud entries
if mapping['artist'] == '': if mapping['artist'] == '':
@@ -117,6 +115,7 @@ def template_mapping(lib, item):
return mapping return mapping
def mount(lib, config, opts, args): def mount(lib, config, opts, args):
# check we have a command line argument # check we have a command line argument
if not args: if not args:
@@ -124,10 +123,9 @@ def mount(lib, config, opts, args):
# build the in-memory folder structure # build the in-memory folder structure
global structure_split global structure_split
structure_split = path_format.split("/") structure_split = PATH_FORMAT.split("/")
global structure_depth global structure_depth
structure_depth = len(structure_split) structure_depth = len(structure_split)
root = {}
templates = {} templates = {}
global library global library
library = lib library = lib
@@ -155,7 +153,8 @@ def mount(lib, config, opts, args):
sub_elements = [] sub_elements = []
for level in range(0, structure_depth - 1): for level in range(0, structure_depth - 1):
# append the path up to the current depth, minus one, to elements # append the path up to the current depth, minus one, to elements
# this means that sub_elements contains the current path, except for the folder to be added # this means that sub_elements contains the current path, except
# for the folder to be added
if level-1 in level_subbed: if level-1 in level_subbed:
sub_elements.append(level_subbed[level-1]) sub_elements.append(level_subbed[level-1])
# add this directory to the master structure # add this directory to the master structure
@@ -169,7 +168,8 @@ def mount(lib, config, opts, args):
sub_elements.append(level_subbed[structure_depth-2]) sub_elements.append(level_subbed[structure_depth-2])
# do the actual add # do the actual add
directory_structure.addfile(sub_elements, level_subbed[structure_depth-1], item.id) directory_structure.addfile(sub_elements,
level_subbed[structure_depth-1], item.id)
server = beetFileSystem(version="%prog " + fuse.__version__, server = beetFileSystem(version="%prog " + fuse.__version__,
usage="", dash_s_do='setsingle') usage="", dash_s_do='setsingle')
@@ -178,42 +178,35 @@ def mount(lib, config, opts, args):
server.multithreaded = 0 server.multithreaded = 0
try: try:
server.main() server.main()
pass
except fuse.FuseError, e: except fuse.FuseError, e:
log.error(str(e)) log.error(str(e))
beetFs_command.func = mount beetFs_command.func = mount
class beetFs(BeetsPlugin): class beetFs(BeetsPlugin):
""" The beets plugin hook """ The beets plugin hook."""
"""
def commands(self): def commands(self):
return [beetFs_command] return [beetFs_command]
import mutagen
from mutagen import flac
from mutagen.flac import FLAC, Padding, MetadataBlock
def to_int_be(string): def to_int_be(string):
"""Convert an arbitrarily-long string to a long using big-endian """Convert an arbitrarily-long string to a long using big-endian
byte order.""" byte order."""
return reduce(lambda a, b: (a << 8) + ord(b), string, 0L) return reduce(lambda a, b: (a << 8) + ord(b), string, 0L)
class InterpolatedID3 (ID3): class InterpolatedID3 (ID3):
def save(self, filename=None, v1=0): def save(self, filename=None, v1=0):
"""Save changes to a file. """Save changes to a file.
If no filename is given, the one most recently loaded is used. If no filename is given, the one most recently loaded is used.
Keyword arguments: Keyword arguments:
v1 -- if 0, ID3v1 tags will be removed v1 -- if 0, ID3v1 tags will be removed
if 1, ID3v1 tags will be updated but not added if 1, ID3v1 tags will be updated but not added
if 2, ID3v1 tags will be created and/or updated if 2, ID3v1 tags will be created and/or updated
The lack of a way to update only an ID3v1 tag is intentional. The lack of a way to update only an ID3v1 tag is intentional.
""" """
# Sort frames by 'importance' # Sort frames by 'importance'
order = ["TIT2", "TPE1", "TRCK", "TALB", "TPOS", "TDRC", "TCON"] order = ["TIT2", "TPE1", "TRCK", "TALB", "TPOS", "TDRC", "TCON"]
order = dict(zip(order, range(len(order)))) order = dict(zip(order, range(len(order))))
@@ -226,26 +219,31 @@ class InterpolatedID3 (ID3):
framedata.extend([data for data in self.unknown_frames framedata.extend([data for data in self.unknown_frames
if len(data) > 10]) if len(data) > 10])
framedata = ''.join(framedata) framedata = ''.join(framedata)
framesize = len(framedata) framesize = len(framedata)
if filename is None: filename = self.filename if filename is None:
filename = self.filename
f = open(filename, 'rb+') f = open(filename, 'rb+')
try: try:
idata = f.read(10) idata = f.read(10)
try: id3, vmaj, vrev, flags, insize = unpack('>3sBBB4s', idata) try:
except struct.error: id3, insize = '', 0 id3, vmaj, vrev, flags, insize = struct.unpack('>3sBBB4s',
idata)
except struct.error:
id3, insize = '', 0
insize = BitPaddedInt(insize) insize = BitPaddedInt(insize)
if id3 != 'ID3': insize = -10 if id3 != 'ID3':
insize = -10
if insize >= framesize: outsize = insize if insize >= framesize:
else: outsize = (framesize + 1023) & ~0x3FF outsize = insize
else:
outsize = (framesize + 1023) & ~0x3FF
framedata += '\x00' * (outsize - framesize) framedata += '\x00' * (outsize - framesize)
framesize = BitPaddedInt.to_str(outsize, width=4) framesize = BitPaddedInt.to_str(outsize, width=4)
flags = 0 flags = 0
header = pack('>3sBBB4s', 'ID3', 4, 0, flags, framesize) header = struct.pack('>3sBBB4s', 'ID3', 4, 0, flags, framesize)
data = header + framedata data = header + framedata
if (insize < outsize): if (insize < outsize):
@@ -255,14 +253,16 @@ class InterpolatedID3 (ID3):
try: try:
f.seek(-128, 2) f.seek(-128, 2)
except IOError, err: except IOError, err:
from errno import EINVAL if err.errno != EINVAL:
if err.errno != EINVAL: raise raise
f.seek(0, 2) # ensure read won't get "TAG" f.seek(0, 2) # ensure read won't get "TAG"
if f.read(3) == "TAG": if f.read(3) == "TAG":
f.seek(-128, 2) f.seek(-128, 2)
if v1 > 0: f.write(MakeID3v1(self)) if v1 > 0:
else: f.truncate() f.write(MakeID3v1(self))
else:
f.truncate()
elif v1 == 2: elif v1 == 2:
f.seek(0, 2) f.seek(0, 2)
f.write(MakeID3v1(self)) f.write(MakeID3v1(self))
@@ -270,8 +270,8 @@ class InterpolatedID3 (ID3):
finally: finally:
f.close() f.close()
class InterpolatedFLAC (FLAC):
class InterpolatedFLAC (FLAC):
def load(self, filedata): def load(self, filedata):
self.metadata_blocks = [] self.metadata_blocks = []
self.tags = None self.tags = None
@@ -300,8 +300,8 @@ class InterpolatedFLAC (FLAC):
try: try:
data = file.read(size) data = file.read(size)
if len(data) != size: if len(data) != size:
raise error( raise Exception("file said %d bytes, read %d bytes"
"file said %d bytes, read %d bytes" % (size, len(data))) % (size, len(data)))
block = self.METADATA_BLOCKS[byte & 0x7F](data) block = self.METADATA_BLOCKS[byte & 0x7F](data)
except (IndexError, TypeError): except (IndexError, TypeError):
block = MetadataBlock(data) block = MetadataBlock(data)
@@ -310,14 +310,20 @@ class InterpolatedFLAC (FLAC):
else: else:
self.metadata_blocks.append(block) self.metadata_blocks.append(block)
if block.code == VCFLACDict.code: if block.code == VCFLACDict.code:
if self.tags is None: self.tags = block if self.tags is None:
else: raise FLACVorbisError("> 1 Vorbis comment block found") self.tags = block
else:
raise FLACVorbisError("> 1 Vorbis comment block found")
elif block.code == CueSheet.code: elif block.code == CueSheet.code:
if self.cuesheet is None: self.cuesheet = block if self.cuesheet is None:
else: raise error("> 1 CueSheet block found") self.cuesheet = block
else:
raise Exception("> 1 CueSheet block found")
elif block.code == SeekTable.code: elif block.code == SeekTable.code:
if self.seektable is None: self.seektable = block if self.seektable is None:
else: raise error("> 1 SeekTable block found") self.seektable = block
else:
raise Exception("> 1 SeekTable block found")
return (byte >> 7) ^ 1 return (byte >> 7) ^ 1
def get_header(self, filename=None): def get_header(self, filename=None):
@@ -331,7 +337,8 @@ class InterpolatedFLAC (FLAC):
MetadataBlock.group_padding(self.metadata_blocks) MetadataBlock.group_padding(self.metadata_blocks)
header = self.__check_header(f) header = self.__check_header(f)
available = self.__find_audio_offset(f) - header # "fLaC" and maybe ID3 # "fLaC" and maybe ID3
available = self.__find_audio_offset(f) - header
data = MetadataBlock.writeblocks(self.metadata_blocks) data = MetadataBlock.writeblocks(self.metadata_blocks)
if len(data) > available: if len(data) > available:
@@ -349,10 +356,6 @@ class InterpolatedFLAC (FLAC):
data = MetadataBlock.writeblocks(self.metadata_blocks) data = MetadataBlock.writeblocks(self.metadata_blocks)
assert len(data) == available assert len(data) == available
if len(data) != available:
# We couldn't reduce the padding enough.
diff = (len(data) - available)
self.__offset = len("fLaC" + data) self.__offset = len("fLaC" + data)
return("fLaC" + data) return("fLaC" + data)
@@ -376,12 +379,14 @@ class InterpolatedFLAC (FLAC):
if header[:3] == "ID3": if header[:3] == "ID3":
size = 14 + BitPaddedInt(fileobj.read(6)[2:]) size = 14 + BitPaddedInt(fileobj.read(6)[2:])
fileobj.seek(size - 4) fileobj.seek(size - 4)
if fileobj.read(4) != "fLaC": size = None if fileobj.read(4) != "fLaC":
size = None
if size is None: if size is None:
raise FLACNoHeaderError( raise FLACNoHeaderError("%r is not a valid FLAC file"
"%r is not a valid FLAC file" % fileobj.name) % fileobj.name)
return size return size
class FSNode(object): class FSNode(object):
""" A directory node. Contains directories (as a dictionary keyed """ A directory node. Contains directories (as a dictionary keyed
by directory name) and files (dictionary keyed by filename to id). by directory name) and files (dictionary keyed by filename to id).
@@ -391,9 +396,8 @@ class FSNode(object):
self.files = files self.files = files
def getnode(self, elements, root=None): def getnode(self, elements, root=None):
if root == None: if root is None:
root = self root = self
if elements: if elements:
topdir = elements.pop(0) topdir = elements.pop(0)
return self.getnode(elements, root=root.dirs[topdir]) return self.getnode(elements, root=root.dirs[topdir])
@@ -402,7 +406,7 @@ class FSNode(object):
return root return root
def adddir(self, elements, directory, root=None): def adddir(self, elements, directory, root=None):
if root == None: if root is None:
root = self root = self
if len(elements) == 1 and elements[0] == '': if len(elements) == 1 and elements[0] == '':
@@ -412,7 +416,7 @@ class FSNode(object):
node.dirs[directory] = FSNode({}, {}) node.dirs[directory] = FSNode({}, {})
def addfile(self, elements, filename, id, root=None): def addfile(self, elements, filename, id, root=None):
if root == None: if root is None:
root = self root = self
if len(elements) == 1 and elements[0] == '': if len(elements) == 1 and elements[0] == '':
@@ -421,9 +425,8 @@ class FSNode(object):
node.files[filename] = id node.files[filename] = id
def listdir(self, elements, directories, root=None): def listdir(self, elements, directories, root=None):
if root == None: if root is None:
root = self root = self
if len(elements) == 1 and elements[0] == '': if len(elements) == 1 and elements[0] == '':
elements = [] elements = []
node = self.getnode(elements, root=root) node = self.getnode(elements, root=root)
@@ -432,16 +435,18 @@ class FSNode(object):
else: else:
return node.files.keys() return node.files.keys()
class FileHandler(object): class FileHandler(object):
def __init__(self, path, lib): def __init__(self, path, lib):
self.path = path self.path = path
self.lib = lib self.lib = lib
pathsplit = path[1:].split('/') pathsplit = path[1:].split('/')
subdepth = path.count('/') + 1
# determine the item and real path # determine the item and real path
self.item = self.lib.get_item(id=directory_structure.getnode(pathsplit[0:structure_depth-1]).files[pathsplit[structure_depth-1]]) self.item = self.lib.get_item(id=directory_structure
.getnode(pathsplit[0:structure_depth-1])
.files[pathsplit[structure_depth-1]])
self.real_path = self.item.path self.real_path = self.item.path
# open the on-disk file for reading # open the on-disk file for reading
@@ -449,7 +454,8 @@ class FileHandler(object):
self.instance_count = 1 self.instance_count = 1
# now get the bounds of the file_class # now get the bounds of the file_class
#TODO: this needs to handle other file formats; use mutagen's detection procedure #TODO: This needs to handle other file formats; use mutagen's
# detection procedure
self.format = os.path.splitext(path)[1][1:].lower() self.format = os.path.splitext(path)[1][1:].lower()
#logging.info(self.real_path) #logging.info(self.real_path)
if self.format == "flac": if self.format == "flac":
@@ -491,7 +497,6 @@ class FileHandler(object):
def read(self, size, offset): def read(self, size, offset):
# check if read is within header boundary # check if read is within header boundary
if offset < self.bound: if offset < self.bound:
if offset + size < len(self.header): if offset + size < len(self.header):
# can just read from header # can just read from header
logging.info("JUST HEADER") logging.info("JUST HEADER")
@@ -501,13 +506,15 @@ class FileHandler(object):
# get the header + some data from file # get the header + some data from file
logging.info("HEADER + DATA") logging.info("HEADER + DATA")
ret = self.header[offset:len(self.header)] ret = self.header[offset:len(self.header)]
ret = ret + self.music_data[0:size - ((len(self.header) - offset))] ret = ret + self.music_data[0:size - ((len(self.header)
- offset))]
return ret return ret
# otherwise, pass read call to underlying file system # otherwise, pass read call to underlying file system
#self.file_object.seek(offset) #self.file_object.seek(offset)
logging.info("JUST MUSIC") logging.info("JUST MUSIC")
return self.music_data[offset - len(self.header):offset - len(self.header) + size] return self.music_data[offset - len(self.header):offset
- len(self.header) + size]
def write(self, offset, buf): def write(self, offset, buf):
# determine if offset is within header; if not, discard write # determine if offset is within header; if not, discard write
@@ -524,14 +531,20 @@ class FileHandler(object):
# create a new normal mutagen object # create a new normal mutagen object
if self.format == "flac": if self.format == "flac":
try: try:
# now obtain a new Interpolated FLAC from the temporary file # now obtain a new Interpolated FLAC from the temporary
# file
self.inf = InterpolatedFLAC(filedata) self.inf = InterpolatedFLAC(filedata)
# instead of putting the values into the FLAC, extract the values # instead of putting the values into the FLAC, extract the
self.item.title = str(self.inf["title"][0]).encode('utf-8') # values
self.item.album = str(self.inf["album"][0]).encode('utf-8') self.item.title = (str(self.inf["title"][0])
self.item.artist = str(self.inf["artist"][0]).encode('utf-8') .encode('utf-8'))
self.item.genre = str(self.inf["genre"][0]).encode('utf-8') self.item.album = (str(self.inf["album"][0])
.encode('utf-8'))
self.item.artist = (str(self.inf["artist"][0])
.encode('utf-8'))
self.item.genre = (str(self.inf["genre"][0])
.encode('utf-8'))
self.lib.store(self.item) self.lib.store(self.item)
self.lib.save() self.lib.save()
@@ -551,6 +564,7 @@ class FileHandler(object):
logging.error("Couldn't update tag.") logging.error("Couldn't update tag.")
pass pass
class Stat(fuse.Stat): class Stat(fuse.Stat):
DIRSIZE = 4096 DIRSIZE = 4096
@@ -575,29 +589,36 @@ class Stat(fuse.Stat):
def _get_dt_atime(self): def _get_dt_atime(self):
return self.epoch_datetime(self.st_atime) return self.epoch_datetime(self.st_atime)
def _set_dt_atime(self, value): def _set_dt_atime(self, value):
self.st_atime = self.datetime_epoch(value) self.st_atime = self.datetime_epoch(value)
dt_atime = property(_get_dt_atime, _set_dt_atime) dt_atime = property(_get_dt_atime, _set_dt_atime)
def _get_dt_mtime(self): def _get_dt_mtime(self):
return self.epoch_datetime(self.st_mtime) return self.epoch_datetime(self.st_mtime)
def _set_dt_mtime(self, value): def _set_dt_mtime(self, value):
self.st_mtime = self.datetime_epoch(value) self.st_mtime = self.datetime_epoch(value)
dt_mtime = property(_get_dt_mtime, _set_dt_mtime) dt_mtime = property(_get_dt_mtime, _set_dt_mtime)
def _get_dt_ctime(self): def _get_dt_ctime(self):
return self.epoch_datetime(self.st_ctime) return self.epoch_datetime(self.st_ctime)
def _set_dt_ctime(self, value): def _set_dt_ctime(self, value):
self.st_ctime = self.datetime_epoch(value) self.st_ctime = self.datetime_epoch(value)
dt_ctime = property(_get_dt_ctime, _set_dt_ctime) dt_ctime = property(_get_dt_ctime, _set_dt_ctime)
@staticmethod @staticmethod
def datetime_epoch(dt): def datetime_epoch(dt):
return calendar.timegm(dt.timetuple()) return calendar.timegm(dt.timetuple())
@staticmethod @staticmethod
def epoch_datetime(seconds): def epoch_datetime(seconds):
return datetime.datetime.utcfromtimestamp(seconds) return datetime.datetime.utcfromtimestamp(seconds)
class beetFileSystem(fuse.Fuse): class beetFileSystem(fuse.Fuse):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
LOG_FILENAME = "LOG" LOG_FILENAME = "LOG"
@@ -624,7 +645,6 @@ class beetFileSystem(fuse.Fuse):
# (disparate locations), so using homedir to fill this in # (disparate locations), so using homedir to fill this in
return os.statvfs(os.path.expanduser("~")) return os.statvfs(os.path.expanduser("~"))
def getattr(self, path): def getattr(self, path):
logging.info("getattr: %s" % path) logging.info("getattr: %s" % path)
@@ -641,7 +661,9 @@ class beetFileSystem(fuse.Fuse):
if len(pathsplit) == structure_depth: if len(pathsplit) == structure_depth:
# it's a file # it's a file
item = self.lib.get_item(directory_structure.getnode(pathsplit[0:structure_depth-1]).files[pathsplit[structure_depth-1]]).path item = self.lib.get_item(directory_structure
.getnode(pathsplit[0:structure_depth-1])
.files[pathsplit[structure_depth-1]]).path
if not item: if not item:
# file not found # file not found
@@ -651,19 +673,33 @@ class beetFileSystem(fuse.Fuse):
logging.error("Returning ENOENT") logging.error("Returning ENOENT")
return -errno.ENOENT return -errno.ENOENT
statinfo = os.stat(item) statinfo = os.stat(item)
st = Stat(st_mode=statinfo.st_mode, st_size=statinfo.st_size, st_uid=statinfo.st_uid, st_gid=statinfo.st_gid, st_nlink=statinfo.st_nlink, dt_atime=datetime.datetime.fromtimestamp(statinfo.st_atime), dt_mtime=datetime.datetime.fromtimestamp(statinfo.st_mtime), dt_ctime=datetime.datetime.fromtimestamp(statinfo.st_ctime)) st = Stat(st_mode=statinfo.st_mode,
st_size=statinfo.st_size,
st_uid=statinfo.st_uid,
st_gid=statinfo.st_gid,
st_nlink=statinfo.st_nlink,
dt_atime=(datetime.datetime
.fromtimestamp(statinfo.st_atime)),
dt_mtime=(datetime.datetime
.fromtimestamp(statinfo.st_mtime)),
dt_ctime=(datetime.datetime
.fromtimestamp(statinfo.st_ctime))
)
return st return st
else: else:
logging.info("dir") logging.info("dir")
# it's a directory # it's a directory
if not pathsplit[len(pathsplit)-1] in directory_structure.getnode(pathsplit[0:len(pathsplit)-1]).dirs: if not (pathsplit[len(pathsplit)-1]
in (directory_structure
.getnode(pathsplit[0:len(pathsplit)-1]).dirs)):
# directory not found # directory not found
logging.error("Returning ENOENT") logging.error("Returning ENOENT")
return -errno.ENOENT return -errno.ENOENT
else: else:
logging.info("gotdir") logging.info("gotdir")
mode = stat.S_IFDIR | 0544 mode = stat.S_IFDIR | 0544
st = Stat(st_mode=mode, st_size=Stat.DIRSIZE, st_nlink=2) st = Stat(st_mode=mode, st_size=Stat.DIRSIZE,
st_nlink=2)
return st return st
except Exception as e: except Exception as e:
@@ -674,15 +710,14 @@ class beetFileSystem(fuse.Fuse):
# utimens takes precedence over utime, so having this here does nothing # utimens takes precedence over utime, so having this here does nothing
# unless you delete utimens. # unless you delete utimens.
def utime(self, path, times): def utime(self, path, times):
atime, mtime = times atime, mtime = times
logging.info("utime: %s (atime %s, mtime %s)" % (path, atime, mtime)) logging.info("utime: %s (atime %s, mtime %s)" % (path, atime, mtime))
return -errno.EOPNOTSUPP return -errno.EOPNOTSUPP
def utimens(self, path, atime, mtime): def utimens(self, path, atime, mtime):
logging.info("utime: %s (atime %s:%s, mtime %s:%s)" logging.info("utime: %s (atime %s:%s, mtime %s:%s)"
% (path,atime.tv_sec,atime.tv_nsec,mtime.tv_sec,mtime.tv_nsec)) % (path, atime.tv_sec, atime.tv_nsec, mtime.tv_sec,
mtime.tv_nsec))
return -errno.EOPNOTSUPP return -errno.EOPNOTSUPP
def access(self, path, flags): def access(self, path, flags):
@@ -696,13 +731,16 @@ class beetFileSystem(fuse.Fuse):
# check for existence # check for existence
if is_dir: if is_dir:
logging.info("dir") logging.info("dir")
if not pathsplit[len(pathsplit) - 1] in directory_structure.getnode(pathsplit[0:len(pathsplit)-1]).dirs: if not (pathsplit[len(pathsplit) - 1] in directory_structure
.getnode(pathsplit[0:len(pathsplit)-1]).dirs):
return -errno.EACCES return -errno.EACCES
else: else:
# if exists, always return allowed for directories # if exists, always return allowed for directories
return 0 return 0
else: else:
item = self.lib.get_item(id=directory_structure.getnode(pathsplit[0:structure_depth-1]).files[pathsplit[structure_depth-1]]).path item = self.lib.get_item(id=directory_structure
.getnode(pathsplit[0:structure_depth-1])
.files[pathsplit[structure_depth-1]]).path
if not item: if not item:
return -errno.EACCES return -errno.EACCES
else: else:
@@ -740,19 +778,20 @@ class beetFileSystem(fuse.Fuse):
# S_IFIFO: 010000 (A fifo buffer, created with mkfifo) # S_IFIFO: 010000 (A fifo buffer, created with mkfifo)
# Potential ones (I have never seen them): # Potential ones (I have never seen them):
# Note that these could be made by copying special devices or sockets # Note that these could be made by copying special devices or
# or using mknod, but I've never gotten FUSE to pass such a request # sockets or using mknod, but I've never gotten FUSE to pass such
# along. # a request along.
# S_IFCHR: 020000 (A character special device, created with mknod) # S_IFCHR: 020000 (character special device, created with mknod)
# S_IFBLK: 060000 (A block special device, created with mknod) # S_IFBLK: 060000 (block special device, created with mknod)
# S_IFSOCK: 0140000 (A socket, created with mkfifo) # S_IFSOCK: 0140000 (socket, created with mkfifo)
# Also note: You can use self.GetContext() to get a dictionary # Also note: You can use self.GetContext() to get a dictionary
# {'uid': ?, 'gid': ?}, which tells you the uid/gid of the user # {'uid': ?, 'gid': ?}, which tells you the uid/gid of
# executing the current syscall. This should be handy when creating # the user executing the current syscall. This should be
# new files and directories, because they should be owned by this # handy when creating new files and directories, because
# user/group. # they should be owned by this user/group.
logging.info("mknod: %s (mode %s, rdev %s)" % (path, oct(mode), rdev)) logging.info("mknod: %s (mode %s, rdev %s)" % (path, oct(mode),
rdev))
return -errno.EOPNOTSUPP return -errno.EOPNOTSUPP
def mkdir(self, path, mode): def mkdir(self, path, mode):
@@ -785,15 +824,15 @@ class beetFileSystem(fuse.Fuse):
The 'target' is special - it works just like any symlink target. It The 'target' is special - it works just like any symlink target. It
may be absolute, in which case it is absolute on the user's system, may be absolute, in which case it is absolute on the user's system,
NOT the mounted filesystem, or it may be relative. It should be NOT the mounted filesystem, or it may be relative. It should be
treated as an opaque string - the filesystem implementation should not treated as an opaque string - the filesystem implementation should
ever need to follow it (that is handled by the OS). not ever need to follow it (that is handled by the OS).
Hence, if the operating system creates a link FROM this system TO Hence, if the operating system creates a link FROM this system TO
another system, it will call this method with a target pointing another system, it will call this method with a target pointing
outside the filesystem. outside the filesystem.
If the operating system creates a link FROM some other system TO this If the operating system creates a link FROM some other system TO
system, it will not touch this system at all (symlinks do not depend this system, it will not touch this system at all (symlinks do not
on the target system unless followed). depend on the target system unless followed).
""" """
logging.info("symlink: target %s, name: %s" % (target, name)) logging.info("symlink: target %s, name: %s" % (target, name))
return -errno.EOPNOTSUPP return -errno.EOPNOTSUPP
@@ -801,8 +840,8 @@ class beetFileSystem(fuse.Fuse):
def link(self, target, name): def link(self, target, name):
""" """
Creates a hard link from name to target. Note that both paths are Creates a hard link from name to target. Note that both paths are
relative to the mounted file system. Hard-links across systems are not relative to the mounted file system. Hard-links across systems are
supported. not supported.
""" """
logging.info("link: target %s, name: %s" % (target, name)) logging.info("link: target %s, name: %s" % (target, name))
return -errno.EOPNOTSUPP return -errno.EOPNOTSUPP
@@ -814,7 +853,8 @@ class beetFileSystem(fuse.Fuse):
Note that both paths are relative to the mounted file system. Note that both paths are relative to the mounted file system.
If the operating system needs to move files across systems, it will If the operating system needs to move files across systems, it will
manually copy and delete the file, and this method will not be called. manually copy and delete the file, and this method will not be
called.
""" """
logging.info("rename: target %s, name: %s" % (old, new)) logging.info("rename: target %s, name: %s" % (old, new))
return -errno.EOPNOTSUPP return -errno.EOPNOTSUPP
@@ -832,10 +872,10 @@ class beetFileSystem(fuse.Fuse):
def truncate(self, path, size): def truncate(self, path, size):
""" """
Shrink or expand a file to a given size. Shrink or expand a file to a given size.
If 'size' is smaller than the existing file size, truncate it from the If 'size' is smaller than the existing file size, truncate it from
end. the end.
If 'size' if larger than the existing file size, extend it with null If 'size' if larger than the existing file size, extend it with
bytes. null bytes.
""" """
logging.info("truncate: %s (size %s)" % (path, size)) logging.info("truncate: %s (size %s)" % (path, size))
return -errno.EOPNOTSUPP return -errno.EOPNOTSUPP
@@ -867,15 +907,17 @@ class beetFileSystem(fuse.Fuse):
if path == "/": if path == "/":
return directory_structure return directory_structure
else: else:
if not pathsplit[len(pathsplit) - 1] in directory_structure.getnode(pathsplit[0:len(pathsplit)-1]).dirs: if not (pathsplit[len(pathsplit) - 1]
in directory_structure
.getnode(pathsplit[0:len(pathsplit)-1]).dirs):
return -errno.EACCES return -errno.EACCES
else: else:
return directory_structure.getnode(pathsplit[0:len(pathsplit)-1]).dirs[pathsplit[len(pathsplit) - 1]] return (directory_structure
.getnode(pathsplit[0:len(pathsplit)-1])
.dirs[pathsplit[len(pathsplit) - 1]])
def releasedir(self, path, dh=None): def releasedir(self, path, dh=None):
""" """ Closes an open directory. Allows filesystem to clean up."""
Closes an open directory. Allows filesystem to clean up.
"""
logging.info("releasedir: %s (dh %s)" % (path, dh)) logging.info("releasedir: %s (dh %s)" % (path, dh))
def fsyncdir(self, path, datasync, dh=None): def fsyncdir(self, path, datasync, dh=None):
@@ -891,12 +933,12 @@ class beetFileSystem(fuse.Fuse):
Generator function. Produces a directory listing. Generator function. Produces a directory listing.
Yields individual fuse.Direntry objects, one per file in the Yields individual fuse.Direntry objects, one per file in the
directory. Should always yield at least "." and "..". directory. Should always yield at least "." and "..".
Should yield nothing if the file is not a directory or does not exist. Should yield nothing if the file is not a directory or does not
(Does not need to raise an error). exist. (Does not need to raise an error).
offset: I don't know what this does, but I think it allows the OS to offset: I don't know what this does, but I think it allows the OS
request starting the listing partway through (which I clearly don't to request starting the listing partway through (which I clearly
yet support). Seems to always be 0 anyway. don't yet support). Seems to always be 0 anyway.
""" """
logging.info("readdir: %s (offset %s, dh %s)" % (path, offset, dh)) logging.info("readdir: %s (offset %s, dh %s)" % (path, offset, dh))
@@ -906,47 +948,50 @@ class beetFileSystem(fuse.Fuse):
try: try:
pathsplit = path[1:].split('/') pathsplit = path[1:].split('/')
if dh is None:
if dh == None:
if path == "/": if path == "/":
logging.info("dh assigned as root") logging.info("dh assigned as root")
dh = directory_structure dh = directory_structure
else: else:
dh = directory_structure.getnode(pathsplit[0:len(pathsplit)-1]).dirs[pathsplit[len(pathsplit)]] dh = (directory_structure
.getnode(pathsplit[0:len(pathsplit)-1])
.dirs[pathsplit[len(pathsplit)]])
if len(pathsplit) == structure_depth - 1: if len(pathsplit) == structure_depth - 1:
# files # files
logging.info("Yielding files: %s" % path) logging.info("Yielding files: %s" % path)
for files in directory_structure.listdir(pathsplit, False): for files in directory_structure.listdir(pathsplit, False):
logging.info("Yielding file: %s" % files.encode('utf-8')) logging.info("Yielding file: %s"
% files.encode('utf-8'))
yield fuse.Direntry(files.encode('utf-8')) yield fuse.Direntry(files.encode('utf-8'))
else: else:
# directories # directories
for files in directory_structure.listdir(pathsplit, True): for files in directory_structure.listdir(pathsplit, True):
#logging.info("Yielding dir: %s" % files.encode('utf-8')) #logging.info("Yielding dir: %s"
# % files.encode('utf-8'))
yield fuse.Direntry(files.encode('utf-8')) yield fuse.Direntry(files.encode('utf-8'))
except Exception as e: except Exception as e:
logging.error(e) logging.error(e)
### FILE OPERATION METHODS ### ### FILE OPERATION METHODS ###
# Methods in this section are operations for opening files and working on # Methods in this section are operations for opening files and working
# open files. # on open files.
# "open" and "create" are methods for opening files. They *may* return an # "open" and "create" are methods for opening files. They *may* return
# arbitrary Python object (not None or int), which is used as a file # an arbitrary Python object (not None or int), which is used as a file
# handle by the methods for working on files. # handle by the methods for working on files.
# All the other methods (fgetattr, release, read, write, fsync, flush, # All the other methods (fgetattr, release, read, write, fsync, flush,
# ftruncate and lock) are methods for working on files. They should all be # ftruncate and lock) are methods for working on files. They should all
# prepared to accept an optional file-handle argument, which is whatever # be prepared to accept an optional file-handle argument, which is
# object "open" or "create" returned. # whatever object "open" or "create" returned.
def open(self, path, flags): def open(self, path, flags):
""" """
Open a file for reading/writing, and check permissions. Open a file for reading/writing, and check permissions.
flags: As described in man 2 open (Linux Programmer's Manual). flags: As described in man 2 open (Linux Programmer's Manual).
ORing of several access flags, including one of os.O_RDONLY, ORing of several access flags, including one of
os.O_WRONLY or os.O_RDWR. All other flags are in os as well. os.O_RDONLY, os.O_WRONLY or os.O_RDWR. All other flags are
in os as well.
On success, *may* return an arbitrary Python object, which will be On success, *may* return an arbitrary Python object, which will be
used as the "fh" argument to all the file operation methods on the used as the "fh" argument to all the file operation methods on the
@@ -956,15 +1001,14 @@ class beetFileSystem(fuse.Fuse):
""" """
logging.info("open: %s (flags %s)" % (path, oct(flags))) logging.info("open: %s (flags %s)" % (path, oct(flags)))
file_path = None
try: try:
if self.files == None: if self.files is None:
self.files = {"x": "y"} self.files = {"x": "y"}
if path in self.files: if path in self.files:
# get a file object # get a file object
logging.info("Retrieving an existing File Handler for: %s" % path) logging.info("Retrieving an existing File Handler for: %s"
% path)
self.files[path].open() self.files[path].open()
else: else:
# create a file open # create a file open
@@ -976,7 +1020,6 @@ class beetFileSystem(fuse.Fuse):
logging.info("Error creating a File Handler: %s" % e) logging.info("Error creating a File Handler: %s" % e)
return -errno.EACCES return -errno.EACCES
def create(self, path, mode, rdev): def create(self, path, mode, rdev):
""" """
Creates a file and opens it for writing. Creates a file and opens it for writing.
@@ -988,18 +1031,19 @@ class beetFileSystem(fuse.Fuse):
Always 0 for regular files or FIFO buffers. Always 0 for regular files or FIFO buffers.
See "open" for return value. See "open" for return value.
""" """
logging.info("create: %s (mode %s, rdev %s)" % (path,oct(mode),rdev)) logging.info("create: %s (mode %s, rdev %s)"
% (path, oct(mode), rdev))
return -errno.EOPNOTSUPP return -errno.EOPNOTSUPP
def fgetattr(self, path, fh=None): def fgetattr(self, path, fh=None):
""" """
Retrieves information about a file (the "stat" of a file). Retrieves information about a file (the "stat" of a file).
Same as Fuse.getattr, but may be given a file handle to an open file, Same as Fuse.getattr, but may be given a file handle to an open
so it can use that instead of having to look up the path. file, so it can use that instead of having to look up the path.
""" """
logging.info("fgetattr: %s (fh %s)" % (path, fh)) logging.info("fgetattr: %s (fh %s)" % (path, fh))
# We could use fh for a more efficient lookup. Here we just call the # We could use fh for a more efficient lookup. Here we just call
# non-file-handle version, getattr. # the non-file-handle version, getattr.
return self.getattr(path) return self.getattr(path)
def release(self, path, flags, fh=None): def release(self, path, flags, fh=None):
@@ -1007,9 +1051,11 @@ class beetFileSystem(fuse.Fuse):
Closes an open file. Allows filesystem to clean up. Closes an open file. Allows filesystem to clean up.
flags: The same flags the file was opened with (see open). flags: The same flags the file was opened with (see open).
""" """
logging.info("release: %s (flags %s, fh %s)" % (path, oct(flags), fh)) logging.info("release: %s (flags %s, fh %s)" % (path, oct(flags),
fh))
if self.files[path].release(): if self.files[path].release():
logging.info("Complete release: %s (flags %s, fh %s)" % (path, oct(flags), fh)) logging.info("Complete release: %s (flags %s, fh %s)"
% (path, oct(flags), fh))
del self.files[path] del self.files[path]
def fsync(self, path, datasync, fh=None): def fsync(self, path, datasync, fh=None):
@@ -1017,13 +1063,14 @@ class beetFileSystem(fuse.Fuse):
Synchronises an open file. Synchronises an open file.
datasync: If True, only flush user data, not metadata. datasync: If True, only flush user data, not metadata.
""" """
logging.info("fsync: %s (datasync %s, fh %s)" % (path, datasync, fh)) logging.info("fsync: %s (datasync %s, fh %s)"
% (path, datasync, fh))
def flush(self, path, fh=None): def flush(self, path, fh=None):
""" """
Flush cached data to the file system. Flush cached data to the file system.
This is NOT an fsync (I think the difference is fsync goes both ways, This is NOT an fsync (I think the difference is fsync goes both
while flush is just one-way). ways, while flush is just one-way).
""" """
logging.info("flush: %s (fh %s)" % (path, fh)) logging.info("flush: %s (fh %s)" % (path, fh))
@@ -1034,25 +1081,23 @@ class beetFileSystem(fuse.Fuse):
offset: Offset in bytes from the start of the file to read from. offset: Offset in bytes from the start of the file to read from.
Does not need to check access rights (operating system will always Does not need to check access rights (operating system will always
call access or open first). call access or open first).
Returns a byte string with the contents of the file, with a length no Returns a byte string with the contents of the file, with a length
greater than 'size'. May also return an int error code. no greater than 'size'. May also return an int error code.
If the length of the returned string is 0, it indicates the end of the If the length of the returned string is 0, it indicates the end of
file, and the OS will not request any more. If the length is nonzero, the file, and the OS will not request any more. If the length is
the OS may request more bytes later. nonzero, the OS may request more bytes later.
To signal that it is NOT the end of file, but no bytes are presently To signal that it is NOT the end of file, but no bytes are
available (and it is a non-blocking read), return -errno.EAGAIN. presently available (and it is a non-blocking read), return
-errno.EAGAIN.
If it is a blocking read, just block until ready. If it is a blocking read, just block until ready.
""" """
logging.info("read: %s (size %s, offset %s, fh %s)" logging.info("read: %s (size %s, offset %s, fh %s)"
% (path, size, offset, fh)) % (path, size, offset, fh))
if fh is None:
if fh == None:
file_path = None
try: try:
if self.files == None: if self.files is None:
self.files = {"x": "y"} self.files = {"x": "y"}
self.files[path] = FileHandler(path, self.lib) self.files[path] = FileHandler(path, self.lib)
except: except:
@@ -1070,17 +1115,15 @@ class beetFileSystem(fuse.Fuse):
Should only overwrite the part of the file from offset to Should only overwrite the part of the file from offset to
offset+len(buf). offset+len(buf).
Must return an int: the number of bytes successfully written (should Must return an int: the number of bytes successfully written
be equal to len(buf) unless an error occured). May also be a negative (should be equal to len(buf) unless an error occured). May also be
int, which is an errno code. a negative int, which is an errno code.
""" """
logging.info("write: %s (offset %s, fh %s)" % (path, offset, fh)) logging.info("write: %s (offset %s, fh %s)" % (path, offset, fh))
if fh == None: if fh is None:
file_path = None
try: try:
if self.files == None: if self.files is None:
self.files = {"x": "y"} self.files = {"x": "y"}
self.files[path] = FileHandler(path, self.lib) self.files[path] = FileHandler(path, self.lib)
except: except:
@@ -1094,10 +1137,8 @@ class beetFileSystem(fuse.Fuse):
def ftruncate(self, path, size, fh=None): def ftruncate(self, path, size, fh=None):
""" """
Shrink or expand a file to a given size. Shrink or expand a file to a given size.
Same as Fuse.truncate, but may be given a file handle to an open file, Same as Fuse.truncate, but may be given a file handle to an open
so it can use that instead of having to look up the path. file, so it can use that instead of having to look up the path.
""" """
logging.info("ftruncate: %s (size %s, fh %s)" % (path, size, fh)) logging.info("ftruncate: %s (size %s, fh %s)" % (path, size, fh))
return -errno.EOPNOTSUPP return -errno.EOPNOTSUPP