def addon(arg: Namespace, yaml: YAML) -> int: """Delete local media. Parameters ---------- arg : argparse.Namespace The ``Namespace`` object of argparse's ``parse_args()`` yaml : matrixctl.handlers.yaml.YAML The configuration file handler. Returns ------- err_code : int Non-zero value indicates error code, or zero on success. """ timestamp = handle_timestamp(arg.timestamp, arg.force) req: RequestBuilder = RequestBuilder( token=yaml.get("server", "api", "token"), domain=yaml.get("server", "api", "domain"), path=f"media/{yaml.get('server', 'api', 'domain')}/delete", params={ "before_ts": timestamp, "keep_profiles": arg.no_keep_profiles, "size_gt": arg.greater_than, }, api_version="v1", method="POST", timeout=1200, ) try: response: Response = request(req) except InternalResponseError: logger.error("The user could not be promoted or demote.") return 1 try: json_response: JsonDict = response.json() except json.decoder.JSONDecodeError as e: logger.fatal("The JSON response could not be loaded by MatrixCtl.") raise InternalResponseError(f"The response was: {response = }") from e try: print(json.dumps(json_response, indent=4)) return 0 except json.decoder.JSONDecodeError: logger.error("Unable to process the response data to JSON.") return 1 return 0
def addon(arg: Namespace, yaml: YAML) -> int: """Delete an empty room from the database. Parameters ---------- arg : argparse.Namespace The ``Namespace`` object of argparse's ``parse_args()`` yaml : matrixctl.handlers.yaml.YAML The configuration file handler. Returns ------- err_code : int Non-zero value indicates error code, or zero on success. """ body: JsonDict = handle_arguments(arg) req: RequestBuilder = RequestBuilder( token=yaml.get("server", "api", "token"), domain=yaml.get("server", "api", "domain"), path=f"rooms/{arg.room}", method="DELETE", api_version="v2", json=body, timeout=1200, ) try: response: Response = request(req) except InternalResponseError: logger.error("Could not delete room.") return 1 try: json_response: JsonDict = response.json() except json.decoder.JSONDecodeError as e: logger.fatal("The JSON response could not be loaded by MatrixCtl.") raise InternalResponseError(f"The response was: {response = }") from e try: json_response = handle_status(yaml, json_response["delete_id"]) except InternalResponseError as e: if e.message: logger.fatal(e.message) logger.fatal( "MatrixCtl was not able to verify the status of the request.") return 1 print(json.dumps(json_response, indent=4)) return 0
def addon(arg: Namespace, yaml: YAML) -> int: """Upload a file or image to the matrix instance. Parameters ---------- arg : argparse.Namespace The ``Namespace`` object of argparse's ``parse_args()``. yaml : matrixctl.handlers.yaml.YAML The configuration file handler. Returns ------- err_code : int Non-zero value indicates error code, or zero on success. """ file_path: Path = Path(arg.file).absolute() logger.debug(f"upload: {file_path=}") mime_types: MimeTypes = MimeTypes() file_type: str = str(mime_types.guess_type(file_path.name)[0]) logger.debug(f"upload: {file_type=}") try: with file_path.open("rb") as fp: file: bytes = fp.read() except FileNotFoundError: print("No such file found. Please check your filepath.") return 1 req: RequestBuilder = RequestBuilder( token=yaml.get("server", "api", "token"), domain=yaml.get("server", "api", "domain"), path="upload/", api_path="_matrix/media", method="POST", api_version="r0", headers={"Content-Type": file_type}, content=file, ) try: response: JsonDict = request(req).json() except InternalResponseError: logger.error("The file was not uploaded.") return 1 try: print("Content URI: ", response["content_uri"]) except KeyError as e: raise InternalResponseError( "Upload was successful, but no content_uri was found.", response) from e return 0
def handle_status(yaml: YAML, delete_id: str) -> JsonDict: # noqa: C901 """Handle the status of a delete room request. Parameters ---------- yaml : matrixctl.handlers.yaml.YAML The configuration file handler. delete_id: str The delete id of a delete room request. Returns ------- response: matrixctl.typehints.JsonDict, optional The response as dict, containing the status. """ req: RequestBuilder = RequestBuilder( token=yaml.get("server", "api", "token"), domain=yaml.get("server", "api", "domain"), path=f"rooms/delete_status/{delete_id}", method="GET", api_version="v2", timeout=1200.0, ) # Lock messages to only print them once msglock_shutting_down: bool = False msglock_purging: bool = False while True: sleep(1) try: response: Response = request(req) except InternalResponseError as e: raise InternalResponseError( "The delete room request was probably successful but the" " status request failed. You just have to wait a bit.") from e try: json_response: JsonDict = response.json() except json.decoder.JSONDecodeError as e: logger.fatal( "The JSON status response could not be loaded by MatrixCtl.") raise InternalResponseError( f"The response was: {response = }") from e if response is not None: logger.debug(f"{response=}") # complete if json_response["status"] == "complete": print( "Status: Complete (the room has been deleted successfully") break # shutting_down if json_response["status"] == "shutting_down": if not msglock_shutting_down: print( "Status: Shutting Down (removing users from the room)") msglock_shutting_down = True logger.info("The server is still shutting_down the room. " "Please wait...") sleep(5) continue # purging if json_response["status"] == "purging": if not msglock_purging: print( "Status: Purging (purging the room and event data from" " database)") msglock_purging = True logger.info( "The server is still purging the room. Please wait...") sleep(5) continue # failed if json_response["status"] == "failed": logger.critical( "The server returned, that the approach failed with the" f" following message: {json_response['status']}.") break break return json_response
async def _arequest(request_config: RequestBuilder, client: httpx.AsyncClient) -> httpx.Response: """Send an asynchronous request to the synapse API and receive a response. Attributes ---------- req : matrixctl.handlers.api.RequestBuilder An instance of an RequestBuilder Returns ------- response : httpx.Response Returns the response """ logger.debug("repr: %s", repr(request_config)) # There is some weird stuff going on in httpx. It is set to None by default response: httpx.Response = await client.request( method=request_config.method, data=request_config.data, # type: ignore json=request_config.json, content=request_config.content, # type: ignore url=str(request_config), params=request_config.params, headers=request_config.headers_with_auth, timeout=request_config.timeout, follow_redirects=False, ) if response.status_code == 302: logger.critical( "The api request resulted in an redirect (302). " "This indicates, that the API might have changed, or your " "playbook is misconfigured.\n" "Please make sure your installation of matrixctl is " "up-to-date and your vars.yml contains:\n\n" "matrix_nginx_proxy_proxy_matrix_client_redirect_root_uri_to" '_domain: ""') raise ExitQWorker() # TODO if response.status_code == 404: logger.critical( "The server returned an 404 error. This can have multiple causes." " One of them is, you try to request a resource, which does not or" " no longer exist. Another one is, your API endpoint is disabled." " Make sure, that your vars.yml contains the following excessive" " long" " line:\n\nmatrix_nginx_proxy_proxy_matrix_client_api_forwarded_location_synapse_admin_api_enabled:" " true") raise ExitQWorker() # TODO logger.debug("JSON response: %s", response.json()) logger.debug("Response Status Code: %d", response.status_code) if response.status_code not in request_config.success_codes: with suppress(Exception): if response.json()["errcode"] == "M_UNKNOWN_TOKEN": logger.critical( "The server rejected your access-token. " "Please make sure, your access-token is correct " "and up-to-date. Your access-token will change every " "time, you log out.") raise ExitQWorker() # TODO raise InternalResponseError(payload=response) return response
def generate_worker_configs( request_config: RequestBuilder, next_token: int, limit: int) -> Generator[RequestBuilder, None, None]: """Create workers for async requests (minus the already done sync request). Notes ----- Warning ``Call-By-Reference`` like behavior! The param ``limit`` and the ``concurrent_limit`` in ``request_config`` will get changed in this function. Make sure to only use them after using this function! Attributes ---------- request_config : matrixctl.handlers.api.RequestBuilder An instance of an RequestBuilder from which was used for an initial synchronous request to get the first part of the data and the other two arguments from the response. next_token : int The value, which defines from where to start in the next request. You get this value from the response of an initial synchronous request. total : int The value which defines how many entries there are. You get this value from the response of an initial synchronous request. Yields ------ request_config : matrixctl.handlers.api.RequestBuilder Yields a fully configured ``RequestsBuilder`` for every request that has to be done to get all entries. """ if limit - next_token < 0: raise InternalResponseError( f"limit - next_token is negative ({limit - next_token}). " "Make sure that you not use generate_worker_configs() if it " "isn't necessary. For example with total > 100.") strategy: RequestStrategy = preplan_request_strategy( limit - next_token, # minus the already queried concurrent_limit=request_config.concurrent_limit, max_step_size=limit if limit < 100 else 100, ) # limit the request "globally" request_config.params["limit"] = strategy.step_size # overwrite the concurrent limit request_config.concurrent_limit = strategy.concurrent_limit # reapply next_token to get the full range back for i logger.debug( "for loop (generator):" "next_token + 1 = %s , strategy.limit + next_token + 1 = %s, " "strategy.step_size = %s", next_token + 1, strategy.limit + next_token + 1, strategy.step_size, ) for i in range(next_token + 1, strategy.limit + next_token + 1, strategy.step_size): worker_config = deepcopy(request_config) # deepcopy needed worker_config.params["from"] = i yield worker_config
def preplan_request_strategy(limit: int, concurrent_limit: int | float, max_step_size: int = 100) -> RequestStrategy: """Use this functiona as helper for optimizing asynchronous requests. Attributes ---------- limit : int A user entered limit or total. concurrent_limit: int The concurrent limit from the config file. max_step_size : int, default=100 The maximal step size, which is a soft limit. It is usually 100, but that value might be different. Check out the API documentation. We usually take the default one. Returns ------- RequestStrategy : matrixctl.handlers.api.RequestStrategy A Named tuple with the RequestStrategy values. """ concurrent_limit = float(concurrent_limit) # limit might be total. if limit > max_step_size: # limit step_size step_size = 100 else: step_size = limit workers: float = min(limit / step_size, concurrent_limit) iterations: float = limit / (workers * step_size) new_iterations: int = math.ceil(iterations) workers_temp: int = math.ceil(limit / (step_size * new_iterations)) new_workers: int = min(workers_temp, math.ceil(concurrent_limit)) new_step_size: int = math.ceil(limit / (new_workers * new_iterations)) new_limit: int = new_step_size * new_workers * new_iterations # total offset: int = new_limit - limit # How many to hold back # Debug output logger.debug("concurrent_limit = %s", concurrent_limit) logger.debug("limit = %s", limit) logger.debug( "step_size = %s -> step_size_n = %s (soft limit = 100)", step_size, new_step_size, ) logger.debug( "workers = %s -> new_workers = %s (hard limit = %s)", workers, new_workers, concurrent_limit, ) logger.debug( 'iterations = %s -> new_iterations = %s ("unlimited")', iterations, new_iterations, ) logger.debug("new_limit (true limit) = %s", new_limit) logger.debug("offset = %s (negative not allowed)", offset) if offset < 0: raise InternalResponseError("The offset must always be positive.") return RequestStrategy(new_limit, new_step_size, new_limit, offset, new_iterations)