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

Various updates #141

Merged
merged 11 commits into from
Sep 7, 2021
2 changes: 1 addition & 1 deletion src/frontend/src/app/media/media-t-v.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ <h3 class="media-title">{{ tmdbShow.name }}</h3>
<div class="form-group row">
<label class="col-lg-6 col-form-label">Quality Profile</label>
<div class="col-lg-6">
<select type="text" class="form-control form-control-sm" [formControl]="qualityProfileControl">
<select class="form-control form-control-sm" [formControl]="qualityProfileControl">
<option *ngFor="let profile of qualityProfiles()" [value]="profile">{{ profile }}</option>
</select>
</div>
Expand Down
24 changes: 15 additions & 9 deletions src/frontend/src/app/media/media-t-v.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ export class MediaTVComponent implements OnInit, OnDestroy {
this._changes = this.apiService.mediaUpdated$.subscribe(
() => {
this.ngZone.run(() => {
this._buildWatchEpisodesForm();
if (this.tmdbShow) {
this._buildWatchEpisodesForm();
}
});
}
);
Expand Down Expand Up @@ -187,7 +189,7 @@ export class MediaTVComponent implements OnInit, OnDestroy {
return this._getWatchShow();
}

public isWatchingAllSeasons() {
public isWatchingAllSeasons(): boolean {
for (const season of this.tmdbShow.seasons) {
if (!this.isWatchingSeason(season)) {
return false;
Expand All @@ -196,12 +198,16 @@ export class MediaTVComponent implements OnInit, OnDestroy {
return true;
}

public isWatchingSeason(season: any) {
public isWatchingSeasonRequest(season: any): boolean {
return Boolean(this._getWatchSeasonRequest(season.season_number));
}

public isWatchingSeason(season: any): boolean {
const watchSeasonRequest = this._getWatchSeasonRequest(season.season_number);
return Boolean(watchSeasonRequest) || this.isWatchingAllEpisodesInSeason(season);
}

public hasCollectedAllEpisodesInSeason(season: any) {
public hasCollectedAllEpisodesInSeason(season: any): boolean {
// watching entire season
if (this.hasCollectedSeason(season)) {
return true;
Expand All @@ -217,7 +223,7 @@ export class MediaTVComponent implements OnInit, OnDestroy {
return true;
}

public isWatchingAllEpisodesInSeason(season: any) {
public isWatchingAllEpisodesInSeason(season: any): boolean {
// watching all episodes in season
let watchingEpisodes = 0;
for (const episode of season.episodes) {
Expand All @@ -228,7 +234,7 @@ export class MediaTVComponent implements OnInit, OnDestroy {
return season.episodes.length === watchingEpisodes;
}

public isWatchingAnyEpisodeInSeason(season: any) {
public isWatchingAnyEpisodeInSeason(season: any): boolean {
for (const episode of season.episodes) {
if (this.isWatchingEpisode(episode.id)) {
return true;
Expand Down Expand Up @@ -283,7 +289,7 @@ export class MediaTVComponent implements OnInit, OnDestroy {
}
}

public isWatchingShow() {
public isWatchingShow(): boolean {
return Boolean(this._getWatchShow());
}

Expand Down Expand Up @@ -321,7 +327,7 @@ export class MediaTVComponent implements OnInit, OnDestroy {
return this.userIsStaff() || (watchEpisode && watchEpisode.requested_by === this.apiService.user.username);
}

public isWatchingEpisode(episodeId): Boolean {
public isWatchingEpisode(episodeId): boolean {
return Boolean(_.find(this.apiService.watchTVEpisodes, (watching) => {
return watching.tmdb_episode_id === episodeId;
}));
Expand Down Expand Up @@ -389,7 +395,7 @@ export class MediaTVComponent implements OnInit, OnDestroy {
// episode form control
const control = new FormControl({
value: this.isWatchingEpisode(episode.id) || this.isWatchingSeason(season),
disabled: this.isWatchingSeason(season) || (
disabled: this.isWatchingSeasonRequest(season) || (
this.isWatchingEpisode(episode.id) && !this.canUnWatchEpisode(episode.id)),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,9 @@
</tr>
</thead>
<tbody>
<tr *ngFor="let result of results">
<td>
<button type="button" class="btn btn-outline-success" *ngIf="result.torrent" placement="right" [ngbPopover]="result.torrent.name" popoverTitle="Torrent Name">
{{ result.watchMedia.name }}
</button>
<button *ngIf="!result.torrent" type="button" class="btn" [ngClass]="{'btn-outline-success': result.watchMedia.collected, 'btn-outline-secondary': !result.watchMedia.collected}">{{ result.watchMedia.name }}</button>
<tr *ngFor="let result of results; trackBy: resultTrackBy">
<td class="text-nowrap">
<button type="button" class="btn" [ngClass]="{'btn-outline-success': result.watchMedia.collected, 'btn-outline-secondary': !result.watchMedia.collected}" placement="right" [ngbPopover]="result.watchMedia.transmission_torrent_name || '(n/a)'" popoverTitle="Torrent Name">{{ result.watchMedia.name }}</button>
</td>
<td><span *ngIf="result.watchMedia.release_date">{{ result.watchMedia.release_date | date | date: 'shortDate' }}</span></td>
<td>{{ result.watchMedia.date_added | date | date: 'shortDate' }}</td>
Expand All @@ -49,7 +46,9 @@
<span *ngIf="result.torrent">{{ result.torrent.format_eta }}</span>
</td>
<td>
<span *ngIf="result.torrent"><button class="btn btn-warning btn-sm" (click)="blacklistRetry(result.watchMedia)"><span class="oi oi-ban"></span></button></span>
<button *ngIf="result.torrent" class="btn btn-warning btn-sm" (click)="blacklistRetry(result.watchMedia, true)" placement="top" ngbTooltip="Blacklist torrent and retry"><span class="oi oi-ban"></span></button>
<button *ngIf="!result.torrent && result.watchMedia.collected" class="btn btn-danger btn-sm" (click)="blacklistRetry(result.watchMedia)" placement="top" ngbTooltip="Retry downloading another torrent"><span class="oi oi-loop-circular"></span></button>
<button *ngIf="!result.torrent && !result.watchMedia.collected" class="btn btn-info btn-sm" (click)="blacklistRetry(result.watchMedia)" placement="top" ngbTooltip="Force retry now"><span class="oi oi-action-redo"></span></button>
</td>
</ng-container>
</tr>
Expand Down
23 changes: 19 additions & 4 deletions src/frontend/src/app/torrent-details/torrent-details.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as _ from 'lodash';

const POLL_TIME = 5000;


@Component({
selector: 'app-torrent-details',
templateUrl: './torrent-details.component.html',
Expand Down Expand Up @@ -63,7 +64,8 @@ export class TorrentDetailsComponent implements OnInit, OnDestroy {
);
}

public blacklistRetry(watchMedia) {
public blacklistRetry(watchMedia, blacklist?: boolean) {
const message = blacklist ? 'Blacklisting torrent and retrying' : 'Retrying download now';
this.isSaving = true;

let endpoint;
Expand All @@ -85,7 +87,7 @@ export class TorrentDetailsComponent implements OnInit, OnDestroy {
endpoint.subscribe(
(data) => {
this.isSaving = false;
this.toastr.success('Successfully blacklisted');
this.toastr.success(message);
},
(error) => {
console.error(error);
Expand All @@ -96,6 +98,10 @@ export class TorrentDetailsComponent implements OnInit, OnDestroy {

}

public resultTrackBy(index, result) {
return result.watchMedia.id;
}

protected _fetchTorrents(): Observable<any> {
this.isFetchingInitialTorrents = false;

Expand All @@ -105,6 +111,7 @@ export class TorrentDetailsComponent implements OnInit, OnDestroy {
};

// update media instances and build torrent params

// tv
if (this.mediaType === this.apiService.SEARCH_MEDIA_TYPE_TV) {
params.watch_tv_shows.push(this.watchMedia.id);
Expand All @@ -118,8 +125,16 @@ export class TorrentDetailsComponent implements OnInit, OnDestroy {
return this.apiService.fetchCurrentTorrents(params);
}

protected _fetchTorrentsSuccess(data) {
this.results = data;
protected _fetchTorrentsSuccess(data: any) {
this.results = data.sort((a: any, b: any) => {
if (a.watchMedia.name < b.watchMedia.name) {
return -1;
}
if (a.watchMedia.name > b.watchMedia.name) {
return 1;
}
return 0;
});
}

protected _fetchTorrentsFailure(error) {
Expand Down
36 changes: 22 additions & 14 deletions src/nefarious/api/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,41 @@ def _watch_media_task(self, watch_media_id: int):

@action(['post'], detail=True, url_path='blacklist-auto-retry')
def blacklist_auto_retry(self, request, pk):

watch_media = self.get_object()
nefarious_settings = NefariousSettings.get()

# add to blacklist
logger_foreground.info('Blacklisting {}'.format(watch_media.transmission_torrent_hash))
TorrentBlacklist.objects.get_or_create(
hash=watch_media.transmission_torrent_hash,
defaults=dict(
name=str(watch_media),
# if media still has a torrent reference
if watch_media.transmission_torrent_hash:

# add to blacklist
logger_foreground.info('Blacklisting {}'.format(watch_media.transmission_torrent_hash))
TorrentBlacklist.objects.get_or_create(
hash=watch_media.transmission_torrent_hash,
defaults=dict(
name=str(watch_media),
)
)
)

# remove torrent and delete data
logger_foreground.info('Removing blacklisted torrent hash: {}'.format(watch_media.transmission_torrent_hash))
transmission_client = get_transmission_client(nefarious_settings=nefarious_settings)
transmission_client.remove_torrent([watch_media.transmission_torrent_hash], delete_data=True)

# unset torrent details
watch_media.transmission_torrent_hash = None
watch_media.transmission_torrent_name = None

# unset previous details
del_transmission_torrent_hash = watch_media.transmission_torrent_hash
watch_media.transmission_torrent_hash = None
watch_media.collected = False
watch_media.collected_date = None
watch_media.download_path = None
watch_media.last_attempt_date = None
watch_media.save()

# re-queue search
self._watch_media_task(watch_media_id=watch_media.id)

# remove torrent and delete data
logger_foreground.info('Removing blacklisted torrent hash: {}'.format(del_transmission_torrent_hash))
transmission_client = get_transmission_client(nefarious_settings=nefarious_settings)
transmission_client.remove_torrent([del_transmission_torrent_hash], delete_data=True)

return Response(self.serializer_class(watch_media).data)


Expand Down
3 changes: 2 additions & 1 deletion src/nefarious/importer/movie.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def _handle_match(self, parser, tmdb_result, title, file_path):
download_path=file_path,
collected=True,
collected_date=timezone.utc.localize(timezone.datetime.utcnow()),
release_date=parse_date(tmdb_result.get('release_date', ''))
release_date=parse_date(tmdb_result.get('release_date', '')),
last_attempt_date=timezone.utc.localize(timezone.datetime.utcnow()),
),
)
return watch_movie
3 changes: 2 additions & 1 deletion src/nefarious/importer/tv.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ def _handle_match(self, parser, tmdb_result, title, file_path):
download_path=file_path,
collected=True,
collected_date=timezone.utc.localize(timezone.datetime.utcnow()),
release_date=parse_date(episode_data.get('air_date', ''))
release_date=parse_date(episode_data.get('air_date', '')),
last_attempt_date=timezone.utc.localize(timezone.datetime.utcnow()),
),
)
return watch_episode
28 changes: 28 additions & 0 deletions src/nefarious/migrations/0066_auto_20210904_2035.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 3.0.2 on 2021-09-04 20:35

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('nefarious', '0065_auto_20210829_2248'),
]

operations = [
migrations.AddField(
model_name='watchmovie',
name='transmission_torrent_name',
field=models.CharField(blank=True, max_length=1000, null=True),
),
migrations.AddField(
model_name='watchtvepisode',
name='transmission_torrent_name',
field=models.CharField(blank=True, max_length=1000, null=True),
),
migrations.AddField(
model_name='watchtvseason',
name='transmission_torrent_name',
field=models.CharField(blank=True, max_length=1000, null=True),
),
]
27 changes: 27 additions & 0 deletions src/nefarious/migrations/0067_populate_last_attempt_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 3.0.2 on 2021-09-05 12:30
from django.db import migrations
from django.utils import timezone


def populate_last_attempt_date(apps, schema_editor):

WatchMovie = apps.get_model('nefarious', 'WatchMovie')
WatchTVEpisode = apps.get_model('nefarious', 'WatchTVEpisode')
WatchTVSeason = apps.get_model('nefarious', 'WatchTVSeason')

# populate missing last_attempt_date for everything already collected
for model in [WatchMovie, WatchTVSeason, WatchTVEpisode]:
for media in model.objects.filter(collected=True, last_attempt_date__isnull=True):
media.last_attempt_date = timezone.utc.localize(timezone.datetime.utcnow())
media.save()


class Migration(migrations.Migration):

dependencies = [
('nefarious', '0066_auto_20210904_2035'),
]

operations = [
migrations.RunPython(populate_last_attempt_date, reverse_code=lambda a, b: None),
]
1 change: 1 addition & 0 deletions src/nefarious/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class WatchMediaBase(models.Model):
download_path = models.CharField(max_length=1000, blank=True, null=True, unique=True)
last_attempt_date = models.DateTimeField(blank=True, null=True)
transmission_torrent_hash = models.CharField(max_length=100, null=True, blank=True)
transmission_torrent_name = models.CharField(max_length=1000, null=True, blank=True)
release_date = models.DateField(null=True, blank=True)

def abs_download_path(self):
Expand Down
23 changes: 16 additions & 7 deletions src/nefarious/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from django.conf import settings
from datetime import datetime
from django.utils import dateparse
from transmissionrpc import Torrent

from nefarious.models import WatchMovie, NefariousSettings, TorrentBlacklist, WatchTVEpisode, WatchTVSeason
from nefarious.parsers.movie import MovieParser
from nefarious.parsers.tv import TVParser
Expand Down Expand Up @@ -35,11 +37,6 @@ def fetch(self):
valid_search_results = []
search = self._get_search_results()

# TODO - last attempt should only populate _after_ an actual attempt
# save this attempt date
self.watch_media.last_attempt_date = datetime.utcnow()
self.watch_media.save()

if search.ok:

for result in search.results:
Expand All @@ -66,7 +63,7 @@ def fetch(self):
# add to transmission
torrent = transmission_client.add_torrent(
best_result['torrent_url'],
paused=True, # start paused to we can verify if the torrent has been blacklisted
paused=True, # start paused so we can verify if the torrent has been blacklisted
download_dir=self._get_download_dir(transmission_session),
)

Expand All @@ -82,6 +79,10 @@ def fetch(self):
# start the torrent
if not settings.DEBUG:
torrent.start()

# set attempt date
self._set_last_attempt_date()

return True
else:
# remove the blacklisted/paused torrent and continue to the next result
Expand All @@ -101,6 +102,9 @@ def fetch(self):

logger_background.info('Unable to find any results for media {}'.format(self.watch_media))

# set attempt date
self._set_last_attempt_date()

return False

def is_match(self, title: str) -> bool:
Expand All @@ -117,6 +121,10 @@ def is_match(self, title: str) -> bool:
)
)

def _set_last_attempt_date(self):
self.watch_media.last_attempt_date = datetime.utcnow()
self.watch_media.save()

def _sanitize_title(self, title: str):
# conditionally remove possessive apostrophes
if self._reprocess_without_possessive_apostrophes:
Expand Down Expand Up @@ -150,7 +158,8 @@ def _get_tmdb_title_key(self):
def _get_media_type(self) -> str:
raise NotImplementedError

def _save_torrent_details(self, torrent):
def _save_torrent_details(self, torrent: Torrent):
self.watch_media.transmission_torrent_name = torrent.name
self.watch_media.transmission_torrent_hash = torrent.hashString
self.watch_media.save()

Expand Down