File size: 2,881 Bytes
edc06cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
"""
リソースファイルを管理する。
"""

import base64
import json
from hashlib import sha256
from pathlib import Path
from typing import Literal


class ResourceManagerError(Exception):
    def __init__(self, message: str):
        self.message = message


def b64encode_str(s: bytes) -> str:
    return base64.b64encode(s).decode("utf-8")


class ResourceManager:
    """
    リソースファイルのパスと、一意なハッシュ値の対応(filemap)を管理する。

    APIでリソースファイルを一意なURLとして返すときに使う。
    ついでにファイルをbase64文字列に変換することもできる。
    """

    def __init__(self, create_filemap_if_not_exist: bool) -> None:
        """
        Parameters
        ----------
        create_filemap_if_not_exist : bool
            `filemap.json`がない場合でも登録時にfilemapを生成するか(開発時を想定)
        """
        self._create_filemap_if_not_exist = create_filemap_if_not_exist
        self._path_to_hash: dict[Path, str] = {}
        self._hash_to_path: dict[str, Path] = {}

    def register_dir(self, resource_dir: Path) -> None:
        """ディレクトリをfilemapに登録する"""
        filemap_json = resource_dir / "filemap.json"
        if filemap_json.exists():
            data: dict[str, str] = json.loads(filemap_json.read_bytes())
            self._path_to_hash |= {resource_dir / k: v for k, v in data.items()}
        elif self._create_filemap_if_not_exist:
            self._path_to_hash |= {
                i: sha256(i.read_bytes()).digest().hex()
                for i in resource_dir.rglob("*")
                if i.is_file()
            }
        else:
            raise ResourceManagerError(f"{filemap_json}が見つかりません")

        self._hash_to_path |= {v: k for k, v in self._path_to_hash.items()}

    def resource_str(
        self,
        resource_path: Path,
        resource_format: Literal["base64", "hash"],
    ) -> str:
        """指定したリソースファイルのbase64文字列やハッシュ値を返す。"""
        # NOTE: 意図しないパスのファイルの結果を返さないようにする
        filehash = self._path_to_hash.get(resource_path)
        if filehash is None:
            raise ResourceManagerError(f"{resource_path}がfilemapに登録されていません")

        if resource_format == "base64":
            return b64encode_str(resource_path.read_bytes())
        return filehash

    def resource_path(self, filehash: str) -> Path:
        """指定したハッシュ値を持つリソースファイルのパスを返す。"""
        resource_path = self._hash_to_path.get(filehash)

        if resource_path is None:
            raise ResourceManagerError(f"'{filehash}'に対応するリソースがありません")
        return resource_path