Add reflink routine

This commit is contained in:
Ruben De Smet 2017-07-28 16:51:19 +02:00
parent 2926b49628
commit 5e2856ef87
4 changed files with 85 additions and 1 deletions

View file

@ -223,7 +223,8 @@ class ImportSession(object):
iconfig['incremental'] = False
if iconfig['reflink']:
iconfig['reflink'] = iconfig['reflink'].as_choice(['auto', True, False])
iconfig['reflink'] = iconfig['reflink'] \
.as_choice(['auto', True, False])
# Copy, move, reflink, link, and hardlink are mutually exclusive.
if iconfig['move']:

View file

@ -747,6 +747,20 @@ class Item(LibModel):
util.hardlink(self.path, dest)
plugins.send("item_hardlinked", item=self, source=self.path,
destination=dest)
elif operation == MoveOperation.REFLINK:
util.reflink(self.path, dest, fallback=False)
plugins.send("item_reflinked", item=self, source=self.path,
destination=dest)
elif operation == MoveOperation.REFLINK_AUTO:
util.reflink(self.path, dest, fallback=True)
plugins.send("item_reflinked", item=self, source=self.path,
destination=dest)
else:
plugins.send("before_item_moved", item=self, source=self.path,
destination=dest)
util.move(self.path, dest)
plugins.send("item_moved", item=self, source=self.path,
destination=dest)
# Either copying or moving succeeded, so update the stored path.
self.path = dest
@ -1087,6 +1101,12 @@ class Album(LibModel):
util.link(old_art, new_art)
elif operation == MoveOperation.HARDLINK:
util.hardlink(old_art, new_art)
elif operation == MoveOperation.REFLINK:
util.reflink(old_art, new_art, fallback=False)
elif operation == MoveOperation.REFLINK_AUTO:
util.reflink(old_art, new_art, fallback=True)
else:
util.move(old_art, new_art)
self.artpath = new_art
def move(self, operation=MoveOperation.MOVE, basedir=None, store=True):

View file

@ -34,6 +34,7 @@ from beets.util import hidden
import six
from unidecode import unidecode
from enum import Enum
import reflink as pyreflink
MAX_FILENAME_LENGTH = 200
@ -547,6 +548,28 @@ def hardlink(path, dest, replace=False):
traceback.format_exc())
def reflink(path, dest, replace=False, fallback=False):
"""Create a reflink from `dest` to `path`. Raises an `OSError` if
`dest` already exists, unless `replace` is True. Does nothing if
`path` == `dest`. When `fallback` is True, `reflink` falls back on
`copy` when the filesystem does not support reflinks.
"""
if samefile(path, dest):
return
if os.path.exists(syspath(dest)) and not replace:
raise FilesystemError(u'file exists', 'rename', (path, dest))
try:
pyreflink.reflink(path, dest)
except (NotImplementedError, pyreflink.ReflinkImpossibleError) as exc:
if fallback:
copy(path, dest, replace)
else:
raise FilesystemError(u'OS/filesystem does not support reflinks.',
'link', (path, dest), traceback.format_exc())
def unique_path(path):
"""Returns a version of ``path`` that does not exist on the
filesystem. Specifically, if ``path` itself already exists, then

View file

@ -86,6 +86,24 @@ class MoveTest(_common.TestCase):
self.i.move(operation=MoveOperation.COPY)
self.assertExists(self.path)
def test_reflink_arrives(self):
self.i.move(operation=MoveOperation.REFLINK_AUTO)
self.assertExists(self.dest)
def test_reflink_does_not_depart(self):
self.i.move(operation=MoveOperation.REFLINK_AUTO)
self.assertExists(self.path)
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink")
def test_force_reflink_arrives(self):
self.i.move(operation=MoveOperation.REFLINK)
self.assertExists(self.dest)
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink")
def test_force_reflink_does_not_depart(self):
self.i.move(operation=MoveOperation.REFLINK)
self.assertExists(self.path)
def test_move_changes_path(self):
self.i.move()
self.assertEqual(self.i.path, util.normpath(self.dest))
@ -249,6 +267,17 @@ class AlbumFileTest(_common.TestCase):
self.assertTrue(os.path.exists(oldpath))
self.assertTrue(os.path.exists(self.i.path))
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink")
def test_albuminfo_move_reflinks_file(self):
oldpath = self.i.path
self.ai.album = u'newAlbumName'
self.ai.move(operation=MoveOperation.REFLINK)
self.ai.store()
self.i.load()
self.assertTrue(os.path.exists(oldpath))
self.assertTrue(os.path.exists(self.i.path))
def test_albuminfo_move_to_custom_dir(self):
self.ai.move(basedir=self.otherdir)
self.i.load()
@ -530,6 +559,12 @@ class SafeMoveCopyTest(_common.TestCase):
self.assertExists(self.dest)
self.assertExists(self.path)
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink")
def test_successful_reflink(self):
util.reflink(self.path, self.dest)
self.assertExists(self.dest)
self.assertExists(self.path)
def test_unsuccessful_move(self):
with self.assertRaises(util.FilesystemError):
util.move(self.path, self.otherpath)
@ -538,6 +573,11 @@ class SafeMoveCopyTest(_common.TestCase):
with self.assertRaises(util.FilesystemError):
util.copy(self.path, self.otherpath)
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink")
def test_unsuccessful_reflink(self):
with self.assertRaises(util.FilesystemError):
util.reflink(self.path, self.otherpath)
def test_self_move(self):
util.move(self.path, self.path)
self.assertExists(self.path)