示例#1
0
    def test_not_a_dir(self):
        empty_file = open(Path(self.test_dir.name) / 'empty', 'a')

        with self.assertRaises(NotADirectoryError):
            Carousel(Path(empty_file.name))

        empty_file.close()
示例#2
0
    def test_with_images(self):
        filenames = ["01.png", "01.xml", "03.jpg", "04.pdf", "foo"]
        for name in filenames:
            (Path(self.test_dir.name) / name).touch()

        specimen = Carousel(Path(self.test_dir.name))

        # Verify that the image files have been picked-up by the object
        self.assertEqual({"01.png", "03.jpg"},
                         set(map(lambda p: p.name, specimen._image_files)))
示例#3
0
文件: view.py 项目: nessdoor/HImaKura
    def __init__(self,
                 context_dir: Path,
                 filter_factory: Optional[FilterBuilder] = None):
        """
        Instantiate a new view over the image/metadata file pairs at the specified path.

        The new view will have most of its data uninitialized, since to load them means to start scanning the contents.
        Therefore, before attempting to retrieve any data, call the `load_next()` method.

        Optionally, a `FilterBuilder` can be provided as a second argument, which will be used for obtaining image
        filters.

        :arg context_dir: path to the directory under which all operations will be performed
        :arg filter_factory: a filter builder providing filters for the new view
        :raise FileNotFoundError: when the path points to an invalid location
        :raise NotADirectoryException: when the path point to a file that is not a directory
        """

        # If given a filter provider, use it to generate a set of filters and apply them on the carousel
        if filter_factory is not None:
            self._carousel = Carousel(context_dir,
                                      filter_factory.get_all_filters())
        else:
            self._carousel = Carousel(context_dir)
示例#4
0
    def test_filter(self):
        filenames_meta = [("included.png", "included"),
                          ("excluded.jpg", "excluded")]
        for name, meta in filenames_meta:
            img_path = (Path(self.test_dir.name) / name)
            img_path.touch()
            # Use the author field as the filtering target
            write_meta(ImageMetadata(uuid4(), name, meta, None, None, None),
                       img_path)

        specimen = Carousel(Path(self.test_dir.name),
                            [lambda meta: meta.author == "included"])

        self.assertEqual([Path(self.test_dir.name) / "included.png"],
                         specimen._image_files)
示例#5
0
文件: view.py 项目: nessdoor/HImaKura
class View(metaclass=ABCMeta):
    """
    The state of the current view.

    It wraps the inner carousel and retrieves images and metadata objects, exposing them to the frontend.

    This class is meant to be subclassed by UI concrete implementations.
    """

    _carousel: Carousel
    _image_path: Path

    # Image metadata
    _id: UUID
    # TODO: this is inconsistent with the real metadata, as there's a URI there, now; rethink these attributes
    _filename: str
    author: Optional[str]
    universe: Optional[str]
    characters: Optional[Iterable[str]]
    tags: Optional[Iterable[str]]

    def __init__(self,
                 context_dir: Path,
                 filter_factory: Optional[FilterBuilder] = None):
        """
        Instantiate a new view over the image/metadata file pairs at the specified path.

        The new view will have most of its data uninitialized, since to load them means to start scanning the contents.
        Therefore, before attempting to retrieve any data, call the `load_next()` method.

        Optionally, a `FilterBuilder` can be provided as a second argument, which will be used for obtaining image
        filters.

        :arg context_dir: path to the directory under which all operations will be performed
        :arg filter_factory: a filter builder providing filters for the new view
        :raise FileNotFoundError: when the path points to an invalid location
        :raise NotADirectoryException: when the path point to a file that is not a directory
        """

        # If given a filter provider, use it to generate a set of filters and apply them on the carousel
        if filter_factory is not None:
            self._carousel = Carousel(context_dir,
                                      filter_factory.get_all_filters())
        else:
            self._carousel = Carousel(context_dir)

    def _update_meta(self, meta: ImageMetadata) -> None:
        self._id = meta.img_id
        self._filename = meta.file.path.name
        self.author = meta.author
        self.universe = meta.universe
        self.characters = meta.characters
        self.tags = meta.tags

    @abstractmethod
    def has_image_data(self) -> bool:
        """Tell if the current view contains valid image data."""

        pass

    def has_prev(self) -> bool:
        """Check whether there is a previous image."""

        return self._carousel.has_prev()

    def has_next(self) -> bool:
        """Check whether there is a next image."""

        return self._carousel.has_next()

    def load_prev(self) -> None:
        """
        Retrieve the previous image and its metadata.

        :raise StopIteration: when the start of the collection has already been reached
        """

        self._image_path = self._carousel.prev()
        self._update_meta(load_meta(self._image_path))

    def load_next(self) -> None:
        """Retrieve the next image and its metadata.

        :raise StopIteration: when the end of the collection has already been reached
        """

        self._image_path = self._carousel.next()
        self._update_meta(load_meta(self._image_path))

    @property
    def image_id(self) -> UUID:
        return self._id

    @property
    def filename(self) -> str:
        return self._filename

    @abstractmethod
    def get_image_data(self):
        """Retrieve the currently-displayed image, if available."""

        pass

    def set_author(self, author: str) -> None:
        if len(author) == 0:
            author = None
        else:
            author = remove_control_and_redundant_space(author)

        self.author = author

    def get_author(self) -> Optional[str]:
        return self.author

    def set_universe(self, universe: str) -> None:
        if len(universe) == 0:
            universe = None
        else:
            universe = remove_control_and_redundant_space(universe)

        self.universe = universe

    def get_universe(self) -> Optional[str]:
        return self.universe

    def set_characters(self, characters: str) -> None:
        """Take a comma-separated list of characters in input and use it to update the metadata."""

        if len(characters) == 0:
            new_chars = None
        else:
            new_chars = remove_control_and_redundant_space(characters)
            new_chars = new_chars.split(',')
            new_chars = [c.strip() for c in new_chars]

        self.characters = new_chars

    def get_characters(self) -> Optional[str]:
        """Return the characters in a comma-separated list, or None if no character metadata is present."""

        if self.characters is not None:
            return ', '.join(self.characters)
        else:
            return None

    def set_tags(self, tags: str) -> None:
        """Take a comma-separated list of tags in input and use it to update the metadata."""

        if len(tags) == 0:
            new_tags = None
        else:
            new_tags = remove_control_and_redundant_space(tags)
            new_tags = new_tags.split(',')
            new_tags = [t.strip() for t in new_tags]

        self.tags = new_tags

    def get_tags(self) -> Optional[str]:
        """Return the image tags in a comma-separated list, or None if no tag metadata is present."""

        if self.tags is not None:
            return ', '.join(self.tags)
        else:
            return None

    def write(self) -> None:
        """
        Persist the updated metadata.

        :raise OSError: when the metadata file couldn't be opened
        """

        meta_obj = ImageMetadata(self._id, URI(self._image_path), self.author,
                                 self.universe, self.characters, self.tags)
        write_meta(meta_obj, self._image_path)
示例#6
0
    def test_regular_iteration(self):
        filenames = ["01.png", "01.xml", "03.jpg"]
        fobs = []
        for name in filenames:
            fobs.append(open(Path(self.test_dir.name) / name, 'a'))
        # Valid paths that should be produced by an iteration over the entire test directory
        expected_paths = {
            Path(self.test_dir.name) / "01.png",
            Path(self.test_dir.name) / "03.jpg"
        }

        specimen = Carousel(Path(self.test_dir.name))

        # Perform forward iteration until StopIteration
        # This movement must uncover all the images
        full_scan_results = set()
        for _ in range(0, len(expected_paths)):
            self.assertTrue(specimen.has_next())
            full_scan_results.add(specimen.next())

        self.assertFalse(specimen.has_next())
        self.assertRaises(StopIteration, lambda: specimen.next())
        self.assertEqual(expected_paths, full_scan_results)

        # Perform reverse iteration until StopIteration
        # This movement must go back to the first item, not returning the current one again
        reverse_scan_results = set()
        self.assertTrue(specimen.has_prev())
        reverse_scan_results.add(specimen.prev())

        self.assertFalse(specimen.has_prev())
        self.assertRaises(StopIteration, lambda: specimen.prev())
        self.assertTrue(len(reverse_scan_results) > 0)
        self.assertTrue(expected_paths > reverse_scan_results)

        # Perform the movement once again and move forward
        # This movement must move again towards the last item, without returning the current one again
        forward_scan_results = set()
        self.assertTrue(specimen.has_next())
        forward_scan_results.add(specimen.next())

        self.assertFalse(specimen.has_next())
        self.assertRaises(StopIteration, lambda: specimen.next())
        self.assertTrue(len(forward_scan_results) > 0)
        self.assertTrue(expected_paths > forward_scan_results)
        self.assertNotEqual(reverse_scan_results, forward_scan_results)

        for file in fobs:
            file.close()
示例#7
0
    def test_iteration_empty(self):
        specimen = Carousel(Path(self.test_dir.name))

        self.assertRaises(StopIteration, lambda: specimen.prev())
        self.assertRaises(StopIteration, lambda: specimen.next())
示例#8
0
 def test_nonexistent_directory(self):
     with self.assertRaises(FileNotFoundError):
         Carousel(Path(Path(self.test_dir.name) / 'nonexistent'))
示例#9
0
    def test_file_deletion_after_creation(self):
        test_dir_path = Path(self.test_dir.name)
        first = "first.png"
        fo = open(test_dir_path / first, 'a')
        second = "second.png"
        so = open(test_dir_path / second, 'a')
        third = "third.png"
        to = open(test_dir_path / third, 'a')
        fourth = "fourth.png"
        yo = open(test_dir_path / fourth, 'a')

        specimen = Carousel(test_dir_path)
        fo.close()
        os.remove(test_dir_path / first)
        results = set()
        # Perform a full scan
        for _ in range(0, 3):
            results.add(specimen.next())

        # 'First' shouldn't be among the results
        self.assertEqual(
            {
                test_dir_path / second, test_dir_path / third,
                test_dir_path / fourth
            }, results)
        self.assertFalse(specimen.has_next())
        self.assertRaises(StopIteration, lambda: specimen.next())

        so.close()
        os.remove(test_dir_path / second)
        to.close()
        os.remove(test_dir_path / third)

        # Iteration should now jump straight at the fourth element
        self.assertTrue(specimen.has_prev())
        self.assertEqual(test_dir_path / fourth, specimen.prev())
        self.assertFalse(specimen.has_prev())
        self.assertRaises(StopIteration, lambda: specimen.prev())

        yo.close()
        os.remove(test_dir_path / fourth)

        # Carousel should now be stuck
        self.assertFalse(specimen.has_next())
        self.assertRaises(StopIteration, lambda: specimen.next())
        self.assertFalse(specimen.has_prev())
        self.assertRaises(StopIteration, lambda: specimen.prev())