Allow aborting a running BAT Pack operation

This commit is contained in:
Sybren A. Stüvel 2018-03-16 12:15:53 +01:00
parent b82bc14fcf
commit 164f65f30c
2 changed files with 81 additions and 23 deletions

View File

@ -310,10 +310,6 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
return filepath return filepath
def quit(self):
super().quit()
bpy.context.window_manager.flamenco_status = 'IDLE'
async def bat_pack(self, filepath: Path) -> (typing.Optional[Path], typing.List[Path]): async def bat_pack(self, filepath: Path) -> (typing.Optional[Path], typing.List[Path]):
"""BAT-packs the blendfile to the destination directory. """BAT-packs the blendfile to the destination directory.
@ -347,7 +343,7 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
return None, [] return None, []
try: try:
outfile, missing_sources = await bat_interface.bat_copy( outfile, missing_sources = await bat_interface.copy(
bpy.context, filepath, projdir, outdir, exclusion_filter) bpy.context, filepath, projdir, outdir, exclusion_filter)
except bat_interface.FileTransferError as ex: except bat_interface.FileTransferError as ex:
self.log.error('Could not transfer %d files, starting with %s', self.log.error('Could not transfer %d files, starting with %s',
@ -356,6 +352,7 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
self.quit() self.quit()
return None, [] return None, []
bpy.context.window_manager.flamenco_status = 'DONE'
return outfile, missing_sources return outfile, missing_sources
@ -398,7 +395,7 @@ class FLAMENCO_OT_copy_files(Operator,
prefs = preferences() prefs = preferences()
exclusion_filter = (prefs.flamenco_exclude_filter or '').strip() exclusion_filter = (prefs.flamenco_exclude_filter or '').strip()
outpath, missing_sources = await bat_interface.bat_copy( outpath, missing_sources = await bat_interface.copy(
context, context,
Path(context.blend_data.filepath), Path(context.blend_data.filepath),
Path(prefs.cloud_project_local_path), Path(prefs.cloud_project_local_path),
@ -411,11 +408,24 @@ class FLAMENCO_OT_copy_files(Operator,
self.report({'ERROR'}, 'Missing source files: %s' % '; '.join(names)) self.report({'ERROR'}, 'Missing source files: %s' % '; '.join(names))
else: else:
self.report({'INFO'}, 'Written %s' % outpath) self.report({'INFO'}, 'Written %s' % outpath)
context.window_manager.flamenco_status = 'DONE'
self.quit() self.quit()
def quit(self):
super().quit() class FLAMENCO_OT_abort(Operator, FlamencoPollMixin):
bpy.context.window_manager.flamenco_status = 'IDLE' """Aborts a running Flamenco file packing/transfer operation."""
bl_idname = 'flamenco.abort'
bl_label = 'Abort'
bl_description = __doc__.rstrip('.')
@classmethod
def poll(cls, context):
return super().poll(context) and context.window_manager.flamenco_status != 'ABORTING'
def execute(self, context):
context.window_manager.flamenco_status = 'ABORTING'
bat_interface.abort()
return {'FINISHED'}
class FLAMENCO_OT_explore_file_path(FlamencoPollMixin, class FLAMENCO_OT_explore_file_path(FlamencoPollMixin,
@ -685,19 +695,26 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
labeled_row.label(str(render_output)) labeled_row.label(str(render_output))
flamenco_status = context.window_manager.flamenco_status flamenco_status = context.window_manager.flamenco_status
if flamenco_status == 'IDLE': if flamenco_status in {'IDLE', 'ABORTED', 'DONE'}:
layout.operator(FLAMENCO_OT_render.bl_idname, layout.operator(FLAMENCO_OT_render.bl_idname,
text='Render on Flamenco', text='Render on Flamenco',
icon='RENDER_ANIMATION') icon='RENDER_ANIMATION')
elif flamenco_status == 'INVESTIGATING': elif flamenco_status == 'INVESTIGATING':
layout.label('Investigating your files') row = layout.row(align=True)
row.label('Investigating your files')
row.operator(FLAMENCO_OT_abort.bl_idname, text='', icon='CANCEL')
elif flamenco_status == 'COMMUNICATING': elif flamenco_status == 'COMMUNICATING':
layout.label('Communicating with Flamenco Server') layout.label('Communicating with Flamenco Server')
elif flamenco_status == 'ABORTING':
row = layout.row(align=True)
row.label('Aborting, please wait.')
row.operator(FLAMENCO_OT_abort.bl_idname, text='', icon='CANCEL')
if flamenco_status == 'TRANSFERRING': if flamenco_status == 'TRANSFERRING':
layout.prop(context.window_manager, 'flamenco_progress', row = layout.row(align=True)
text=context.window_manager.flamenco_status_txt) row.prop(context.window_manager, 'flamenco_progress',
elif flamenco_status != 'IDLE': text=context.window_manager.flamenco_status_txt)
row.operator(FLAMENCO_OT_abort.bl_idname, text='', icon='CANCEL')
elif flamenco_status != 'IDLE' and context.window_manager.flamenco_status_txt:
layout.label(context.window_manager.flamenco_status_txt) layout.label(context.window_manager.flamenco_status_txt)
@ -746,6 +763,7 @@ def register():
bpy.utils.register_class(FLAMENCO_OT_explore_file_path) bpy.utils.register_class(FLAMENCO_OT_explore_file_path)
bpy.utils.register_class(FLAMENCO_OT_enable_output_path_override) bpy.utils.register_class(FLAMENCO_OT_enable_output_path_override)
bpy.utils.register_class(FLAMENCO_OT_disable_output_path_override) bpy.utils.register_class(FLAMENCO_OT_disable_output_path_override)
bpy.utils.register_class(FLAMENCO_OT_abort)
bpy.utils.register_class(FLAMENCO_PT_render) bpy.utils.register_class(FLAMENCO_PT_render)
scene = bpy.types.Scene scene = bpy.types.Scene
@ -804,6 +822,8 @@ def register():
('TRANSFERRING', 'TRANSFERRING', 'Transferring all dependencies.'), ('TRANSFERRING', 'TRANSFERRING', 'Transferring all dependencies.'),
('COMMUNICATING', 'COMMUNICATING', 'Communicating with Flamenco Server.'), ('COMMUNICATING', 'COMMUNICATING', 'Communicating with Flamenco Server.'),
('DONE', 'DONE', 'Not doing anything, but doing something earlier.'), ('DONE', 'DONE', 'Not doing anything, but doing something earlier.'),
('ABORTING', 'ABORTING', 'User requested we stop doing something.'),
('ABORTED', 'ABORTED', 'We stopped doing something.'),
], ],
name='flamenco_status', name='flamenco_status',
default='IDLE', default='IDLE',

View File

@ -2,6 +2,7 @@
import asyncio import asyncio
import logging import logging
import threading
import typing import typing
import pathlib import pathlib
@ -14,6 +15,9 @@ import bpy
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
_running_packer = None # type: pack.Packer
_packer_lock = threading.RLock()
class BatProgress(progress.Callback): class BatProgress(progress.Callback):
"""Report progress of BAT Packing to the UI. """Report progress of BAT Packing to the UI.
@ -30,10 +34,20 @@ class BatProgress(progress.Callback):
def txt(self, msg: str): def txt(self, msg: str):
"""Set a text in a thread-safe way.""" """Set a text in a thread-safe way."""
async def set_text(): async def set_text():
self.wm.flamenco_status_txt = msg self.wm.flamenco_status_txt = msg
asyncio.run_coroutine_threadsafe(set_text(), loop=self.loop) asyncio.run_coroutine_threadsafe(set_text(), loop=self.loop)
def status(self, status: str):
"""Set the flamenco_status property in a thread-safe way."""
async def set_status():
self.wm.flamenco_status = status
asyncio.run_coroutine_threadsafe(set_status(), loop=self.loop)
def pack_start(self) -> None: def pack_start(self) -> None:
self.txt('Starting BAT Pack operation') self.txt('Starting BAT Pack operation')
@ -45,6 +59,10 @@ class BatProgress(progress.Callback):
else: else:
self.txt('Pack of %s done' % output_blendfile.name) self.txt('Pack of %s done' % output_blendfile.name)
def pack_aborted(self):
self.txt('Aborted')
self.status('ABORTED')
def trace_blendfile(self, filename: pathlib.Path) -> None: def trace_blendfile(self, filename: pathlib.Path) -> None:
"""Called for every blendfile opened when tracing dependencies.""" """Called for every blendfile opened when tracing dependencies."""
self.txt('Inspecting %s' % filename.name) self.txt('Inspecting %s' % filename.name)
@ -71,26 +89,29 @@ class BatProgress(progress.Callback):
pass pass
async def bat_copy(context, async def copy(context,
base_blendfile: pathlib.Path, base_blendfile: pathlib.Path,
project: pathlib.Path, project: pathlib.Path,
target: pathlib.Path, target: pathlib.Path,
exclusion_filter: str) -> typing.Tuple[pathlib.Path, typing.Set[pathlib.Path]]: exclusion_filter: str) -> typing.Tuple[pathlib.Path, typing.Set[pathlib.Path]]:
"""Use BAT🦇 to copy the given file and dependencies to the target location. """Use BAT🦇 to copy the given file and dependencies to the target location.
:raises: FileTransferError if a file couldn't be transferred. :raises: FileTransferError if a file couldn't be transferred.
:returns: the path of the packed blend file, and a set of missing sources. :returns: the path of the packed blend file, and a set of missing sources.
""" """
global _running_packer
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
wm = bpy.context.window_manager wm = bpy.context.window_manager
with pack.Packer(base_blendfile, project, target) as packer: with pack.Packer(base_blendfile, project, target) as packer:
if exclusion_filter: with _packer_lock:
packer.exclude(*exclusion_filter.split()) if exclusion_filter:
packer.exclude(*exclusion_filter.split())
packer.progress_cb = BatProgress(context) packer.progress_cb = BatProgress(context)
_running_packer = packer
log.debug('awaiting strategise') log.debug('awaiting strategise')
wm.flamenco_status = 'INVESTIGATING' wm.flamenco_status = 'INVESTIGATING'
@ -103,4 +124,21 @@ async def bat_copy(context,
log.debug('done') log.debug('done')
wm.flamenco_status = 'DONE' wm.flamenco_status = 'DONE'
with _packer_lock:
_running_packer = None
return packer.output_path, packer.missing_files return packer.output_path, packer.missing_files
def abort() -> None:
"""Abort a running copy() call.
No-op when there is no running copy(). Can be called from any thread.
"""
with _packer_lock:
if _running_packer is None:
log.debug('No running packer, ignoring call to bat_abort()')
return
log.info('Aborting running packer')
_running_packer.abort()