File: FileHandler.py

package info (click to toggle)
uranium 5.0.0-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,304 kB
  • sloc: python: 31,765; sh: 132; makefile: 12
file content (195 lines) | stat: -rw-r--r-- 8,153 bytes parent folder | download
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# Copyright (c) 2021 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.

from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING, cast

from .FileReader import FileReader
from .FileWriter import FileWriter
from PyQt6.QtCore import QFileInfo, QObject, pyqtProperty, pyqtSlot, QUrl

from UM.Logger import Logger
from UM.Platform import Platform
from UM.PluginRegistry import PluginRegistry

from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("uranium")

if TYPE_CHECKING:
    from UM.Qt.QtApplication import QtApplication


def resolveAnySymlink(file_name: str) -> str:
    """ Some OSs have different extensions for linked files (for example, one possibility is .lnk-files on Windows).
    In such cases, the literal .lnk file will be attempted to open, not the file this resolves:
    """

    info = QFileInfo(file_name)
    if info.exists() and info.isSymLink():  # Use 'exists': Not certain to be a literal file, depending on subclass.
        file_name = info.symLinkTarget()
    return file_name


class FileHandler(QObject):
    """Central class for reading and writing meshes.
    This class is created by Application and handles reading and writing mesh files.
    """

    def __init__(self, application: "QtApplication", writer_type: str = "unknown_file_writer", reader_type: str = "unknown_file_reader", parent: QObject = None) -> None:
        if cast(FileHandler, self.__class__).__instance is not None:
            raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)

        super().__init__(parent)
        cast(FileHandler, self.__class__).__instance = self

        self._application = application
        self._readers = {} # type: Dict[str, FileReader]
        self._writers = {} # type: Dict[str, FileWriter]

        self._writer_type = writer_type # type: str
        self._reader_type = reader_type # type: str

        self._add_to_recent_files_hints = [] # type: List[QUrl]

        PluginRegistry.addType(self._writer_type, self.addWriter)
        PluginRegistry.addType(self._reader_type, self.addReader)

    @pyqtProperty("QStringList", constant = True)
    def supportedReadFileTypes(self) -> List[str]:
        file_types = []
        all_types = []

        if Platform.isLinux():
            for ext, desc in self.getSupportedFileTypesRead().items():
                file_types.append("{0} (*.{1} *.{2})".format(desc, ext.lower(), ext.upper()))
                all_types.append("*.{0} *.{1}".format(ext.lower(), ext.upper()))
        else:
            for ext, desc in self.getSupportedFileTypesRead().items():
                file_types.append("{0} (*.{1})".format(desc, ext))
                all_types.append("*.{0}".format(ext))

        file_types.sort()
        file_types.insert(0, i18n_catalog.i18nc("@item:inlistbox", "All Supported Types ({0})", " ".join(all_types)))
        file_types.append(i18n_catalog.i18nc("@item:inlistbox", "All Files (*)"))

        return file_types

    @pyqtProperty("QStringList", constant = True)
    def supportedWriteFileTypes(self) -> List[str]:
        file_types = []

        for item in self.getSupportedFileTypesWrite():
            file_types.append("{0} (*.{1})".format(item["description"], item["extension"]))

        file_types.sort()

        return file_types

    @pyqtSlot(QUrl, bool)
    @pyqtSlot(QUrl)
    def readLocalFile(self, file: QUrl, add_to_recent_files_hint: bool = True) -> None:
        if not file.isValid():
            return
        if add_to_recent_files_hint:
            self._add_to_recent_files_hints.append(file)
        self._readLocalFile(file, add_to_recent_files_hint)

    def _readLocalFile(self, file: QUrl, add_to_recent_files_hint: bool = True) -> None:
        raise NotImplementedError("_readLocalFile needs to be implemented by subclasses")

    def getSupportedFileTypesWrite(self) -> List[Dict[str, Union[str, int]]]:
        """Get list of all supported filetypes for writing.
        :return: List of dicts containing id, extension, description and mime_type for all supported file types.
        """

        supported_types = []
        meta_data = PluginRegistry.getInstance().getAllMetaData(filter={self._writer_type: {}}, active_only=True)
        for entry in meta_data:
            for output in entry[self._writer_type].get("output", []):
                ext = output.get("extension", "")
                description = output.get("description", ext)
                mime_type = output.get("mime_type", "text/plain")
                mode = output.get("mode", FileWriter.OutputMode.TextMode)
                hide_in_file_dialog = output.get("hide_in_file_dialog", False)
                supported_types.append({
                    "id": entry["id"],
                    "extension": ext,
                    "description": description,
                    "mime_type": mime_type,
                    "mode": mode,
                    "hide_in_file_dialog": hide_in_file_dialog,
                })
        return supported_types

    # Get list of all supported file types for reading.
    # \returns For each supported file type, the description of the plug-in.
    def getSupportedFileTypesRead(self) -> Dict[str, str]:
        supported_types = {}
        meta_data = PluginRegistry.getInstance().getAllMetaData(filter={self._reader_type: {}}, active_only=True)
        for entry in meta_data:
            if self._reader_type in entry:
                for input_type in entry[self._reader_type]:
                    ext = input_type.get("extension", None)
                    if ext:
                        description = input_type.get("description", ext)
                        supported_types[ext] = description
        return supported_types

    def addReader(self, reader: "FileReader") -> None:
        self._readers[reader.getPluginId()] = reader

    def addWriter(self, writer: "FileWriter") -> None:
        self._writers[writer.getPluginId()] = writer

    # Try to read the data from a file using a specified Reader.
    # \param reader Reader to read the file with.
    # \param file_name The name of the file to load.
    # \param kwargs Keyword arguments.
    # \returns None if nothing was found
    def readerRead(self, reader, file_name: str, **kwargs: Any):
        raise NotImplementedError("readerRead must be implemented by subclasses.")

    def getWriterByMimeType(self, mime: str) -> Optional["FileWriter"]:
        """Get a mesh writer object that supports writing the specified mime type

        :param mime: The mime type that should be supported.
        :return: A FileWriter instance or None if no mesh writer supports the specified mime type. If there are multiple
        writers that support the specified mime type, the first entry is returned.
        """

        writer_data = PluginRegistry.getInstance().getAllMetaData(filter={self._writer_type: {}}, active_only=True)
        for entry in writer_data:
            for output in entry[self._writer_type].get("output", []):
                if mime == output["mime_type"]:
                    return self._writers[entry["id"]]

        return None

    def getWriter(self, writer_id: str) -> Optional["FileWriter"]:
        """Get an instance of a mesh writer by ID"""

        if writer_id not in self._writers:
            return None

        return self._writers[writer_id]

    def getReaderForFile(self, file_name: str) -> Optional["FileReader"]:
        """Find a Reader that accepts the given file name.
        :param file_name: The name of file to load.
        :returns: Reader that accepts the given file name. If no acceptable Reader is found None is returned.
        """

        file_name = resolveAnySymlink(file_name)
        for id, reader in self._readers.items():
            try:
                if reader.acceptsFile(file_name):
                    return reader
            except Exception as e:
                Logger.log("e", str(e))

        return None

    __instance = None   # type: FileHandler

    @classmethod
    def getInstance(cls, *args, **kwargs) -> "FileHandler":
        return cls.__instance