Example #1
0
    def connect(self) -> None:
        self._connection = SMBConnection(
            username=self._info.username,
            password=self._info.password,
            domain=self._info.domain,
            my_name=socket.gethostname(),
            remote_name=self._info.hostname,
            use_ntlm_v2=self._info.use_ntlm_v2,
            sign_options=self._info.sign_options,
            is_direct_tcp=self._info.is_direct_tcp)

        try:
            server_ip: str = socket.gethostbyname(self._info.hostname)
        except socket.gaierror:
            raise utils.PluginOperationError(
                f'Could not find server {self._info.hostname}. '
                'Maybe you need to open a VPN connection or the server is not available.'
            )

        logger.debug(
            f'Connecting to {server_ip} ({self._info.hostname}) port {self._info.port}'
        )

        try:
            success = self._connection.connect(server_ip, self._info.port)
        except (ConnectionRefusedError, socket.timeout):
            raise utils.PluginOperationError(
                f'Could not connect to {server_ip}:{self._info.port}')
        # FIXME Can be removed once https://github.com/miketeo/pysmb/issues/108 is fixed
        except ProtocolError:
            success = False

        if not success:
            raise utils.AuthenticationError(
                f'Authentication failed for {server_ip}:{self._info.port}')
Example #2
0
    def _list_files_in_course(
            self, course_path: pathlib.PurePath
    ) -> typing.Iterable[pathlib.PurePath]:
        course = str(course_path)
        if not self._courses:
            self._list_courses()

        if course not in self._courses:
            raise utils.PluginOperationError(
                f"The remote-dir '{course}' was not found.")
        course_id: int = self._courses[course]

        lessons: typing.List[utils.JsonType] = self._request(
            'core_course_get_contents', courseid=str(course_id))

        for section in lessons:
            section_nr = "{:02d}".format(section['section'])
            section_path: pathlib.PurePath = course_path / f"{section_nr} - {section['name']}"
            for module in section['modules']:
                module_path: pathlib.PurePath = section_path / module['name']
                for elem in module.get('contents', []):
                    filename: str = elem['filename']
                    if 'mimetype' not in elem:
                        # Unfortunately, Moodle returns a size of 0 for HTML files in its API.
                        # Also, HTML files are the only entries without a mimetype set.
                        assert elem['filesize'] == 0, elem
                        if not filename.endswith('.html'):
                            filename += '.html'
                    full_path: pathlib.PurePath = module_path / filename
                    self._files[full_path] = _MoodleFile(
                        elem['fileurl'], elem['filesize'],
                        elem['timemodified'])
                    logger.debug(
                        f'New file at {full_path}: {self._files[full_path]}')
                    yield full_path
Example #3
0
    def retrieve_file(self,
                      path: pathlib.PurePath,
                      fileobj: typing.IO[bytes]) -> typing.Optional[int]:
        assert self._files, "list_path was never called, no files available."
        moodle_file: _MoodleFile = self._files[path]
        logger.debug(f'Getting {moodle_file.url}')

        if not moodle_file.url.startswith(self._url):
            fileobj.write(f'''[Desktop Entry]
Encoding=UTF-8
Name=Internet Link
Type=Link
URL={moodle_file.url}
'''.encode())
            return moodle_file.changed_at

        req: requests.Response = requests.get(moodle_file.url, {'token': self._token})
        try:
            req.raise_for_status()
        except requests.exceptions.HTTPError as ex:
            raise utils.PluginOperationError(f"HTTP error: {ex}.")

        # Errors from Moodle are delivered as json.
        if 'json' in req.headers['content-type']:
            data: utils.JsonType = req.json()
            self._check_json_answer(data)

        for chunk in req:
            fileobj.write(chunk)

        return moodle_file.changed_at
Example #4
0
    def list_path(self,
                  path: pathlib.PurePath) -> typing.Iterable[pathlib.PurePath]:
        if self.error_list_path:
            raise utils.PluginOperationError("Could not list path")

        assert self.is_connected
        for filename in sorted(self.remote_digests):
            if str(filename).startswith(str(path)):
                yield filename
Example #5
0
    def _check_json_answer(self, data: utils.JsonType) -> None:
        if not isinstance(data, dict):
            # For some requests, Moodle responds with an JSON array (list) and
            # not a JSON object (dict). However, in case of an error, we always
            # get a dict - so we bail out early and assume a correct answer if
            # we get something else.
            return

        errorcode: str = data.get("errorcode", None)
        if errorcode == "invalidtoken":
            raise utils.AuthenticationError(data['message'])
        elif errorcode == "invalidrecord":  # e.g. invalid ws_function, invalid course ID
            # given error message is hard to understand,
            # writing a better to understand one instead
            raise utils.PluginOperationError(
                "You requested something from Moodle which it couldn't get.")
        elif "exception" in data:  # base case for errors
            raise utils.PluginOperationError(data["message"])
Example #6
0
    def retrieve_file(self, path: pathlib.PurePath,
                      fileobj: typing.IO[bytes]) -> typing.Optional[int]:
        logger.debug(f'Retrieving file {path}')
        try:
            self._connection.retrieveFile(self._info.share, str(path), fileobj)
        except OperationFailure:
            raise utils.PluginOperationError(
                f'Could not download {path} from share "{self._info.share}"')

        mtime: int = self._attributes[path].last_write_time
        return mtime
Example #7
0
    def create_remote_digest(self, path: pathlib.PurePath) -> str:
        try:
            attributes = self._connection.getAttributes(
                self._info.share, str(path))
        except OperationFailure:
            raise utils.PluginOperationError(
                f'Could not find remote file {path} in share "{self._info.share}"'
            )

        self._attributes[path] = attributes
        return self._create_digest(size=attributes.file_size,
                                   mtime=attributes.last_write_time)
Example #8
0
    def list_path(self,
                  path: pathlib.PurePath) -> typing.Iterable[pathlib.PurePath]:
        try:
            entries = self._connection.listPath(self._info.share, str(path))
        except OperationFailure:
            raise utils.PluginOperationError(f'Folder "{path}" not found')

        for entry in entries:
            if entry.isDirectory:
                if entry.filename not in [".", ".."]:
                    yield from self.list_path(
                        pathlib.PurePath(path / entry.filename))
            else:
                yield pathlib.PurePath(path / entry.filename)
Example #9
0
    def _request(self, func: str, **kwargs: str) -> typing.Any:
        url = self._url + 'webservice/rest/server.php'
        req_data: typing.Dict[str, str] = {
            'wstoken': self._token,
            'moodlewsrestformat': 'json',
            'wsfunction': func,
        }
        req_data.update(**kwargs)
        logger.debug(f'Getting {url} with data {req_data}')

        req: requests.Response = requests.get(url, req_data)
        try:
            req.raise_for_status()
        except requests.exceptions.HTTPError as ex:
            raise utils.PluginOperationError(f"HTTP error: {ex}.")

        data: utils.JsonType = req.json()
        logger.debug(f'Got data: {data}')
        self._check_json_answer(data)
        return data
Example #10
0
    def create_remote_digest(self, path: pathlib.PurePath) -> str:
        if self.error_create_remote_digest:
            raise utils.PluginOperationError("Could not create remote digest")

        assert self.is_connected
        return self.remote_digests[path]
Example #11
0
    def connect(self) -> None:
        if self.error_connect:
            raise utils.PluginOperationError("Could not connect")

        assert not self.is_connected
        self.is_connected = True