Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions maxdiff/freezing_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,11 @@ def get_patcher_dict(entry: device_entry_with_data):

device_data_text = ""
try:
if patch_data[len(patch_data) - 1] == 0:
device_data_text = patch_data[: len(patch_data) - 1].decode("utf-8")
else:
device_data_text = patch_data.decode("utf-8")
device_data_text = patch_data.decode("utf-8")

# Past Max versions could write patcher data with a trailing 0
# or with trailing garbage.
device_data_text = strip_trailing_nonjson(device_data_text)
except Exception as e:
print(f"Error getting patch data as text for entry {name}: {e}")
return {}
Expand All @@ -106,3 +107,29 @@ def get_patcher_dict(entry: device_entry_with_data):
except:
print(f"Content of entry {name} does not seem to be a patcher")
return {}


def strip_trailing_nonjson(device_data_text: str) -> str:
"""
This function returns (possibly trimmed) JSON text to pass to json.loads().
"""
decoder = json.JSONDecoder()
try:
_, end = decoder.raw_decode(device_data_text)
return device_data_text[:end].rstrip()
except json.JSONDecodeError:
pass

# find all candidate closing positions for objects/arrays
closers = [i for i, ch in enumerate(device_data_text) if ch in ("}", "]")]
# try from last to first — prefer the longest valid prefix
for pos in reversed(closers):
candidate = device_data_text[: pos + 1]
try:
json.loads(candidate)
return candidate
except Exception:
continue

# nothing worked; return original so caller can log/handle the parse error
return device_data_text
10 changes: 10 additions & 0 deletions maxdiff/tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ def test_parse_maxpat_with_merge_conficts(self):
actual = parse(test_path)
self.assertEqual(expected, actual)

def test_parse_with_garbage(self):
self.maxDiff = None

expected_path, test_path = get_test_path_files("WithGarbage.amxd")

with open(expected_path, mode="r") as expected_file:
expected = expected_file.read()
actual = parse(test_path)
self.assertEqual(expected, actual)


def get_test_path_files(file_name):
expected = get_test_path_file(f"test_baselines/{file_name}.txt")
Expand Down
12 changes: 12 additions & 0 deletions maxdiff/tests/test_baselines/WithGarbage.amxd.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Audio Effect Device
-------------------
Device is frozen
----- Contents -----
Livesync Leader.amxd: 88337 bytes, modified at 2020/06/14 09:47:41 UTC <= Device

Total - Counting every abstraction instance - Indicates loading time
Object instances: 130
Connections: 151
Unique - Counting abstractions once - Indicates maintainability
Object instances: 130
Connections: 151
Binary file added maxdiff/tests/test_files/WithGarbage.amxd
Binary file not shown.
1 change: 1 addition & 0 deletions maxdiff/tests/test_rewrite_baselines.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def run():
rewrite_file("Test.amxd")
rewrite_file("EncryptedTest.amxd")
rewrite_file("FrozenTest.amxd")
rewrite_file("WithGarbage.amxd")
rewrite_file("Test.maxpat")
rewrite_file("Test Project/Zipped.als")
rewrite_file("Test Project/Test.als")
Expand Down
Loading