1# -*- coding: utf-8 -*-
2# This file is part of beets.
3# Copyright 2016, Stig Inge Lea Bjornsen.
4#
5# Permission is hereby granted, free of charge, to any person obtaining
6# a copy of this software and associated documentation files (the
7# "Software"), to deal in the Software without restriction, including
8# without limitation the rights to use, copy, modify, merge, publish,
9# distribute, sublicense, and/or sell copies of the Software, and to
10# permit persons to whom the Software is furnished to do so, subject to
11# the following conditions:
12#
13# The above copyright notice and this permission notice shall be
14# included in all copies or substantial portions of the Software.
15
16from __future__ import division, absolute_import, print_function
17
18"""Tests for the `importadded` plugin."""
19
20import os
21import unittest
22
23from test.test_importer import ImportHelper, AutotagStub
24from beets import importer
25from beets import util
26from beetsplug.importadded import ImportAddedPlugin
27
28_listeners = ImportAddedPlugin.listeners
29
30
31def preserve_plugin_listeners():
32    """Preserve the initial plugin listeners as they would otherwise be
33    deleted after the first setup / tear down cycle.
34    """
35    if not ImportAddedPlugin.listeners:
36        ImportAddedPlugin.listeners = _listeners
37
38
39def modify_mtimes(paths, offset=-60000):
40    for i, path in enumerate(paths, start=1):
41        mstat = os.stat(path)
42        os.utime(path, (mstat.st_atime, mstat.st_mtime + offset * i))
43
44
45class ImportAddedTest(unittest.TestCase, ImportHelper):
46
47    # The minimum mtime of the files to be imported
48    min_mtime = None
49
50    def setUp(self):
51        preserve_plugin_listeners()
52        self.setup_beets()
53        self.load_plugins('importadded')
54        self._create_import_dir(2)
55        # Different mtimes on the files to be imported in order to test the
56        # plugin
57        modify_mtimes((mfile.path for mfile in self.media_files))
58        self.min_mtime = min(os.path.getmtime(mfile.path)
59                             for mfile in self.media_files)
60        self.matcher = AutotagStub().install()
61        self.matcher.macthin = AutotagStub.GOOD
62        self._setup_import_session()
63        self.importer.add_choice(importer.action.APPLY)
64
65    def tearDown(self):
66        self.unload_plugins()
67        self.teardown_beets()
68        self.matcher.restore()
69
70    def find_media_file(self, item):
71        """Find the pre-import MediaFile for an Item"""
72        for m in self.media_files:
73            if m.title.replace('Tag', 'Applied') == item.title:
74                return m
75        raise AssertionError(u"No MediaFile found for Item " +
76                             util.displayable_path(item.path))
77
78    def assertEqualTimes(self, first, second, msg=None):  # noqa
79        """For comparing file modification times at a sufficient precision"""
80        self.assertAlmostEqual(first, second, places=4, msg=msg)
81
82    def assertAlbumImport(self):  # noqa
83        self.importer.run()
84        album = self.lib.albums().get()
85        self.assertEqual(album.added, self.min_mtime)
86        for item in album.items():
87            self.assertEqual(item.added, self.min_mtime)
88
89    def test_import_album_with_added_dates(self):
90        self.assertAlbumImport()
91
92    def test_import_album_inplace_with_added_dates(self):
93        self.config['import']['copy'] = False
94        self.config['import']['move'] = False
95        self.config['import']['link'] = False
96        self.config['import']['hardlink'] = False
97        self.assertAlbumImport()
98
99    def test_import_album_with_preserved_mtimes(self):
100        self.config['importadded']['preserve_mtimes'] = True
101        self.importer.run()
102        album = self.lib.albums().get()
103        self.assertEqual(album.added, self.min_mtime)
104        for item in album.items():
105            self.assertEqualTimes(item.added, self.min_mtime)
106            mediafile_mtime = os.path.getmtime(self.find_media_file(item).path)
107            self.assertEqualTimes(item.mtime, mediafile_mtime)
108            self.assertEqualTimes(os.path.getmtime(item.path),
109                                  mediafile_mtime)
110
111    def test_reimported_album_skipped(self):
112        # Import and record the original added dates
113        self.importer.run()
114        album = self.lib.albums().get()
115        album_added_before = album.added
116        items_added_before = dict((item.path, item.added)
117                                  for item in album.items())
118        # Newer Item path mtimes as if Beets had modified them
119        modify_mtimes(items_added_before.keys(), offset=10000)
120        # Reimport
121        self._setup_import_session(import_dir=album.path)
122        self.importer.run()
123        # Verify the reimported items
124        album = self.lib.albums().get()
125        self.assertEqualTimes(album.added, album_added_before)
126        items_added_after = dict((item.path, item.added)
127                                 for item in album.items())
128        for item_path, added_after in items_added_after.items():
129            self.assertEqualTimes(items_added_before[item_path], added_after,
130                                  u"reimport modified Item.added for " +
131                                  util.displayable_path(item_path))
132
133    def test_import_singletons_with_added_dates(self):
134        self.config['import']['singletons'] = True
135        self.importer.run()
136        for item in self.lib.items():
137            mfile = self.find_media_file(item)
138            self.assertEqualTimes(item.added, os.path.getmtime(mfile.path))
139
140    def test_import_singletons_with_preserved_mtimes(self):
141        self.config['import']['singletons'] = True
142        self.config['importadded']['preserve_mtimes'] = True
143        self.importer.run()
144        for item in self.lib.items():
145            mediafile_mtime = os.path.getmtime(self.find_media_file(item).path)
146            self.assertEqualTimes(item.added, mediafile_mtime)
147            self.assertEqualTimes(item.mtime, mediafile_mtime)
148            self.assertEqualTimes(os.path.getmtime(item.path),
149                                  mediafile_mtime)
150
151    def test_reimported_singletons_skipped(self):
152        self.config['import']['singletons'] = True
153        # Import and record the original added dates
154        self.importer.run()
155        items_added_before = dict((item.path, item.added)
156                                  for item in self.lib.items())
157        # Newer Item path mtimes as if Beets had modified them
158        modify_mtimes(items_added_before.keys(), offset=10000)
159        # Reimport
160        import_dir = os.path.dirname(list(items_added_before.keys())[0])
161        self._setup_import_session(import_dir=import_dir, singletons=True)
162        self.importer.run()
163        # Verify the reimported items
164        items_added_after = dict((item.path, item.added)
165                                 for item in self.lib.items())
166        for item_path, added_after in items_added_after.items():
167            self.assertEqualTimes(items_added_before[item_path], added_after,
168                                  u"reimport modified Item.added for " +
169                                  util.displayable_path(item_path))
170
171
172def suite():
173    return unittest.TestLoader().loadTestsFromName(__name__)
174
175if __name__ == '__main__':
176    unittest.main(defaultTest='suite')
177