Example #1
0
class CloudqubeClient:
    """Handles calls to Terraqube Cloud."""

    def __init__(self, server):
        self._nam = QgsNetworkAccessManager()
        self._server = server
        self._token = None
        self._replies = []

    # Private methods

    def get_url(self, url):
        if url.startswith('http'):
            return url
        else:
            return "{0}/api/1.0.0/{1}".format(self._server, url)

    def finished(self, reply):
        self._replies.remove(reply)

    def set_token(self, token):
        self._token = token

    def str_to_byte_array(self, input_string):
        return bytes(input_string, encoding='utf-8')

    def byte_array_to_string(self, byte_array):
        return byte_array.data().decode('utf-8')

    def prepare_request(self, url, content_type):
        url = self.get_url(url)
        req = QNetworkRequest(QUrl(url))
        req.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
        req.setRawHeader(b'Accept', b'application/json')
        if self._token:
            req.setRawHeader(b'Authorization', self.str_to_byte_array(
                'Bearer {0}'.format(self._token)))
        return req

    def get(self, url, callback, error, content_type='application/json'):
        req = self.prepare_request(url, content_type)
        self._replies.append(CloudqubeJsonReply(
            self._nam.get(req), None, callback, self.finished, error))

    def post_json(self, url, data, callback, error,
            content_type='application/json'):
        req = self.prepare_request(url, content_type)
        byte_array = self.str_to_byte_array(json.dumps(data)) if data else None
        self._replies.append(CloudqubeJsonReply(self._nam.post(
            req, byte_array), data, callback, self.finished, error))

    def put_json(self, url, data, callback, error,
            content_type='application/json'):
        req = self.prepare_request(url, content_type)
        byte_array = self.str_to_byte_array(json.dumps(data)) if data else None
        self._replies.append(CloudqubeJsonReply(self._nam.put(
            req, byte_array), data, callback, self.finished, error))

    def post_bytes(self, url, data, callback, error,
            content_type='application/octet-stream'):
        req = self.prepare_request(url, content_type)
        self._replies.append(CloudqubeJsonReply(self._nam.post(
            req, data), data, callback, self.finished, error))

    def delete_nam(self, url, callback, error, content_type='application/json'):
        req = self.prepare_request(url, content_type)
        self._replies.append(CloudqubeJsonReply(
            self._nam.deleteResource(req), None, callback, self.finished, error))

    def add_text_parts(self, multi_part, fields):
        """Adds all text fields to the multipart object."""
        for key in fields:
            text_part = QHttpPart()
            text_part.setHeader(QNetworkRequest.ContentDispositionHeader,
                QVariant('form-data; name="{0}"'.format(key)))
            text_part.setBody(self.str_to_byte_array(fields[key]))
            multi_part.append(text_part)

    def add_image_part(self, multi_part, filename):
        """Adds the image field to the multipart object."""
        image_part = QHttpPart()
        image_part.setHeader(QNetworkRequest.ContentTypeHeader,
                             QVariant('application/octet-stream'))
        image_part.setHeader(
            QNetworkRequest.ContentDispositionHeader,
            QVariant('form-data; name="file"'))
        f = QFile(filename)
        f.open(QIODevice.ReadOnly)
        image_part.setBodyDevice(f)
        f.setParent(multi_part)
        multi_part.append(image_part)

    def complete_hiperqube_upload(self, hiperqube_id, uploaded_parts, callback, error):
        QgsMessageLog.logMessage('complete_hiperqube_upload: started')
        payload = uploaded_parts
        self.put_json(
            "hiperqubes/{0}/upload".format(hiperqube_id),
            payload,
            callback,
            error)

    def create_hiperqube_upload(self, hiperqube_id, size, callback, error):
        """Creates a new upload for the given hiperqube."""
        QgsMessageLog.logMessage('create_hiperqube_upload: started')
        payload = {
            'size': size
        }
        self.post_json(
            "hiperqubes/{0}/upload".format(hiperqube_id),
            payload,
            callback,
            error)

    # Public methods

    # Authentication

    def login_user(self, username, password, callback, error):
        """Login user to Terraqube Cloud using username and password."""
        login_callback = LoginCallback(self, callback)
        response = self.post_json(
            'user/login',
            {'username': username, 'password': password},
            login_callback.notify,
            error)

    # Projects

    def create_project(self, name, callback, error):
        """Creates a new project with the specified name."""
        payload = {
            'name': name
        }
        self.post_json("projects", payload, callback, error)

    def get_projects(self, callback, error):
        """Get list of projects for current user."""
        self.get('projects', callback, error)

    def delete_project(self, project_id, callback, error):
        """Deletes a project."""
        self.delete_nam("projects/{0}".format(project_id), callback, error)

    # Hiperqubes

    def get_hiperqubes(self, project_id, callback, error):
        """Get list of hiperqubes for current user."""
        self.get(
            "projects/{0}/hiperqubes".format(project_id), callback, error)

    def get_hiperqube_details(self, hiperqube_id, callback, error):
        """Get hiperqube details."""
        self.get("hiperqubes/{0}".format(hiperqube_id), callback, error)

    def create_hiperqube(self, project_id, name, captured_date, callback,
            error):
        """Create a new hiperqube."""
        captured_date = captured_date.replace(microsecond=0).replace(
            tzinfo=pytz.reference.LocalTimezone())
        captured_date_str = captured_date.isoformat()
        payload = {
            'name': name,
            'capturedDate': captured_date_str
        }
        self.post_json(
            "projects/{0}/hiperqubes".format(project_id),
            payload,
            callback,
            error)

    def delete_hiperqube(self, hiperqube_id, callback, error):
        """Deletes a hiperqube."""
        self.delete_nam("hiperqubes/{0}".format(hiperqube_id), callback, error)

    def upload_hiperqube_hdr(self, hiperqube_id, filename, callback, error):
        """Upload an HDR file to an existing hiperqube."""
        f = QFile(filename)
        f.open(QIODevice.ReadOnly)
        self.post_bytes("hiperqubes/{0}/hdr".format(hiperqube_id),
                            f,
                            callback,
                            error,
                            content_type='application/octet-stream')

    def upload_hiperqube_bil(self, url, fields, filename, progress, callback,
            error):
        """Upload a BIL file to an existing hiperqube."""
        req = QNetworkRequest(QUrl(url))
        multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)

        self.add_text_parts(multi_part, fields)
        f = self.add_image_part(multi_part, filename)
        rep = self._nam.post(req, multi_part)
        multi_part.setParent(rep)
        reply = CloudqubeProgressReply(
            req, progress, callback, self.finished, error)
        self._replies.append(reply)

    def upload_hiperqube_bil_multipart(self, hiperqube_id, filename, progress, callback,
            error):
        """Upload a BIL file to an existing hiperqube in multiple parts."""

        def hiperqube_upload_created(parts):
            QgsMessageLog.logMessage('hiperqube_upload_created: sorting part numbers')
            parts = sorted(parts, key=lambda part: part['partNumber'])
            QgsMessageLog.logMessage('hiperqube_upload_created: sorted!')
            uploaded_parts = []
            f = QFile(filename)
            QgsMessageLog.logMessage('hiperqube_upload_created: opening file')
            f.open(QIODevice.ReadOnly)
            QgsMessageLog.logMessage('hiperqube_upload_created: file open')

            def upload_part(index):
                QgsMessageLog.logMessage('hiperqube_upload_created: uploading part {0}'.format(index))
                def part_uploaded():
                    nonlocal uploaded_size
                    nonlocal index
                    e_tag = self.byte_array_to_string(rep.rawHeader(b'ETag'))
                    QgsMessageLog.logMessage('part_uplaoded: eTag {0}'.format(e_tag))
                    uploaded_size = uploaded_size + size
                    uploaded_parts.append({ 'eTag': e_tag, 'partNumber': part_number })
                    index = index + 1
                    if index < len(parts):
                        upload_part(index)
                    else:
                        QgsMessageLog.logMessage('part_uplaoded: closing file'.format(e_tag))
                        f.close()
                        self.complete_hiperqube_upload(hiperqube_id, uploaded_parts, callback, error)

                nonlocal uploaded_size
                part = parts[index]
                url = part['url']
                size = part['size']
                part_number = part['partNumber']
                req = QNetworkRequest(QUrl(url))
                data = f.read(size)
                QgsMessageLog.logMessage('upload_part: making put request')
                rep = self._nam.put(req, data)
                QgsMessageLog.logMessage('upload_part: got reply')
                reply = CloudqubeMultipartProgressReply(
                    uploaded_size, total_size, rep, progress, part_uploaded, self.finished, error)
                QgsMessageLog.logMessage('upload_part: appending reply')
                self._replies.append(reply)

            upload_part(0)

        uploaded_size = 0
        total_size = os.path.getsize(filename)
        self.create_hiperqube_upload(hiperqube_id, total_size, hiperqube_upload_created, error)

    # Signatures

    def create_signature(self, hiperqube_id, name, pixels, callback, error):
        """Creates a new signature with the given name and pixels."""
        points = []
        for pixel in pixels:
            points.append({
                'line': pixel[0],
                'col': pixel[1]
            })
        payload = {
            'name': name,
            'points': points
        }
        self.post_json(
            "hiperqubes/{0}/signatures".format(hiperqube_id),
            payload,
            callback,
            error)

    def get_signatures(self, hiperqube_id, callback, error):
        """Gets all signatures from a hiperqube."""
        self.get(
            "hiperqubes/{0}/signatures".format(hiperqube_id), callback, error)

    def get_signature(self, signature_id, callback, error):
        """Gets a signatures by id."""
        self.get(
            "signatures/{0}".format(signature_id), callback, error)

    def delete_signature(self, signature_id, callback, error):
        """Deletes a signature."""
        self.delete_nam(
            "signatures/{0}".format(signature_id),
            callback,
            error)
        
    def post_signature_chart(self, signature_ids, callback, error):
        """Creates a chart from an array of signature_ids."""
        self.post_json(
            'signatures/chart',
            signature_ids,
            callback,
            error)

    # File download

    def download_file(self, uri, callback, error):
        """Downloads the file in the uri in a temporary file and returns its
        name."""
        req = QNetworkRequest(QUrl(uri))
        reply = CloudqubeFileReply(self._nam.get(
            req), callback, self.finished, error)
        self._replies.append(reply)
        return reply.filename()

    # Abort

    def abort_upload(self):
        """Aborts any pending upload."""
        for reply in self._replies:
            reply.abort()
        self._replies = []