Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/display downloaded file infos #146

Merged
merged 4 commits into from
Aug 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Added

- display downloaded torrent informations in movie details card (/~https://github.com/iam4x/bobarr/pull/146)
- choose the origanize file strategy between symlink, move or copy (/~https://github.com/iam4x/bobarr/issues/130)
- upload own .torrent or paste magnet link (/~https://github.com/iam4x/bobarr/issues/123)
- env variable to change movies/tvshows folder name (/~https://github.com/iam4x/bobarr/issues/116)
Expand All @@ -12,6 +13,7 @@

### Fixes

- sort movies and tvshows by recently added
- handle multiple files with same extensions, like a sample of the movie downloaded
- cache requests to tmdb api (perf)
- fix discover download tvshow fails (/~https://github.com/iam4x/bobarr/issues/104)
Expand Down
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"prebuild": "rimraf dist",
"build": "nest build",
"start": "nest start",
"dev": "yarn && yarn ts-node-dev --respawn --transpileOnly -r tsconfig-paths/register --ignore-watch node_modules src/main.ts",
"dev": "yarn && yarn ts-node-dev --interval 1000 --respawn --transpileOnly -r tsconfig-paths/register --ignore-watch node_modules src/main.ts",
"start:debug": "nest start --debug --watch",
"start:prod": "ENV=production node dist/main"
},
Expand Down
8 changes: 8 additions & 0 deletions packages/api/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ type LibraryCalendar {
tvEpisodes: [EnrichedTVEpisode!]!
}

type LibraryFileDetails {
id: Float!
libraryPath: String!
torrentFileName: String
libraryFileSize: BigInt
}

type Movie {
id: Float!
tmdbId: Float!
Expand Down Expand Up @@ -200,6 +207,7 @@ type Query {
getMissingMovies: [EnrichedMovie!]!
getTVSeasonDetails(seasonNumber: Int!, tvShowTMDBId: Int!): [EnrichedTVEpisode!]!
getCalendar: LibraryCalendar!
getMovieFileDetails(tmdbId: Int!): LibraryFileDetails!
omdbSearch(title: String!): OMDBInfo!
}

Expand Down
9 changes: 9 additions & 0 deletions packages/api/src/modules/library/library.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ObjectType, Field, InputType } from '@nestjs/graphql';
import BigInt from 'graphql-bigint';

import { Movie } from 'src/entities/movie.entity';
import { TVShow } from 'src/entities/tvshow.entity';
Expand Down Expand Up @@ -64,3 +65,11 @@ export class LibraryCalendar {
@Field((_type) => [EnrichedTVEpisode])
public tvEpisodes!: EnrichedTVEpisode[];
}

@ObjectType()
export class LibraryFileDetails {
@Field() public id!: number;
@Field() public libraryPath!: string;
@Field({ nullable: true }) public torrentFileName?: string;
@Field((_type) => BigInt, { nullable: true }) public libraryFileSize?: number;
}
9 changes: 9 additions & 0 deletions packages/api/src/modules/library/library.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import {
JackettInput,
SearchingMedia,
LibraryCalendar,
LibraryFileDetails,
} from './library.dto';

import { makeCacheInterceptor } from '../redis/cache.interceptor';

@Resolver()
Expand Down Expand Up @@ -78,6 +80,13 @@ export class LibraryResolver {
return this.libraryService.calendar();
}

@Query((_returns) => LibraryFileDetails)
public getMovieFileDetails(
@Args('tmdbId', { type: () => Int }) tmdbId: number
) {
return this.libraryService.getMovieFileDetails(tmdbId);
}

@Mutation((_returns) => GraphQLCommonResponse)
public async downloadMovie(
@Args('movieId', { type: () => Int }) movieId: number,
Expand Down
26 changes: 25 additions & 1 deletion packages/api/src/modules/library/library.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export class LibraryService {
private readonly jobsService: JobsService,
private readonly transmissionService: TransmissionService,
private readonly mediaViewDAO: MediaViewDAO,
private readonly paramsService: ParamsService
private readonly paramsService: ParamsService,
private readonly torrentDAO: TorrentDAO
) {
this.logger = logger.child({ context: 'LibraryService' });

Expand Down Expand Up @@ -159,6 +160,29 @@ export class LibraryService {
return { movies, tvEpisodes };
}

public async getMovieFileDetails(tmdbId: number) {
const movie = await this.movieDAO
.findOneOrFail({ where: { tmdbId } })
.then(this.enrichMovie);

const torrentEntity = await this.torrentDAO.findOne({
where: { resourceType: FileType.MOVIE, resourceId: movie.id },
});

const transmissionTorrent = torrentEntity
? await this.transmissionService.getTorrent(torrentEntity.torrentHash)
: null;

const year = dayjs(movie.releaseDate).format('YYYY');

return {
id: tmdbId,
libraryPath: `library/movies/${movie.title} (${year})`,
libraryFileSize: transmissionTorrent?.totalSize,
torrentFileName: transmissionTorrent?.name,
};
}

@LazyTransaction()
public async removeMovie(
{ tmdbId, softDelete = false }: { tmdbId: number; softDelete?: boolean },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { useRemoveLibrary } from './use-remove-library.hook';

import { MovieDetailsStyles } from './movie-details.styles';
import { RatingDetailComponent } from './rating-details.component';
import { MovieFileDetailsComponent } from './movie-file-details.component';

interface MovieDetailsProps {
movie: TmdbSearchResult | EnrichedMovie;
Expand Down Expand Up @@ -131,6 +132,9 @@ export function MovieDetailsComponent(props: MovieDetailsProps) {
</div>
)}
</div>
{inLibrary && (
<MovieFileDetailsComponent tmdbId={props.movie.tmdbId} />
)}
</div>
</div>
</div>
Expand Down
20 changes: 20 additions & 0 deletions packages/web/components/movie-details/movie-details.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,24 @@ export const MovieDetailsStyles = styled.div`
margin-right: 12px;
}
}

.file-details {
margin-top: 12px;

li {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 570px;
}

strong {
font-weight: bold;
}

em {
margin-left: 8px;
font-family: monospace;
}
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import prettySize from 'prettysize';

import { useGetMovieFileDetailsQuery } from '../../utils/graphql';

export function MovieFileDetailsComponent({ tmdbId }: { tmdbId: number }) {
const { data } = useGetMovieFileDetailsQuery({ variables: { tmdbId } });
return (
<ul className="file-details">
<li>
<strong>Library path:</strong>
<em>{data?.details?.libraryPath}</em>
</li>
{data?.details?.torrentFileName && (
<>
<li>
<strong>Torrent name:</strong>
<em>{data?.details?.torrentFileName}</em>
</li>
<li>
<strong>Torrent size:</strong>
<em>{prettySize(data?.details?.libraryFileSize)}</em>
</li>
</>
)}
</ul>
);
}
4 changes: 2 additions & 2 deletions packages/web/components/sortable/sortable.component.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { Button, Input, Empty } from 'antd';
import { orderBy } from 'lodash';
import { orderBy, last } from 'lodash';

import {
SortDescendingOutlined,
Expand All @@ -21,7 +21,7 @@ export function useSortable<TEntity>(props: UseSortableProps<TEntity>) {
const [results, setResults] = useState(rows || []);
const [searchQuery, setSearchQuery] = useState('');
const [orderByAttribute, setOrderByAttribute] = useState(
`${sortAttributes[0].key}:asc`
`${last(sortAttributes)!.key}:desc`
);

const [key, order] = orderByAttribute.split(':') as [string, 'desc' | 'asc'];
Expand Down
8 changes: 8 additions & 0 deletions packages/web/queries/get-movie-file-details.query.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
query getMovieFileDetails($tmdbId: Int!) {
details: getMovieFileDetails(tmdbId: $tmdbId) {
id
libraryPath
libraryFileSize
torrentFileName
}
}
63 changes: 63 additions & 0 deletions packages/web/utils/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@ export type LibraryCalendar = {
tvEpisodes: Array<EnrichedTvEpisode>;
};

export type LibraryFileDetails = {
__typename?: 'LibraryFileDetails';
id: Scalars['Float'];
libraryPath: Scalars['String'];
torrentFileName?: Maybe<Scalars['String']>;
libraryFileSize?: Maybe<Scalars['BigInt']>;
};

export type Movie = {
__typename?: 'Movie';
id: Scalars['Float'];
Expand Down Expand Up @@ -277,6 +285,7 @@ export type Query = {
getMissingMovies: Array<EnrichedMovie>;
getTVSeasonDetails: Array<EnrichedTvEpisode>;
getCalendar: LibraryCalendar;
getMovieFileDetails: LibraryFileDetails;
omdbSearch: OmdbInfo;
};

Expand Down Expand Up @@ -322,6 +331,11 @@ export type QueryGetTvSeasonDetailsArgs = {
};


export type QueryGetMovieFileDetailsArgs = {
tmdbId: Scalars['Int'];
};


export type QueryOmdbSearchArgs = {
title: Scalars['String'];
};
Expand Down Expand Up @@ -772,6 +786,19 @@ export type GetMissingQuery = (
)> }
);

export type GetMovieFileDetailsQueryVariables = {
tmdbId: Scalars['Int'];
};


export type GetMovieFileDetailsQuery = (
{ __typename?: 'Query' }
& { details: (
{ __typename?: 'LibraryFileDetails' }
& Pick<LibraryFileDetails, 'id' | 'libraryPath' | 'libraryFileSize' | 'torrentFileName'>
) }
);

export type GetParamsQueryVariables = {};


Expand Down Expand Up @@ -1770,6 +1797,42 @@ export function useGetMissingLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryH
export type GetMissingQueryHookResult = ReturnType<typeof useGetMissingQuery>;
export type GetMissingLazyQueryHookResult = ReturnType<typeof useGetMissingLazyQuery>;
export type GetMissingQueryResult = ApolloReactCommon.QueryResult<GetMissingQuery, GetMissingQueryVariables>;
export const GetMovieFileDetailsDocument = gql`
query getMovieFileDetails($tmdbId: Int!) {
details: getMovieFileDetails(tmdbId: $tmdbId) {
id
libraryPath
libraryFileSize
torrentFileName
}
}
`;

/**
* __useGetMovieFileDetailsQuery__
*
* To run a query within a React component, call `useGetMovieFileDetailsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetMovieFileDetailsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetMovieFileDetailsQuery({
* variables: {
* tmdbId: // value for 'tmdbId'
* },
* });
*/
export function useGetMovieFileDetailsQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<GetMovieFileDetailsQuery, GetMovieFileDetailsQueryVariables>) {
return ApolloReactHooks.useQuery<GetMovieFileDetailsQuery, GetMovieFileDetailsQueryVariables>(GetMovieFileDetailsDocument, baseOptions);
}
export function useGetMovieFileDetailsLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<GetMovieFileDetailsQuery, GetMovieFileDetailsQueryVariables>) {
return ApolloReactHooks.useLazyQuery<GetMovieFileDetailsQuery, GetMovieFileDetailsQueryVariables>(GetMovieFileDetailsDocument, baseOptions);
}
export type GetMovieFileDetailsQueryHookResult = ReturnType<typeof useGetMovieFileDetailsQuery>;
export type GetMovieFileDetailsLazyQueryHookResult = ReturnType<typeof useGetMovieFileDetailsLazyQuery>;
export type GetMovieFileDetailsQueryResult = ApolloReactCommon.QueryResult<GetMovieFileDetailsQuery, GetMovieFileDetailsQueryVariables>;
export const GetParamsDocument = gql`
query getParams {
params: getParams {
Expand Down