MrDraw/src/octoprint/filemanager/__init__.py
Gina Häußge 8ff0096eb6 Fix & Docs: Plugins may only have one mixin implementation
Multiple mixins are allowed of course. Allowing multiple implementations lead to too many problems due to plugin names for referring to the APIs of SimpleApiPlugins or the assets of AssetPlugins.

 Hence __plugin_implementations__ has been deprecated in favor of __plugin_implementation__. The plugin subsystem will automatically copy the first implementation from __plugin_implementations__ to __plugin_implementation__ and log a deprecation warning.

 Adjusted documentation accordingly. Also added docs for helpers.
2015-03-30 16:50:06 +02:00

381 lines
14 KiB
Python

# coding=utf-8
from __future__ import absolute_import
__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
import logging
import os
import octoprint.plugin
from octoprint.events import eventManager, Events
from .destinations import FileDestinations
from .analysis import QueueEntry, AnalysisQueue
from .storage import LocalFileStorage
extensions = dict(
# extensions for 3d model files
model=dict(
stl=["stl"]
),
# extensions for printable machine code
machinecode=dict(
gcode=["gcode", "gco", "g"]
)
)
def get_extensions(type, subtree=None):
if not subtree:
subtree = extensions
for key, value in subtree.items():
if key == type:
return get_all_extensions(subtree=value)
elif isinstance(value, dict):
sub_extensions = get_extensions(type, subtree=value)
if sub_extensions:
return sub_extensions
return None
def get_all_extensions(subtree=None):
if not subtree:
subtree = extensions
result = []
if isinstance(subtree, dict):
for key, value in subtree.items():
if isinstance(value, dict):
result += get_all_extensions(value)
elif isinstance(value, (list, tuple)):
result += value
elif isinstance(subtree, (list, tuple)):
result = subtree
return result
def get_path_for_extension(extension, subtree=None):
if not subtree:
subtree = extensions
for key, value in subtree.items():
if isinstance(value, (list, tuple)) and extension in value:
return [key]
elif isinstance(value, dict):
path = get_path_for_extension(extension, subtree=value)
if path:
return [key] + path
return None
all_extensions = get_all_extensions()
def valid_extension(extension, type=None):
if not type:
return extension in all_extensions
else:
extensions = get_extensions(type)
if extensions:
return extension in extensions
def valid_file_type(filename, type=None):
_, extension = os.path.splitext(filename)
extension = extension[1:].lower()
return valid_extension(extension, type=type)
def get_file_type(filename):
_, extension = os.path.splitext(filename)
extension = extension[1:].lower()
return get_path_for_extension(extension)
class NoSuchStorage(Exception):
pass
class FileManager(object):
def __init__(self, analysis_queue, slicing_manager, printer_profile_manager, initial_storage_managers=None):
self._logger = logging.getLogger(__name__)
self._analysis_queue = analysis_queue
self._analysis_queue.register_finish_callback(self._on_analysis_finished)
self._storage_managers = dict()
if initial_storage_managers:
self._storage_managers.update(initial_storage_managers)
self._slicing_manager = slicing_manager
self._printer_profile_manager = printer_profile_manager
import threading
self._slicing_jobs = dict()
self._slicing_jobs_mutex = threading.Lock()
self._slicing_progress_callbacks = []
self._last_slicing_progress = None
self._progress_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.ProgressPlugin)
for storage_type, storage_manager in self._storage_managers.items():
self._determine_analysis_backlog(storage_type, storage_manager)
def register_slicingprogress_callback(self, callback):
self._slicing_progress_callbacks.append(callback)
def unregister_slicingprogress_callback(self, callback):
self._slicing_progress_callbacks.remove(callback)
def _determine_analysis_backlog(self, storage_type, storage_manager):
self._logger.info("Adding backlog items from {storage_type} to analysis queue".format(**locals()))
for entry, path, printer_profile in storage_manager.analysis_backlog:
file_type = get_file_type(path)[-1]
# we'll use the default printer profile for the backlog since we don't know better
queue_entry = QueueEntry(entry, file_type, storage_type, path, self._printer_profile_manager.get_default())
self._analysis_queue.enqueue(queue_entry, high_priority=False)
def add_storage(self, storage_type, storage_manager):
self._storage_managers[storage_type] = storage_manager
self._determine_analysis_backlog(storage_type, storage_manager)
def remove_storage(self, type):
if not type in self._storage_managers:
return
del self._storage_managers[type]
@property
def slicing_enabled(self):
return self._slicing_manager.slicing_enabled
@property
def registered_slicers(self):
return self._slicing_manager.registered_slicers
@property
def default_slicer(self):
return self._slicing_manager.default_slicer
def slice(self, slicer_name, source_location, source_path, dest_location, dest_path,
position=None, profile=None, printer_profile_id=None, overrides=None, callback=None, callback_args=None):
absolute_source_path = self.path_on_disk(source_location, source_path)
def stlProcessed(source_location, source_path, tmp_path, dest_location, dest_path, start_time, printer_profile_id, callback, callback_args, _error=None, _cancelled=False, _analysis=None):
try:
if _error:
eventManager().fire(Events.SLICING_FAILED, {"stl": source_path, "gcode": dest_path, "reason": _error})
elif _cancelled:
eventManager().fire(Events.SLICING_CANCELLED, {"stl": source_path, "gcode": dest_path})
else:
source_meta = self.get_metadata(source_location, source_path)
hash = source_meta["hash"]
class Wrapper(object):
def __init__(self, stl_name, temp_path, hash):
self.stl_name = stl_name
self.temp_path = temp_path
self.hash = hash
def save(self, absolute_dest_path):
with open(absolute_dest_path, "w") as d:
d.write(";Generated from {stl_name} {hash}\r".format(**vars(self)))
with open(tmp_path, "r") as s:
import shutil
shutil.copyfileobj(s, d)
links = [("model", dict(name=source_path))]
_, stl_name = self.split_path(source_location, source_path)
file_obj = Wrapper(stl_name, temp_path, hash)
printer_profile = self._printer_profile_manager.get(printer_profile_id)
self.add_file(dest_location, dest_path, file_obj, links=links, allow_overwrite=True, printer_profile=printer_profile, analysis=_analysis)
end_time = time.time()
eventManager().fire(Events.SLICING_DONE, {"stl": source_path, "gcode": dest_path, "time": end_time - start_time})
if callback is not None:
if callback_args is None:
callback_args = ()
callback(*callback_args)
finally:
os.remove(tmp_path)
source_job_key = (source_location, source_path)
dest_job_key = (dest_location, dest_path)
with self._slicing_jobs_mutex:
if source_job_key in self._slicing_jobs:
del self._slicing_jobs[source_job_key]
if dest_job_key in self._slicing_jobs:
del self._slicing_jobs[dest_job_key]
slicer = self._slicing_manager.get_slicer(slicer_name)
import time
start_time = time.time()
eventManager().fire(Events.SLICING_STARTED, {"stl": source_path, "gcode": dest_path, "progressAvailable": slicer.get_slicer_properties()["progress_report"] if slicer else False})
import tempfile
f = tempfile.NamedTemporaryFile(suffix=".gco", delete=False)
temp_path = f.name
f.close()
with self._slicing_jobs_mutex:
source_job_key = (source_location, source_path)
dest_job_key = (dest_location, dest_path)
if dest_job_key in self._slicing_jobs:
job_slicer_name, job_absolute_source_path, job_temp_path = self._slicing_jobs[dest_job_key]
self._slicing_manager.cancel_slicing(job_slicer_name, job_absolute_source_path, job_temp_path)
del self._slicing_jobs[dest_job_key]
self._slicing_jobs[dest_job_key] = self._slicing_jobs[source_job_key] = (slicer_name, absolute_source_path, temp_path)
args = (source_location, source_path, temp_path, dest_location, dest_path, start_time, printer_profile_id, callback, callback_args)
self._slicing_manager.slice(slicer_name,
absolute_source_path,
temp_path,
profile,
stlProcessed,
position=position,
callback_args=args,
overrides=overrides,
printer_profile_id=printer_profile_id,
on_progress=self.on_slicing_progress,
on_progress_args=(slicer_name, source_location, source_path, dest_location, dest_path))
def on_slicing_progress(self, slicer, source_location, source_path, dest_location, dest_path, _progress=None):
if not _progress:
return
progress_int = int(_progress * 100)
if self._last_slicing_progress != progress_int:
self._last_slicing_progress = progress_int
for callback in self._slicing_progress_callbacks:
try: callback.sendSlicingProgress(slicer, source_location, source_path, dest_location, dest_path, progress_int)
except: self._logger.exception("Exception while pushing slicing progress")
if progress_int:
def call_plugins(slicer, source_location, source_path, dest_location, dest_path, progress):
for plugin in self._progress_plugins:
try:
plugin.on_slicing_progress(slicer, source_location, source_path, dest_location, dest_path, progress)
except:
self._logger.exception("Exception while sending slicing progress to plugin %s" % plugin._identifier)
import threading
thread = threading.Thread(target=call_plugins, args=(slicer, source_location, source_path, dest_location, dest_path, progress_int))
thread.daemon = False
thread.start()
def get_busy_files(self):
return self._slicing_jobs.keys()
def file_exists(self, destination, path):
return self._storage(destination).file_exists(path)
def list_files(self, destinations=None, path=None, filter=None, recursive=None):
if not destinations:
destinations = self._storage_managers.keys()
if isinstance(destinations, (str, unicode, basestring)):
destinations = [destinations]
result = dict()
for dst in destinations:
result[dst] = self._storage_managers[dst].list_files(path=path, filter=filter, recursive=recursive)
return result
def add_file(self, destination, path, file_object, links=None, allow_overwrite=False, printer_profile=None, analysis=None):
if printer_profile is None:
printer_profile = self._printer_profile_manager.get_current_or_default()
file_path = self._storage(destination).add_file(path, file_object, links=links, printer_profile=printer_profile, allow_overwrite=allow_overwrite)
absolute_path = self._storage(destination).path_on_disk(file_path)
if analysis is None:
file_type = get_file_type(absolute_path)
if file_type:
queue_entry = QueueEntry(file_path, file_type[-1], destination, absolute_path, printer_profile)
self._analysis_queue.enqueue(queue_entry, high_priority=True)
else:
self._add_analysis_result(destination, path, analysis)
eventManager().fire(Events.UPDATED_FILES, dict(type="printables"))
return file_path
def remove_file(self, destination, path):
self._storage(destination).remove_file(path)
eventManager().fire(Events.UPDATED_FILES, dict(type="printables"))
def add_folder(self, destination, path, ignore_existing=True):
folder_path = self._storage(destination).add_folder(path, ignore_existing=ignore_existing)
eventManager().fire(Events.UPDATED_FILES, dict(type="printables"))
return folder_path
def remove_folder(self, destination, path, recursive=True):
self._storage(destination).remove_folder(path, recursive=recursive)
eventManager().fire(Events.UPDATED_FILES, dict(type="printables"))
def get_metadata(self, destination, path):
return self._storage(destination).get_metadata(path)
def add_link(self, destination, path, rel, data):
self._storage(destination).add_link(path, rel, data)
def remove_link(self, destination, path, rel, data):
self._storage(destination).remove_link(path, rel, data)
def log_print(self, destination, path, timestamp, print_time, success, printer_profile):
try:
if success:
self._storage(destination).add_history(path, dict(timestamp=timestamp, printTime=print_time, success=success, printerProfile=printer_profile))
else:
self._storage(destination).add_history(path, dict(timestamp=timestamp, success=success, printerProfile=printer_profile))
eventManager().fire(Events.METADATA_STATISTICS_UPDATED, dict(storage=destination, path=path))
except NoSuchStorage:
# if there's no storage configured where to log the print, we'll just not log it
pass
def set_additional_metadata(self, destination, path, key, data, overwrite=False, merge=False):
self._storage(destination).set_additional_metadata(path, key, data, overwrite=overwrite, merge=merge)
def remove_additional_metadata(self, destination, path, key):
self._storage(destination).remove_additional_metadata(path, key)
def path_on_disk(self, destination, path):
return self._storage(destination).path_on_disk(path)
def sanitize(self, destination, path):
return self._storage(destination).sanitize(path)
def sanitize_name(self, destination, name):
return self._storage(destination).sanitize_name(name)
def sanitize_path(self, destination, path):
return self._storage(destination).sanitize_path(path)
def split_path(self, destination, path):
return self._storage(destination).split_path(path)
def join_path(self, destination, *path):
return self._storage(destination).join_path(*path)
def path_in_storage(self, destination, path):
return self._storage(destination).path_in_storage(path)
def _storage(self, destination):
if not destination in self._storage_managers:
raise NoSuchStorage("No storage configured for destination {destination}".format(**locals()))
return self._storage_managers[destination]
def _add_analysis_result(self, destination, path, result):
if not destination in self._storage_managers:
return
storage_manager = self._storage_managers[destination]
storage_manager.set_additional_metadata(path, "analysis", result)
def _on_analysis_finished(self, entry, result):
self._add_analysis_result(entry.location, entry.path, result)