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