Return tuple containing int representing number of Track model state changes found in transaction and set of processed track IDs .

def track_state_update(
    self,
    update_task: DatabaseTask,
    session: Session,
    track_factory_txs,
    block_number,
    block_timestamp,
    block_hash,
    ipfs_metadata,
    blacklisted_cids,
) -> Tuple[int, Set]:
    """Return tuple containing int representing number of Track model state changes found in transaction and set of processed track IDs."""
    blockhash = update_task.web3.toHex(block_hash)
    num_total_changes = 0
    skipped_tx_count = 0
    # This stores the track_ids created or updated in the set of transactions
    track_ids: Set[int] = set()

    if not track_factory_txs:
        return num_total_changes, track_ids

    pending_track_routes: List[TrackRoute] = []
    track_events: Dict[int, Dict[str, Any]] = {}
    for tx_receipt in track_factory_txs:
        txhash = update_task.web3.toHex(tx_receipt.transactionHash)
        for event_type in track_event_types_arr:
            track_events_tx = get_track_events_tx(update_task, event_type, tx_receipt)
            processedEntries = 0  # if record does not get added, do not count towards num_total_changes
            for entry in track_events_tx:
                event_args = entry["args"]
                track_id = (
                    helpers.get_tx_arg(entry, "_trackId")
                    if "_trackId" in event_args
                    else helpers.get_tx_arg(entry, "_id")
                )
                existing_track_record = None
                track_metadata = None
                try:
                    # look up or populate existing record
                    if track_id in track_events:
                        existing_track_record = track_events[track_id]["track"]
                    else:
                        existing_track_record = lookup_track_record(
                            update_task,
                            session,
                            entry,
                            track_id,
                            block_number,
                            blockhash,
                            txhash,
                        )
                    # parse track event to add metadata to record
                    if event_type in [
                        track_event_types_lookup["new_track"],
                        track_event_types_lookup["update_track"],
                    ]:
                        track_metadata_digest = event_args._multihashDigest.hex()
                        track_metadata_hash_fn = event_args._multihashHashFn
                        buf = multihash.encode(
                            bytes.fromhex(track_metadata_digest), track_metadata_hash_fn
                        )
                        cid = multihash.to_b58_string(buf)
                        # do not process entry if cid is blacklisted
                        if cid in blacklisted_cids:
                            continue
                        track_metadata = ipfs_metadata[cid]

                    parsed_track = parse_track_event(
                        self,
                        session,
                        update_task,
                        entry,
                        event_type,
                        existing_track_record,
                        block_number,
                        block_timestamp,
                        track_metadata,
                        pending_track_routes,
                    )

                    # If track record object is None, it has a blacklisted metadata CID
                    if parsed_track is not None:
                        if track_id not in track_events:
                            track_events[track_id] = {
                                "track": parsed_track,
                                "events": [],
                            }
                        else:
                            track_events[track_id]["track"] = parsed_track
                        track_events[track_id]["events"].append(event_type)
                        track_ids.add(track_id)
                        processedEntries += 1
                except EntityMissingRequiredFieldError as e:
                    logger.warning(f"Skipping tx {txhash} with error {e}")
                    skipped_tx_count += 1
                    add_node_level_skipped_transaction(
                        session, block_number, blockhash, txhash
                    )
                    pass
                except Exception as e:
                    logger.info("Error in parse track transaction")
                    raise IndexingError(
                        "track", block_number, blockhash, txhash, str(e)
                    ) from e

            num_total_changes += processedEntries

    logger.info(
        f"index.py | tracks.py | [track indexing] There are {num_total_changes} events processed and {skipped_tx_count} skipped transactions."
    )

    for track_id, value_obj in track_events.items():
        if value_obj["events"]:
            logger.info(f"index.py | tracks.py | Adding {value_obj['track']}")
            invalidate_old_track(session, track_id)
            session.add(value_obj["track"])

    return num_total_changes, track_ids


def update_track_routes_table(
    session, track_record, track_metadata, pending_track_routes
):
    """Creates the route for the given track"""

    # Check if the title is staying the same, and if so, return early
    if track_record.title == track_metadata["title"]:
        return

    # Get the title slug, and set the new slug to that
    # (will check for conflicts later)
    new_track_slug_title = helpers.create_track_slug(
        track_metadata["title"], track_record.track_id
    )
    new_track_slug = new_track_slug_title

    # Find the current route for the track
    # Check the pending track route updates first
    prev_track_route_record = next(
        (
            route
            for route in pending_track_routes
            if route.is_current and route.track_id == track_record.track_id
        ),
        None,
    )
    # Then query the DB if necessary
    if prev_track_route_record is None:
        prev_track_route_record = (
            session.query(TrackRoute)
            .filter(
                TrackRoute.track_id == track_record.track_id,
                TrackRoute.is_current == True,
            )  # noqa: E712
            .one_or_none()
        )

    if prev_track_route_record is not None:
        if prev_track_route_record.title_slug == new_track_slug_title:
            # If the title slug hasn't changed, we have no work to do
            return
        # The new route will be current
        prev_track_route_record.is_current = False

    # Check for collisions by slug titles, and get the max collision_id
    max_collision_id: Optional[int] = None
    # Check pending updates first
    for route in pending_track_routes:
        if (
            route.title_slug == new_track_slug_title
            and route.owner_id == track_record.owner_id
        ):
            max_collision_id = (
                route.collision_id
                if max_collision_id is None
                else max(max_collision_id, route.collision_id)
            )
    # Check DB if necessary
    if max_collision_id is None:
        max_collision_id = (
            session.query(functions.max(TrackRoute.collision_id))
            .filter(
                TrackRoute.title_slug == new_track_slug_title,
                TrackRoute.owner_id == track_record.owner_id,
            )
            .one_or_none()
        )[0]

    existing_track_route: Optional[TrackRoute] = None
    # If the new track_slug ends in a digit, there's a possibility it collides
    # with an existing route when the collision_id is appended to its title_slug
    if new_track_slug[-1].isdigit():
        existing_track_route = next(
            (
                route
                for route in pending_track_routes
                if route.slug == new_track_slug
                and route.owner_id == track_record.owner_id
            ),
            None,
        )
        if existing_track_route is None:
            existing_track_route = (
                session.query(TrackRoute)
                .filter(
                    TrackRoute.slug == new_track_slug,
                    TrackRoute.owner_id == track_record.owner_id,
                )
                .one_or_none()
            )

    new_collision_id = 0
    has_collisions = existing_track_route is not None

    if max_collision_id is not None:
        has_collisions = True
        new_collision_id = max_collision_id
    while has_collisions:
        # If there is an existing track by the user with that slug,
        # then we need to append the collision number to the slug
        new_collision_id += 1
        new_track_slug = helpers.create_track_slug(
            track_metadata["title"], track_record.track_id, new_collision_id
        )

        # Check for new collisions after making the new slug
        # In rare cases the user may have track names that end in numbers that
        # conflict with this track name when the collision id is appended,
        # for example they could be trying to create a route that conflicts
        # with the old routing (of appending -{track_id}) This is a fail safe
        # to increment the collision ID until no such collisions are present.
        #
        # Example scenario:
        #   - User uploads track titled "Track" (title_slug: 'track')
        #   - User uploads track titled "Track 1" (title_slug: 'track-1')
        #   - User uploads track titled "Track" (title_slug: 'track')
        #       - Try collision_id: 1, slug: 'track-1' and find new collision
        #       - Use collision_id: 2, slug: 'track-2'
        #   - User uploads track titled "Track" (title_slug: 'track')
        #       - Use collision_id: 3, slug: 'track-3'
        #   - User uploads track titled "Track 1" (title_slug: 'track-1')
        #       - Use collision_id: 1, slug: 'track-1-1'
        #
        # This may be expensive with many collisions, but should be rare.
        existing_track_route = next(
            (
                route
                for route in pending_track_routes
                if route.slug == new_track_slug
                and route.owner_id == track_record.owner_id
            ),
            None,
        )
        if existing_track_route is None:
            existing_track_route = (
                session.query(TrackRoute)
                .filter(
                    TrackRoute.slug == new_track_slug,
                    TrackRoute.owner_id == track_record.owner_id,
                )
                .one_or_none()
            )
        has_collisions = existing_track_route is not None

    # Add the new track route
    new_track_route = TrackRoute()
    new_track_route.slug = new_track_slug
    new_track_route.title_slug = new_track_slug_title
    new_track_route.collision_id = new_collision_id
    new_track_route.owner_id = track_record.owner_id
    new_track_route.track_id = track_record.track_id
    new_track_route.is_current = True
    new_track_route.blockhash = track_record.blockhash
    new_track_route.blocknumber = track_record.blocknumber
    new_track_route.txhash = track_record.txhash
    session.add(new_track_route)

    # Add to pending track routes so we don't add the same route twice
    pending_track_routes.append(new_track_route)


def cache_trending_playlists(self):
    """Caches trending playlists for time period"""

    db = cache_trending_playlists.db
    redis = cache_trending_playlists.redis

    have_lock = False
    update_lock = redis.lock("cache_trending_playlists_lock", timeout=7200)

    try:
        have_lock = update_lock.acquire(blocking=False)

        if have_lock:
            trending_playlist_versions = (
                trending_strategy_factory.get_versions_for_type(
                    TrendingType.PLAYLISTS
                ).keys()
            )
            for version in trending_playlist_versions:
                logger.info(
                    f"cache_trending_playlists.py ({version.name} version) | Starting"
                )
                strategy = trending_strategy_factory.get_strategy(
                    TrendingType.PLAYLISTS, version
                )
                start_time = time.time()
                cache_trending(db, redis, strategy)
                end_time = time.time()
                logger.info(
                    f"cache_trending_playlists.py ({version.name} version) | \
                    Finished in {end_time - start_time} seconds"
                )
                redis.set(trending_playlists_last_completion_redis_key, int(end_time))
        else:
            logger.info("cache_trending_playlists.py | Failed to acquire lock")
    except Exception as e:
        logger.error(
            "cache_trending_playlists.py | Fatal error in main loop", exc_info=True
        )
        raise e
    finally:
        if have_lock:
            update_lock.release()