diff --git a/multi_user/bl_types/bl_file.py b/multi_user/bl_types/bl_file.py index 2682077..6b07c43 100644 --- a/multi_user/bl_types/bl_file.py +++ b/multi_user/bl_types/bl_file.py @@ -63,16 +63,10 @@ class BlFile(ReplicatedDatablock): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.instance = kwargs.get('instance', None) - # TODO: ensure absolute path - # TODO: ensure file exist - self.preferences = utils.get_preferences() self.diff_method = DIFF_BINARY def resolve(self): - # TODO: generic check - os.makedirs(self.preferences.cache_directory, exist_ok=True) - if self.data: self.instance = Path(get_filepath(self.data['name'])) diff --git a/multi_user/operators.py b/multi_user/operators.py index 381d824..7353823 100644 --- a/multi_user/operators.py +++ b/multi_user/operators.py @@ -27,6 +27,8 @@ from operator import itemgetter from pathlib import Path from subprocess import PIPE, Popen, TimeoutExpired import zmq +import shutil +from pathlib import Path import bpy import mathutils @@ -608,6 +610,34 @@ class ApplyArmatureOperator(bpy.types.Operator): stop_modal_executor = False +class ClearCache(bpy.types.Operator): + "Clear local session cache" + bl_idname = "session.clear_cache" + bl_label = "Modal Executor Operator" + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + cache_dir = utils.get_preferences().cache_directory + try: + for root, dirs, files in os.walk(cache_dir): + for name in files: + Path(root, name).unlink() + + except Exception as e: + self.report({'ERROR'}, repr(e)) + + return {"FINISHED"} + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self) + + def draw(self, context): + row = self.layout + row.label(text=f" Do you really want to remove local cache ? ") + classes = ( SessionStartOperator, SessionStopOperator, @@ -620,7 +650,7 @@ classes = ( ApplyArmatureOperator, SessionKickOperator, SessionInitOperator, - + ClearCache, ) diff --git a/multi_user/preferences.py b/multi_user/preferences.py index 0af44c9..905833f 100644 --- a/multi_user/preferences.py +++ b/multi_user/preferences.py @@ -20,6 +20,9 @@ import logging import bpy import string import re +import os + +from pathlib import Path from . import bl_types, environment, addon_updater_ops, presence, ui from .utils import get_preferences, get_expanded_icon @@ -68,6 +71,16 @@ def update_port(self, context): self['ipc_port'] = random.randrange(self.port+4, 10000) +def update_directory(self, context): + new_dir = Path(self.cache_directory) + if new_dir.exists() and any(Path(self.cache_directory).iterdir()): + logging.error("The folder is not empty, choose another one.") + self['cache_directory'] = environment.DEFAULT_CACHE_DIR + elif not new_dir.exists(): + logging.info("Target cache folder doesn't exist, creating it.") + os.makedirs(self.cache_directory, exist_ok=True) + + def set_log_level(self, value): logging.getLogger().setLevel(value) @@ -136,7 +149,8 @@ class SessionPrefs(bpy.types.AddonPreferences): cache_directory: bpy.props.StringProperty( name="cache directory", subtype="DIR_PATH", - default=environment.DEFAULT_CACHE_DIR) + default=environment.DEFAULT_CACHE_DIR, + update=update_directory) connection_timeout: bpy.props.IntProperty( name='connection timeout', description='connection timeout before disconnection', diff --git a/multi_user/ui.py b/multi_user/ui.py index 429cd57..954a1f0 100644 --- a/multi_user/ui.py +++ b/multi_user/ui.py @@ -19,7 +19,7 @@ import bpy from . import operators -from .utils import get_preferences, get_expanded_icon +from .utils import get_preferences, get_expanded_icon, get_folder_size from replication.constants import (ADDED, ERROR, FETCHED, MODIFIED, RP_COMMON, UP, STATE_ACTIVE, STATE_AUTH, @@ -371,7 +371,8 @@ class SESSION_PT_advanced_settings(bpy.types.Panel): cache_section_row = cache_section.row() cache_section_row.label(text="Clear memory filecache:") cache_section_row.prop(settings, "clear_memory_filecache", text="") - + cache_section_row = cache_section.row() + cache_section_row.operator('session.clear_cache', text=f"Clear cache ({get_folder_size(settings.cache_directory)})") log_section = layout.row().box() log_section.prop( settings, diff --git a/multi_user/utils.py b/multi_user/utils.py index f66ab90..177cd4b 100644 --- a/multi_user/utils.py +++ b/multi_user/utils.py @@ -21,8 +21,10 @@ import logging import os import sys import time -from uuid import uuid4 from collections.abc import Iterable +from pathlib import Path +from uuid import uuid4 +import math import bpy import mathutils @@ -78,6 +80,7 @@ def resolve_from_id(id, optionnal_type=None): return root[id] return None + def get_datablock_from_uuid(uuid, default, ignore=[]): if not uuid: return default @@ -87,9 +90,10 @@ def get_datablock_from_uuid(uuid, default, ignore=[]): if isinstance(root, Iterable) and category not in ignore: for item in root: if getattr(item, 'uuid', None) == uuid: - return item + return item return default + def get_preferences(): return bpy.context.preferences.addons[__package__].preferences @@ -103,3 +107,61 @@ def get_expanded_icon(prop: bpy.types.BoolProperty) -> str: return 'DISCLOSURE_TRI_DOWN' else: return 'DISCLOSURE_TRI_RIGHT' + + +# Taken from here: https://stackoverflow.com/a/55659577 +def get_folder_size(folder): + return ByteSize(sum(file.stat().st_size for file in Path(folder).rglob('*'))) + + +class ByteSize(int): + + _kB = 1024 + _suffixes = 'B', 'kB', 'MB', 'GB', 'PB' + + def __new__(cls, *args, **kwargs): + return super().__new__(cls, *args, **kwargs) + + def __init__(self, *args, **kwargs): + self.bytes = self.B = int(self) + self.kilobytes = self.kB = self / self._kB**1 + self.megabytes = self.MB = self / self._kB**2 + self.gigabytes = self.GB = self / self._kB**3 + self.petabytes = self.PB = self / self._kB**4 + *suffixes, last = self._suffixes + suffix = next(( + suffix + for suffix in suffixes + if 1 < getattr(self, suffix) < self._kB + ), last) + self.readable = suffix, getattr(self, suffix) + + super().__init__() + + def __str__(self): + return self.__format__('.2f') + + def __repr__(self): + return '{}({})'.format(self.__class__.__name__, super().__repr__()) + + def __format__(self, format_spec): + suffix, val = self.readable + return '{val:{fmt}} {suf}'.format(val=math.ceil(val), fmt=format_spec, suf=suffix) + + def __sub__(self, other): + return self.__class__(super().__sub__(other)) + + def __add__(self, other): + return self.__class__(super().__add__(other)) + + def __mul__(self, other): + return self.__class__(super().__mul__(other)) + + def __rsub__(self, other): + return self.__class__(super().__sub__(other)) + + def __radd__(self, other): + return self.__class__(super().__add__(other)) + + def __rmul__(self, other): + return self.__class__(super().__rmul__(other))