def get_playlist_tracks(args):
    playlists = []
    current_user_id = args.get("current_user_id")
    limit = args.get("limit")
    offset = args.get("offset")

    db = get_db_read_replica()
    with db.scoped_session() as session:
        try:
            playlist_id = args.get("playlist_id")
            playlist = (
                session
                .query(Playlist)
                .filter(
                    Playlist.is_current == True,
                    Playlist.playlist_id == playlist_id
                )
                .first()
            )
            if playlist is None:
                return None

            playlist_track_ids = [track_id['track']
                                  for track_id in playlist.playlist_contents['track_ids']]
            if limit and offset:
                playlist_track_ids = playlist_track_ids[offset:offset+limit]

            playlist_tracks = (
                session
                .query(Track)
                .filter(
                    Track.is_current == True,
                    Track.track_id.in_(playlist_track_ids)
                )
                .all()
            )

            tracks = helpers.query_result_to_list(playlist_tracks)
            tracks = populate_track_metadata(
                session, playlist_track_ids, tracks, current_user_id)

            if args.get("with_users", False):
                add_users_to_tracks(session, tracks, current_user_id)

            tracks_dict = {track['track_id']: track for track in tracks}

            playlist_tracks = []
            for track_id in playlist_track_ids:
                playlist_tracks.append(tracks_dict[track_id])

            return playlist_tracks

        except sqlalchemy.orm.exc.NoResultFound:
            pass
    return playlists
def _get_user_listening_history(session: Session,
                                args: GetUserListeningHistoryArgs):
    user_id = args["user_id"]
    current_user_id = args["current_user_id"]
    limit = args["limit"]
    offset = args["offset"]

    if user_id != current_user_id:
        return []

    listening_history_results = (session.query(
        UserListeningHistory.listening_history).filter(
            UserListeningHistory.user_id == current_user_id)).scalar()

    if not listening_history_results:
        return []

    # add query pagination
    listening_history_results = listening_history_results[offset:offset +
                                                          limit]

    track_ids = []
    listen_dates = []
    for listen in listening_history_results:
        track_ids.append(listen["track_id"])
        listen_dates.append(listen["timestamp"])

    track_results = (session.query(Track).filter(
        Track.track_id.in_(track_ids))).all()

    track_results_dict = {
        track_result.track_id: track_result
        for track_result in track_results
    }

    # sort tracks in listening history order
    sorted_track_results = []
    for track_id in track_ids:
        if track_id in track_results_dict:
            sorted_track_results.append(track_results_dict[track_id])

    tracks = helpers.query_result_to_list(sorted_track_results)

    # bundle peripheral info into track results
    tracks = populate_track_metadata(session, track_ids, tracks,
                                     current_user_id)
    add_users_to_tracks(session, tracks, current_user_id)

    for idx, track in enumerate(tracks):
        track[response_name_constants.activity_timestamp] = listen_dates[idx]

    return tracks
Beispiel #3
0
def get_remix_track_parents(args):
    """Fetch remix parents for a given track.

    Args:
        args:dict
        args.track_id: track id
        args.limit: limit
        args.offset: offset
        args.with_users: with users
        args.current_user_id: current user ID
    """
    track_id = args.get("track_id")
    current_user_id = args.get("current_user_id")
    limit = args.get("limit")
    offset = args.get("offset")
    db = get_db_read_replica()

    with db.scoped_session() as session:

        def get_unpopulated_remix_parents():
            base_query = (
                session.query(Track)
                .join(
                    Remix,
                    and_(
                        Remix.parent_track_id == Track.track_id,
                        Remix.child_track_id == track_id,
                    ),
                )
                .filter(Track.is_current == True, Track.is_unlisted == False)
                .order_by(desc(Track.created_at), desc(Track.track_id))
            )

            tracks = add_query_pagination(base_query, limit, offset).all()
            tracks = helpers.query_result_to_list(tracks)
            track_ids = list(map(lambda track: track["track_id"], tracks))
            return (tracks, track_ids)

        key = make_cache_key(args)
        (tracks, track_ids) = use_redis_cache(
            key,
            UNPOPULATED_REMIX_PARENTS_CACHE_DURATION_SEC,
            get_unpopulated_remix_parents,
        )

        tracks = populate_track_metadata(session, track_ids, tracks, current_user_id)
        if args.get("with_users", False):
            add_users_to_tracks(session, tracks, current_user_id)

    return tracks
Beispiel #4
0
def get_remix_track_parents(track_id, args):
    db = get_db_read_replica()
    with db.scoped_session() as session:
        base_query = (session.query(Track).join(
            Remix,
            and_(Remix.parent_track_id == Track.track_id,
                 Remix.child_track_id == track_id)).filter(
                     Track.is_current == True,
                     Track.is_unlisted == False).order_by(
                         desc(Track.created_at), desc(Track.track_id)))

        tracks = paginate_query(base_query).all()
        tracks = helpers.query_result_to_list(tracks)
        track_ids = list(map(lambda track: track["track_id"], tracks))
        current_user_id = get_current_user_id(required=False)
        tracks = populate_track_metadata(session, track_ids, tracks,
                                         current_user_id)

        if args.get("with_users", False):
            add_users_to_tracks(session, tracks)

    return tracks
def get_remixes_of(args):
    track_id = args.get("track_id")
    current_user_id = args.get("current_user_id")
    limit, offset = args.get("limit"), args.get("offset")
    db = get_db_read_replica()

    with db.scoped_session() as session:

        def get_unpopulated_remixes():

            # Fetch the parent track to get the track's owner id
            parent_track_res = get_unpopulated_tracks(session, [track_id],
                                                      False, False)

            if not parent_track_res or parent_track_res[0] is None:
                raise exceptions.ArgumentError("Invalid track_id provided")

            parent_track = parent_track_res[0]
            track_owner_id = parent_track['owner_id']

            # Create subquery for save counts for sorting
            save_count_subquery = create_save_count_subquery(
                session, SaveType.track)

            # Create subquery for repost counts for sorting
            repost_count_subquery = create_repost_count_subquery(
                session, RepostType.track)

            # Get the 'children' remix tracks
            # Use the track owner id to fetch reposted/saved tracks returned first
            base_query = (
                session.query(
                    Track
                )
                .join(
                    Remix,
                    and_(
                        Remix.child_track_id == Track.track_id,
                        Remix.parent_track_id == track_id
                    )
                ).outerjoin(
                    Save,
                    and_(
                        Save.save_item_id == Track.track_id,
                        Save.save_type == SaveType.track,
                        Save.is_current == True,
                        Save.is_delete == False,
                        Save.user_id == track_owner_id
                    )
                ).outerjoin(
                    Repost,
                    and_(
                        Repost.repost_item_id == Track.track_id,
                        Repost.user_id == track_owner_id,
                        Repost.repost_type == RepostType.track,
                        Repost.is_current == True,
                        Repost.is_delete == False
                    )
                ).outerjoin(
                    repost_count_subquery,
                    repost_count_subquery.c['id'] == Track.track_id
                ).outerjoin(
                    save_count_subquery,
                    save_count_subquery.c['id'] == Track.track_id
                )
                .filter(
                    Track.is_current == True,
                    Track.is_delete == False,
                    Track.is_unlisted == False
                )
                # 1. Co-signed tracks ordered by save + repost count
                # 2. Other tracks ordered by save + repost count
                .order_by(
                    desc(
                        # If there is no "co-sign" for the track (no repost or save from the parent owner),
                        # defer to secondary sort
                        case(
                            [
                                (and_(Repost.created_at == None,
                                      Save.created_at == None), 0),
                            ],
                            else_=(
                                func.coalesce(repost_count_subquery.c.repost_count, 0) + \
                                func.coalesce(save_count_subquery.c.save_count, 0)
                            )
                        )
                    ),
                    # Order by saves + reposts
                    desc(
                        func.coalesce(repost_count_subquery.c.repost_count, 0) + \
                        func.coalesce(save_count_subquery.c.save_count, 0)
                    ),
                    # Ties, pick latest track id
                    desc(Track.track_id)
                )
            )

            (tracks, count) = add_query_pagination(base_query, limit, offset,
                                                   True, True)
            tracks = tracks.all()
            tracks = helpers.query_result_to_list(tracks)
            track_ids = list(map(lambda track: track["track_id"], tracks))
            return (tracks, track_ids, count)

        key = make_cache_key(args)
        (tracks, track_ids,
         count) = use_redis_cache(key, UNPOPULATED_REMIXES_CACHE_DURATION_SEC,
                                  get_unpopulated_remixes)

        tracks = populate_track_metadata(session, track_ids, tracks,
                                         current_user_id)
        if args.get("with_users", False):
            add_users_to_tracks(session, tracks, current_user_id)

    return {'tracks': tracks, 'count': count}
def get_remixable_tracks(args):
    """Gets a list of remixable tracks"""
    db = get_db_read_replica()
    limit = args.get("limit", 25)
    current_user_id = args.get("current_user_id", None)

    StemTrack = aliased(Track)

    with db.scoped_session() as session:
        # Subquery to get current tracks that have stems
        remixable_tracks_subquery = (session.query(Track).join(
            Stem, Stem.parent_track_id == Track.track_id).join(
                StemTrack, Stem.child_track_id == StemTrack.track_id).filter(
                    Track.is_current == True,
                    Track.is_unlisted == False,
                    Track.is_delete == False,
                    StemTrack.is_current == True,
                    StemTrack.is_unlisted == False,
                    StemTrack.is_delete == False,
                ).distinct(Track.track_id).subquery())
        track_alias = aliased(Track, remixable_tracks_subquery)

        count_subquery = session.query(
            AggregateTrack.track_id.label("id"),
            (AggregateTrack.repost_count +
             AggregateTrack.save_count).label("count"),
        ).subquery()

        query = (session.query(
            track_alias,
            count_subquery.c["count"],
            decayed_score(count_subquery.c["count"],
                          track_alias.created_at).label("score"),
        ).join(
            count_subquery,
            count_subquery.c["id"] == track_alias.track_id,
        ).order_by(desc("score"), desc(track_alias.track_id)).limit(limit))

        results = query.all()

        tracks = []
        for result in results:
            track = result[0]
            score = result[-1]
            track = helpers.model_to_dictionary(track)
            track["score"] = score
            tracks.append(track)

        track_ids = list(map(lambda track: track["track_id"], tracks))

        # Get user specific data for tracks
        tracks = populate_track_metadata(session, track_ids, tracks,
                                         current_user_id)

        if args.get("with_users", False):
            add_users_to_tracks(session, tracks, current_user_id)
        else:
            # Remove the user from the tracks
            tracks = [{key: val
                       for key, val in dict.items() if key != "user"}
                      for dict in tracks]

    return tracks
Beispiel #7
0
def get_trending_playlists(args, strategy):
    """Returns Trending Playlists. Checks Redis cache for unpopulated playlists."""
    db = get_db_read_replica()
    with db.scoped_session() as session:
        current_user_id = args.get("current_user_id", None)
        with_tracks = args.get("with_tracks", False)
        time = args.get("time")
        limit, offset = args.get("limit"), args.get("offset")
        key = make_trending_cache_key(time, strategy.version)

        # Get unpopulated playlists,
        # cached if it exists.
        (playlists, playlist_ids) = use_redis_cache(
            key, None, make_get_unpopulated_playlists(session, time, strategy))

        # Apply limit + offset early to reduce the amount of
        # population work we have to do
        if limit is not None and offset is not None:
            playlists = playlists[offset:limit + offset]
            playlist_ids = playlist_ids[offset:limit + offset]

        # Populate playlist metadata
        playlists = populate_playlist_metadata(
            session, playlist_ids, playlists,
            [RepostType.playlist, RepostType.album],
            [SaveType.playlist, SaveType.album], current_user_id)

        trimmed_track_ids = None
        for playlist in playlists:
            playlist["track_count"] = len(playlist["tracks"])
            playlist["tracks"] = playlist["tracks"][:PLAYLIST_TRACKS_LIMIT]
            # Trim track_ids, which ultimately become added_timestamps
            # and need to match the tracks.
            trimmed_track_ids = {
                track["track_id"]
                for track in playlist["tracks"]
            }
            playlist_track_ids = playlist["playlist_contents"]["track_ids"]
            playlist_track_ids = list(
                filter(lambda track_id: track_id["track"] in trimmed_track_ids,
                       playlist_track_ids))
            playlist["playlist_contents"]["track_ids"] = playlist_track_ids

        playlists_map = {
            playlist['playlist_id']: playlist
            for playlist in playlists
        }

        if with_tracks:
            # populate track metadata
            tracks = []
            for playlist in playlists:
                playlist_tracks = playlist["tracks"]
                tracks.extend(playlist_tracks)
            track_ids = [track["track_id"] for track in tracks]
            populated_tracks = populate_track_metadata(session, track_ids,
                                                       tracks, current_user_id)

            # Add users if necessary
            add_users_to_tracks(session, populated_tracks, current_user_id)

            # Re-associate tracks with playlists
            # track_id -> populated_track
            populated_track_map = {
                track["track_id"]: track
                for track in populated_tracks
            }
            for playlist in playlists_map.values():
                for i in range(len(playlist["tracks"])):
                    track_id = playlist["tracks"][i]["track_id"]
                    populated = populated_track_map[track_id]
                    playlist["tracks"][i] = populated
                playlist["tracks"] = list(map(extend_track,
                                              playlist["tracks"]))

        # re-sort playlists to original order, because populate_playlist_metadata
        # unsorts.
        sorted_playlists = [
            playlists_map[playlist_id] for playlist_id in playlist_ids
        ]

        # Add users to playlists
        user_id_list = get_users_ids(sorted_playlists)
        users = get_users_by_id(session, user_id_list, current_user_id)
        for playlist in sorted_playlists:
            user = users[playlist['playlist_owner_id']]
            if user:
                playlist['user'] = user

        # Extend the playlists
        playlists = list(map(extend_playlist, playlists))
        return sorted_playlists
def get_playlist_tracks(session, args):
    """Accepts args:
        {
            # optionally pass in full playlists to avoid having to fetch
            "playlists": Playlist[]

            # not needed if playlists are passed
            "playlist_ids": string[]
            "current_user_id": int
            "populate_tracks": boolean # whether to add users & metadata to tracks
        }

        Returns: {
            playlist_id: Playlist
        }
    """

    try:
        playlists = args.get("playlists")
        if not playlists:
            playlist_ids = args.get("playlist_ids", [])
            playlists = (session.query(Playlist).filter(
                Playlist.is_current == True,
                Playlist.playlist_id.in_(playlist_ids)))
            playlists = list(map(helpers.model_to_dictionary, playlists))

        if not playlists:
            return {}

        # track_id -> [playlist_id]
        track_ids_set = set()
        for playlist in playlists:
            playlist_id = playlist['playlist_id']
            for track_id_dict in playlist['playlist_contents']['track_ids']:
                track_id = track_id_dict['track']
                track_ids_set.add(track_id)

        playlist_tracks = (session.query(Track).filter(
            Track.is_current == True,
            Track.track_id.in_(list(track_ids_set))).all())

        tracks = helpers.query_result_to_list(playlist_tracks)

        if args.get("populate_tracks"):
            current_user_id = args.get("current_user_id")
            tracks = populate_track_metadata(session, list(track_ids_set),
                                             tracks, current_user_id)

            add_users_to_tracks(session, tracks, current_user_id)

        # { track_id => track }
        track_ids_map = {track["track_id"]: track for track in tracks}

        # { playlist_id => [track]}
        playlists_map = {}
        for playlist in playlists:
            playlist_id = playlist["playlist_id"]
            playlists_map[playlist_id] = []
            for track_id_dict in playlist['playlist_contents']['track_ids']:
                track_id = track_id_dict['track']
                track = track_ids_map[track_id]
                playlists_map[playlist_id].append(track)

        return playlists_map

    except sqlalchemy.orm.exc.NoResultFound:
        return {}
Beispiel #9
0
def get_tracks(args: GetTrackArgs):
    """
    Gets tracks.
    A note on caching strategy:
        - This method is cached at two layers: at the API via the @cache decorator,
        and within this method using the shared get_unpopulated_tracks cache.

        The shared cache only works when fetching via ID, so calls to fetch tracks
        via handle, asc/desc sort, or filtering by block_number won't hit the shared cache.
        These will hit the API cache unless they have a current_user_id included.

    """
    tracks = []

    db = get_db_read_replica()
    with db.scoped_session() as session:

        def get_tracks_and_ids():
            if "handle" in args:
                handle = args.get("handle")
                user = (session.query(User.user_id).filter(
                    User.handle_lc == handle.lower()).first())
                args["user_id"] = user.user_id

            if "routes" in args:
                # Convert the handles to user_ids
                routes = args.get("routes")
                handles = [route["handle"].lower() for route in routes]
                user_id_tuples = (session.query(
                    User.user_id,
                    User.handle_lc).filter(User.handle_lc.in_(handles),
                                           User.is_current == True).all())
                user_id_map = {
                    handle: user_id
                    for (user_id, handle) in user_id_tuples
                }
                args["routes"] = []
                for route in routes:
                    if route["handle"].lower() in user_id_map:
                        args["routes"].append({
                            "slug":
                            route["slug"],
                            "owner_id":
                            user_id_map[route["handle"].lower()],
                        })
                # If none of the handles were found, return empty lists
                if not args["routes"]:
                    return ([], [])

            can_use_shared_cache = ("id" in args
                                    and "min_block_number" not in args
                                    and "sort" not in args
                                    and "user_id" not in args)

            if can_use_shared_cache:
                should_filter_deleted = args.get("filter_deleted", False)
                tracks = get_unpopulated_tracks(session, args["id"],
                                                should_filter_deleted)
                track_ids = list(map(lambda track: track["track_id"], tracks))
                return (tracks, track_ids)

            (limit, offset) = get_pagination_vars()
            args["limit"] = limit
            args["offset"] = offset

            tracks = _get_tracks(session, args)

            track_ids = list(map(lambda track: track["track_id"], tracks))

            return (tracks, track_ids)

        (tracks, track_ids) = get_tracks_and_ids()

        # bundle peripheral info into track results
        current_user_id = args.get("current_user_id")

        # remove track segments and download cids from deactivated user tracks and deleted tracks
        for track in tracks:
            if track["user"][0]["is_deactivated"] or track["is_delete"]:
                track["track_segments"] = []
                if track["download"] is not None:
                    track["download"]["cid"] = None

        tracks = populate_track_metadata(session, track_ids, tracks,
                                         current_user_id)

        if args.get("with_users", False):
            add_users_to_tracks(session, tracks, current_user_id)
        else:
            # Remove the user from the tracks
            tracks = [{key: val
                       for key, val in dict.items() if key != "user"}
                      for dict in tracks]
    return tracks