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}')
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
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
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
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"])
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
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)
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)
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
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]
def connect(self) -> None: if self.error_connect: raise utils.PluginOperationError("Could not connect") assert not self.is_connected self.is_connected = True