Exemple #1
0
    def test_windows_atomic_move(self):
        'Test book file open in another process when changing metadata'
        cl = self.cloned_library
        cache = self.init_cache(cl)
        fpath = cache.format_abspath(1, 'FMT1')
        f = open(fpath, 'rb')
        with self.assertRaises(IOError):
            cache.set_field('title', {1:'Moved'})
        with self.assertRaises(IOError):
            cache.remove_books({1})
        f.close()
        self.assertNotEqual(cache.field_for('title', 1), 'Moved', 'Title was changed despite file lock')

        # Test on folder with hardlinks
        from calibre.ptempfile import TemporaryDirectory
        from calibre.utils.filenames import hardlink_file, WindowsAtomicFolderMove
        raw = b'xxx'
        with TemporaryDirectory() as tdir1, TemporaryDirectory() as tdir2:
            a, b = os.path.join(tdir1, 'a'), os.path.join(tdir1, 'b')
            a = os.path.join(tdir1, 'a')
            with open(a, 'wb') as f:
                f.write(raw)
            hardlink_file(a, b)
            wam = WindowsAtomicFolderMove(tdir1)
            wam.copy_path_to(a, os.path.join(tdir2, 'a'))
            wam.copy_path_to(b, os.path.join(tdir2, 'b'))
            wam.delete_originals()
            self.assertEqual([], os.listdir(tdir1))
            self.assertEqual({'a', 'b'}, set(os.listdir(tdir2)))
            self.assertEqual(raw, open(os.path.join(tdir2, 'a'), 'rb').read())
            self.assertEqual(raw, open(os.path.join(tdir2, 'b'), 'rb').read())
Exemple #2
0
    def test_windows_atomic_move(self):
        "Test book file open in another process when changing metadata"
        cl = self.cloned_library
        cache = self.init_cache(cl)
        fpath = cache.format_abspath(1, "FMT1")
        f = open(fpath, "rb")
        with self.assertRaises(IOError):
            cache.set_field("title", {1: "Moved"})
        with self.assertRaises(IOError):
            cache.remove_books({1})
        f.close()
        self.assertNotEqual(cache.field_for("title", 1), "Moved", "Title was changed despite file lock")

        # Test on folder with hardlinks
        from calibre.ptempfile import TemporaryDirectory
        from calibre.utils.filenames import hardlink_file, WindowsAtomicFolderMove

        raw = b"xxx"
        with TemporaryDirectory() as tdir1, TemporaryDirectory() as tdir2:
            a, b = os.path.join(tdir1, "a"), os.path.join(tdir1, "b")
            a = os.path.join(tdir1, "a")
            with open(a, "wb") as f:
                f.write(raw)
            hardlink_file(a, b)
            wam = WindowsAtomicFolderMove(tdir1)
            wam.copy_path_to(a, os.path.join(tdir2, "a"))
            wam.copy_path_to(b, os.path.join(tdir2, "b"))
            wam.delete_originals()
            self.assertEqual([], os.listdir(tdir1))
            self.assertEqual({"a", "b"}, set(os.listdir(tdir2)))
            self.assertEqual(raw, open(os.path.join(tdir2, "a"), "rb").read())
            self.assertEqual(raw, open(os.path.join(tdir2, "b"), "rb").read())
Exemple #3
0
 def windows_check_if_files_in_use(self, paths):
     '''
     Raises an EACCES IOError if any of the files in the folder of book_id
     are opened in another program on windows.
     '''
     if iswindows:
         for path in paths:
             spath = os.path.join(self.library_path, *path.split('/'))
             wam = None
             if os.path.exists(spath):
                 try:
                     wam = WindowsAtomicFolderMove(spath)
                 finally:
                     if wam is not None:
                         wam.close_handles()
Exemple #4
0
 def windows_check_if_files_in_use(self, paths):
     '''
     Raises an EACCES IOError if any of the files in the folder of book_id
     are opened in another program on windows.
     '''
     if iswindows:
         for path in paths:
             spath = os.path.join(self.library_path, *path.split('/'))
             wam = None
             if os.path.exists(spath):
                 try:
                     wam = WindowsAtomicFolderMove(spath)
                 finally:
                     if wam is not None:
                         wam.close_handles()
Exemple #5
0
    def test_windows_atomic_move(self):
        'Test book file open in another process when changing metadata'
        cl = self.cloned_library
        cache = self.init_cache(cl)
        fpath = cache.format_abspath(1, 'FMT1')
        f = open(fpath, 'rb')
        with self.assertRaises(IOError):
            cache.set_field('title', {1:'Moved'})
        with self.assertRaises(IOError):
            cache.remove_books({1})
        f.close()
        self.assertNotEqual(cache.field_for('title', 1), 'Moved', 'Title was changed despite file lock')

        # Test on folder with hardlinks
        from calibre.ptempfile import TemporaryDirectory
        from calibre.utils.filenames import hardlink_file, WindowsAtomicFolderMove
        raw = b'xxx'
        with TemporaryDirectory() as tdir1, TemporaryDirectory() as tdir2:
            a, b = os.path.join(tdir1, 'a'), os.path.join(tdir1, 'b')
            a = os.path.join(tdir1, 'a')
            with open(a, 'wb') as f:
                f.write(raw)
            hardlink_file(a, b)
            wam = WindowsAtomicFolderMove(tdir1)
            wam.copy_path_to(a, os.path.join(tdir2, 'a'))
            wam.copy_path_to(b, os.path.join(tdir2, 'b'))
            wam.delete_originals()
            self.assertEqual([], os.listdir(tdir1))
            self.assertEqual({'a', 'b'}, set(os.listdir(tdir2)))
            self.assertEqual(raw, open(os.path.join(tdir2, 'a'), 'rb').read())
            self.assertEqual(raw, open(os.path.join(tdir2, 'b'), 'rb').read())
Exemple #6
0
    def update_path(self, book_id, title, author, path_field, formats_field):
        path = self.construct_path_name(book_id, title, author)
        current_path = path_field.for_book(book_id)
        formats = formats_field.for_book(book_id, default_value=())
        fname = self.construct_file_name(book_id, title, author)
        # Check if the metadata used to construct paths has changed
        changed = False
        for fmt in formats:
            name = formats_field.format_fname(book_id, fmt)
            if name and name != fname:
                changed = True
                break
        if path == current_path and not changed:
            return
        spath = os.path.join(self.library_path, *current_path.split('/'))
        tpath = os.path.join(self.library_path, *path.split('/'))

        source_ok = current_path and os.path.exists(spath)
        wam = WindowsAtomicFolderMove(
            spath) if iswindows and source_ok else None
        try:
            if not os.path.exists(tpath):
                os.makedirs(tpath)

            if source_ok:  # Migrate existing files
                dest = os.path.join(tpath, 'cover.jpg')
                self.copy_cover_to(current_path,
                                   dest,
                                   windows_atomic_move=wam,
                                   use_hardlink=True)
                for fmt in formats:
                    dest = os.path.join(tpath, fname + '.' + fmt.lower())
                    self.copy_format_to(book_id,
                                        fmt,
                                        formats_field.format_fname(
                                            book_id, fmt),
                                        current_path,
                                        dest,
                                        windows_atomic_move=wam,
                                        use_hardlink=True)
            # Update db to reflect new file locations
            for fmt in formats:
                formats_field.table.set_fname(book_id, fmt, fname, self)
            path_field.table.set_path(book_id, path, self)

            # Delete not needed directories
            if source_ok:
                if os.path.exists(spath) and not samefile(spath, tpath):
                    if wam is not None:
                        wam.delete_originals()
                    self.rmtree(spath, permanent=True)
                    parent = os.path.dirname(spath)
                    if len(os.listdir(parent)) == 0:
                        self.rmtree(parent, permanent=True)
        finally:
            if wam is not None:
                wam.close_handles()

        curpath = self.library_path
        c1, c2 = current_path.split('/'), path.split('/')
        if not self.is_case_sensitive and len(c1) == len(c2):
            # On case-insensitive systems, title and author renames that only
            # change case don't cause any changes to the directories in the file
            # system. This can lead to having the directory names not match the
            # title/author, which leads to trouble when libraries are copied to
            # a case-sensitive system. The following code attempts to fix this
            # by checking each segment. If they are different because of case,
            # then rename the segment. Note that the code above correctly
            # handles files in the directories, so no need to do them here.
            for oldseg, newseg in zip(c1, c2):
                if oldseg.lower() == newseg.lower() and oldseg != newseg:
                    try:
                        os.rename(os.path.join(curpath, oldseg),
                                  os.path.join(curpath, newseg))
                    except:
                        break  # Fail silently since nothing catastrophic has happened
                curpath = os.path.join(curpath, newseg)
Exemple #7
0
    def update_path(self, book_id, title, author, path_field, formats_field):
        path = self.construct_path_name(book_id, title, author)
        current_path = path_field.for_book(book_id)
        formats = formats_field.for_book(book_id, default_value=())
        fname = self.construct_file_name(book_id, title, author)
        # Check if the metadata used to construct paths has changed
        changed = False
        for fmt in formats:
            name = formats_field.format_fname(book_id, fmt)
            if name and name != fname:
                changed = True
                break
        if path == current_path and not changed:
            return
        spath = os.path.join(self.library_path, *current_path.split('/'))
        tpath = os.path.join(self.library_path, *path.split('/'))

        source_ok = current_path and os.path.exists(spath)
        wam = WindowsAtomicFolderMove(spath) if iswindows and source_ok else None
        try:
            if not os.path.exists(tpath):
                os.makedirs(tpath)

            if source_ok:  # Migrate existing files
                dest = os.path.join(tpath, 'cover.jpg')
                self.copy_cover_to(current_path, dest,
                        windows_atomic_move=wam, use_hardlink=True)
                for fmt in formats:
                    dest = os.path.join(tpath, fname+'.'+fmt.lower())
                    self.copy_format_to(book_id, fmt, formats_field.format_fname(book_id, fmt), current_path,
                                        dest, windows_atomic_move=wam, use_hardlink=True)
            # Update db to reflect new file locations
            for fmt in formats:
                formats_field.table.set_fname(book_id, fmt, fname, self)
            path_field.table.set_path(book_id, path, self)

            # Delete not needed directories
            if source_ok:
                if os.path.exists(spath) and not samefile(spath, tpath):
                    if wam is not None:
                        wam.delete_originals()
                    self.rmtree(spath, permanent=True)
                    parent = os.path.dirname(spath)
                    if len(os.listdir(parent)) == 0:
                        self.rmtree(parent, permanent=True)
        finally:
            if wam is not None:
                wam.close_handles()

        curpath = self.library_path
        c1, c2 = current_path.split('/'), path.split('/')
        if not self.is_case_sensitive and len(c1) == len(c2):
            # On case-insensitive systems, title and author renames that only
            # change case don't cause any changes to the directories in the file
            # system. This can lead to having the directory names not match the
            # title/author, which leads to trouble when libraries are copied to
            # a case-sensitive system. The following code attempts to fix this
            # by checking each segment. If they are different because of case,
            # then rename the segment. Note that the code above correctly
            # handles files in the directories, so no need to do them here.
            for oldseg, newseg in zip(c1, c2):
                if oldseg.lower() == newseg.lower() and oldseg != newseg:
                    try:
                        os.rename(os.path.join(curpath, oldseg),
                                os.path.join(curpath, newseg))
                    except:
                        break  # Fail silently since nothing catastrophic has happened
                curpath = os.path.join(curpath, newseg)