%PDF- %PDF-
| Direktori : /lib/python3/dist-packages/fs/ |
| Current File : //lib/python3/dist-packages/fs/_bulk.py |
"""
Implements a thread pool for parallel copying of files.
"""
from __future__ import unicode_literals
import threading
import typing
from six.moves.queue import Queue
from .copy import copy_file_internal
from .errors import BulkCopyFailed
from .tools import copy_file_data
if typing.TYPE_CHECKING:
from .base import FS
from types import TracebackType
from typing import IO, List, Optional, Text, Type
class _Worker(threading.Thread):
"""Worker thread that pulls tasks from a queue."""
def __init__(self, copier):
# type (Copier) -> None
self.copier = copier
super(_Worker, self).__init__()
self.daemon = True
def run(self):
# type () -> None
queue = self.copier.queue
while True:
task = queue.get(block=True)
try:
if task is None:
break # Sentinel to exit thread
task()
except Exception as error:
self.copier.add_error(error)
finally:
queue.task_done()
class _Task(object):
"""Base class for a task."""
def __call__(self):
# type: () -> None
"""Task implementation."""
class _CopyTask(_Task):
"""A callable that copies from one file another."""
def __init__(self, src_file, dst_file):
# type: (IO, IO) -> None
self.src_file = src_file
self.dst_file = dst_file
def __call__(self):
# type: () -> None
try:
copy_file_data(self.src_file, self.dst_file, chunk_size=1024 * 1024)
finally:
try:
self.src_file.close()
finally:
self.dst_file.close()
class Copier(object):
"""Copy files in worker threads."""
def __init__(self, num_workers=4):
# type: (int) -> None
if num_workers < 0:
raise ValueError("num_workers must be >= 0")
self.num_workers = num_workers
self.queue = None # type: Optional[Queue[_Task]]
self.workers = [] # type: List[_Worker]
self.errors = [] # type: List[Exception]
self.running = False
def start(self):
"""Start the workers."""
if self.num_workers:
self.queue = Queue(maxsize=self.num_workers)
self.workers = [_Worker(self) for _ in range(self.num_workers)]
for worker in self.workers:
worker.start()
self.running = True
def stop(self):
"""Stop the workers (will block until they are finished)."""
if self.running and self.num_workers:
for _worker in self.workers:
self.queue.put(None)
for worker in self.workers:
worker.join()
# Free up references held by workers
del self.workers[:]
self.queue.join()
self.running = False
def add_error(self, error):
"""Add an exception raised by a task."""
self.errors.append(error)
def __enter__(self):
self.start()
return self
def __exit__(
self,
exc_type, # type: Optional[Type[BaseException]]
exc_value, # type: Optional[BaseException]
traceback, # type: Optional[TracebackType]
):
self.stop()
if traceback is None and self.errors:
raise BulkCopyFailed(self.errors)
def copy(self, src_fs, src_path, dst_fs, dst_path):
# type: (FS, Text, FS, Text) -> None
"""Copy a file from one fs to another."""
if self.queue is None:
# This should be the most performant for a single-thread
copy_file_internal(src_fs, src_path, dst_fs, dst_path)
else:
src_file = src_fs.openbin(src_path, "r")
try:
dst_file = dst_fs.openbin(dst_path, "w")
except Exception:
src_file.close()
raise
task = _CopyTask(src_file, dst_file)
self.queue.put(task)