2017-11-16 00:43:27 +01:00
|
|
|
from argparse import ArgumentParser
|
2017-07-19 05:56:47 +02:00
|
|
|
from typing import Any, List
|
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.actions import (
|
|
|
|
bulk_add_subscriptions,
|
|
|
|
bulk_remove_subscriptions,
|
|
|
|
do_deactivate_stream,
|
|
|
|
)
|
2017-07-19 05:56:47 +02:00
|
|
|
from zerver.lib.cache import cache_delete_many, to_dict_cache_key_id
|
2017-08-07 21:42:21 +02:00
|
|
|
from zerver.lib.management import ZulipBaseCommand
|
2020-02-18 17:25:43 +01:00
|
|
|
from zerver.models import Message, Subscription, get_stream
|
2020-01-14 21:59:46 +01:00
|
|
|
|
2017-07-19 05:56:47 +02:00
|
|
|
|
2017-10-26 11:35:57 +02:00
|
|
|
def bulk_delete_cache_keys(message_ids_to_clear: List[int]) -> None:
|
2017-07-19 05:56:47 +02:00
|
|
|
while len(message_ids_to_clear) > 0:
|
|
|
|
batch = message_ids_to_clear[0:5000]
|
|
|
|
|
2017-10-20 20:29:49 +02:00
|
|
|
keys_to_delete = [to_dict_cache_key_id(message_id) for message_id in batch]
|
2017-07-19 05:56:47 +02:00
|
|
|
cache_delete_many(keys_to_delete)
|
|
|
|
|
|
|
|
message_ids_to_clear = message_ids_to_clear[5000:]
|
|
|
|
|
2017-08-07 21:42:21 +02:00
|
|
|
class Command(ZulipBaseCommand):
|
2017-07-19 05:56:47 +02:00
|
|
|
help = """Merge two streams."""
|
|
|
|
|
2017-10-26 11:35:57 +02:00
|
|
|
def add_arguments(self, parser: ArgumentParser) -> None:
|
2017-07-19 05:56:47 +02:00
|
|
|
parser.add_argument('stream_to_keep', type=str,
|
|
|
|
help='name of stream to keep')
|
|
|
|
parser.add_argument('stream_to_destroy', type=str,
|
|
|
|
help='name of stream to merge into the stream being kept')
|
2017-08-07 21:42:21 +02:00
|
|
|
self.add_realm_args(parser, True)
|
2017-07-19 05:56:47 +02:00
|
|
|
|
2017-10-26 11:35:57 +02:00
|
|
|
def handle(self, *args: Any, **options: str) -> None:
|
2017-08-07 21:42:21 +02:00
|
|
|
realm = self.get_realm(options)
|
2017-09-26 01:25:39 +02:00
|
|
|
assert realm is not None # Should be ensured by parser
|
2017-07-19 05:56:47 +02:00
|
|
|
stream_to_keep = get_stream(options["stream_to_keep"], realm)
|
|
|
|
stream_to_destroy = get_stream(options["stream_to_destroy"], realm)
|
|
|
|
|
2020-02-18 17:25:43 +01:00
|
|
|
recipient_to_destroy = stream_to_destroy.recipient
|
|
|
|
recipient_to_keep = stream_to_keep.recipient
|
2017-07-19 05:56:47 +02:00
|
|
|
|
|
|
|
# The high-level approach here is to move all the messages to
|
|
|
|
# the surviving stream, deactivate all the subscriptions on
|
|
|
|
# the stream to be removed and deactivate the stream, and add
|
|
|
|
# new subscriptions to the stream to keep for any users who
|
|
|
|
# were only on the now-deactivated stream.
|
|
|
|
|
|
|
|
# Move the messages, and delete the old copies from caches.
|
|
|
|
message_ids_to_clear = list(Message.objects.filter(
|
|
|
|
recipient=recipient_to_destroy).values_list("id", flat=True))
|
|
|
|
count = Message.objects.filter(recipient=recipient_to_destroy).update(recipient=recipient_to_keep)
|
2020-06-10 06:41:04 +02:00
|
|
|
print(f"Moved {count} messages")
|
2017-07-19 05:56:47 +02:00
|
|
|
bulk_delete_cache_keys(message_ids_to_clear)
|
|
|
|
|
|
|
|
# Move the Subscription objects. This algorithm doesn't
|
|
|
|
# preserve any stream settings/colors/etc. from the stream
|
|
|
|
# being destroyed, but it's convenient.
|
|
|
|
existing_subs = Subscription.objects.filter(recipient=recipient_to_keep)
|
2020-04-09 21:51:58 +02:00
|
|
|
users_already_subscribed = {sub.user_profile_id: sub.active for sub in existing_subs}
|
2017-07-19 05:56:47 +02:00
|
|
|
|
|
|
|
subs_to_deactivate = Subscription.objects.filter(recipient=recipient_to_destroy, active=True)
|
|
|
|
users_to_activate = [
|
|
|
|
sub.user_profile for sub in subs_to_deactivate
|
|
|
|
if not users_already_subscribed.get(sub.user_profile_id, False)
|
|
|
|
]
|
|
|
|
|
|
|
|
if len(subs_to_deactivate) > 0:
|
2020-06-10 06:41:04 +02:00
|
|
|
print(f"Deactivating {len(subs_to_deactivate)} subscriptions")
|
2017-07-19 05:56:47 +02:00
|
|
|
bulk_remove_subscriptions([sub.user_profile for sub in subs_to_deactivate],
|
2018-03-14 00:13:21 +01:00
|
|
|
[stream_to_destroy],
|
2020-06-29 13:15:10 +02:00
|
|
|
self.get_client(), acting_user=None)
|
2020-06-29 15:02:07 +02:00
|
|
|
do_deactivate_stream(stream_to_destroy, acting_user=None)
|
2017-07-19 05:56:47 +02:00
|
|
|
if len(users_to_activate) > 0:
|
2020-06-10 06:41:04 +02:00
|
|
|
print(f"Adding {len(users_to_activate)} subscriptions")
|
2017-07-19 05:56:47 +02:00
|
|
|
bulk_add_subscriptions([stream_to_keep], users_to_activate)
|