Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename misnamed configs #102

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions notifier/config/user.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from functools import lru_cache
import logging
from operator import itemgetter
import re
Expand Down Expand Up @@ -30,6 +31,7 @@
delivery = "%%form_raw{method}%%"
user_base_notified = """%%created_at%%"""
tags = """%%tags%%"""
title = """%%title%%"""
subscriptions = """
%%form_data{subscriptions}%%"""
unsubscriptions = """
Expand Down Expand Up @@ -77,6 +79,7 @@ def user_config_is_valid(slug: str, config: RawUserConfig) -> bool:
return ":" in slug and slug.split(":")[1] == config["user_id"]


@lru_cache(maxsize=1)
def fetch_user_configs(
local_config: LocalConfig, wikidot: Wikidot
) -> List[Tuple[str, RawUserConfig]]:
Expand Down
1 change: 0 additions & 1 deletion notifier/database/drivers/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

from notifier.database.drivers.base import BaseDatabaseDriver
from notifier.database.utils import BaseDatabaseWithSqlFileCache
from notifier.deletions import delete_posts
from notifier.types import (
ActivationLogDump,
CachedUserConfig,
Expand Down
35 changes: 35 additions & 0 deletions notifier/deletions.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,41 @@ def rename_invalid_user_config_pages(
continue


def rename_misnamed_user_config_pages(
local_config: LocalConfig, connection: Connection
) -> None:
"""Renames user configs to fix an error in the page creation link.

The link syntax breaks if it contains a space, but spaces are permitted in usernames. Therefore if a user has a space in their username, their user config create link will be broken and will have a nonsensical name.

Users are allowed to name their pages whatever they like but this cleans up that one case specifically.
"""
logger.info("Finding user configs from users with spaces in their name")
misnamed_configs = [
(slug, config)
for slug, config in fetch_user_configs(local_config, connection)
if " " in config["username"]
and config["title"] == config["username"].split(" ")[0]
]
logger.debug(
"Found misnamed configs to rename %s", {"count": len(misnamed_configs)}
)
for slug, config in misnamed_configs:
try:
connection.rename_page(
local_config["config_wiki"],
slug,
f"{local_config['user_config_category']}:{config['user_id']}",
)
except Exception as error:
logger.error(
"Couldn't rename config page %s",
{"slug": slug},
exc_info=error,
)
continue


def delete_prepared_invalid_user_pages(
local_config: LocalConfig, wikidot: Wikidot
) -> None:
Expand Down
1 change: 1 addition & 0 deletions notifier/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class RawUserConfig(TypedDict):
delivery: DeliveryMethod
user_base_notified: int
tags: str
title: str
subscriptions: List[Subscription]
unsubscriptions: List[Subscription]

Expand Down
83 changes: 82 additions & 1 deletion notifier/wikidot.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from contextlib import contextmanager
from functools import lru_cache
import logging
import re
import time
Expand Down Expand Up @@ -63,6 +65,10 @@ class OngoingConnectionError(Exception):
by trying it multiple times."""


class LockNotEstablished(Exception):
"""The requested edit lock could not be established."""


class Wikidot:
"""Connection to Wikidot facilitating communications with it."""

Expand Down Expand Up @@ -398,6 +404,7 @@ def get_contacts(self) -> EmailAddresses:
addresses[username.strip()] = address
return addresses

@lru_cache(maxsize=1)
def get_page_id(self, wiki_id: str, slug: str) -> int:
"""Get a page's ID from its source."""
try:
Expand All @@ -418,8 +425,80 @@ def get_page_id(self, wiki_id: str, slug: str) -> int:
cast(Match[str], re.search(r"pageId = ([0-9]+);", page)).group(1)
)

@contextmanager
def edit_lock(self, wiki_id: str, slug: str) -> Iterator[Tuple[str, str]]:
"""Establishes an edit lock to commit an edit to a page.

Exposes the lock id and secret to be used in an edit request.

Connection needs to be logged in.
"""
logger.debug(
"Establishing edit lock %s", {"wiki_id": wiki_id, "slug": slug}
)
page_id = self.get_page_id(wiki_id, slug)
response = self.module(
wiki_id,
"edit/PageEditModule",
page_id=str(page_id),
wiki_page=slug,
mode="page",
)
lock_id = str(response.get("lock_id"))
lock_secret = str(response.get("lock_secret"))
if response.get("locked", False) or not lock_id or not lock_secret:
logger.debug(
"Failed to acquire edit lock %s",
{"wiki_id": wiki_id, "slug": slug},
)
raise LockNotEstablished
try:
yield lock_id, lock_secret
except:
# If the edit fails, release the lock
logger.debug(
"Releasing edit lock %s", {"wiki_id": wiki_id, "slug": slug}
)
self.module(
wiki_id,
"Empty",
action="WikiPageAction",
event="removePageEditLock",
page_id="1453558361",
wiki_page=slug,
lock_id=lock_id,
lock_secret=lock_secret,
leave_draft="false",
)
raise

def edit_page_title(self, wiki_id: str, slug: str, title: str) -> None:
"""Changes the title of a page.

Connection needs to be logged in.
"""
page_id = self.get_page_id(wiki_id, slug)
with
logger.debug(
"Renaming page %s",
{
"with id": page_id,
"from slug": slug,
"to slug": to_slug,
"in wiki": wiki_id,
},
)
self.module(
wiki_id,
"Empty",
action="WikiPageAction",
event="renamePage",
page_id=str(page_id),
new_name=to_slug,
)

def rename_page(self, wiki_id: str, from_slug: str, to_slug: str) -> None:
"""Renames a page.
"""Renames (i.e. moves) a page.

Renames can take a while (30+ seconds) to take effect, so if
renaming for safe deletion, probably don't bother deleting until
Expand Down Expand Up @@ -449,6 +528,8 @@ def rename_page(self, wiki_id: str, from_slug: str, to_slug: str) -> None:
def delete_page(self, wiki_id: str, slug: str) -> None:
"""Deletes a page.

Very rarely, a delete semi-fails and leaves the page in a corrupted state where it can no longer be interacted with. To avoid this affecting anything, rename the page to something random first.

Connection needs to be logged in.
"""
if not slug.startswith("deleted:"):
Expand Down
Loading