Skip to content
Open
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
17 changes: 15 additions & 2 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ pub fn run() {
hide_channel,
hide_group,
remove_from_history,
get_all_expiries
get_all_expiries,
get_season_episodes,
get_series_episodes
])
.setup(|app| {
app.manage(Mutex::new(AppState {
Expand Down Expand Up @@ -439,12 +441,23 @@ async fn download(
channel: Channel,
download_id: String,
path: Option<String>,
directory: Option<String>,
) -> Result<(), String> {
utils::download(state.clone(), app, channel, &download_id, path)
utils::download(state.clone(), app, channel, &download_id, path, directory)
.await
.map_err(map_err_frontend)
}

#[tauri::command(async)]
fn get_season_episodes(season_id: i64) -> Result<Vec<Channel>, String> {
sql::get_season_episodes(season_id).map_err(map_err_frontend)
}

#[tauri::command(async)]
fn get_series_episodes(series_id: i64, source_id: i64) -> Result<Vec<Channel>, String> {
sql::get_series_episodes(series_id, source_id).map_err(map_err_frontend)
}

#[tauri::command]
async fn abort_download(
state: State<'_, Mutex<AppState>>,
Expand Down
41 changes: 41 additions & 0 deletions src-tauri/src/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1827,6 +1827,47 @@ pub fn find_all_episodes_after(channel: &Channel) -> Result<Vec<String>> {
.collect())
}

pub fn get_season_episodes(season_id: i64) -> Result<Vec<Channel>> {
let sql = get_conn()?;
Ok(sql
.prepare(
r#"
SELECT * FROM channels
WHERE season_id = ?
AND media_type = ?
AND url IS NOT NULL
AND hidden = 0
ORDER BY episode_num, name
"#,
)?
.query_map(params![season_id, media_type::MOVIE], row_to_channel)?
.filter_map(Result::ok)
.collect())
}

pub fn get_series_episodes(series_id: i64, source_id: i64) -> Result<Vec<Channel>> {
let sql = get_conn()?;
Ok(sql
.prepare(
r#"
SELECT channels.* FROM channels
LEFT JOIN seasons ON seasons.id = channels.season_id
WHERE channels.series_id = ?
AND channels.source_id = ?
AND channels.media_type = ?
AND channels.url IS NOT NULL
AND channels.hidden = 0
ORDER BY seasons.season_number, channels.episode_num, channels.name
"#,
)?
.query_map(
params![series_id, source_id, media_type::MOVIE],
row_to_channel,
)?
.filter_map(Result::ok)
.collect())
}

pub fn update_source_last_updated(source_id: i64) -> Result<()> {
let sql = get_conn()?;
sql.execute(
Expand Down
14 changes: 13 additions & 1 deletion src-tauri/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub async fn download(
channel: Channel,
download_id: &str,
path: Option<String>,
directory: Option<String>,
) -> Result<()> {
let source_id = channel.source_id.context("no source id provided")?;
let source = sql::get_source_from_id(source_id)
Expand Down Expand Up @@ -119,7 +120,18 @@ pub async fn download(
let mut downloaded = 0;
let path = match path {
Some(p) => p,
None => get_download_path(get_filename(name, url)?)?,
None => {
let filename = get_filename(name, url)?;
// Bulk downloads pick a directory once instead of a path per episode
match directory {
Some(dir) => {
let mut dir = Path::new(&dir).to_path_buf();
dir.push(filename);
dir.to_string_lossy().to_string()
}
None => get_download_path(filename)?,
}
}
};
let mut file = tokio::fs::File::create(&path).await?;
let mut send_threshold: f64 = 0.1;
Expand Down
9 changes: 8 additions & 1 deletion src/app/channel-tile/channel-tile.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
<ng-container *ngIf="alreadyExistsInFav">Unfavorite</ng-container>
<ng-container *ngIf="!alreadyExistsInFav">Favorite</ng-container>
</button>
<button [hidden]="isCustom() || viewMode == viewModeEnum.History" mat-menu-item (click)="hide()"> <ng-container
<button [hidden]="isCustom() || isSeason() || viewMode == viewModeEnum.History" mat-menu-item (click)="hide()">
<ng-container
*ngIf="alreadyHidden">Unhide</ng-container>
<ng-container *ngIf="!alreadyHidden">Hide</ng-container>
</button>
Expand All @@ -51,6 +52,12 @@
<button [hidden]="!isMovie() || !downloading" mat-menu-item (click)="cancelDownload()">
Cancel download
</button>
<button [hidden]="!isSeason()" mat-menu-item (click)="downloadSeason()">
Download season
</button>
<button [hidden]="!isSeries()" mat-menu-item (click)="downloadSeries()">
Download all episodes
</button>
<button [hidden]="!showEPG()" mat-menu-item (click)="showEPGModal()">EPG</button>
<button [hidden]="!isCustom()" mat-menu-item (click)="edit()">Edit</button>
<button [hidden]="!isCustom()" mat-menu-item (click)="share()">Share</button>
Expand Down
66 changes: 64 additions & 2 deletions src/app/channel-tile/channel-tile.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { RestreamModalComponent } from "../restream-modal/restream-modal.compone
import { DownloadService } from "../download.service";
import { Download } from "../models/download";
import { Subscription, take } from "rxjs";
import { save } from "@tauri-apps/plugin-dialog";
import { open, save } from "@tauri-apps/plugin-dialog";
import { CHANNEL_EXTENSION, GROUP_EXTENSION, RECORD_EXTENSION } from "../models/extensions";
import { getDateFormatted, getExtension, sanitizeFileName } from "../utils";
import { NodeType, fromMediaType } from "../models/nodeType";
Expand Down Expand Up @@ -139,7 +139,6 @@ export class ChannelTileComponent implements OnDestroy, AfterViewInit {
}

onRightClick(event: MouseEvent) {
if (this.channel?.media_type == MediaType.season) return;
this.alreadyExistsInFav = this.channel!.favorite!;
this.alreadyHidden = this.channel!.hidden!;
this.downloading = this.isDownloading();
Expand Down Expand Up @@ -414,6 +413,69 @@ export class ChannelTileComponent implements OnDestroy, AfterViewInit {
await this.download.abortDownload(this.channel!.id!.toString());
}

isSeason() {
return this.channel?.media_type == MediaType.season;
}

isSeries() {
return this.channel?.media_type == MediaType.serie;
}

async downloadSeason() {
await this.bulkDownload("episodes", () =>
invoke<Channel[]>("get_season_episodes", { seasonId: this.channel!.id }),
);
}

async downloadSeries() {
if (!this.memory.SeriesRefreshed.has(this.channel!.id!)) {
try {
await invoke("get_episodes", { channel: this.channel });
this.memory.SeriesRefreshed.set(this.channel!.id!, true);
} catch (e) {
this.error.handleError(e, "Failed to fetch series");
return;
}
}
await this.bulkDownload("episodes", () =>
invoke<Channel[]>("get_series_episodes", {
seriesId: parseInt(this.channel!.url!),
sourceId: this.channel!.source_id,
}),
);
}

private async bulkDownload(entityName: string, getChannels: () => Promise<Channel[]>) {
let channels: Channel[];
try {
channels = await getChannels();
} catch (e) {
this.error.handleError(e, `Failed to fetch ${entityName}`);
return;
}
if (channels.length == 0) {
this.toastr.info(`No ${entityName} to download`);
return;
}
let directory = undefined;
if (this.memory.IsContainer || this.memory.AlwaysAskSave) {
directory = await open({
directory: true,
canCreateDirectories: true,
title: `Select where to download ${entityName}`,
});
if (!directory) {
return;
}
}
let queued = await this.download.addBulkDownloads(channels, directory ?? undefined);
if (queued == 0) {
this.toastr.info(`All ${entityName} are already downloading`);
return;
}
this.toastr.success(`Queued ${queued} ${queued == 1 ? "episode" : entityName}`);
}

getExistingDownload() {
let download = this.download.Downloads.get(this.channel!.id!.toString());
if (download) {
Expand Down
10 changes: 10 additions & 0 deletions src/app/download-manager/download-manager.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ button.btn-outline-light:hover {
background-color: #2b2d30;
}

.download-list {
max-height: 40vh;
overflow-y: auto;
}

.queued {
color: #aaa;
font-style: italic;
}

.stop {
width: 1.5rem;
height: 1.5rem;
Expand Down
14 changes: 9 additions & 5 deletions src/app/download-manager/download-manager.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="position-fixed bottom-0 end-0 m-3 shadow-sm rounded download-box" [class.collapsed]="isMinimized">
<div class="d-flex justify-content-between align-items-center px-3 py-2 download-header">
<strong class="text-light">Downloads</strong>
<strong class="text-light">Downloads ({{ getDownloads().length }})</strong>
<button class="btn btn-sm btn-outline-light" (click)="toggleMinimize()">
<span *ngIf="isMinimized">
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
Expand All @@ -18,16 +18,20 @@
</button>
</div>

<div *ngIf="!isMinimized" class="p-3 d-grid gap-3">
<div *ngIf="!isMinimized" class="p-3 d-grid gap-3 download-list">
<div *ngFor="let download of getDownloads()">
<div class="d-flex justify-content-between text-light small mb-1">
<span>{{ download.channel.name }}</span>
<span>{{ download.progress }}%</span>
<span *ngIf="isQueued(download)" class="queued">Queued</span>
<span *ngIf="!isQueued(download)">{{ download.progress }}%</span>
</div>
<div class="d-flex align-items-center">
<div class="progress" style="height: 6px; width: 90%">
<div class="progress-bar bg-success" role="progressbar" [style.width.%]="download.progress"
[attr.aria-valuenow]="download.progress" aria-valuemin="0" aria-valuemax="100"></div>
<div *ngIf="isQueued(download)" class="progress-bar progress-bar-striped bg-secondary" role="progressbar"
style="width: 100%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
<div *ngIf="!isQueued(download)" class="progress-bar bg-success" role="progressbar"
[style.width.%]="download.progress" [attr.aria-valuenow]="download.progress" aria-valuemin="0"
aria-valuemax="100"></div>
</div>
<svg fill="red" (click)="cancelDownload(download.id)" class="stop ms-3" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
Expand Down
4 changes: 4 additions & 0 deletions src/app/download-manager/download-manager.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export class DownloadManagerComponent implements OnInit {
return Array.from(this.downloadService.Downloads.values());
}

isQueued(download: Download) {
return this.downloadService.isQueued(download);
}

toggleMinimize() {
this.isMinimized = !this.isMinimized;
}
Expand Down
Loading