--- /dev/null
+#
+# Copyright 2016 RIFT.IO Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Author(s): Varun Prasad
+# Creation Date: 09/25/2016
+#
+
+import abc
+import enum
+import os
+import uuid
+import time
+
+
+class InvalidDestinationError(Exception):
+ pass
+
+
+class DownloaderProtocol:
+ """Listener of this class can implement the following method to get a
+ callback
+ """
+ def on_download_started(self, job):
+ """Called when the download starts
+
+ Args:
+ job (DownloadJob): Yang Model
+
+ """
+ pass
+
+ def on_download_progress(self, job):
+ """Called after each chunk is downloaded
+
+ Args:
+ job (DownloadJob): Yang Model
+
+ """
+ pass
+
+ def on_download_succeeded(self, job):
+ """Called when the download is completed successfully
+
+ Args:
+ job (DownloadJob): Yang Model
+
+ """
+ pass
+
+ def on_download_failed(self, job):
+ """Called when the download fails
+
+ Args:
+ job (DownloadJob): Yang Model
+
+ """
+ pass
+
+ def on_download_cancelled(self, job):
+ """Called when the download is canceled
+
+ Args:
+ job (DownloadJob): Yang Model
+
+ """
+ pass
+
+ def on_download_finished(self, job):
+ """Called when the download finishes regardless of the status of the
+ download (success, failed or canceled)
+
+ Args:
+ job (DownloadJob): Yang Model
+
+ """
+ pass
+
+
+class DownloadStatus(enum.Enum):
+ STARTED = 1
+ IN_PROGRESS = 2
+ COMPLETED = 3
+ FAILED = 4
+ CANCELLED = 5
+
+
+class DownloadMeta:
+ """Model data used by the downloader.
+ """
+ def __init__(self, url, dest_file):
+ self.url = url
+ self.filepath = dest_file
+ self.download_id = str(uuid.uuid4())
+ self.bytes_total = 0
+ self.progress_percent = 0
+ self.bytes_downloaded = 0
+ self.bytes_per_second = 0
+
+
+ self.start_time = 0
+ self.stop_time = 0
+ self.detail = ""
+
+ @property
+ def filename(self):
+ return os.path.basename(self.filepath)
+
+ def start_download(self):
+ self.start_time = time.time()
+
+ def end_download(self):
+ self.end_time = time.time()
+
+ def set_state(self, state):
+ self.status = state
+
+ def update_with_data(self, downloaded_chunk):
+ self.bytes_downloaded += len(downloaded_chunk)
+
+ if self.bytes_total != 0:
+ self.progress_percent = \
+ int((self.bytes_downloaded / self.bytes_total) * 100)
+
+ # compute bps
+ seconds_elapsed = time.time() - self.start_time
+ self.bytes_per_second = self.bytes_downloaded // seconds_elapsed
+
+ def update_data_with_head(self, headers):
+ """Update the model from the header of HEAD request
+
+ Args:
+ headers (dict): headers from HEAD response
+ """
+ if 'Content-Length' in headers:
+ self.bytes_total = int(headers['Content-Length'])
+
+ def as_dict(self):
+ return self.__dict__
+
+
+class AbstractDownloader:
+
+ def __init__(self):
+ self._delegate = None
+
+ @property
+ def delegate(self):
+ return self._delegate
+
+ @delegate.setter
+ def delegate(self, delegate):
+ self._delegate = delegate
+
+ @abc.abstractproperty
+ def download_id(self):
+ pass
+
+ @abc.abstractmethod
+ def cancel_download(self):
+ pass
+
+ @abc.abstractmethod
+ def close(self):
+ pass
+
+ @abc.abstractmethod
+ def download(self):
+ pass