Skip to content

Commit

Permalink
Merge pull request #5 from fxsth/develop
Browse files Browse the repository at this point in the history
Merge develop into master
  • Loading branch information
fxsth authored Jan 14, 2023
2 parents 4f25821 + 9ffcbf4 commit 3b7a0cd
Show file tree
Hide file tree
Showing 29 changed files with 475 additions and 249 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# pledo
Plex downloader
# pledo - Plex Downloader

This is a simple downloader for movies and tv shows of accessible plex media servers.
If you either want a copy of your friends media or your bandwidth cant just handle a stream - this can be your solution.
Deploy on your media server as container and access by web frontend.

Focused on proper functioning rather than good looking design. Features:
- .Net backend + React frontend
- .Net backend + React.js frontend
- Log in by plex.tv, no need for typing in password into this app
- Sync all media metadata of all accessible servers, backed by local db
- Browse all media, select and download directly
- docker support

Use `docker pull ghcr.io/fxsth/pledo:master` to get the docker image of current master version.

![Download screenshot](images/screenshot-downloads.png)
24 changes: 12 additions & 12 deletions Web/ClientApp/package-lock.json

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

9 changes: 5 additions & 4 deletions Web/ClientApp/src/AppRoutes.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Home } from "./components/Home";
import {Movies} from "./components/Movies";
import {Tasks} from "./components/Tasks";
import {SyncButton} from "./components/SyncButton";
import {TvShows} from "./components/TvShows";
import {Settings} from "./components/Settings";
import {Downloads} from "./components/Downloads";

const AppRoutes = [
{
Expand All @@ -11,9 +12,9 @@ const AppRoutes = [
},{
path: '/settings',
element: <Settings/>
},,{
path: '/tasks',
element: <Tasks />
},{
path: '/downloads',
element: <Downloads />
},{
path: '/movies',
element: <Movies/>
Expand Down
97 changes: 97 additions & 0 deletions Web/ClientApp/src/components/Downloads.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, {Component} from 'react';
import {Badge, Button, Progress, Table} from "reactstrap";

export class Downloads extends Component {
static displayName = Downloads.name;

constructor(props) {
super(props);
this.state = {downloads: [], loading: true};
}

componentDidMount() {
this.populateData();
this.timerID = setInterval(
() => this.populateData(),
2000
);
}

componentWillUnmount()
{
clearInterval(this.timerID);
}

renderDownloadTable(downloads) {
return (
<Table>
<thead>
<tr>
<th>Name</th>
<th>Started at</th>
<th>Status</th>
<th>Cancel</th>
</tr>
</thead>
<tbody>
{downloads.map(download =>
<tr key={download.id}>
<td>{download.name}</td>
<td>{download.started!= null ? (new Date(download.started)).toLocaleString() : ""}</td>
<td>
{download.started != null ?
download.progress < 1 ?
download.finished == null ?
(<Progress visible={true} value={download.progress * 100}>
{Math.round(download.progress * 100)}%
</Progress>
) : <Badge color="danger" pill>Cancelled</Badge> : (<Badge color="success" pill>Finished</Badge>) : (<Badge color="info" pill>Pending</Badge>)
}
</td>
<td>
{(download.progress == null || download.progress<1) && download.finished == null ?
<Button color="danger" outline size="sm" onClick={() => this.handleClick(download.mediaKey)}>Cancel</Button>
:
""}
</td>
</tr>
)}
</tbody>
</Table>
);
}

handleClick(key){
const settings = {
method: 'DELETE'
};
fetch('api/download/' + key, settings)
.then(response => {
if (response.status >= 200 && response.status < 300) {
console.log("Download cancelled.");
} else {
alert('Could not cancel download due to an unknown error.');
}
})
}

render() {
let contents = this.state.loading
? <p><em>Loading...</em></p>
: this.renderDownloadTable(this.state.downloads);

return (
<div>
<h1 id="tabelLabel">Downloads</h1>
<p>Your download history:</p>
{contents}
</div>
);
}

async populateData() {
const response = await fetch('api/download');
const data = await response.json();
this.setState({downloads: data, loading: false});
}
}
5 changes: 3 additions & 2 deletions Web/ClientApp/src/components/Movies.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {Component} from 'react';
import Dropdown from "./Dropdown";
import DownloadButton from "./DownloadButton";
import {Table, Button} from "reactstrap";

export class Movies extends Component {
static displayName = Movies.name;
Expand Down Expand Up @@ -106,7 +107,7 @@ export class Movies extends Component {

static renderMoviesTable(movies) {
return (
<table className='table table-striped' aria-labelledby="tabelLabel">
<Table striped>
<thead>
<tr>
<th>Title</th>
Expand All @@ -125,7 +126,7 @@ export class Movies extends Component {
</tr>
)}
</tbody>
</table>
</Table>
);
}

Expand Down
2 changes: 1 addition & 1 deletion Web/ClientApp/src/components/NavMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class NavMenu extends Component {
<NavLink tag={Link} className="text-dark" to="/tvshows">TV Shows</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/tasks">Tasks</NavLink>
<NavLink tag={Link} className="text-dark" to="/downloads">Downloads</NavLink>
</NavItem>
</ul>
</Collapse>
Expand Down
59 changes: 43 additions & 16 deletions Web/ClientApp/src/components/Overview.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import React, {Component} from 'react';
import {
Card,
CardBody,
CardImg,
CardSubtitle,
CardTitle,
Container,
Row
} from "reactstrap";
import {SyncButton} from "./SyncButton";

export class Overview extends Component {
static displayName = Overview.name;
Expand Down Expand Up @@ -28,14 +38,40 @@ export class Overview extends Component {
if (this.state.account) {
return (
<div>
<h1>Hello, {this.state.account ? this.state.account.username : "User"}!</h1>
<p>Welcome to pledo, the Plex Downloader</p>
<h2>Hello, {this.state.account ? this.state.account.username : "User"}!</h2>
<SyncButton/>
<p>You have access to following servers:</p>
<ul>
{this.state.servers ? this.state.servers.map(server =>
<li><strong>{server.sourceTitle == null ? server.name : server.sourceTitle}</strong> {server.name}</li>
) : null}
</ul>
<Container>
<Row>
{this.state.servers ? this.state.servers.map(server =>
<Card className="my-2"
color="secondary"
inverse
style={{
width: '10rem',
margin: '1rem'
}}
>
<CardImg
style={{
height: 20,
background:'#e5a00d'
}}
top
width="100%"
/>
<CardBody>
<CardTitle tag="h5">
{server.name}
</CardTitle>
<CardSubtitle>
{server.sourceTitle}
</CardSubtitle>
</CardBody>
</Card>
) : null}
</Row>
</Container>
</div>
);
} else {
Expand All @@ -45,9 +81,6 @@ export class Overview extends Component {
<p>Welcome to pledo, the Plex Downloader</p>
<p>To access movies and series, you have to log into your plex account.</p>
<div>
<a href={this.state.loginuri} target="_blank" rel="noopener noreferrer">
Login with plex
</a>
<hr/>
<button onClick={this.openInNewTab.bind(this)}>
Login with plex
Expand All @@ -64,12 +97,6 @@ export class Overview extends Component {
this.setState({account: data, loading: false});
}

async populateLoginData() {
const response = await fetch('api/account/loginuri');
const data = await response.text();
this.setState({loginuri: data, loading: false});
}

async populateServerData() {
const response = await fetch('api/server');
const data = await response.json();
Expand Down
Loading

0 comments on commit 3b7a0cd

Please sign in to comment.