maya-umbrella 0.5.0__py2.py3-none-any.whl → 0.6.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of maya-umbrella might be problematic. Click here for more details.

@@ -0,0 +1,161 @@
1
+ # Import built-in modules
2
+ from contextlib import contextmanager
3
+ import logging
4
+
5
+ # Import local modules
6
+ from maya_umbrella.cleaner import MayaVirusCleaner
7
+ from maya_umbrella.collector import MayaVirusCollector
8
+ from maya_umbrella.filesystem import get_hooks
9
+ from maya_umbrella.filesystem import load_hook
10
+ from maya_umbrella.i18n import Translator
11
+ from maya_umbrella.log import setup_logger
12
+ from maya_umbrella.maya_funs import is_maya_standalone
13
+ from maya_umbrella.maya_funs import om
14
+
15
+
16
+ # Global list to store IDs of Maya callbacks
17
+ MAYA_UMBRELLA_CALLBACK_IDS = []
18
+
19
+
20
+ def _add_callbacks_id(id_):
21
+ """Add a callback ID to the global list if it's not already present.
22
+
23
+ Args:
24
+ id_ (int): ID of the callback to be added.
25
+ """
26
+ global MAYA_UMBRELLA_CALLBACK_IDS
27
+ if id_ not in MAYA_UMBRELLA_CALLBACK_IDS:
28
+ MAYA_UMBRELLA_CALLBACK_IDS.append(id_)
29
+
30
+
31
+ class MayaVirusDefender(object):
32
+ """A class to defend against Maya viruses.
33
+
34
+ Attributes:
35
+ _vaccines (list): List to store vaccines.
36
+ callback_maps (dict): Dictionary to map callback names to MSceneMessage constants.
37
+ auto_fix (bool): Whether to automatically fix issues.
38
+ logger (Logger): Logger object for logging purposes.
39
+ translator (Translator): Translator object for translation purposes.
40
+ collector (MayaVirusCollector): MayaVirusCollector object for collecting issues.
41
+ virus_cleaner (MayaVirusCleaner): MayaVirusCleaner object for fixing issues.
42
+ hooks (list): List of hooks to run.
43
+ """
44
+ _vaccines = []
45
+ callback_maps = {
46
+ "after_open": om.MSceneMessage.kAfterOpen,
47
+ "maya_initialized": om.MSceneMessage.kMayaInitialized,
48
+ "after_import": om.MSceneMessage.kAfterImport,
49
+ "after_import_reference": om.MSceneMessage.kAfterImportReference,
50
+ "after_load_reference": om.MSceneMessage.kAfterLoadReference,
51
+ "before_save": om.MSceneMessage.kBeforeSave,
52
+ "before_import": om.MSceneMessage.kBeforeImport,
53
+ "before_load_reference": om.MSceneMessage.kBeforeLoadReference,
54
+ "before_import_reference": om.MSceneMessage.kBeforeImportReference,
55
+ "maya_exiting": om.MSceneMessage.kMayaExiting,
56
+ }
57
+
58
+ def __init__(self, auto_fix=True):
59
+ """Initialize the MayaVirusDefender.
60
+
61
+ Args:
62
+ auto_fix (bool): Whether to automatically fix issues.
63
+ """
64
+ logger = logging.getLogger(__name__)
65
+ self.auto_fix = auto_fix
66
+ self.logger = setup_logger(logger)
67
+ self.translator = Translator()
68
+ self.collector = MayaVirusCollector(self.logger, self.translator)
69
+ self.virus_cleaner = MayaVirusCleaner(self.collector, self.logger)
70
+ self.hooks = get_hooks()
71
+
72
+ def run_hooks(self):
73
+ """Run all hooks, only works in non-batch mode."""
74
+ if not is_maya_standalone():
75
+ for hook_file in self.hooks:
76
+ self.logger.debug("run_hook: %s", hook_file)
77
+ try:
78
+ load_hook(hook_file).hook(virus_cleaner=self.virus_cleaner)
79
+ except Exception as e:
80
+ self.logger.debug("Error running hook: %s", e)
81
+
82
+ def collect(self):
83
+ """Collect all issues related to the Maya virus."""
84
+ self.collector.collect()
85
+
86
+ def fix(self):
87
+ """Fix all issues related to the Maya virus."""
88
+ self.virus_cleaner.fix()
89
+
90
+ def report(self):
91
+ """Report all issues related to the Maya virus."""
92
+ self.collect()
93
+ self.collector.report()
94
+
95
+ @property
96
+ def have_issues(self):
97
+ """Check if any issues are found.
98
+
99
+ Returns:
100
+ bool: True if any issues are found, False otherwise.
101
+ """
102
+ return self.collector.have_issues
103
+
104
+ def setup(self):
105
+ """Set up the MayaVirusDefender."""
106
+ self.virus_cleaner.setup_default_callbacks()
107
+ for name, callbacks in self.collector.registered_callbacks.items():
108
+ maya_callback = self.callback_maps[name]
109
+ self.logger.debug("%s setup.", name)
110
+ for func in callbacks:
111
+ _add_callbacks_id(om.MSceneMessage.addCallback(maya_callback, func))
112
+ for name, callbacks in self.callback_maps.items():
113
+ self.logger.debug("setup callback %s.", name)
114
+ _add_callbacks_id(om.MSceneMessage.addCallback(callbacks, self._callback))
115
+
116
+ def stop(self):
117
+ """Stop the MayaVirusDefender."""
118
+ for ids in MAYA_UMBRELLA_CALLBACK_IDS:
119
+ self.logger.debug("remove callback. %s", ids)
120
+ om.MSceneMessage.removeCallback(ids)
121
+ MAYA_UMBRELLA_CALLBACK_IDS.remove(ids)
122
+
123
+ def get_unfixed_references(self):
124
+ """Get the list of unfixed reference files.
125
+
126
+ Returns:
127
+ list: List of unfixed reference files.
128
+ """
129
+ self.collect()
130
+ return self.collector.infected_reference_files
131
+
132
+ def _callback(self, *args, **kwargs):
133
+ """Callback function for MayaVirusDefender.
134
+
135
+ Args:
136
+ *args: Variable length argument list.
137
+ **kwargs: Arbitrary keyword arguments.
138
+ """
139
+ if self.auto_fix:
140
+ self.collect()
141
+ self.fix()
142
+ self.run_hooks()
143
+ else:
144
+ self.report()
145
+
146
+ def start(self):
147
+ """Start the MayaVirusDefender."""
148
+ self._callback()
149
+
150
+
151
+ @contextmanager
152
+ def context_defender():
153
+ """Context manager for MayaVirusDefender.
154
+
155
+ Yields:
156
+ MayaVirusDefender: An instance of MayaVirusDefender.
157
+ """
158
+ defender = MayaVirusDefender()
159
+ defender.stop()
160
+ yield defender
161
+ defender.setup()
@@ -2,7 +2,7 @@
2
2
  from contextlib import contextmanager
3
3
  import glob
4
4
  import importlib
5
- import logging
5
+ import json
6
6
  import os
7
7
  import random
8
8
  import re
@@ -53,6 +53,17 @@ def read_file(path):
53
53
  return content
54
54
 
55
55
 
56
+ def read_json(path):
57
+ """Read the content of the file at the given path."""
58
+ options = {"encoding": "utf-8"} if PY3 else {}
59
+ with open(path, **options) as file_:
60
+ try:
61
+ content = json.load(file_)
62
+ except UnicodeDecodeError:
63
+ return {}
64
+ return content
65
+
66
+
56
67
  def write_file(path, content):
57
68
  """Write the given content to the file at the given path."""
58
69
  options = {"encoding": "utf-8"} if PY3 else {}
@@ -62,6 +73,21 @@ def write_file(path, content):
62
73
 
63
74
  @contextmanager
64
75
  def atomic_writes(src, mode, **options):
76
+ """Context manager for atomic writes to a file.
77
+
78
+ This context manager ensures that the file is only written to disk if the write operation completes without errors.
79
+
80
+ Args:
81
+ src (str): Path to the file to be written.
82
+ mode (str): Mode in which the file is opened, like 'r', 'w', 'a', etc.
83
+ **options: Arbitrary keyword arguments that are passed to the built-in open() function.
84
+
85
+ Yields:
86
+ file object: The opened file object.
87
+
88
+ Raises:
89
+ AttributeError: If the os module does not have the 'replace' function (Python 2 compatibility).
90
+ """
65
91
  temp_path = os.path.join(os.path.dirname(src), "._{}".format(id_generator()))
66
92
  with open(temp_path, mode, **options) as f:
67
93
  yield f
@@ -71,6 +97,7 @@ def atomic_writes(src, mode, **options):
71
97
  shutil.move(temp_path, src)
72
98
 
73
99
 
100
+
74
101
  def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
75
102
  """Generate a random string of the given size using the given characters."""
76
103
  return "".join(random.choice(chars) for _ in range(size))
@@ -148,21 +175,45 @@ def get_log_file():
148
175
 
149
176
 
150
177
  def remove_virus_file_by_signature(file_path, signatures, output_file_path=None):
151
- logger = logging.getLogger(__name__)
178
+ """Remove virus content from a file by matching signatures.
179
+
180
+ Args:
181
+ file_path (str): Path to the file to be cleaned.
182
+ signatures (list): List of signatures to match and remove.
183
+ output_file_path (str, optional): Path to the cleaned output file.
184
+ Defaults to None, which overwrites the input file.
185
+ """
152
186
  data = read_file(file_path)
153
187
  if check_virus_by_signature(data, signatures):
154
- logger.warning("%s: Infected by Malware!", file_path)
155
188
  fixed_data = replace_content_by_signatures(data, signatures)
156
189
  write_file(output_file_path or file_path, fixed_data)
157
190
 
158
191
 
159
192
  def replace_content_by_signatures(content, signatures):
193
+ """Replace content in a string that matches given signatures.
194
+
195
+ Args:
196
+ content (str): The input content.
197
+ signatures (list): List of signatures to match and remove.
198
+
199
+ Returns:
200
+ str: The cleaned content.
201
+ """
160
202
  for signature in signatures:
161
203
  content = re.sub(signature, "", content)
162
204
  return content
163
205
 
164
206
 
165
207
  def check_virus_file_by_signature(file_path, signatures=None):
208
+ """Check if a file contains a virus by matching signatures.
209
+
210
+ Args:
211
+ file_path (str): Path to the file to be checked.
212
+ signatures (list, optional): List of signatures to match. Defaults to None, which uses FILE_VIRUS_SIGNATURES.
213
+
214
+ Returns:
215
+ bool: True if a virus signature is found, False otherwise.
216
+ """
166
217
  signatures = signatures or FILE_VIRUS_SIGNATURES
167
218
  try:
168
219
  data = read_file(file_path)
@@ -174,8 +225,44 @@ def check_virus_file_by_signature(file_path, signatures=None):
174
225
 
175
226
 
176
227
  def check_virus_by_signature(content, signatures=None):
228
+ """Check if a content contains a virus by matching signatures.
229
+
230
+ Args:
231
+ content (str): The input content.
232
+ signatures (list, optional): List of signatures to match. Defaults to None, which uses FILE_VIRUS_SIGNATURES.
233
+
234
+ Returns:
235
+ bool: True if a virus signature is found, False otherwise.
236
+ """
177
237
  signatures = signatures or FILE_VIRUS_SIGNATURES
178
238
  for signature in signatures:
179
239
  if re.search(signature, content):
180
240
  return True
181
241
  return False
242
+
243
+
244
+ def get_backup_path(path, root_path=None):
245
+ """Get the backup path for a given file path based on environment variables.
246
+
247
+ Args:
248
+ path (str): Path to the original file.
249
+ root_path (str, optional): Path to the root folder where backups should be saved.
250
+ Defaults to None, which saves backups in the original file's folder.
251
+
252
+ Returns:
253
+ str: The backup path.
254
+ """
255
+ ignore_backup = os.getenv("MAYA_UMBRELLA_IGNORE_BACKUP", "false").lower() == "true"
256
+ if ignore_backup:
257
+ return path
258
+ root, filename = os.path.split(path)
259
+ backup_folder_name = os.getenv("MAYA_UMBRELLA_BACKUP_FOLDER_NAME", "_virus")
260
+ backup_path = os.path.join(root, backup_folder_name)
261
+ if root_path:
262
+ _, base_path = os.path.splitdrive(root)
263
+ backup_path = os.path.join(root_path, base_path.strip(os.sep))
264
+ try:
265
+ os.makedirs(backup_path)
266
+ except (OSError, IOError): # noqa: UP024
267
+ pass
268
+ return os.path.join(backup_path, filename)
maya_umbrella/i18n.py ADDED
@@ -0,0 +1,76 @@
1
+ # Import built-in modules
2
+ import glob
3
+ import os
4
+ from string import Template
5
+
6
+ # Import local modules
7
+ from maya_umbrella.filesystem import read_json
8
+ from maya_umbrella.filesystem import this_root
9
+ from maya_umbrella.maya_funs import maya_ui_language
10
+
11
+
12
+ class Translator(object):
13
+ """A class to handle translations for different locales.
14
+
15
+ Attributes:
16
+ data (dict): Dictionary containing translation data for different locales.
17
+ locale (str): The current locale.
18
+ """
19
+ def __init__(self, file_format="json", default_locale=None):
20
+ """Initialize the Translator.
21
+
22
+ Args:
23
+ file_format (str, optional): File format of the translation files. Defaults to "json".
24
+ default_locale (str, optional): Default locale to use for translations. Defaults to None,
25
+ which uses the MAYA_UMBRELLA_LANG environment variable or the Maya UI language.
26
+ """
27
+ _default_locale = os.getenv("MAYA_UMBRELLA_LANG", maya_ui_language())
28
+ default_locale = default_locale or _default_locale
29
+ self.data = {}
30
+ self.locale = default_locale
31
+ translations_folder = os.path.join(this_root(), "locales")
32
+
33
+ # get list of files with specific extensions
34
+ files = glob.glob(os.path.join(translations_folder, "*.{file_format}".format(file_format=file_format)))
35
+ for fil in files:
36
+ # get the name of the file without extension, will be used as locale name
37
+ loc = os.path.splitext(os.path.basename(fil))[0]
38
+ self.data[loc] = read_json(fil)
39
+
40
+ def set_locale(self, locale):
41
+ """Set the current locale.
42
+
43
+ Args:
44
+ locale (str): The locale to set.
45
+
46
+ Raises:
47
+ ValueError: If the provided locale is not supported.
48
+ """
49
+ if locale in self.data:
50
+ self.locale = locale
51
+ else:
52
+ raise ValueError("Invalid locale: {loc}".format(loc=locale))
53
+
54
+ def get_locale(self):
55
+ """Get the current locale.
56
+
57
+ Returns:
58
+ str: The current locale.
59
+ """
60
+ return self.locale
61
+
62
+ def translate(self, key, **kwargs):
63
+ """Translate a text based on the current locale.
64
+
65
+ Args:
66
+ key (str): The key to be translated.
67
+ **kwargs: Arbitrary keyword arguments that are used to replace placeholders in the translation text.
68
+
69
+ Returns:
70
+ str: The translated text.
71
+ """
72
+ # return the key instead of translation text if locale is not supported
73
+ if self.locale not in self.data:
74
+ return key
75
+ text = self.data[self.locale].get(key, key)
76
+ return Template(text).safe_substitute(**kwargs)
@@ -0,0 +1,19 @@
1
+ {
2
+ "start_fix_issues": "Start fixing all problems related to Maya virus $name",
3
+ "finish_fix_issues": "Done.",
4
+ "init_message": "Successfully loaded <hl>maya_umbrella</hl> under protection.",
5
+ "init_standalone_message": "-----------------------Loading maya_umbrella successfully----------------------",
6
+ "report_issue": "$name: Infected by Malware!",
7
+ "infected_nodes": "Infected nodes: $name: ",
8
+ "bad_files": "Bad files: $name",
9
+ "infected_script_jobs": "Infected script jobs: $name",
10
+ "infected_files": "Infected files: $name",
11
+ "infected_reference_files": "Infected reference files: $name",
12
+ "fix_infected_files": "Clean infected files: $name",
13
+ "fix_infected_nodes": "Delete infected nodes:$name",
14
+ "fix_infected_reference_nodes": "trying fix infected nodes from reference:$name",
15
+ "delete": "Deleting: $name",
16
+ "remove_file": "Deleting file:$name",
17
+ "remove_path": "Deleting path:$name",
18
+ "fix_script_job": "Kill script job: %s"
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "start_fix_issues": "开始修复与 Maya 病毒相关的所有问题 $name",
3
+ "finish_fix_issues": "修复结束.",
4
+ "init_message": "成功加载 <hl>maya_umbrella</hl> 保护中.",
5
+ "init_standalone_message": "-----------------------成功加载 maya_umbrella-----------------------",
6
+ "report_issue": "$name:被恶意感染!",
7
+ "infected_nodes": "被感染节点:$name: ",
8
+ "bad_files": "需要被清理的文件:$name",
9
+ "infected_script_jobs": "被感染的script jobs:$name",
10
+ "infected_files": "被感染的脚本:$name",
11
+ "infected_reference_files": "被感染的参考文件:$name",
12
+ "fix_infected_files": "清理被感染的文件:$name",
13
+ "fix_infected_nodes": "删除被感染的节点:$name",
14
+ "fix_infected_reference_nodes": "尝试修复被感染的参考节点:$name",
15
+ "delete": "删除感染文件:$name",
16
+ "remove_file": "删除文件:$name",
17
+ "remove_path": "删除文件夹:$name",
18
+ "fix_script_job": "删除被感染的节点:$name"
19
+ }
@@ -22,15 +22,115 @@ except ImportError:
22
22
  om = MagicMock()
23
23
  mel = MagicMock()
24
24
 
25
+ # Import built-in modules
26
+ from functools import wraps
27
+
25
28
 
26
29
  def is_maya_standalone():
27
- """Return True if Maya is standalone."""
30
+ """Check if Maya is running in standalone mode.
31
+
32
+ Returns:
33
+ bool: True if Maya is running in standalone mode, False otherwise.
34
+ """
28
35
  return cmds.about(batch=True)
29
36
 
30
37
 
31
38
  def check_reference_node_exists(node_name):
32
- """Check if reference node exists."""
39
+ """Check if a reference node exists in the Maya scene.
40
+
41
+ Args:
42
+ node_name (str): Name of the reference node.
43
+
44
+ Returns:
45
+ bool: True if the reference node exists, False otherwise.
46
+ """
33
47
  try:
34
48
  return cmds.referenceQuery(node_name, isNodeReferenced=True)
35
49
  except RuntimeError:
36
50
  return False
51
+
52
+
53
+ def get_reference_file_by_node(node_name):
54
+ """Get the reference file associated with a node.
55
+
56
+ Args:
57
+ node_name (str): Name of the node.
58
+
59
+ Returns:
60
+ str: Path of the reference file, empty string if the node is not associated with a reference file.
61
+ """
62
+ try:
63
+ return cmds.referenceQuery(node_name, filename=True)
64
+ except RuntimeError:
65
+ return ""
66
+
67
+
68
+ def get_attr_value(node_name, attr_name):
69
+ """Get the value of an attribute of a node.
70
+
71
+ Args:
72
+ node_name (str): Name of the node.
73
+ attr_name (str): Name of the attribute.
74
+
75
+ Returns:
76
+ Any: Value of the attribute, None if the attribute does not exist.
77
+ """
78
+ try:
79
+ return cmds.getAttr("{node_name}.{attr}".format(node_name=node_name, attr=attr_name))
80
+ except ValueError:
81
+ return None
82
+
83
+
84
+ def maya_ui_language():
85
+ """Get the language of the Maya user interface.
86
+
87
+ Returns:
88
+ str: The language of the Maya user interface.
89
+ """
90
+ return cmds.about(uiLocaleLanguage=True)
91
+
92
+
93
+ def block_prompt(func):
94
+ """Decorator to block file prompt dialogs in Maya.
95
+
96
+ Args:
97
+ func (function): The function to decorate.
98
+
99
+ Returns:
100
+ function: The decorated function.
101
+ """
102
+ @wraps(func)
103
+ def wrap(*args, **kwargs):
104
+ # Grabs the initial value.
105
+ prompt_val = cmds.file(prompt=True, q=True)
106
+
107
+ try:
108
+ cmds.file(prompt=False)
109
+ return func(*args, **kwargs)
110
+
111
+ finally:
112
+ # Resets to the original value, this way you don't suddenly turn the prompt on, when someone wanted it off.
113
+ cmds.file(prompt=prompt_val)
114
+
115
+ return wrap
116
+
117
+
118
+ @block_prompt
119
+ def open_maya_file(maya_file):
120
+ """Open a Maya file.
121
+
122
+ Args:
123
+ maya_file (str): Path to the Maya file.
124
+ """
125
+ cmds.file(maya_file, open=True, force=True, ignoreVersion=True, executeScriptNodes=False)
126
+
127
+
128
+ @block_prompt
129
+ def save_as_file(file_name):
130
+ """Save the current Maya scene as a file.
131
+
132
+ Args:
133
+ file_name (str): Path to the output file.
134
+ """
135
+ cmds.file(rename=file_name)
136
+ cmds.file(s=True, f=True)
@@ -0,0 +1,101 @@
1
+ # Import built-in modules
2
+ import glob
3
+ import logging
4
+ import os
5
+ import shutil
6
+
7
+ # Import local modules
8
+ from maya_umbrella import maya_funs
9
+ from maya_umbrella.defender import context_defender
10
+ from maya_umbrella.filesystem import get_backup_path
11
+ from maya_umbrella.filesystem import read_file
12
+ from maya_umbrella.maya_funs import cmds
13
+
14
+
15
+ class MayaVirusScanner(object):
16
+ """A class to scan and fix Maya files containing viruses.
17
+
18
+ Attributes:
19
+ _failed_files (list): List of files that failed to be fixed.
20
+ _fixed_files (list): List of files that have been fixed.
21
+ logger (Logger): Logger object for logging purposes.
22
+ defender (MayaVirusDefender): MayaVirusDefender object for fixing issues.
23
+ _env (dict): Custom environment variables.
24
+ output_path (str, optional): Path to save the fixed files. Defaults to None, which overwrites the original
25
+ files.
26
+ """
27
+
28
+ def __init__(self, output_path=None, env=None):
29
+ """Initialize the MayaVirusScanner.
30
+
31
+ Args:
32
+ output_path (str, optional): Path to save the fixed files. Defaults to None, which overwrites the original
33
+ files.
34
+ env (dict, optional): Custom environment variables. Defaults to None,
35
+ which sets the 'MAYA_COLOR_MANAGEMENT_SYNCOLOR' variable to '1'.
36
+ """
37
+ self.logger = logging.getLogger(__name__)
38
+ self.defender = None
39
+ self.output_path = output_path
40
+ self._failed_files = []
41
+ self._fixed_files = []
42
+ # Custom env.
43
+ self._env = env or {
44
+ "MAYA_COLOR_MANAGEMENT_SYNCOLOR": "1"
45
+ }
46
+
47
+ def scan_files_from_pattern(self, pattern):
48
+ """Scan and fix Maya files matching a given pattern.
49
+
50
+ Args:
51
+ pattern (str): The file pattern to match.
52
+ """
53
+ os.environ.update(self._env)
54
+ return self.scan_files_from_list(glob.iglob(pattern))
55
+
56
+ def scan_files_from_list(self, files):
57
+ """Scan and fix Maya files from a given list.
58
+
59
+ Args:
60
+ files (list): List of file paths to scan and fix.
61
+ """
62
+ with context_defender() as defender:
63
+ self.defender = defender
64
+ for maya_file in files:
65
+ self._fix(maya_file)
66
+ return self._fixed_files
67
+
68
+ def scan_files_from_file(self, text_file):
69
+ """Scan and fix Maya files from a given text file containing a list of file paths.
70
+
71
+ Args:
72
+ text_file (str): Path to the text file containing the list of file paths.
73
+ """
74
+ file_data = read_file(text_file)
75
+ files = file_data.splitlines()
76
+ return self.scan_files_from_list(files)
77
+
78
+ def _fix(self, maya_file):
79
+ """Fix a single Maya file containing a virus.
80
+
81
+ Args:
82
+ maya_file (str): Path to the Maya file to be fixed.
83
+ """
84
+ if not maya_file and maya_file in self._fixed_files:
85
+ self.logger.debug("Already fixed: {maya_file}".format(maya_file=maya_file))
86
+ return
87
+ try:
88
+ maya_funs.open_maya_file(maya_file)
89
+ self.defender.collect()
90
+ except Exception:
91
+ self._failed_files.append(maya_file)
92
+ if self.defender.have_issues:
93
+ self.defender.fix()
94
+ backup_path = get_backup_path(maya_file, root_path=self.output_path)
95
+ self.logger.debug("Backup saved to: {backup_path}".format(backup_path=backup_path))
96
+ shutil.copy2(maya_file, backup_path)
97
+ cmds.file(s=True, f=True)
98
+ self._fixed_files.append(maya_file)
99
+ cmds.file(new=True, force=True)
100
+ for ref in self.defender.collector.infected_reference_files:
101
+ self._fix(ref)
@@ -0,0 +1,10 @@
1
+ # Import built-in modules
2
+ from collections import namedtuple
3
+
4
+
5
+ VirusSignature = namedtuple("VirusSignature", ["name", "signature"])
6
+
7
+ # https://regex101.com/r/0MNzF7/1
8
+ virus20240430_sig1 = VirusSignature("virus20240430", "python(.*);.+exec.+(pyCode).+;")
9
+ # https://regex101.com/r/2D14UA/1
10
+ virus20240430_sig2 = VirusSignature("virus20240430", r"^\['.+']")