diff --git a/ChangeLog b/ChangeLog index e5a8273..029ad9b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,7 @@ ChangeLog 0.2.0dev * Improve automatic thumbnail and preview selection * `sl1s` added as sla extension gcode type + * Add cache file versioning, distrust cache files with a different version 0.1.0 * Initial release diff --git a/gcode_metadata/metadata.py b/gcode_metadata/metadata.py index ffbdd9c..5758f35 100644 --- a/gcode_metadata/metadata.py +++ b/gcode_metadata/metadata.py @@ -9,6 +9,7 @@ import zipfile from typing import Dict, Any, Type, Callable, List, Optional from logging import getLogger +from importlib.metadata import version GCODE_EXTENSIONS = (".gcode", ".gc", ".g", ".gco") SLA_EXTENSIONS = ("sl1", "sl1s") @@ -238,14 +239,48 @@ def cache_name(self): return new_path def is_cache_fresh(self): - """If cache is fresher than file, returns True""" + """Checks if we can use the current cache file""" + return self.is_cache_recent() and self.is_cache_correct_version() + + def is_cache_recent(self): + """Checks if the cache file is newer than the source file""" try: file_time_created = os.path.getctime(self.path) cache_time_created = os.path.getctime(self.cache_name) - return file_time_created < cache_time_created except FileNotFoundError: return False + if file_time_created > cache_time_created: + return False + return True + + def is_cache_correct_version(self): + """Checks if the cache file was created with the same version + of gcode-metadata""" + + def isallowed(char): + """Filters out dissalowed characters, used with str.filter""" + return char not in "\",\n} " + + # This expects the first item in the json file to be the + # gcode-metadata version, with which the cache was created. + # If it's not there, or the version is different, the cache is deleted + with open(self.cache_name, "r", encoding="utf-8") as file: + for line in file: + if text := "".join(filter(str.isalpha, line)): + if text.startswith("version"): + break + return False + else: # didn't reach break + return False + first_pair = line.split(",", 1)[0] + _, version_part = first_pair.split(":", 1) + file_version = "".join(filter(isallowed, version_part)) + if file_version == version('gcode-metadata'): + return True + + return False + def save_cache(self): """Take metadata from source file and save them as JSON to .cache file. @@ -264,6 +299,7 @@ def get_cache_data(info): try: if self.data: cache = { + "version": version('gcode-metadata'), "metadata": self.data, } diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 5fc7cd9..b70c88b 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -1,7 +1,10 @@ """Tests for gcode-metadata tool for g-code files.""" +import json import os import tempfile import shutil +from importlib.metadata import version + import time import pytest @@ -23,6 +26,18 @@ def tmp_dir(): del temp +def give_cache_version(path, version_to_give): + """Modifies the cache file and adds a valid version number""" + with open(path, "r", encoding='utf-8') as cache_file: + cache = json.load(cache_file) + my_cache = { + "version": version_to_give + } + my_cache.update(cache) + with open(path, "w", encoding='utf-8') as cache_file: + json.dump(my_cache, cache_file) + + def test_get_metadata_file_does_not_exist(): """Test get_metadata() with a non-existing file""" fname = '/somehwere/in/the/rainbow/my.gcode' @@ -56,26 +71,52 @@ def test_load_cache_key_error(): MetaData(fname).load_cache() -def test_is_cache_fresh_fresher(tmp_dir): - """is_cache_fresh, when cache file is fresher, than original file""" +def test_is_cache_recent_fresher(tmp_dir): + """is_cache_recent, when cache file is fresher, than original file""" fn_gcode = os.path.join(gcodes_dir, "fdn_filename.gcode") temp_gcode = shutil.copy(fn_gcode, tmp_dir) # Create the time difference time.sleep(0.01) fn_cache = os.path.join(gcodes_dir, ".fdn_filename.gcode.cache") shutil.copy(fn_cache, tmp_dir) - assert MetaData(temp_gcode).is_cache_fresh() + assert MetaData(temp_gcode).is_cache_recent() -def test_is_cache_fresh_older(tmp_dir): - """is_cache_fresh, when cache file is older, than original file""" +def test_is_cache_recent_older(tmp_dir): + """is_cache_recent, when cache file is older, than original file""" fn_cache = os.path.join(gcodes_dir, ".fdn_filename.gcode.cache") shutil.copy(fn_cache, tmp_dir) # Create the time difference time.sleep(0.01) fn_gcode = os.path.join(gcodes_dir, "fdn_filename.gcode") temp_gcode = shutil.copy(fn_gcode, tmp_dir) - assert MetaData(temp_gcode).is_cache_fresh() is False + assert MetaData(temp_gcode).is_cache_recent() is False + + +def test_is_cache_correct_version_none(): + """is_cache_correct_version, when cache file has no version""" + fn_gcode = os.path.join(gcodes_dir, "fdn_filename.gcode") + assert not MetaData(fn_gcode).is_cache_correct_version() + + +def test_is_cache_correct_version_mismatch(tmp_dir): + """is_cache_correct_version, when cache file has different version""" + fn_cache = os.path.join(gcodes_dir, ".fdn_filename.gcode.cache") + fn_gcode = os.path.join(gcodes_dir, "fdn_filename.gcode") + temp_gcode = shutil.copy(fn_gcode, tmp_dir) + cache_path = shutil.copy(fn_cache, tmp_dir) + give_cache_version(cache_path, "0.0.0") + assert MetaData(temp_gcode).is_cache_correct_version() is False + + +def test_is_cache_correct_version_match(tmp_dir): + """is_cache_correct_version, when cache file has the same version""" + fn_cache = os.path.join(gcodes_dir, ".fdn_filename.gcode.cache") + fn_gcode = os.path.join(gcodes_dir, "fdn_filename.gcode") + temp_gcode = shutil.copy(fn_gcode, tmp_dir) + cache_path = shutil.copy(fn_cache, tmp_dir) + give_cache_version(cache_path, version('gcode-metadata')) + assert MetaData(temp_gcode).is_cache_correct_version() is True def test_get_metadata_invalid_file():