Ejemplo n.º 1
0
def create(
    title: str,
    filepath: Path,
    sha256_initial: bytes,
    release_id: int,
    artists: list[dict],
    duration: int,
    track_number: str,
    disc_number: str,
    conn: Connection,
    sha256: Optional[bytes] = None,
) -> T:
    """
    Create a track with the provided parameters.

    If a track already exists with the same SHA256, the filepath of that track will be
    set to the passed-in filepath and nothing else will be done.

    :param title: The title of the track.
    :param filepath: The filepath of the track.
    :param sha256_initial: The SHA256 of the first 1KB of the track file.
    :param release_id: The ID of the release that this track belongs to.
    :param artists: The artists that contributed to this track. A list of
                    ``{"artist_id": int, "role": ArtistRole}`` mappings.
    :param duration: The duration of this track, in seconds.
    :param track_number: The track number.
    :param disc_number: The disc number.
    :param sha256: The full SHA256 of the track file. This should generally not be
                   passed in--calculating a SHA256 requires a filesystem read of several
                   MB, and we want to do that lazily. But we allow it to be passed in
                   for testing and cases where efficiency doesn't matter.
    :return: The newly created track.
    :raises NotFound: If no release has the given release ID or no artist
                      corresponds with any of the given artist IDs.
    :raises Duplicate: If a track with the same filepath already exists. The duplicate
                       track is passed as the ``entity`` argument.
    """
    if not librelease.exists(release_id, conn):
        logger.debug(f"Release {release_id} does not exist.")
        raise NotFound(f"Release {release_id} does not exist.")

    if bad_ids := [
            d["artist_id"] for d in artists
            if not artist.exists(d["artist_id"], conn)
    ]:
        logger.debug(
            f"Artist(s) {', '.join(str(i) for i in bad_ids)} do not exist.")
        raise NotFound(
            f"Artist(s) {', '.join(str(i) for i in bad_ids)} do not exist.")
Ejemplo n.º 2
0
def resolve_star_artist(_, info: GraphQLResolveInfo, id: int) -> artist.T:
    art = artist.from_id(id, info.context.db)
    if not art:
        raise NotFound(f"Artist {id} does not exist.")

    artist.star(art, info.context.user.id, info.context.db)
    return art
Ejemplo n.º 3
0
def resolve_star_playlist(_, info: GraphQLResolveInfo, id: int) -> playlist.T:
    ply = playlist.from_id(id, info.context.db)
    if not ply:
        raise NotFound(f"Playlist {id} does not exist.")

    playlist.star(ply, info.context.user.id, info.context.db)
    return ply
Ejemplo n.º 4
0
def resolve_unstar_collection(_, info: GraphQLResolveInfo,
                              id: int) -> collection.T:
    col = collection.from_id(id, info.context.db)
    if not col:
        raise NotFound(f"Collection {id} does not exist.")

    collection.unstar(col, info.context.user.id, info.context.db)
    return col
Ejemplo n.º 5
0
def delete_playlist_entries(
    obj: Any,
    info: GraphQLResolveInfo,
    playlistId: int,
    trackId: int,
) -> dict:
    for ety in pentry.from_playlist_and_track(playlistId, trackId, info.context.db):
        pentry.delete(ety, info.context.db)

    ply = playlist.from_id(playlistId, info.context.db)
    if not ply:
        raise NotFound(f"Playlist {playlistId} does not exist.")

    trk = track.from_id(trackId, info.context.db)
    if not trk:
        raise NotFound(f"Track {trackId} does not exist.")

    return {"playlist": ply, "track": trk}
Ejemplo n.º 6
0
def add_release(col: T, release_id: int, conn: Connection) -> T:
    """
    Add the provided release to the provided collection.

    :param col: The collection to add the release to.
    :param release_id: The ID of the release to add.
    :param conn: A connection to the database.
    :return: The collection with the number of tracks (if present) updated.
    :raises NotFound: If no release has the given release ID.
    :raises AlreadyExists: If the release is already in the collection.
    """
    if not release.exists(release_id, conn):
        logger.debug(f"Release {release_id} does not exist.")
        raise NotFound(f"Release {release_id} does not exist.")

    cursor = conn.execute(
        """
        SELECT 1 FROM music__collections_releases
        WHERE collection_id = ? AND release_id = ?
        """,
        (col.id, release_id),
    )
    if cursor.fetchone():
        logger.debug(f"Release {release_id} already in collection {col.id}.")
        raise AlreadyExists("Release is already in collection.")

    conn.execute(
        """
        INSERT INTO music__collections_releases (collection_id, release_id)
        VALUES (?, ?)
        """,
        (col.id, release_id),
    )

    now = datetime.now()

    conn.execute(
        """
        UPDATE music__collections
        SET last_updated_on = ?
        WHERE id = ?
        """,
        (
            col.id,
            now,
        ),
    )

    logger.info(f"Added release {release_id} to collection {col.id}.")

    return update_dataclass(
        col,
        num_releases=(col.num_releases +
                      1 if col.num_releases is not None else col.num_releases),
        last_updated_on=now,
    )
Ejemplo n.º 7
0
def del_release(col: T, release_id: int, conn: Connection) -> T:
    """
    Remove the provided release from the provided collection.

    :param col: The collection to remove the release from.
    :param release_id: The release to remove.
    :param conn: A connection to the database.
    :return: The collection with the number of tracks (if present) updated.
    :raises NotFound: If no release has the given release ID.
    :raises DoesNotExist: If the release is not in the collection.
    """
    if not release.exists(release_id, conn):
        logger.debug(f"Release {release_id} does not exist.")
        raise NotFound(f"Release {release_id} does not exist.")

    cursor = conn.execute(
        """
        SELECT 1 FROM music__collections_releases
        WHERE collection_id = ? AND release_id = ?
        """,
        (col.id, release_id),
    )
    if not cursor.fetchone():
        logger.debug(f"Release {release_id} not in collection {col.id}.")
        raise DoesNotExist("Release is not in collection.")

    conn.execute(
        """
        DELETE FROM music__collections_releases
        WHERE collection_id = ? AND release_id = ?
        """,
        (col.id, release_id),
    )

    now = datetime.utcnow()

    conn.execute(
        """
        UPDATE music__collections
        SET last_updated_on = ?
        WHERE id = ?
        """,
        (
            now,
            col.id,
        ),
    )

    logger.info(f"Deleted release {release_id} from collection {col.id}.")

    return update_dataclass(
        col,
        num_releases=(col.num_releases -
                      1 if col.num_releases is not None else col.num_releases),
        last_updated_on=now,
    )
Ejemplo n.º 8
0
def create(playlist_id: int, track_id: int, conn: Connection) -> T:
    """
    Add the provided track to the provided playlist.

    :param playlist_id: The ID of the playlist to add the entry to.
    :param track_id: The ID of the track to insert.
    :param conn: A connection to the database.
    :return: The new playlist entry.
    :raises NotFound: If no track has the given track ID.
    """
    if not libtrack.exists(track_id, conn):
        logger.debug(f"Track {track_id} does not exist.")
        raise NotFound(f"Track {track_id} does not exist.")

    if not libplaylist.exists(playlist_id, conn):
        logger.debug(f"Playlist {playlist_id} does not exist.")
        raise NotFound(f"Playlist {playlist_id} does not exist.")

    cursor = conn.execute(
        """
        INSERT INTO music__playlists_tracks (playlist_id, track_id, position)
        VALUES (?, ?, ?)
        """,
        (playlist_id, track_id, _highest_position(playlist_id, conn) + 1),
    )

    conn.execute(
        """
        UPDATE music__playlists
        SET last_updated_on = CURRENT_TIMESTAMP
        WHERE id = ?
        """,
        (playlist_id,),
    )

    logger.info(
        f"Created entry {cursor.lastrowid} with "
        f"track {track_id} and playlist {playlist_id}."
    )
    pety = from_id(cursor.lastrowid, conn)
    assert pety is not None
    return pety
Ejemplo n.º 9
0
def resolve_update_track(
    _,
    info: GraphQLResolveInfo,
    id: int,
    **changes,
) -> track.T:
    trk = track.from_id(id, info.context.db)
    if not trk:
        raise NotFound(f"Track {id} does not exist.")

    return track.update(trk, info.context.db, **convert_keys_case(changes))
Ejemplo n.º 10
0
def resolve_update_artist(
    _,
    info: GraphQLResolveInfo,
    id: int,
    **changes,
) -> artist.T:
    art = artist.from_id(id, info.context.db)
    if not art:
        raise NotFound(f"Artist {id} does not exist.")

    return artist.update(art, info.context.db, **convert_keys_case(changes))
Ejemplo n.º 11
0
def resolve_update_playlist(
    _,
    info: GraphQLResolveInfo,
    id: int,
    **changes,
) -> playlist.T:
    ply = playlist.from_id(id, info.context.db)
    if not ply:
        raise NotFound(f"Playlist {id} does not exist.")

    return playlist.update(ply, info.context.db, **convert_keys_case(changes))
Ejemplo n.º 12
0
def resolve_update_collection(
    _,
    info: GraphQLResolveInfo,
    id: int,
    **changes,
) -> collection.T:
    col = collection.from_id(id, info.context.db)
    if not col:
        raise NotFound(f"Collection {id} does not exist.")

    return collection.update(col, info.context.db,
                             **convert_keys_case(changes))
Ejemplo n.º 13
0
def resolve_del_release_from_collection(
    _,
    info: GraphQLResolveInfo,
    collectionId: int,
    releaseId: int,
) -> dict:
    col = collection.from_id(collectionId, info.context.db)
    if not col:
        raise NotFound(f"Collection {collectionId} does not exist.")

    col = collection.del_release(col, releaseId, info.context.db)
    rls = release.from_id(releaseId, info.context.db)

    return {"collection": col, "release": rls}
Ejemplo n.º 14
0
def resolve_del_artist_from_track(
    _,
    info: GraphQLResolveInfo,
    trackId: int,
    artistId: int,
    role: ArtistRole,
) -> dict:
    trk = track.from_id(trackId, info.context.db)
    if not trk:
        raise NotFound("Track does not exist.")

    trk = track.del_artist(trk, artistId, role, info.context.db)
    art = artist.from_id(artistId, info.context.db)

    return {"track": trk, "track_artist": {"role": role, "artist": art}}
Ejemplo n.º 15
0
def resolve_del_artist_from_release(
    _,
    info: GraphQLResolveInfo,
    releaseId: int,
    artistId: int,
    role: ArtistRole,
) -> dict:
    rls = release.from_id(releaseId, info.context.db)
    if not rls:
        raise NotFound(f"Release {releaseId} does not exist.")

    rls = release.del_artist(rls, artistId, role, info.context.db)
    art = artist.from_id(artistId, info.context.db)

    return {"release": rls, "artist": art}
Ejemplo n.º 16
0
def update(trk: T, conn: Connection, **changes) -> T:
    """
    Update a track and persist changes to the database. To update a value, pass it
    in as a keyword argument. To keep the original value, do not pass in a keyword
    argument.

    :param trk: The track to update.
    :param conn: A connection to the database.
    :param title: New track title.
    :type  title: :py:obj:`str`
    :param release_id: ID of the new release.
    :type  release_id: :py:obj:`int`
    :param track_number: New track number.
    :type  track_number: :py:obj:`str`
    :param disc_number: New disc number.
    :type  disc_number: :py:obj:`str`
    :return: The updated track.
    :raise NotFound: If the new release ID does not exist.
    """
    if "release_id" in changes and not librelease.exists(
            changes["release_id"], conn):
        logger.debug(f"Release {changes['release_id']} does not exist.")
        raise NotFound(f"Release {changes['release_id']} does not exist.")

    conn.execute(
        """
        UPDATE music__tracks
        SET title = ?,
            release_id = ?,
            track_number = ?,
            disc_number = ?
        WHERE id = ?
        """,
        (
            changes.get("title", trk.title),
            changes.get("release_id", trk.release_id),
            changes.get("track_number", trk.track_number),
            changes.get("disc_number", trk.disc_number),
            trk.id,
        ),
    )

    logger.info(f"Updated track {trk.id} with {changes}.")

    return update_dataclass(trk, **changes)
Ejemplo n.º 17
0
def add_artist(trk: T, artist_id: int, role: ArtistRole,
               conn: Connection) -> T:
    """
    Add the provided artist/role combo to the provided track.

    :param trk: The track to add the artist to.
    :param artist_id: The ID of the artist to add.
    :param role: The role to add the artist with.
    :param conn: A connection to the database.
    :return: The track that was passed in.
    :raises NotFound: If no artist has the given artist ID.
    :raises AlreadyExists: If the artist/role combo is already on the track.
    """
    if not artist.exists(artist_id, conn):
        logger.debug(f"Artist {artist_id} does not exist.")
        raise NotFound(f"Artist {artist_id} does not exist.")

    cursor = conn.execute(
        """
        SELECT 1 FROM music__tracks_artists
        WHERE track_id = ? AND artist_id = ? AND role = ?
        """,
        (trk.id, artist_id, role.value),
    )
    if cursor.fetchone():
        logger.debug(
            f"Artist {artist_id} is already on track {trk.id} with role {role}."
        )
        raise AlreadyExists("Artist already on track with this role.")

    conn.execute(
        """
        INSERT INTO music__tracks_artists (track_id, artist_id, role)
        VALUES (?, ?, ?)
        """,
        (trk.id, artist_id, role.value),
    )

    logger.info(
        f"Added artist {artist_id} to track {trk.id} with role {role}.")

    return trk
Ejemplo n.º 18
0
def resolve_update_release(
    _,
    info: GraphQLResolveInfo,
    id: int,
    **changes,
) -> release.T:
    rls = release.from_id(id, info.context.db)
    if not rls:
        raise NotFound(f"Release {id} does not exist.")

    # Convert the "releaseDate" update from a string to a `date` object. If it is not in
    # the changes dict, do nothing.
    try:
        changes["releaseDate"] = date.fromisoformat(changes["releaseDate"])
    except ValueError:
        raise ParseError("Invalid release date.")
    except KeyError:
        pass

    return release.update(rls, info.context.db, **convert_keys_case(changes))
Ejemplo n.º 19
0
def del_artist(trk: T, artist_id: int, role: ArtistRole,
               conn: Connection) -> T:
    """
    Delete the provided artist/role combo to the provided track.

    :param trk: The track to delete the artist from.
    :param artist_id: The ID of the artist to delete.
    :param role: The role of the artist on the track.
    :param conn: A connection to the database.
    :return: The track that was passed in.
    :raises NotFound: If no artist has the given artist ID.
    :raises DoesNotExist: If the artist is not on the track.
    """
    if not artist.exists(artist_id, conn):
        logger.debug(f"Artist {artist_id} does not exist.")
        raise NotFound(f"Artist {artist_id} does not exist.")

    cursor = conn.execute(
        """
        SELECT 1 FROM music__tracks_artists
        WHERE track_id = ? AND artist_id = ? AND role = ?
        """,
        (trk.id, artist_id, role.value),
    )
    if not cursor.fetchone():
        logger.debug(
            f"Artist {artist_id} is not on track {trk.id} with role {role}.")
        raise DoesNotExist("No artist on track with this role.")

    conn.execute(
        """
        DELETE FROM music__tracks_artists
        WHERE track_id = ? AND artist_id = ? AND role = ?
        """,
        (trk.id, artist_id, role.value),
    )

    logger.info(
        f"Deleted artist {artist_id} from track {trk.id} with role {role}.")

    return trk
Ejemplo n.º 20
0
def create(
    title: str,
    artists: list[dict],
    release_type: ReleaseType,
    release_year: Optional[int],
    conn: Connection,
    release_date: Optional[date] = None,
    rating: Optional[int] = None,
    image_id: Optional[int] = None,
    allow_duplicate: bool = True,
) -> T:
    """
    Create a release with the provided parameters.

    :param title: The title of the release.
    :param artists: The artists that contributed to this release. A list of
                    ``{"artist_id": int, "role": ArtistRole}`` mappings.
    :param release_type: The type of the release.
    :param release_year: The year the release came out.
    :param conn: A connection to the database.
    :param release_date: The date the release came out.
    :param rating: A rating for the release.
    :param image_id: An ID of an image to serve as cover art.
    :param allow_duplicate: Whether to allow creation of a duplicate release or not. If
                             this is ``False``, then ``Duplicate`` will never be raised.
                             All releases will be created.
    :return: The newly created release.
    :raises NotFound: If the list of artists contains an invalid ID.
    :raises Duplicate: If a release with the same name and artists already exists. The
                       duplicate release is passed as the ``entity`` argument.
    """
    if bad_ids := [
            d["artist_id"] for d in artists
            if not artist.exists(d["artist_id"], conn)
    ]:
        logger.debug(
            f"Artist(s) {', '.join(str(i) for i in bad_ids)} do not exist.")
        raise NotFound(
            f"Artist(s) {', '.join(str(i) for i in bad_ids)} do not exist.")
Ejemplo n.º 21
0
from src.graphql.util import commit
from src.library import collection, release
from src.library import user as libuser
from src.util import convert_keys_case, del_pagination_keys

gql_collection = ObjectType("Collection")
gql_collections = ObjectType("Collections")


@query.field("collection")
def resolve_collection(obj: Any, info: GraphQLResolveInfo,
                       id: int) -> collection.T:
    if col := collection.from_id(id, info.context.db):
        return col

    raise NotFound(f"Collection {id} not found.")


@query.field("collectionFromNameTypeUser")
def resolve_collection_from_name_type_user(
    obj: Any,
    info: GraphQLResolveInfo,
    name: str,
    type: CollectionType,
    user: Optional[int] = None,
) -> collection.T:
    if col := collection.from_name_type_user(name,
                                             type,
                                             info.context.db,
                                             user_id=user):
        return col
Ejemplo n.º 22
0
from src.library import playlist
from src.library import playlist_entry as pentry
from src.library import user as libuser
from src.util import convert_keys_case, del_pagination_keys

gql_playlist = ObjectType("Playlist")
gql_playlists = ObjectType("Playlists")


@query.field("playlist")
def resolve_playlist(obj: Any, info: GraphQLResolveInfo,
                     id: int) -> playlist.T:
    if ply := playlist.from_id(id, info.context.db):
        return ply

    raise NotFound(f"Playlist {id} not found.")


@query.field("playlistFromNameTypeUser")
def resolve_playlist_from_name_type_user(
    obj: Any,
    info: GraphQLResolveInfo,
    name: str,
    type: PlaylistType,
    user: Optional[int] = None,
) -> playlist.T:
    if ply := playlist.from_name_type_user(name,
                                           type,
                                           info.context.db,
                                           user_id=user):
        return ply
Ejemplo n.º 23
0
from src.graphql.mutation import mutation
from src.graphql.query import query
from src.graphql.util import commit
from src.library import invite, user
from src.util import convert_keys_case, del_pagination_keys

gql_invite = ObjectType("Invite")
gql_invites = ObjectType("Invites")


@query.field("invite")
def resolve_invite(_: Any, info: GraphQLResolveInfo, id: int) -> invite.T:
    if inv := invite.from_id(id, info.context.db):
        return inv

    raise NotFound(f"Invite {id} does not exist.")


@query.field("invites")
def resolve_invites(_: Any, info: GraphQLResolveInfo, **kwargs) -> dict:
    kwargs = convert_keys_case(kwargs)
    return {
        "results": invite.search(info.context.db, **kwargs),
        "total": invite.count(info.context.db, **del_pagination_keys(kwargs)),
    }


@gql_invite.field("code")
def resolve_releases(obj: invite.T, _: GraphQLResolveInfo) -> str:
    return obj.code.hex()
Ejemplo n.º 24
0
from src.graphql.mutation import mutation
from src.graphql.query import query
from src.graphql.util import commit
from src.library import artist, release
from src.util import convert_keys_case, del_pagination_keys

gql_artist = ObjectType("Artist")
gql_artists = ObjectType("Artists")


@query.field("artist")
def resolve_artist(obj: Any, info: GraphQLResolveInfo, id: int) -> artist.T:
    if art := artist.from_id(id, info.context.db):
        return art

    raise NotFound(f"Artist {id} does not exist.")


@query.field("artistFromName")
def resolve_artist_from_name(obj: Any, info: GraphQLResolveInfo, name: str) -> artist.T:
    if art := artist.from_name(name, info.context.db):
        return art

    raise NotFound(f'Artist "{name}" does not exist.')


@query.field("artists")
def resolve_artists(obj: Any, info: GraphQLResolveInfo, **kwargs) -> dict:
    kwargs = convert_keys_case(kwargs)
    return {
        "results": artist.search(info.context.db, **kwargs),
Ejemplo n.º 25
0
from src.errors import NotFound
from src.graphql.mutation import mutation
from src.graphql.query import query
from src.graphql.util import commit
from src.library import artist, release, track
from src.util import convert_keys_case, del_pagination_keys

gql_track = ObjectType("Track")


@query.field("track")
def resolve_track(obj: Any, info: GraphQLResolveInfo, id: int) -> track.T:
    if trk := track.from_id(id, info.context.db):
        return trk

    raise NotFound(f"Track {id} not found.")


@query.field("tracks")
def resolve_tracks(obj: Any, info: GraphQLResolveInfo, **kwargs) -> dict:
    kwargs = convert_keys_case(kwargs)
    return {
        "results": track.search(info.context.db, **kwargs),
        "total": track.count(info.context.db, **del_pagination_keys(kwargs)),
    }


@gql_track.field("inFavorites")
def resolve_in_favorites(obj: track.T, info: GraphQLResolveInfo) -> bool:
    return track.in_favorites(obj, info.context.user.id, info.context.db)
Ejemplo n.º 26
0
from src.graphql.query import query
from src.graphql.util import commit
from src.library import artist, collection, release, track
from src.util import convert_keys_case, del_pagination_keys

gql_release = ObjectType("Release")

gql_releases = ObjectType("Releases")


@query.field("release")
def resolve_release(_: Any, info: GraphQLResolveInfo, id: int) -> release.T:
    if rls := release.from_id(id, info.context.db):
        return rls

    raise NotFound(f"Release {id} not found.")


@query.field("releases")
def resolve_releases(_: Any, info: GraphQLResolveInfo, **kwargs) -> dict:
    kwargs = convert_keys_case(kwargs)
    return {
        "results": release.search(info.context.db, **kwargs),
        "total": release.count(info.context.db, **del_pagination_keys(kwargs)),
    }


@gql_release.field("inInbox")
def resolve_in_inbox(obj: release.T, info: GraphQLResolveInfo) -> bool:
    return release.in_inbox(obj, info.context.user.id, info.context.db)
Ejemplo n.º 27
0
@mutation.field("delPlaylistEntry")
@commit
def delete_playlist_entry(
    obj: Any,
    info: GraphQLResolveInfo,
    id: int,
) -> dict:
    if ety := pentry.from_id(id, info.context.db):
        pentry.delete(ety, info.context.db)
        return {
            "playlist": playlist.from_id(ety.playlist_id, info.context.db),
            "track": track.from_id(ety.track_id, info.context.db),
        }

    raise NotFound(f"Playlist entry {id} does not exist.")


@mutation.field("delPlaylistEntries")
@commit
def delete_playlist_entries(
    obj: Any,
    info: GraphQLResolveInfo,
    playlistId: int,
    trackId: int,
) -> dict:
    for ety in pentry.from_playlist_and_track(playlistId, trackId, info.context.db):
        pentry.delete(ety, info.context.db)

    ply = playlist.from_id(playlistId, info.context.db)
    if not ply: