Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Fredolx committed Dec 20, 2024
1 parent dde51bc commit 0843f98
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 30 deletions.
22 changes: 16 additions & 6 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ simplelog = "0.12.2"
log = "0.4.22"
rusqlite_migration = "1.3.1"
base64 = "0.22.1"
sanitize-filename = "0.6.0"
[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
shell-words = "1.1.0"
[target.'cfg(target_os = "windows")'.dependencies]
Expand Down
10 changes: 9 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::Error;
use tauri::AppHandle;
use types::{
Channel, CustomChannel, CustomChannelExtraData, Filters, Group, IdName, Settings, Source, EPG,
};
Expand Down Expand Up @@ -59,7 +60,8 @@ pub fn run() {
import,
channel_exists,
update_source,
get_epg
get_epg,
download
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down Expand Up @@ -272,3 +274,9 @@ async fn get_epg(channel: Channel) -> Result<Vec<EPG>, String> {
xtream::get_short_epg(channel).await.map_err(map_err_frontend)
}

#[tauri::command]
async fn download(app: AppHandle, channel: Channel) -> Result<(), String> {
utils::download(app, channel).await.map_err(map_err_frontend)
}


10 changes: 1 addition & 9 deletions src-tauri/src/mpv.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::settings::get_default_record_path;
use crate::types::ChannelHttpHeaders;
use crate::{log, sql};
use crate::{media_type, settings::get_settings, types::Channel};
use anyhow::{bail, Context, Result};
use chrono::Local;
use directories::UserDirs;
use std::sync::LazyLock;
use std::{
env::{consts::OS, current_exe},
Expand Down Expand Up @@ -184,11 +184,3 @@ fn get_file_name() -> String {
let formatted_time = current_time.format("%Y-%m-%d-%H-%M-%S").to_string();
format!("{formatted_time}.mp4")
}

fn get_default_record_path() -> Result<String> {
let user_dirs = UserDirs::new().context("Failed to get user dirs")?;
let mut path = user_dirs.video_dir().context("No videos dir")?.to_owned();
path.push("open-tv");
std::fs::create_dir_all(&path)?;
Ok(path.to_string_lossy().to_string())
}
11 changes: 10 additions & 1 deletion src-tauri/src/settings.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashMap;

use anyhow::Result;
use anyhow::{Context, Result};
use directories::UserDirs;

use crate::{sql, types::Settings};

Expand Down Expand Up @@ -50,3 +51,11 @@ pub fn update_settings(settings: Settings) -> Result<()> {
sql::update_settings(map)?;
Ok(())
}

pub fn get_default_record_path() -> Result<String> {
let user_dirs = UserDirs::new().context("Failed to get user dirs")?;
let mut path = user_dirs.video_dir().context("No videos dir")?.to_owned();
path.push("open-tv");
std::fs::create_dir_all(&path)?;
Ok(path.to_string_lossy().to_string())
}
45 changes: 43 additions & 2 deletions src-tauri/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use crate::{m3u, source_type, sql, types::Source, xtream};
use anyhow::{anyhow, Result};
use std::{io::Write, path::Path};

use crate::{m3u, settings::{get_default_record_path, get_settings}, source_type, sql, types::{Channel, Source}, xtream};
use anyhow::{anyhow, bail, Context, Result};
use reqwest::Client;
use sanitize_filename::sanitize;
use tauri::{AppHandle, Emitter};

pub async fn refresh_source(source: Source) -> Result<()> {
match source.source_type {
Expand All @@ -19,3 +24,39 @@ pub async fn refresh_all() -> Result<()> {
}
Ok(())
}

pub async fn download(app: AppHandle, channel: Channel) -> Result<()> {
let client = Client::new();
let mut response = client.get(channel.url.context("no url")?).send().await?;
let total_size = response.content_length().unwrap_or(0);
let mut downloaded = 0;
let mut file = std::fs::File::create(get_download_path(channel.name)?)?;
let mut send_threshold: u8 = 5;
if !response.status().is_success() {
let error = response.status();
bail!("Failed to download movie: HTTP {error}")
}
while let Some(chunk) = response.chunk().await? {
file.write(&chunk)?;
downloaded += chunk.len() as u64;
if total_size > 0 {
let progress: u8 = ((downloaded as f64 / total_size as f64) * 100.0) as u8;
if progress > send_threshold {
app.emit("progress", progress)?;
send_threshold = progress + 5;
}
}
}
Ok(())
}

fn get_download_path(file_name: String) -> Result<String> {
let settings = get_settings()?;
let path = match settings.recording_path {
Some(path) => path,
None => get_default_record_path()?
};
let mut path = Path::new(&path).to_path_buf();
path.push(sanitize(file_name));
Ok(path.to_string_lossy().to_string())
}
4 changes: 3 additions & 1 deletion src/app/channel-tile/channel-tile.component.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<div id="tile-{{id}}" placement="top" tabindex="0" (keyup.enter)="click()" [ngClass]="{'playing': starting}"
<div backgroundFill id="tile-{{id}}" placement="top" tabindex="0" (keyup.enter)="click()" [ngClass]="{'playing': starting}"
[ngbTooltip]="channel?.name" triggers="hover" (click)="click()" class="channel d-inline-flex p-2 align-items-center" (contextmenu)="onRightClick($event)">
<div style="height: 100%; min-width: 50px;">
<img (error)="showImage = false" class="channel-image" *ngIf="channel?.image && showImage" src="{{channel?.image}}">
</div>
<div class="channel-title">{{channel?.name}}</div>
{{progress}}
</div>

<div style="visibility: hidden; position: fixed;"
Expand All @@ -18,6 +19,7 @@
<ng-container *ngIf="!alreadyExistsInFav">Favorite</ng-container>
</button>
<button [hidden]="isMovie()" mat-menu-item (click)="record()">Record</button>
<button [disabled]="this.memory.Loading" [hidden]="!isMovie()" mat-menu-item (click)="download()">Download</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
50 changes: 41 additions & 9 deletions src/app/channel-tile/channel-tile.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input, ViewChild } from '@angular/core';
import { Component, Input, OnDestroy, ViewChild } from '@angular/core';
import { MatMenuTrigger } from '@angular/material/menu';
import { Channel } from '../models/channel';
import { MemoryService } from '../memory.service';
Expand All @@ -13,13 +13,25 @@ import { DeleteGroupModalComponent } from '../delete-group-modal/delete-group-mo
import { SourceType } from '../models/sourceType';
import { EpgModalComponent } from '../epg-modal/epg-modal.component';
import { EPG } from '../models/epg';
import { listen, UnlistenFn } from '@tauri-apps/api/event';
import { animate, state, style, transition, trigger } from '@angular/animations';

@Component({
selector: 'app-channel-tile',
templateUrl: './channel-tile.component.html',
styleUrl: './channel-tile.component.css'
styleUrl: './channel-tile.component.css',
animations: [
trigger('backgroundFill', [
state('0', style({ background: 'linear-gradient(to right, green 0%, white 0%)' })),
state(
'100',
style({ background: 'linear-gradient(to right, green 100%, white 0%)' })
),
transition('* => *', animate('1s ease')),
]),
],
})
export class ChannelTileComponent {
export class ChannelTileComponent implements OnDestroy {
constructor(public memory: MemoryService, private toastr: ToastrService, private error: ErrorService, private modal: NgbModal) { }
@Input() channel?: Channel;
@Input() id!: Number;
Expand All @@ -30,7 +42,8 @@ export class ChannelTileComponent {
// less reactive but prevents the user from seeing the change in action
alreadyExistsInFav = false;
mediaTypeEnum = MediaType;

progress = 0;
toUnlisten?: UnlistenFn;
ngOnInit(): void {
}

Expand Down Expand Up @@ -127,12 +140,12 @@ export class ChannelTileComponent {
this.toastr.info("No EPG data for this channel");
return;
}
this.memory.ModalRef = this.modal.open(EpgModalComponent, { backdrop: 'static', size: 'xl', keyboard: false});
this.memory.ModalRef = this.modal.open(EpgModalComponent, { backdrop: 'static', size: 'xl', keyboard: false });
this.memory.ModalRef.result.then(_ => this.memory.ModalRef = undefined);
this.memory.ModalRef.componentInstance.epg = data;
this.memory.ModalRef.componentInstance.name = this.channel?.name;
}
catch(e) {
catch (e) {
this.error.handleError(e);
}
}
Expand All @@ -146,7 +159,7 @@ export class ChannelTileComponent {
}

edit_group() {
this.memory.ModalRef = this.modal.open(EditGroupModalComponent, { backdrop: 'static', size: 'xl', keyboard: false});
this.memory.ModalRef = this.modal.open(EditGroupModalComponent, { backdrop: 'static', size: 'xl', keyboard: false });
this.memory.ModalRef.result.then(_ => this.memory.ModalRef = undefined);
this.memory.ModalRef.componentInstance.name = "EditCustomGroupModal";
this.memory.ModalRef.componentInstance.editing = true;
Expand All @@ -155,7 +168,7 @@ export class ChannelTileComponent {
}

edit_channel() {
this.memory.ModalRef = this.modal.open(EditChannelModalComponent, { backdrop: 'static', size: 'xl', keyboard: false});
this.memory.ModalRef = this.modal.open(EditChannelModalComponent, { backdrop: 'static', size: 'xl', keyboard: false });
this.memory.ModalRef.result.then(_ => this.memory.ModalRef = undefined);
this.memory.ModalRef.componentInstance.name = "EditCustomChannelModal";
this.memory.ModalRef.componentInstance.editing = true;
Expand Down Expand Up @@ -207,7 +220,7 @@ export class ChannelTileComponent {
}

openDeleteGroupModal() {
this.memory.ModalRef = this.modal.open(DeleteGroupModalComponent, { backdrop: 'static', size: 'xl', keyboard: false});
this.memory.ModalRef = this.modal.open(DeleteGroupModalComponent, { backdrop: 'static', size: 'xl', keyboard: false });
this.memory.ModalRef.result.then(_ => this.memory.ModalRef = undefined);
this.memory.ModalRef.componentInstance.name = "DeleteGroupModal";
this.memory.ModalRef.componentInstance.group = { ...this.channel };
Expand All @@ -218,4 +231,23 @@ export class ChannelTileComponent {
() => invoke('delete_custom_channel', { id: this.channel?.id }))
this.memory.Refresh.next(false);
}

async download() {
if (this.memory.Loading)
return;
if (this.toUnlisten)
this.toUnlisten();
this.toUnlisten = await listen<number>("progress", (event) => {
this.progress = event.payload;
});
await this.memory.tryIPC("Successfully download movie", "Failed to download movie",
async () => await invoke("download", { channel: this.channel }));
if (this.toUnlisten)
this.toUnlisten();
}

ngOnDestroy() {
if (this.toUnlisten)
this.toUnlisten();
}
}
9 changes: 8 additions & 1 deletion src/app/setup/setup.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,19 @@ export class SetupComponent {

@HostListener('document:keydown', ['$event'])
onKeyDown(event: KeyboardEvent) {
if ((event.key == "Escape" || event.key == "Backspace") && this.memory.AddingAdditionalSource) {
if ((event.key == "Escape" || event.key == "Backspace") && this.memory.AddingAdditionalSource && !this.isInputFocused()) {
this.goBack();
event.preventDefault();
}
}

isInputFocused(): boolean {
const activeElement = document.activeElement;
return activeElement instanceof HTMLInputElement ||
activeElement instanceof HTMLTextAreaElement ||
activeElement instanceof HTMLSelectElement;
}

ngOnInit(): void {
}

Expand Down

0 comments on commit 0843f98

Please sign in to comment.