Example #1
0
    def test_basic(self):
        """
        Upload a basic file using the FileUploadResource, in just a single chunk.
        """
        upload_dir = FilePath(self.mktemp())
        upload_dir.makedirs()
        temp_dir = FilePath(self.mktemp())
        temp_dir.makedirs()

        fields = {
            "file_name": "resumableFilename",
            "mime_type": "resumableType",
            "total_size": "resumableTotalSize",
            "chunk_number": "resumableChunkNumber",
            "chunk_size": "resumableChunkSize",
            "total_chunks": "resumableTotalChunks",
            "content": "file",
            "on_progress": "on_progress",
            "session": "session"
        }

        mock_session = Mock()

        resource = FileUploadResource(upload_dir.path, temp_dir.path, fields,
                                      mock_session)

        mp = Multipart()
        mp.add_part(b"resumableChunkNumber", b"1")
        mp.add_part(b"resumableChunkSize", b"1048576")
        mp.add_part(b"resumableCurrentChunkSize", b"16")
        mp.add_part(b"resumableTotalSize", b"16")
        mp.add_part(b"resumableType", b"text/plain")
        mp.add_part(b"resumableIdentifier", b"16-examplefiletxt")
        mp.add_part(b"resumableFilename", b"examplefile.txt")
        mp.add_part(b"resumableRelativePath", b"examplefile.txt")
        mp.add_part(b"resumableTotalChunks", b"1")
        mp.add_part(b"on_progress", b"com.example.upload.on_progress")
        mp.add_part(b"session", b"6891276359801283")
        mp.add_part(b"file",
                    b"hello Crossbar!\n",
                    content_type=b"application/octet-stream",
                    filename=b"blob")
        body, headers = mp.render()

        d = renderResource(resource,
                           b"/",
                           method="POST",
                           headers=headers,
                           body=body)

        res = self.successResultOf(d)
        res.setResponseCode.assert_called_once_with(200)

        self.assertEqual(len(mock_session.method_calls), 2)

        # Starting the upload
        self.assertEqual(mock_session.method_calls[0][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[0][1][1]["status"],
                         "started")
        self.assertEqual(mock_session.method_calls[0][1][1]["id"],
                         "examplefile.txt")

        # Upload complete
        self.assertEqual(mock_session.method_calls[1][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[1][1][1]["status"],
                         "finished")
        self.assertEqual(mock_session.method_calls[1][1][1]["id"],
                         "examplefile.txt")

        # Nothing in the temp dir, one file in the upload
        self.assertEqual(len(temp_dir.listdir()), 0)
        self.assertEqual(len(upload_dir.listdir()), 1)
        with upload_dir.child("examplefile.txt").open("rb") as f:
            self.assertEqual(f.read(), b"hello Crossbar!\n")
Example #2
0
    def test_multichunk_shuffle(self):
        """
        Uploading files that are in multiple chunks and are uploaded in different order works.
        """
        upload_dir = FilePath(self.mktemp())
        upload_dir.makedirs()
        temp_dir = FilePath(self.mktemp())
        temp_dir.makedirs()

        fields = {
            "file_name": "resumableFilename",
            "mime_type": "resumableType",
            "total_size": "resumableTotalSize",
            "chunk_number": "resumableChunkNumber",
            "chunk_size": "resumableChunkSize",
            "total_chunks": "resumableTotalChunks",
            "content": "file",
            "on_progress": "on_progress",
            "session": "session"
        }

        mock_session = Mock()

        resource = FileUploadResource(upload_dir.path, temp_dir.path, fields,
                                      mock_session)

        #
        # Chunk 2

        multipart_body = b"""-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableChunkNumber"\r\n\r\n2\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableChunkSize"\r\n\r\n10\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableCurrentChunkSize"\r\n\r\n6\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableTotalSize"\r\n\r\n16\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableType"\r\n\r\ntext/plain\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableIdentifier"\r\n\r\n16-examplefiletxt\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableFilename"\r\n\r\nexamplefile.txt\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableRelativePath"\r\n\r\nexamplefile.txt\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="resumableTotalChunks"\r\n\r\n2\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="on_progress"\r\n\r\ncom.example.upload.on_progress\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="session"\r\n\r\n8887465641628580\r\n-----------------------------42560029919436807832069165364\r\nContent-Disposition: form-data; name="file"; filename="blob"\r\nContent-Type: application/octet-stream\r\n\r\nsbar!\n\r\n-----------------------------42560029919436807832069165364--\r\n"""

        d = renderResource(
            resource,
            b"/",
            method="POST",
            headers={
                b"content-type": [
                    b"multipart/form-data; boundary=---------------------------42560029919436807832069165364"
                ],
                b"Content-Length": [b"1688"]
            },
            body=multipart_body)

        res = self.successResultOf(d)
        res.setResponseCode.assert_called_once_with(200)

        # One directory in the temp dir, nothing in the upload dir, temp dir
        # contains one chunk
        self.assertEqual(len(temp_dir.listdir()), 1)
        self.assertEqual(len(temp_dir.child("examplefile.txt").listdir()), 1)
        with temp_dir.child("examplefile.txt").child("chunk_2").open(
                "rb") as f:
            # print(f.read())
            self.assertEqual(f.read(), b"sbar!\n")
        self.assertEqual(len(upload_dir.listdir()), 0)
        #
        # Chunk 1

        multipart_body = b"""-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableChunkNumber"\r\n\r\n1\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableChunkSize"\r\n\r\n10\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableCurrentChunkSize"\r\n\r\n10\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableTotalSize"\r\n\r\n16\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableType"\r\n\r\ntext/plain\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableIdentifier"\r\n\r\n16-examplefiletxt\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableFilename"\r\n\r\nexamplefile.txt\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableRelativePath"\r\n\r\nexamplefile.txt\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="resumableTotalChunks"\r\n\r\n2\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="on_progress"\r\n\r\ncom.example.upload.on_progress\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="session"\r\n\r\n8887465641628580\r\n-----------------------------1311987731215707521443909311\r\nContent-Disposition: form-data; name="file"; filename="blob"\r\nContent-Type: application/octet-stream\r\n\r\nhello Cros\r\n-----------------------------1311987731215707521443909311--\r\n"""

        d = renderResource(
            resource,
            b"/",
            method="POST",
            headers={
                b"content-type": [
                    b"multipart/form-data; boundary=---------------------------1311987731215707521443909311"
                ],
                b"Content-Length": [b"1680"]
            },
            body=multipart_body)

        res = self.successResultOf(d)
        res.setResponseCode.assert_called_once_with(200)

        self.assertEqual(len(mock_session.method_calls), 4)

        # Starting the upload
        self.assertEqual(mock_session.method_calls[0][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[0][1][1]["status"],
                         "started")
        self.assertEqual(mock_session.method_calls[0][1][1]["id"],
                         "examplefile.txt")
        self.assertEqual(mock_session.method_calls[0][1][1]["chunk"], 2)

        # Progress, first chunk done
        self.assertEqual(mock_session.method_calls[1][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[1][1][1]["status"],
                         "progress")
        self.assertEqual(mock_session.method_calls[1][1][1]["id"],
                         "examplefile.txt")
        self.assertEqual(mock_session.method_calls[1][1][1]["chunk"], 2)

        # Progress, second chunk done
        self.assertEqual(mock_session.method_calls[2][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[2][1][1]["status"],
                         "progress")
        self.assertEqual(mock_session.method_calls[2][1][1]["id"],
                         "examplefile.txt")
        self.assertEqual(mock_session.method_calls[2][1][1]["chunk"], 1)

        # Upload complete
        self.assertEqual(mock_session.method_calls[3][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[3][1][1]["status"],
                         "finished")
        self.assertEqual(mock_session.method_calls[3][1][1]["id"],
                         "examplefile.txt")
        self.assertEqual(mock_session.method_calls[3][1][1]["chunk"], 1)

        # Nothing in the temp dir, one file in the upload
        self.assertEqual(len(temp_dir.listdir()), 0)
        self.assertEqual(len(upload_dir.listdir()), 1)
        with upload_dir.child("examplefile.txt").open("rb") as f:
            self.assertEqual(f.read(), b"hello Crossbar!\n")
Example #3
0
    def create_resource(self, path_config):
        """
        Creates child resource to be added to the parent.

        :param path_config: Configuration for the new child resource.
        :type path_config: dict

        :returns: Resource -- the new child resource
        """
        # WAMP-WebSocket resource
        #
        if path_config['type'] == 'websocket':

            ws_factory = WampWebSocketServerFactory(
                self._router_session_factory, self.config.extra.cbdir,
                path_config, self._templates)

            # FIXME: Site.start/stopFactory should start/stop factories wrapped as Resources
            ws_factory.startFactory()

            return WebSocketResource(ws_factory)

        # Static file hierarchy resource
        #
        elif path_config['type'] == 'static':

            static_options = path_config.get('options', {})

            if 'directory' in path_config:

                static_dir = os.path.abspath(
                    os.path.join(self.config.extra.cbdir,
                                 path_config['directory']))

            elif 'package' in path_config:

                if 'resource' not in path_config:
                    raise ApplicationError(
                        "crossbar.error.invalid_configuration",
                        "missing resource")

                try:
                    mod = importlib.import_module(path_config['package'])
                except ImportError as e:
                    emsg = "ERROR: could not import resource '{}' from package '{}' - {}".format(
                        path_config['resource'], path_config['package'], e)
                    log.msg(emsg)
                    raise ApplicationError(
                        "crossbar.error.invalid_configuration", emsg)
                else:
                    try:
                        static_dir = os.path.abspath(
                            pkg_resources.resource_filename(
                                path_config['package'],
                                path_config['resource']))
                    except Exception as e:
                        emsg = "ERROR: could not import resource '{}' from package '{}' - {}".format(
                            path_config['resource'], path_config['package'], e)
                        log.msg(emsg)
                        raise ApplicationError(
                            "crossbar.error.invalid_configuration", emsg)

            else:

                raise ApplicationError("crossbar.error.invalid_configuration",
                                       "missing web spec")

            static_dir = static_dir.encode(
                'ascii',
                'ignore')  # http://stackoverflow.com/a/20433918/884770

            # create resource for file system hierarchy
            #
            if static_options.get('enable_directory_listing', False):
                static_resource_class = StaticResource
            else:
                static_resource_class = StaticResourceNoListing

            cache_timeout = static_options.get('cache_timeout',
                                               DEFAULT_CACHE_TIMEOUT)

            static_resource = static_resource_class(
                static_dir, cache_timeout=cache_timeout)

            # set extra MIME types
            #
            static_resource.contentTypes.update(EXTRA_MIME_TYPES)
            if 'mime_types' in static_options:
                static_resource.contentTypes.update(
                    static_options['mime_types'])
            patchFileContentTypes(static_resource)

            # render 404 page on any concrete path not found
            #
            static_resource.childNotFound = Resource404(
                self._templates, static_dir)

            return static_resource

        # WSGI resource
        #
        elif path_config['type'] == 'wsgi':

            if not _HAS_WSGI:
                raise ApplicationError("crossbar.error.invalid_configuration",
                                       "WSGI unsupported")

            # wsgi_options = path_config.get('options', {})

            if 'module' not in path_config:
                raise ApplicationError("crossbar.error.invalid_configuration",
                                       "missing WSGI app module")

            if 'object' not in path_config:
                raise ApplicationError("crossbar.error.invalid_configuration",
                                       "missing WSGI app object")

            # import WSGI app module and object
            mod_name = path_config['module']
            try:
                mod = importlib.import_module(mod_name)
            except ImportError as e:
                raise ApplicationError(
                    "crossbar.error.invalid_configuration",
                    "WSGI app module '{}' import failed: {} - Python search path was {}"
                    .format(mod_name, e, sys.path))
            else:
                obj_name = path_config['object']
                if obj_name not in mod.__dict__:
                    raise ApplicationError(
                        "crossbar.error.invalid_configuration",
                        "WSGI app object '{}' not in module '{}'".format(
                            obj_name, mod_name))
                else:
                    app = getattr(mod, obj_name)

            # create a Twisted Web WSGI resource from the user's WSGI application object
            try:
                wsgi_resource = WSGIResource(reactor, reactor.getThreadPool(),
                                             app)
            except Exception as e:
                raise ApplicationError(
                    "crossbar.error.invalid_configuration",
                    "could not instantiate WSGI resource: {}".format(e))
            else:
                return wsgi_resource

        # Redirecting resource
        #
        elif path_config['type'] == 'redirect':
            redirect_url = path_config['url'].encode('ascii', 'ignore')
            return RedirectResource(redirect_url)

        # JSON value resource
        #
        elif path_config['type'] == 'json':
            value = path_config['value']

            return JsonResource(value)

        # CGI script resource
        #
        elif path_config['type'] == 'cgi':

            cgi_processor = path_config['processor']
            cgi_directory = os.path.abspath(
                os.path.join(self.config.extra.cbdir,
                             path_config['directory']))
            cgi_directory = cgi_directory.encode(
                'ascii',
                'ignore')  # http://stackoverflow.com/a/20433918/884770

            return CgiDirectory(cgi_directory, cgi_processor,
                                Resource404(self._templates, cgi_directory))

        # WAMP-Longpoll transport resource
        #
        elif path_config['type'] == 'longpoll':

            path_options = path_config.get('options', {})

            lp_resource = WampLongPollResource(
                self._router_session_factory,
                timeout=path_options.get('request_timeout', 10),
                killAfter=path_options.get('session_timeout', 30),
                queueLimitBytes=path_options.get('queue_limit_bytes',
                                                 128 * 1024),
                queueLimitMessages=path_options.get('queue_limit_messages',
                                                    100),
                debug=path_options.get('debug', False),
                debug_transport_id=path_options.get('debug_transport_id',
                                                    None))
            lp_resource._templates = self._templates

            return lp_resource

        # Publisher resource (part of REST-bridge)
        #
        elif path_config['type'] == 'publisher':

            # create a vanilla session: the publisher will use this to inject events
            #
            publisher_session_config = ComponentConfig(
                realm=path_config['realm'], extra=None)
            publisher_session = ApplicationSession(publisher_session_config)

            # add the publisher session to the router
            #
            self._router_session_factory.add(publisher_session,
                                             authrole=path_config.get(
                                                 'role', 'anonymous'))

            # now create the publisher Twisted Web resource
            #
            return PublisherResource(path_config.get('options', {}),
                                     publisher_session)

        # Caller resource (part of REST-bridge)
        #
        elif path_config['type'] == 'caller':

            # create a vanilla session: the caller will use this to inject calls
            #
            caller_session_config = ComponentConfig(realm=path_config['realm'],
                                                    extra=None)
            caller_session = ApplicationSession(caller_session_config)

            # add the calling session to the router
            #
            self._router_session_factory.add(caller_session,
                                             authrole=path_config.get(
                                                 'role', 'anonymous'))

            # now create the caller Twisted Web resource
            #
            return CallerResource(path_config.get('options', {}),
                                  caller_session)

        # File Upload resource
        #
        elif path_config['type'] == 'upload':

            upload_directory = os.path.abspath(
                os.path.join(self.config.extra.cbdir,
                             path_config['directory']))
            upload_directory = upload_directory.encode(
                'ascii',
                'ignore')  # http://stackoverflow.com/a/20433918/884770
            if not os.path.isdir(upload_directory):
                emsg = "configured upload directory '{}' in file upload resource isn't a directory".format(
                    upload_directory)
                log.msg(emsg)
                raise ApplicationError("crossbar.error.invalid_configuration",
                                       emsg)

            if 'temp_directory' in path_config:
                temp_directory = os.path.abspath(
                    os.path.join(self.config.extra.cbdir,
                                 path_config['temp_directory']))
                temp_directory = temp_directory.encode(
                    'ascii',
                    'ignore')  # http://stackoverflow.com/a/20433918/884770
            else:
                temp_directory = os.path.abspath(tempfile.gettempdir())
                temp_directory = os.path.join(temp_directory,
                                              'crossbar-uploads')
                if not os.path.exists(temp_directory):
                    os.makedirs(temp_directory)

            if not os.path.isdir(temp_directory):
                emsg = "configured temp directory '{}' in file upload resource isn't a directory".format(
                    temp_directory)
                log.msg(emsg)
                raise ApplicationError("crossbar.error.invalid_configuration",
                                       emsg)

            # file upload progress and finish events are published via this session
            #
            upload_session_config = ComponentConfig(realm=path_config['realm'],
                                                    extra=None)
            upload_session = ApplicationSession(upload_session_config)

            self._router_session_factory.add(upload_session,
                                             authrole=path_config.get(
                                                 'role', 'anonymous'))

            return FileUploadResource(upload_directory, temp_directory,
                                      path_config['form_fields'],
                                      upload_session,
                                      path_config.get('options', {}))

        # Generic Twisted Web resource
        #
        elif path_config['type'] == 'resource':

            try:
                klassname = path_config['classname']

                if self.debug:
                    log.msg("Starting class '{}'".format(klassname))

                c = klassname.split('.')
                module_name, klass_name = '.'.join(c[:-1]), c[-1]
                module = importlib.import_module(module_name)
                make = getattr(module, klass_name)

                return make(path_config.get('extra', {}))

            except Exception as e:
                emsg = "Failed to import class '{}' - {}".format(klassname, e)
                log.msg(emsg)
                log.msg("PYTHONPATH: {}".format(sys.path))
                raise ApplicationError("crossbar.error.class_import_failed",
                                       emsg)

        # Schema Docs resource
        #
        elif path_config['type'] == 'schemadoc':

            realm = path_config['realm']

            if realm not in self.realm_to_id:
                raise ApplicationError(
                    "crossbar.error.no_such_object",
                    "No realm with URI '{}' configured".format(realm))

            realm_id = self.realm_to_id[realm]

            realm_schemas = self.realms[realm_id].session._schemas

            return SchemaDocResource(self._templates, realm, realm_schemas)

        # Nested subpath resource
        #
        elif path_config['type'] == 'path':

            nested_paths = path_config.get('paths', {})

            if '/' in nested_paths:
                nested_resource = self.create_resource(nested_paths['/'])
            else:
                nested_resource = Resource()

            # nest subpaths under the current entry
            #
            self.add_paths(nested_resource, nested_paths)

            return nested_resource

        else:
            raise ApplicationError(
                "crossbar.error.invalid_configuration",
                "invalid Web path type '{}'".format(path_config['type']))
Example #4
0
    def test_resumed_upload(self):
        """
        Uploading part of a file, simulating a Crossbar restart, and continuing
        to upload works.
        """

        upload_dir = FilePath(self.mktemp())
        upload_dir.makedirs()
        temp_dir = FilePath(self.mktemp())
        temp_dir.makedirs()

        fields = {
            "file_name": "resumableFilename",
            "mime_type": "resumableType",
            "total_size": "resumableTotalSize",
            "chunk_number": "resumableChunkNumber",
            "chunk_size": "resumableChunkSize",
            "total_chunks": "resumableTotalChunks",
            "content": "file",
            "on_progress": "on_progress",
            "session": "session"
        }

        mock_session = Mock()

        resource = FileUploadResource(upload_dir.path, temp_dir.path, fields,
                                      mock_session)

        # Make some stuff in the temp dir that wasn't there when it started but
        # is put there before a file upload
        temp_dir.child("otherjunk").makedirs()

        #
        # Chunk 1

        mp = Multipart()
        mp.add_part(b"resumableChunkNumber", b"1")
        mp.add_part(b"resumableChunkSize", b"10")
        mp.add_part(b"resumableCurrentChunkSize", b"10")
        mp.add_part(b"resumableTotalSize", b"16")
        mp.add_part(b"resumableType", b"text/plain")
        mp.add_part(b"resumableIdentifier", b"16-examplefiletxt")
        mp.add_part(b"resumableFilename", b"examplefile.txt")
        mp.add_part(b"resumableRelativePath", b"examplefile.txt")
        mp.add_part(b"resumableTotalChunks", b"2")
        mp.add_part(b"on_progress", b"com.example.upload.on_progress")
        mp.add_part(b"session", b"6891276359801283")
        mp.add_part(b"file",
                    b"hello Cros",
                    content_type=b"application/octet-stream",
                    filename=b"blob")
        body, headers = mp.render()

        d = renderResource(resource,
                           b"/",
                           method="POST",
                           headers=headers,
                           body=body)

        res = self.successResultOf(d)
        res.setResponseCode.assert_called_once_with(200)

        # One directory in the temp dir, nothing in the upload dir, temp dir
        # contains one chunk
        self.assertEqual(len(temp_dir.listdir()), 1)
        self.assertEqual(len(temp_dir.child("examplefile.txt").listdir()), 1)
        with temp_dir.child("examplefile.txt").child("chunk_1").open(
                "rb") as f:
            self.assertEqual(f.read(), b"hello Cros")
        self.assertEqual(len(upload_dir.listdir()), 0)

        del resource

        # Add some random junk in there that Crossbar isn't expecting
        temp_dir.child("junk").setContent(b"just some junk")
        temp_dir.child("examplefile.txt").child("hi").setContent(b"what")

        # Simulate restarting Crossbar by reinitialising the FileUploadResource
        resource = FileUploadResource(upload_dir.path, temp_dir.path, fields,
                                      mock_session)

        #
        # Chunk 2

        mp = Multipart()
        mp.add_part(b"resumableChunkNumber", b"2")
        mp.add_part(b"resumableChunkSize", b"10")
        mp.add_part(b"resumableCurrentChunkSize", b"6")
        mp.add_part(b"resumableTotalSize", b"16")
        mp.add_part(b"resumableType", b"text/plain")
        mp.add_part(b"resumableIdentifier", b"16-examplefiletxt")
        mp.add_part(b"resumableFilename", b"examplefile.txt")
        mp.add_part(b"resumableRelativePath", b"examplefile.txt")
        mp.add_part(b"resumableTotalChunks", b"2")
        mp.add_part(b"on_progress", b"com.example.upload.on_progress")
        mp.add_part(b"session", b"6891276359801283")
        mp.add_part(b"file",
                    b"sbar!\n",
                    content_type=b"application/octet-stream",
                    filename=b"blob")
        body, headers = mp.render()

        d = renderResource(resource,
                           b"/",
                           method="POST",
                           headers=headers,
                           body=body)

        res = self.successResultOf(d)
        res.setResponseCode.assert_called_once_with(200)

        self.assertEqual(len(mock_session.method_calls), 4)

        # Starting the upload
        self.assertEqual(mock_session.method_calls[0][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[0][1][1]["status"],
                         "started")
        self.assertEqual(mock_session.method_calls[0][1][1]["id"],
                         "examplefile.txt")
        self.assertEqual(mock_session.method_calls[0][1][1]["chunk"], 1)

        # Progress, first chunk done
        self.assertEqual(mock_session.method_calls[1][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[1][1][1]["status"],
                         "progress")
        self.assertEqual(mock_session.method_calls[1][1][1]["id"],
                         "examplefile.txt")
        self.assertEqual(mock_session.method_calls[1][1][1]["chunk"], 1)

        # Progress, second chunk done
        self.assertEqual(mock_session.method_calls[2][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[2][1][1]["status"],
                         "progress")
        self.assertEqual(mock_session.method_calls[2][1][1]["id"],
                         "examplefile.txt")
        self.assertEqual(mock_session.method_calls[2][1][1]["chunk"], 2)

        # Upload complete
        self.assertEqual(mock_session.method_calls[3][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[3][1][1]["status"],
                         "finished")
        self.assertEqual(mock_session.method_calls[3][1][1]["id"],
                         "examplefile.txt")
        self.assertEqual(mock_session.method_calls[3][1][1]["chunk"], 2)

        # No item in the temp dir which we made earlier, one item in the
        # upload dir. Otherjunk is removed because it belongs to no upload.
        self.assertEqual(len(temp_dir.listdir()), 0)
        self.assertEqual(len(upload_dir.listdir()), 1)
        with upload_dir.child("examplefile.txt").open("rb") as f:
            self.assertEqual(f.read(), b"hello Crossbar!\n")
Example #5
0
    def test_multichunk(self):
        """
        Uploading files that are in multiple chunks works.
        """
        upload_dir = self.mktemp()
        os.makedirs(upload_dir)
        temp_dir = self.mktemp()
        os.makedirs(temp_dir)

        fields = {
            "file_name": "resumableFilename",
            "mime_type": "resumableType",
            "total_size": "resumableTotalSize",
            "chunk_number": "resumableChunkNumber",
            "chunk_size": "resumableChunkSize",
            "total_chunks": "resumableTotalChunks",
            "content": "file",
            "on_progress": "on_progress",
            "session": "session"
        }

        mock_session = Mock()

        resource = FileUploadResource(upload_dir, temp_dir, fields,
                                      mock_session)

        #
        # Chunk 1

        mp = Multipart()
        mp.add_part(b"resumableChunkNumber", b"1")
        mp.add_part(b"resumableChunkSize", b"10")
        mp.add_part(b"resumableCurrentChunkSize", b"10")
        mp.add_part(b"resumableTotalSize", b"16")
        mp.add_part(b"resumableType", b"text/plain")
        mp.add_part(b"resumableIdentifier", b"16-examplefiletxt")
        mp.add_part(b"resumableFilename", b"examplefile.txt")
        mp.add_part(b"resumableRelativePath", b"examplefile.txt")
        mp.add_part(b"resumableTotalChunks", b"2")
        mp.add_part(b"on_progress", b"com.example.upload.on_progress")
        mp.add_part(b"session", b"6891276359801283")
        mp.add_part(b"file",
                    b"hello Cros",
                    content_type=b"application/octet-stream",
                    filename=b"blob")
        body, headers = mp.render()

        d = renderResource(resource,
                           b"/",
                           method="POST",
                           headers=headers,
                           body=body)

        res = self.successResultOf(d)
        self.assertEqual(res.code, 200)

        # One directory in the temp dir, nothing in the upload dir, temp dir
        # contains one chunk
        self.assertEqual(len(os.listdir(temp_dir)), 1)
        self.assertEqual(
            len(os.listdir(os.path.join(temp_dir, "examplefile.txt"))), 1)
        with open(os.path.join(temp_dir, "examplefile.txt", "chunk_1"),
                  "rb") as f:
            self.assertEqual(f.read(), b"hello Cros")
        self.assertEqual(len(os.listdir(upload_dir)), 0)

        #
        # Chunk 2

        mp = Multipart()
        mp.add_part(b"resumableChunkNumber", b"2")
        mp.add_part(b"resumableChunkSize", b"10")
        mp.add_part(b"resumableCurrentChunkSize", b"6")
        mp.add_part(b"resumableTotalSize", b"16")
        mp.add_part(b"resumableType", b"text/plain")
        mp.add_part(b"resumableIdentifier", b"16-examplefiletxt")
        mp.add_part(b"resumableFilename", b"examplefile.txt")
        mp.add_part(b"resumableRelativePath", b"examplefile.txt")
        mp.add_part(b"resumableTotalChunks", b"2")
        mp.add_part(b"on_progress", b"com.example.upload.on_progress")
        mp.add_part(b"session", b"6891276359801283")
        mp.add_part(b"file",
                    b"sbar!\n",
                    content_type=b"application/octet-stream",
                    filename=b"blob")
        body, headers = mp.render()

        d = renderResource(resource,
                           b"/",
                           method="POST",
                           headers=headers,
                           body=body)

        res = self.successResultOf(d)
        self.assertEqual(res.code, 200)

        self.assertEqual(len(mock_session.method_calls), 4)

        # Starting the upload
        self.assertEqual(mock_session.method_calls[0][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[0][1][1]["status"],
                         "started")
        self.assertEqual(mock_session.method_calls[0][1][1]["id"],
                         "examplefile.txt")
        self.assertEqual(mock_session.method_calls[0][1][1]["chunk"], 1)

        # Progress, first chunk done
        self.assertEqual(mock_session.method_calls[1][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[1][1][1]["status"],
                         "progress")
        self.assertEqual(mock_session.method_calls[1][1][1]["id"],
                         "examplefile.txt")
        self.assertEqual(mock_session.method_calls[1][1][1]["chunk"], 1)

        # Progress, second chunk done
        self.assertEqual(mock_session.method_calls[2][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[2][1][1]["status"],
                         "progress")
        self.assertEqual(mock_session.method_calls[2][1][1]["id"],
                         "examplefile.txt")
        self.assertEqual(mock_session.method_calls[2][1][1]["chunk"], 2)

        # Upload complete
        self.assertEqual(mock_session.method_calls[3][1][0],
                         u"com.example.upload.on_progress")
        self.assertEqual(mock_session.method_calls[3][1][1]["status"],
                         "finished")
        self.assertEqual(mock_session.method_calls[3][1][1]["id"],
                         "examplefile.txt")
        self.assertEqual(mock_session.method_calls[3][1][1]["chunk"], 2)

        # Nothing in the temp dir, one file in the upload
        self.assertEqual(len(os.listdir(temp_dir)), 0)
        self.assertEqual(len(os.listdir(upload_dir)), 1)
        with open(os.path.join(upload_dir, "examplefile.txt"), "rb") as f:
            self.assertEqual(f.read(), b"hello Crossbar!\n")