Exemple #1
0
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
Exemple #2
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
Exemple #3
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
Exemple #4
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
Exemple #5
0
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
Exemple #6
0
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
Exemple #7
0
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)