예제 #1
0
class CleanOwnerCaseSyncOperation(object):

    def __init__(self, timing_context, restore_state, async_task=None):
        self.timing_context = timing_context
        self.restore_state = restore_state
        self.case_accessor = CaseAccessors(self.restore_state.domain)
        self.async_task = async_task

    @property
    @memoized
    def cleanliness_flags(self):
        return dict(
            OwnershipCleanlinessFlag.objects.filter(
                domain=self.restore_state.domain,
                owner_id__in=self.restore_state.owner_ids
            ).values_list('owner_id', 'is_clean')
        )

    @property
    def payload_class(self):
        if self.async_task is not None:
            return partial(AsyncCleanOwnerPayload, current_task=self.async_task)
        return CleanOwnerSyncPayload

    def is_clean(self, owner_id):
        return self.cleanliness_flags.get(owner_id, False)

    def is_new_owner(self, owner_id):
        return (
            self.restore_state.is_initial or
            owner_id not in self.restore_state.last_sync_log.owner_ids_on_phone
        )

    def get_payload(self):
        self.restore_state.mark_as_new_format()
        with self.timing_context('get_case_ids_to_sync'):
            case_ids_to_sync = self.get_case_ids_to_sync()
        sync_payload = self.payload_class(self.timing_context, case_ids_to_sync, self.restore_state)
        return sync_payload.get_payload()

    def get_case_ids_to_sync(self):
        case_ids_to_sync = set()
        for owner_id in self.restore_state.owner_ids:
            case_ids_to_sync = case_ids_to_sync | set(self.get_case_ids_for_owner(owner_id))

        if (not self.restore_state.is_initial and
                any([not self.is_clean(owner_id) for owner_id in self.restore_state.owner_ids])):
            # if it's a steady state sync and we have any dirty owners, then we also need to
            # include ALL cases on the phone that have been modified since the last sync as
            # possible candidates to sync (since they may have been closed or reassigned by someone else)

            # don't bother checking ones we've already decided to check
            other_ids_to_check = self.restore_state.last_sync_log.case_ids_on_phone - case_ids_to_sync
            case_ids_to_sync = case_ids_to_sync | set(filter_cases_modified_since(
                self.case_accessor, list(other_ids_to_check), self.restore_state.last_sync_log.date
            ))
        return case_ids_to_sync

    def get_case_ids_for_owner(self, owner_id):
        if EXTENSION_CASES_SYNC_ENABLED.enabled(self.restore_state.domain):
            return self._get_case_ids_for_owners_with_extensions(owner_id)
        else:
            return self._get_case_ids_for_owners_without_extensions(owner_id)

    def _get_case_ids_for_owners_without_extensions(self, owner_id):
        if self.is_clean(owner_id):
            if self.is_new_owner(owner_id):
                # for a clean owner's initial sync the base set is just the open ids
                return set(self.case_accessor.get_open_case_ids_for_owner(owner_id))
            else:
                # for a clean owner's steady state sync, the base set is anything modified since last sync
                return set(self.case_accessor.get_case_ids_modified_with_owner_since(
                    owner_id, self.restore_state.last_sync_log.date
                ))
        else:
            # TODO: we may want to be smarter than this
            # right now just return the whole footprint and do any filtering later
            # Note: This will also return extensions if they exist.
            return get_case_footprint_info(self.restore_state.domain, owner_id).all_ids

    def _get_case_ids_for_owners_with_extensions(self, owner_id):
        """Fetches base and extra cases when extensions are enabled"""
        if not self.is_clean(owner_id) or self.restore_state.is_first_extension_sync:
            # If this is the first time a user with extensions has synced after
            # the extension flag is toggled, pull all the cases so that the
            # extension parameters get set correctly
            return get_case_footprint_info(self.restore_state.domain, owner_id).all_ids
        else:
            if self.is_new_owner(owner_id):
                # for a clean owner's initial sync the base set is just the open ids and their extensions
                all_case_ids = set(self.case_accessor.get_open_case_ids_for_owner(owner_id))
                new_case_ids = set(all_case_ids)
                while new_case_ids:
                    all_case_ids = all_case_ids | new_case_ids
                    extension_case_ids = set(self.case_accessor.get_extension_case_ids(new_case_ids))
                    new_case_ids = extension_case_ids - all_case_ids
                return all_case_ids
            else:
                # for a clean owner's steady state sync, the base set is anything modified since last sync
                modified_non_extension_cases = set(self.case_accessor.get_case_ids_modified_with_owner_since(
                    owner_id, self.restore_state.last_sync_log.date
                ))
                # we also need to fetch unowned extension cases that have been modified
                extension_case_ids = self.restore_state.last_sync_log.extension_index_tree.indices.keys()
                modified_extension_cases = set(filter_cases_modified_since(
                    self.case_accessor, extension_case_ids, self.restore_state.last_sync_log.date
                ))
                return modified_non_extension_cases | modified_extension_cases
예제 #2
0
class CleanOwnerCaseSyncOperation(object):
    def __init__(self, timing_context, restore_state, async_task=None):
        self.timing_context = timing_context
        self.restore_state = restore_state
        self.case_accessor = CaseAccessors(self.restore_state.domain)
        self.async_task = async_task

    @property
    @memoized
    def cleanliness_flags(self):
        return dict(
            OwnershipCleanlinessFlag.objects.filter(
                domain=self.restore_state.domain,
                owner_id__in=self.restore_state.owner_ids).values_list(
                    'owner_id', 'is_clean'))

    @property
    def payload_class(self):
        if self.async_task is not None:
            return partial(AsyncCleanOwnerPayload,
                           current_task=self.async_task)
        return CleanOwnerSyncPayload

    def is_clean(self, owner_id):
        return self.cleanliness_flags.get(owner_id, False)

    def is_new_owner(self, owner_id):
        return (self.restore_state.is_initial or owner_id
                not in self.restore_state.last_sync_log.owner_ids_on_phone)

    def extend_response(self, response):
        with self.timing_context('get_case_ids_to_sync'):
            case_ids_to_sync = self.get_case_ids_to_sync()
        sync_payload = self.payload_class(self.timing_context,
                                          case_ids_to_sync, self.restore_state)
        return sync_payload.extend_response(response)

    def get_case_ids_to_sync(self):
        case_ids_to_sync = set()
        for owner_id in self.restore_state.owner_ids:
            case_ids_to_sync = case_ids_to_sync | set(
                self.get_case_ids_for_owner(owner_id))

        if (not self.restore_state.is_initial and any([
                not self.is_clean(owner_id)
                for owner_id in self.restore_state.owner_ids
        ])):
            # if it's a steady state sync and we have any dirty owners, then we also need to
            # include ALL cases on the phone that have been modified since the last sync as
            # possible candidates to sync (since they may have been closed or reassigned by someone else)

            # don't bother checking ones we've already decided to check
            other_ids_to_check = self.restore_state.last_sync_log.case_ids_on_phone - case_ids_to_sync
            case_ids_to_sync = case_ids_to_sync | set(
                filter_cases_modified_since(
                    self.case_accessor, list(other_ids_to_check),
                    self.restore_state.last_sync_log.date))
        return case_ids_to_sync

    def get_case_ids_for_owner(self, owner_id):
        if EXTENSION_CASES_SYNC_ENABLED.enabled(self.restore_state.domain):
            return self._get_case_ids_for_owners_with_extensions(owner_id)
        else:
            return self._get_case_ids_for_owners_without_extensions(owner_id)

    def _get_case_ids_for_owners_without_extensions(self, owner_id):
        if self.is_clean(owner_id):
            if self.is_new_owner(owner_id):
                # for a clean owner's initial sync the base set is just the open ids
                return set(
                    self.case_accessor.get_open_case_ids_for_owner(owner_id))
            else:
                # for a clean owner's steady state sync, the base set is anything modified since last sync
                return set(
                    self.case_accessor.get_case_ids_modified_with_owner_since(
                        owner_id, self.restore_state.last_sync_log.date))
        else:
            # TODO: we may want to be smarter than this
            # right now just return the whole footprint and do any filtering later
            # Note: This will also return extensions if they exist.
            return get_case_footprint_info(self.restore_state.domain,
                                           owner_id).all_ids

    def _get_case_ids_for_owners_with_extensions(self, owner_id):
        """Fetches base and extra cases when extensions are enabled"""
        if not self.is_clean(
                owner_id) or self.restore_state.is_first_extension_sync:
            # If this is the first time a user with extensions has synced after
            # the extension flag is toggled, pull all the cases so that the
            # extension parameters get set correctly
            return get_case_footprint_info(self.restore_state.domain,
                                           owner_id).all_ids
        else:
            if self.is_new_owner(owner_id):
                # for a clean owner's initial sync the base set is just the open ids and their extensions
                all_case_ids = set(
                    self.case_accessor.get_open_case_ids_for_owner(owner_id))
                new_case_ids = set(all_case_ids)
                while new_case_ids:
                    all_case_ids = all_case_ids | new_case_ids
                    extension_case_ids = set(
                        self.case_accessor.get_extension_case_ids(
                            new_case_ids))
                    new_case_ids = extension_case_ids - all_case_ids
                return all_case_ids
            else:
                # for a clean owner's steady state sync, the base set is anything modified since last sync
                modified_non_extension_cases = set(
                    self.case_accessor.get_case_ids_modified_with_owner_since(
                        owner_id, self.restore_state.last_sync_log.date))
                # we also need to fetch unowned extension cases that have been modified
                extension_case_ids = list(self.restore_state.last_sync_log.
                                          extension_index_tree.indices.keys())
                modified_extension_cases = set(
                    filter_cases_modified_since(
                        self.case_accessor, extension_case_ids,
                        self.restore_state.last_sync_log.date))
                return modified_non_extension_cases | modified_extension_cases
예제 #3
0
class CleanOwnerCaseSyncOperation(object):

    def __init__(self, restore_state):
        self.restore_state = restore_state
        self.case_accessor = CaseAccessors(self.restore_state.domain)

    @property
    @memoized
    def cleanliness_flags(self):
        return dict(
            OwnershipCleanlinessFlag.objects.filter(
                domain=self.restore_state.domain,
                owner_id__in=self.restore_state.owner_ids
            ).values_list('owner_id', 'is_clean')
        )

    def is_clean(self, owner_id):
        return self.cleanliness_flags.get(owner_id, False)

    def get_payload(self):
        response = self.restore_state.restore_class()
        case_ids_to_sync = set()
        for owner_id in self.restore_state.owner_ids:
            case_ids_to_sync = case_ids_to_sync | set(self.get_case_ids_for_owner(owner_id))

        if (not self.restore_state.is_initial and
                any([not self.is_clean(owner_id) for owner_id in self.restore_state.owner_ids])):
            # if it's a steady state sync and we have any dirty owners, then we also need to
            # include ALL cases on the phone that have been modified since the last sync as
            # possible candidates to sync (since they may have been closed or reassigned by someone else)

            # don't bother checking ones we've already decided to check
            other_ids_to_check = self.restore_state.last_sync_log.case_ids_on_phone - case_ids_to_sync
            case_ids_to_sync = case_ids_to_sync | set(filter_cases_modified_since(
                self.case_accessor, list(other_ids_to_check), self.restore_state.last_sync_log.date
            ))

        all_maybe_syncing = copy(case_ids_to_sync)
        all_synced = set()
        child_indices = defaultdict(set)
        extension_indices = defaultdict(set)
        all_dependencies_syncing = set()
        closed_cases = set()
        potential_updates_to_sync = []
        while case_ids_to_sync:
            ids = pop_ids(case_ids_to_sync, chunk_size)
            # todo: see if we can avoid wrapping - serialization depends on it heavily for now
            case_batch = filter(
                partial(case_needs_to_sync, last_sync_log=self.restore_state.last_sync_log),
                [case for case in self.case_accessor.get_cases(ids)
                 if not case.is_deleted]
            )
            updates = get_case_sync_updates(
                self.restore_state.domain, case_batch, self.restore_state.last_sync_log
            )
            for update in updates:
                case = update.case
                all_synced.add(case.case_id)
                potential_updates_to_sync.append(update)

                # update the indices in the new sync log
                if case.indices:
                    # and double check footprint for non-live cases
                    extension_indices[case.case_id] = {
                        index.identifier: index.referenced_id
                        for index in case.indices
                        if index.relationship == CASE_INDEX_EXTENSION
                    }
                    child_indices[case.case_id] = {index.identifier: index.referenced_id for index in case.indices
                                               if index.relationship == CASE_INDEX_CHILD}
                    for index in case.indices:
                        if index.referenced_id not in all_maybe_syncing:
                            case_ids_to_sync.add(index.referenced_id)

                if not _is_live(case, self.restore_state):
                    all_dependencies_syncing.add(case.case_id)
                    if case.closed:
                        closed_cases.add(case.case_id)

            # commtrack ledger sections for this batch
            commtrack_elements = get_stock_payload(
                self.restore_state.project, self.restore_state.stock_settings,
                [CaseStub(update.case.case_id, update.case.type) for update in updates]
            )
            response.extend(commtrack_elements)

            # add any new values to all_syncing
            all_maybe_syncing = all_maybe_syncing | case_ids_to_sync

        # update sync token - marking it as the new format
        self.restore_state.current_sync_log = SimplifiedSyncLog.wrap(
            self.restore_state.current_sync_log.to_json()
        )
        self.restore_state.current_sync_log.log_format = LOG_FORMAT_SIMPLIFIED
        self.restore_state.current_sync_log.extensions_checked = True

        index_tree = IndexTree(indices=child_indices)
        extension_index_tree = IndexTree(indices=extension_indices)
        case_ids_on_phone = all_synced
        primary_cases_syncing = all_synced - all_dependencies_syncing
        if not self.restore_state.is_initial:
            case_ids_on_phone = case_ids_on_phone | self.restore_state.last_sync_log.case_ids_on_phone
            # subtract primary cases from dependencies since they must be newly primary
            all_dependencies_syncing = all_dependencies_syncing | (
                self.restore_state.last_sync_log.dependent_case_ids_on_phone -
                primary_cases_syncing
            )
            index_tree = self.restore_state.last_sync_log.index_tree.apply_updates(index_tree)

        self.restore_state.current_sync_log.case_ids_on_phone = case_ids_on_phone
        self.restore_state.current_sync_log.dependent_case_ids_on_phone = all_dependencies_syncing
        self.restore_state.current_sync_log.index_tree = index_tree
        self.restore_state.current_sync_log.extension_index_tree = extension_index_tree
        self.restore_state.current_sync_log.closed_cases = closed_cases

        _move_no_longer_owned_cases_to_dependent_list_if_necessary(self.restore_state, self.case_accessor)
        self.restore_state.current_sync_log.purge_dependent_cases()

        purged_cases = case_ids_on_phone - self.restore_state.current_sync_log.case_ids_on_phone

        # don't sync purged cases that were never on the phone
        if self.restore_state.is_initial:
            irrelevant_cases = purged_cases
        else:
            irrelevant_cases = purged_cases - self.restore_state.last_sync_log.case_ids_on_phone

        for update in potential_updates_to_sync:
            if update.case.case_id not in irrelevant_cases:
                append_update_to_response(response, update, self.restore_state)

        return response

    def get_case_ids_for_owner(self, owner_id):
        if EXTENSION_CASES_SYNC_ENABLED.enabled(self.restore_state.domain):
            return self._get_case_ids_for_owners_with_extensions(owner_id)
        else:
            return self._get_case_ids_for_owners_without_extensions(owner_id)

    def _get_case_ids_for_owners_without_extensions(self, owner_id):
        if self.is_clean(owner_id):
            if self.restore_state.is_initial:
                # for a clean owner's initial sync the base set is just the open ids
                return set(self.case_accessor.get_open_case_ids(owner_id))
            else:
                # for a clean owner's steady state sync, the base set is anything modified since last sync
                return set(self.case_accessor.get_case_ids_modified_with_owner_since(
                    owner_id, self.restore_state.last_sync_log.date
                ))
        else:
            # TODO: we may want to be smarter than this
            # right now just return the whole footprint and do any filtering later
            # Note: This will also return extensions if they exist.
            return get_case_footprint_info(self.restore_state.domain, owner_id).all_ids

    def _get_case_ids_for_owners_with_extensions(self, owner_id):
        """Fetches base and extra cases when extensions are enabled"""
        if not self.is_clean(owner_id) or self.restore_state.is_first_extension_sync:
            # If this is the first time a user with extensions has synced after
            # the extension flag is toggled, pull all the cases so that the
            # extension parameters get set correctly
            return get_case_footprint_info(self.restore_state.domain, owner_id).all_ids
        else:
            if self.restore_state.is_initial:
                # for a clean owner's initial sync the base set is just the open ids and their extensions
                all_case_ids = set(self.case_accessor.get_open_case_ids(owner_id))
                new_case_ids = set(all_case_ids)
                while new_case_ids:
                    all_case_ids = all_case_ids | new_case_ids
                    extension_case_ids = set(self.case_accessor.get_extension_case_ids(new_case_ids))
                    new_case_ids = extension_case_ids - all_case_ids
                return all_case_ids
            else:
                # for a clean owner's steady state sync, the base set is anything modified since last sync
                modified_non_extension_cases = set(self.case_accessor.get_case_ids_modified_with_owner_since(
                    owner_id, self.restore_state.last_sync_log.date
                ))
                # we also need to fetch unowned extension cases that have been modified
                extension_case_ids = self.restore_state.last_sync_log.extension_index_tree.indices.keys()
                modified_extension_cases = set(filter_cases_modified_since(
                    self.case_accessor, extension_case_ids, self.restore_state.last_sync_log.date
                ))
                return modified_non_extension_cases | modified_extension_cases