mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-01-22 14:41:39 +00:00
Added tox configuration
... and fixed pep8 warnings. There's a lot of them that are still ignored, but that's because it's too much of a step to take at once.
This commit is contained in:
63
core/fs.py
63
core/fs.py
@@ -1,9 +1,9 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-10-22
|
||||
# Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
#
|
||||
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/bsd_license
|
||||
|
||||
# This is a fork from hsfs. The reason for this fork is that hsfs has been designed for musicGuru
|
||||
@@ -32,6 +32,7 @@ NOT_SET = object()
|
||||
|
||||
class FSError(Exception):
|
||||
cls_message = "An error has occured on '{name}' in '{parent}'"
|
||||
|
||||
def __init__(self, fsobject, parent=None):
|
||||
message = self.cls_message
|
||||
if isinstance(fsobject, str):
|
||||
@@ -42,7 +43,7 @@ class FSError(Exception):
|
||||
name = ''
|
||||
parentname = str(parent) if parent is not None else ''
|
||||
Exception.__init__(self, message.format(name=name, parent=parentname))
|
||||
|
||||
|
||||
|
||||
class AlreadyExistsError(FSError):
|
||||
"The directory or file name we're trying to add already exists"
|
||||
@@ -57,7 +58,7 @@ class InvalidDestinationError(FSError):
|
||||
cls_message = "'{name}' is an invalid destination for this operation."
|
||||
|
||||
class OperationError(FSError):
|
||||
"""A copy/move/delete operation has been called, but the checkup after the
|
||||
"""A copy/move/delete operation has been called, but the checkup after the
|
||||
operation shows that it didn't work."""
|
||||
cls_message = "Operation on '{name}' failed."
|
||||
|
||||
@@ -74,15 +75,15 @@ class File:
|
||||
# files, I saved 35% memory usage with "unread" files (no _read_info() call) and gains become
|
||||
# even greater when we take into account read attributes (70%!). Yeah, it's worth it.
|
||||
__slots__ = ('path', 'is_ref', 'words') + tuple(INITIAL_INFO.keys())
|
||||
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
for attrname in self.INITIAL_INFO:
|
||||
setattr(self, attrname, NOT_SET)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<{} {}>".format(self.__class__.__name__, str(self.path))
|
||||
|
||||
|
||||
def __getattribute__(self, attrname):
|
||||
result = object.__getattribute__(self, attrname)
|
||||
if result is NOT_SET:
|
||||
@@ -94,12 +95,12 @@ class File:
|
||||
if result is NOT_SET:
|
||||
result = self.INITIAL_INFO[attrname]
|
||||
return result
|
||||
|
||||
|
||||
#This offset is where we should start reading the file to get a partial md5
|
||||
#For audio file, it should be where audio data starts
|
||||
def _get_md5partial_offset_and_size(self):
|
||||
return (0x4000, 0x4000) #16Kb
|
||||
|
||||
|
||||
def _read_info(self, field):
|
||||
if field in ('size', 'mtime'):
|
||||
stats = self.path.stat()
|
||||
@@ -129,24 +130,24 @@ class File:
|
||||
fp.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _read_all_info(self, attrnames=None):
|
||||
"""Cache all possible info.
|
||||
|
||||
|
||||
If `attrnames` is not None, caches only attrnames.
|
||||
"""
|
||||
if attrnames is None:
|
||||
attrnames = self.INITIAL_INFO.keys()
|
||||
for attrname in attrnames:
|
||||
getattr(self, attrname)
|
||||
|
||||
|
||||
#--- Public
|
||||
@classmethod
|
||||
def can_handle(cls, path):
|
||||
"""Returns whether this file wrapper class can handle ``path``.
|
||||
"""
|
||||
return not path.islink() and path.isfile()
|
||||
|
||||
|
||||
def rename(self, newname):
|
||||
if newname == self.name:
|
||||
return
|
||||
@@ -160,42 +161,42 @@ class File:
|
||||
if not destpath.exists():
|
||||
raise OperationError(self)
|
||||
self.path = destpath
|
||||
|
||||
|
||||
def get_display_info(self, group, delta):
|
||||
"""Returns a display-ready dict of dupe's data.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
#--- Properties
|
||||
@property
|
||||
def extension(self):
|
||||
return get_file_ext(self.name)
|
||||
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.path.name
|
||||
|
||||
|
||||
@property
|
||||
def folder_path(self):
|
||||
return self.path.parent()
|
||||
|
||||
|
||||
|
||||
class Folder(File):
|
||||
"""A wrapper around a folder path.
|
||||
|
||||
|
||||
It has the size/md5 info of a File, but it's value are the sum of its subitems.
|
||||
"""
|
||||
__slots__ = File.__slots__ + ('_subfolders', )
|
||||
|
||||
|
||||
def __init__(self, path):
|
||||
File.__init__(self, path)
|
||||
self._subfolders = None
|
||||
|
||||
|
||||
def _all_items(self):
|
||||
folders = self.subfolders
|
||||
files = get_files(self.path)
|
||||
return folders + files
|
||||
|
||||
|
||||
def _read_info(self, field):
|
||||
if field in {'size', 'mtime'}:
|
||||
size = sum((f.size for f in self._all_items()), 0)
|
||||
@@ -208,31 +209,31 @@ class Folder(File):
|
||||
# different md5 if a file gets moved in a different subdirectory.
|
||||
def get_dir_md5_concat():
|
||||
items = self._all_items()
|
||||
items.sort(key=lambda f:f.path)
|
||||
items.sort(key=lambda f: f.path)
|
||||
md5s = [getattr(f, field) for f in items]
|
||||
return b''.join(md5s)
|
||||
|
||||
|
||||
md5 = hashlib.md5(get_dir_md5_concat())
|
||||
digest = md5.digest()
|
||||
setattr(self, field, digest)
|
||||
|
||||
|
||||
@property
|
||||
def subfolders(self):
|
||||
if self._subfolders is None:
|
||||
subfolders = [p for p in self.path.listdir() if not p.islink() and p.isdir()]
|
||||
self._subfolders = [self.__class__(p) for p in subfolders]
|
||||
return self._subfolders
|
||||
|
||||
|
||||
@classmethod
|
||||
def can_handle(cls, path):
|
||||
return not path.islink() and path.isdir()
|
||||
|
||||
|
||||
|
||||
def get_file(path, fileclasses=[File]):
|
||||
"""Wraps ``path`` around its appropriate :class:`File` class.
|
||||
|
||||
|
||||
Whether a class is "appropriate" is decided by :meth:`File.can_handle`
|
||||
|
||||
|
||||
:param Path path: path to wrap
|
||||
:param fileclasses: List of candidate :class:`File` classes
|
||||
"""
|
||||
@@ -242,7 +243,7 @@ def get_file(path, fileclasses=[File]):
|
||||
|
||||
def get_files(path, fileclasses=[File]):
|
||||
"""Returns a list of :class:`File` for each file contained in ``path``.
|
||||
|
||||
|
||||
:param Path path: path to scan
|
||||
:param fileclasses: List of candidate :class:`File` classes
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user