mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2025-09-08 15:08:44 +00:00
fixed bugs in torrent client
This commit is contained in:
@@ -1,403 +1,415 @@
|
|||||||
/* global WebTorrent, angular, moment, prompt */
|
/* global WebTorrent, angular, moment */
|
||||||
|
|
||||||
const VERSION = '1.1'
|
const VERSION = "1.1";
|
||||||
const trackers = ['wss://tracker.btorrent.xyz', 'wss://tracker.openwebtorrent.com']
|
|
||||||
const rtcConfig = {
|
// A smaller set of WebSocket trackers for reliability.
|
||||||
'iceServers': [
|
const trackers = [
|
||||||
{
|
"wss://tracker.btorrent.xyz",
|
||||||
'urls': ['stun:stun.l.google.com:19305', 'stun:stun1.l.google.com:19305']
|
"wss://tracker.openwebtorrent.com",
|
||||||
}
|
];
|
||||||
]
|
|
||||||
|
// Basic torrent options.
|
||||||
|
const torrentOpts = { announce: trackers };
|
||||||
|
const trackerOpts = { announce: trackers };
|
||||||
|
|
||||||
|
// Simple debug logger.
|
||||||
|
function dbg(msg) {
|
||||||
|
console.log("[DEBUG]", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
const torrentOpts = {
|
// Create a WebTorrent client.
|
||||||
announce: trackers
|
const client = new WebTorrent({ tracker: trackerOpts });
|
||||||
}
|
|
||||||
|
|
||||||
const trackerOpts = {
|
// Angular app definition.
|
||||||
announce: trackers,
|
const app = angular.module("BTorrent", [
|
||||||
rtcConfig: rtcConfig
|
"ngRoute",
|
||||||
}
|
"ui.grid",
|
||||||
|
"ui.grid.resizeColumns",
|
||||||
|
"ui.grid.selection",
|
||||||
|
"ngFileUpload",
|
||||||
|
"ngNotify",
|
||||||
|
]);
|
||||||
|
|
||||||
const debug = window.localStorage.getItem('debug') !== null
|
/**
|
||||||
|
* Optional: inline CSS for row lines in the grid.
|
||||||
function dbg (message, item, color = '#333333') {
|
*/
|
||||||
if (debug) {
|
const styleEl = document.createElement("style");
|
||||||
if (item && item.name) {
|
styleEl.textContent = `
|
||||||
console.debug(
|
.ui-grid-row {
|
||||||
`%cβTorrent:${item.infoHash ? 'torrent ' : 'torrent ' + item._torrent.name + ':file '}${item.name}${item.infoHash ? ' (' + item.infoHash + ')' : ''} %c${message}`,
|
border-bottom: 1px solid #ccc;
|
||||||
'color: #33C3F0',
|
|
||||||
`color: ${color}`
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
console.debug(`%cβTorrent:client %c${message}`, 'color: #33C3F0', `color: ${color}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
`;
|
||||||
|
document.head.appendChild(styleEl);
|
||||||
|
|
||||||
function er (err, item) {
|
// Configure Angular routes.
|
||||||
dbg(err, item, '#FF0000')
|
app.config([
|
||||||
}
|
"$compileProvider",
|
||||||
|
"$locationProvider",
|
||||||
dbg(`Starting v${VERSION}. WebTorrent ${WebTorrent.VERSION}`)
|
"$routeProvider",
|
||||||
|
|
||||||
// Create WebTorrent client
|
|
||||||
const client = new WebTorrent({ tracker: trackerOpts })
|
|
||||||
|
|
||||||
// Angular app
|
|
||||||
const app = angular.module('BTorrent', [
|
|
||||||
'ngRoute',
|
|
||||||
'ui.grid',
|
|
||||||
'ui.grid.resizeColumns',
|
|
||||||
'ui.grid.selection',
|
|
||||||
'ngFileUpload',
|
|
||||||
'ngNotify'
|
|
||||||
], [
|
|
||||||
'$compileProvider',
|
|
||||||
'$locationProvider',
|
|
||||||
'$routeProvider',
|
|
||||||
function ($compileProvider, $locationProvider, $routeProvider) {
|
function ($compileProvider, $locationProvider, $routeProvider) {
|
||||||
// Allow magnet: and blob: links
|
// Allow magnet, blob, etc. in Angular URLs.
|
||||||
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|magnet|blob|javascript):/)
|
$compileProvider.aHrefSanitizationWhitelist(
|
||||||
|
/^\s*(https?|magnet|blob|javascript):/
|
||||||
|
);
|
||||||
|
$locationProvider.html5Mode(false).hashPrefix("");
|
||||||
|
|
||||||
// Disable HTML5 mode, only use # routing so no rewrites are needed
|
// Define basic routes.
|
||||||
$locationProvider.html5Mode(false).hashPrefix('')
|
|
||||||
|
|
||||||
// Define routes
|
|
||||||
$routeProvider
|
$routeProvider
|
||||||
.when('/view', {
|
.when("/view", {
|
||||||
templateUrl: 'views/view.html',
|
templateUrl: "views/view.html",
|
||||||
controller: 'ViewCtrl'
|
controller: "ViewCtrl",
|
||||||
})
|
})
|
||||||
.when('/download', {
|
.when("/download", {
|
||||||
templateUrl: 'views/download.html',
|
templateUrl: "views/download.html",
|
||||||
controller: 'DownloadCtrl'
|
controller: "DownloadCtrl",
|
||||||
})
|
})
|
||||||
.otherwise({
|
.otherwise({
|
||||||
templateUrl: 'views/full.html',
|
templateUrl: "views/full.html",
|
||||||
controller: 'FullCtrl'
|
controller: "FullCtrl",
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
])
|
]);
|
||||||
|
|
||||||
app.controller('BTorrentCtrl', [
|
// Warn user before they unload if torrents are still active.
|
||||||
'$scope',
|
app.run([
|
||||||
'$rootScope',
|
"$rootScope",
|
||||||
'$http',
|
function ($rootScope) {
|
||||||
'$log',
|
window.addEventListener("beforeunload", (e) => {
|
||||||
'$location',
|
if ($rootScope.client && $rootScope.client.torrents.length > 0) {
|
||||||
'ngNotify',
|
e.preventDefault();
|
||||||
function ($scope, $rootScope, $http, $log, $location, ngNotify) {
|
e.returnValue =
|
||||||
$rootScope.version = VERSION
|
"Transfers are in progress. Are you sure you want to leave or refresh?";
|
||||||
$rootScope.webtorrentVersion = WebTorrent.VERSION
|
return e.returnValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
ngNotify.config({
|
// Main BTorrent controller.
|
||||||
duration: 5000,
|
app.controller("BTorrentCtrl", [
|
||||||
html: true
|
"$scope",
|
||||||
})
|
"$rootScope",
|
||||||
|
"$http",
|
||||||
|
"$log",
|
||||||
|
"ngNotify",
|
||||||
|
function ($scope, $rootScope, $http, $log, ngNotify) {
|
||||||
|
dbg("Starting app.js version " + VERSION);
|
||||||
|
|
||||||
if (!WebTorrent.WEBRTC_SUPPORT) {
|
if (!WebTorrent.WEBRTC_SUPPORT) {
|
||||||
$rootScope.disabled = true
|
ngNotify.set("Please use a browser with WebRTC support.", "error");
|
||||||
ngNotify.set('Please use a WebRTC compatible browser', {
|
|
||||||
type: 'error',
|
|
||||||
sticky: true,
|
|
||||||
button: false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$rootScope.client = client
|
$rootScope.client = client;
|
||||||
|
$rootScope.selectedTorrent = null;
|
||||||
|
$rootScope.processing = false;
|
||||||
|
|
||||||
function updateAll () {
|
// Global error handler.
|
||||||
if (!$rootScope.client.processing) {
|
client.on("error", (err) => {
|
||||||
$rootScope.$applyAsync()
|
dbg("Torrent client error: " + err);
|
||||||
}
|
ngNotify.set(err.message || err, "error");
|
||||||
}
|
$rootScope.processing = false;
|
||||||
|
});
|
||||||
setInterval(updateAll, 500)
|
|
||||||
|
|
||||||
$rootScope.seedFiles = function (files) {
|
|
||||||
if (files && files.length > 0) {
|
|
||||||
dbg(`Seeding ${files.length} file(s)`)
|
|
||||||
$rootScope.client.processing = true
|
|
||||||
$rootScope.client.seed(files, torrentOpts, $rootScope.onSeed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$rootScope.openTorrentFile = function (file) {
|
|
||||||
if (file) {
|
|
||||||
dbg(`Adding torrent file ${file.name}`)
|
|
||||||
$rootScope.client.processing = true
|
|
||||||
$rootScope.client.add(file, torrentOpts, $rootScope.onTorrent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$rootScope.client.on('error', function (err, torrent) {
|
|
||||||
$rootScope.client.processing = false
|
|
||||||
ngNotify.set(err, 'error')
|
|
||||||
er(err, torrent)
|
|
||||||
})
|
|
||||||
|
|
||||||
$rootScope.addMagnet = function (magnet, onTorrent) {
|
|
||||||
if (magnet && magnet.length > 0) {
|
|
||||||
dbg(`Adding magnet/hash ${magnet}`)
|
|
||||||
$rootScope.client.processing = true
|
|
||||||
$rootScope.client.add(magnet, torrentOpts, onTorrent || $rootScope.onTorrent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$rootScope.destroyedTorrent = function (err) {
|
|
||||||
if (err) throw err
|
|
||||||
dbg('Destroyed torrent', $rootScope.selectedTorrent)
|
|
||||||
$rootScope.selectedTorrent = null
|
|
||||||
$rootScope.client.processing = false
|
|
||||||
}
|
|
||||||
|
|
||||||
$rootScope.changePriority = function (file) {
|
|
||||||
if (file.priority === '-1') {
|
|
||||||
dbg('Deselected', file)
|
|
||||||
file.deselect()
|
|
||||||
} else {
|
|
||||||
dbg(`Selected with priority ${file.priority}`, file)
|
|
||||||
file.select(file.priority)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever a new torrent is added or we seed files.
|
||||||
|
*/
|
||||||
$rootScope.onTorrent = function (torrent, isSeed) {
|
$rootScope.onTorrent = function (torrent, isSeed) {
|
||||||
dbg(torrent.magnetURI)
|
dbg("Torrent added: " + torrent.magnetURI);
|
||||||
torrent.safeTorrentFileURL = torrent.torrentFileBlobURL
|
$rootScope.processing = false;
|
||||||
torrent.fileName = `${torrent.name}.torrent`
|
|
||||||
if (!isSeed) {
|
if (!isSeed) {
|
||||||
dbg('Received metadata', torrent)
|
ngNotify.set(`Received ${torrent.name} metadata`);
|
||||||
ngNotify.set(`Received ${torrent.name} metadata`)
|
|
||||||
if (!$rootScope.selectedTorrent) {
|
|
||||||
$rootScope.selectedTorrent = torrent
|
|
||||||
}
|
|
||||||
$rootScope.client.processing = false
|
|
||||||
}
|
}
|
||||||
torrent.files.forEach(function (file) {
|
if (!$rootScope.selectedTorrent) {
|
||||||
file.getBlobURL(function (err, url) {
|
$rootScope.selectedTorrent = torrent;
|
||||||
if (err) throw err
|
}
|
||||||
file.url = url
|
|
||||||
if (isSeed) {
|
// Generate file.blobURL for direct downloading in the file table.
|
||||||
dbg('Started seeding', torrent)
|
torrent.files.forEach((file) => {
|
||||||
if (!$rootScope.selectedTorrent) {
|
file.getBlobURL((err, url) => {
|
||||||
$rootScope.selectedTorrent = torrent
|
if (!err) {
|
||||||
}
|
file.url = url;
|
||||||
$rootScope.client.processing = false
|
|
||||||
} else {
|
|
||||||
dbg('Done ', file)
|
|
||||||
ngNotify.set(`<b>${file.name}</b> ready for download`, 'success')
|
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
torrent.on('done', function () {
|
};
|
||||||
if (!isSeed) {
|
|
||||||
dbg('Done', torrent)
|
/**
|
||||||
ngNotify.set(`<b>${torrent.name}</b> has finished downloading`, 'success')
|
* Add a magnet link or .torrent URL.
|
||||||
|
*/
|
||||||
|
$rootScope.addMagnet = function (magnet) {
|
||||||
|
if (!magnet) return;
|
||||||
|
$rootScope.processing = true;
|
||||||
|
dbg("Adding magnet: " + magnet);
|
||||||
|
client.add(magnet, torrentOpts, (torrent) => {
|
||||||
|
$rootScope.onTorrent(torrent, false);
|
||||||
|
$scope.$applyAsync();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed local files.
|
||||||
|
*/
|
||||||
|
$rootScope.seedFiles = function (files) {
|
||||||
|
if (!files || !files.length) return;
|
||||||
|
$rootScope.processing = true;
|
||||||
|
dbg(`Seeding ${files.length} file(s)`);
|
||||||
|
client.seed(files, torrentOpts, (torrent) => {
|
||||||
|
$rootScope.onTorrent(torrent, true);
|
||||||
|
$scope.$applyAsync();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove/destroy a selected torrent.
|
||||||
|
*/
|
||||||
|
$rootScope.destroyedTorrent = function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("Failed to destroy torrent:", err);
|
||||||
|
}
|
||||||
|
dbg("Destroyed torrent", $rootScope.selectedTorrent);
|
||||||
|
$rootScope.selectedTorrent = null;
|
||||||
|
$rootScope.processing = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the priority of an individual file (high, low, or don't download).
|
||||||
|
*/
|
||||||
|
$rootScope.changePriority = function (file) {
|
||||||
|
if (file.priority === "-1") {
|
||||||
|
file.deselect();
|
||||||
|
dbg("Deselected file", file);
|
||||||
|
} else {
|
||||||
|
file.select(file.priority);
|
||||||
|
dbg(`Selected with priority ${file.priority}`, file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download all files in the selected torrent if it's 100% done.
|
||||||
|
* Creates a blob for each file, triggers a native download with <a>.
|
||||||
|
*/
|
||||||
|
$rootScope.downloadAll = function (torrent) {
|
||||||
|
if (!torrent) return;
|
||||||
|
if (torrent.progress < 1) {
|
||||||
|
alert("Torrent is not finished downloading yet.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
torrent.files.forEach((file) => {
|
||||||
|
file.getBlob((err, blob) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Failed to get blob for file:", file.name, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Create an anchor to trigger the download.
|
||||||
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.style.display = "none";
|
||||||
|
a.href = blobUrl;
|
||||||
|
a.download = file.name;
|
||||||
|
// Append, click, remove, revoke.
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy the magnet URI of a torrent to the clipboard.
|
||||||
|
*/
|
||||||
|
$rootScope.copyMagnetURI = function (torrent) {
|
||||||
|
if (!torrent || !torrent.magnetURI) return;
|
||||||
|
const magnetURI = torrent.magnetURI;
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(magnetURI)
|
||||||
|
.then(() =>
|
||||||
|
ngNotify.set("Magnet URI copied to clipboard!", "success")
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Clipboard error:", err);
|
||||||
|
ngNotify.set("Failed to copy magnet URI", "error");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const textarea = document.createElement("textarea");
|
||||||
|
textarea.value = magnetURI;
|
||||||
|
textarea.style.position = "fixed";
|
||||||
|
textarea.style.left = "-9999px";
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
ngNotify.set("Magnet URI copied (fallback)!", "success");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Clipboard fallback error:", err);
|
||||||
|
ngNotify.set("Failed to copy magnet URI", "error");
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
torrent.on('wire', function (wire, addr) {
|
};
|
||||||
dbg(`Wire ${addr}`, torrent)
|
|
||||||
})
|
|
||||||
torrent.on('error', er)
|
|
||||||
}
|
|
||||||
|
|
||||||
$rootScope.onSeed = function (torrent) {
|
/**
|
||||||
$rootScope.onTorrent(torrent, true)
|
* Save the .torrent file itself (via torrent.torrentFileBlobURL).
|
||||||
}
|
*/
|
||||||
|
$rootScope.saveTorrentFile = function (torrent) {
|
||||||
|
if (!torrent || !torrent.torrentFileBlobURL) return;
|
||||||
|
const fileName = torrent.fileName || `${torrent.name}.torrent`;
|
||||||
|
// Create a hidden <a> to force download of the .torrent file.
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.style.display = "none";
|
||||||
|
a.href = torrent.torrentFileBlobURL;
|
||||||
|
a.download = fileName;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
dbg('Angular ready')
|
// FullCtrl: sets up the ui-grid and magnet input for /full route.
|
||||||
}
|
app.controller("FullCtrl", [
|
||||||
])
|
"$scope",
|
||||||
|
"$rootScope",
|
||||||
// Full View Controller
|
"$location",
|
||||||
app.controller('FullCtrl', [
|
"ngNotify",
|
||||||
'$scope',
|
function ($scope, $rootScope, $location, ngNotify) {
|
||||||
'$rootScope',
|
// Handle magnet input
|
||||||
'$http',
|
|
||||||
'$log',
|
|
||||||
'$location',
|
|
||||||
'ngNotify',
|
|
||||||
function ($scope, $rootScope, $http, $log, $location, ngNotify) {
|
|
||||||
ngNotify.config({
|
|
||||||
duration: 5000,
|
|
||||||
html: true
|
|
||||||
})
|
|
||||||
$scope.addMagnet = function () {
|
$scope.addMagnet = function () {
|
||||||
$rootScope.addMagnet($scope.torrentInput)
|
$rootScope.addMagnet($scope.torrentInput);
|
||||||
$scope.torrentInput = ''
|
$scope.torrentInput = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we have a #magnet in the URL, add it automatically
|
||||||
|
if ($location.hash()) {
|
||||||
|
$rootScope.addMagnet($location.hash());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define columns for ui-grid.
|
||||||
$scope.columns = [
|
$scope.columns = [
|
||||||
{ field: 'name', cellTooltip: true, minWidth: 200 },
|
{ field: "name", displayName: "Name", minWidth: 200 },
|
||||||
{ field: 'length', name: 'Size', cellFilter: 'pbytes', width: 80 },
|
{
|
||||||
{ field: 'received', displayName: 'Downloaded', cellFilter: 'pbytes', width: 135 },
|
field: "progress",
|
||||||
{ field: 'downloadSpeed', displayName: '↓ Speed', cellFilter: 'pbytes:1', width: 100 },
|
displayName: "Progress",
|
||||||
{ field: 'progress', displayName: 'Progress', cellFilter: 'progress', width: 100 },
|
cellFilter: "progress",
|
||||||
{ field: 'timeRemaining', displayName: 'ETA', cellFilter: 'humanTime', width: 140 },
|
width: 100,
|
||||||
{ field: 'uploaded', displayName: 'Uploaded', cellFilter: 'pbytes', width: 125 },
|
},
|
||||||
{ field: 'uploadSpeed', displayName: '↑ Speed', cellFilter: 'pbytes:1', width: 100 },
|
{
|
||||||
{ field: 'numPeers', displayName: 'Peers', width: 80 },
|
field: "downloadSpeed",
|
||||||
{ field: 'ratio', cellFilter: 'number:2', width: 80 }
|
displayName: "↓ Speed",
|
||||||
]
|
cellFilter: "pbytes:1",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "uploadSpeed",
|
||||||
|
displayName: "↑ Speed",
|
||||||
|
cellFilter: "pbytes:1",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{ field: "numPeers", displayName: "Peers", width: 80 },
|
||||||
|
{
|
||||||
|
field: "ratio",
|
||||||
|
displayName: "Ratio",
|
||||||
|
cellFilter: "number:2",
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Create gridOptions and update each second.
|
||||||
$scope.gridOptions = {
|
$scope.gridOptions = {
|
||||||
columnDefs: $scope.columns,
|
columnDefs: $scope.columns,
|
||||||
data: $rootScope.client.torrents,
|
|
||||||
enableColumnResizing: true,
|
enableColumnResizing: true,
|
||||||
enableColumnMenus: false,
|
enableColumnMenus: false,
|
||||||
enableRowSelection: true,
|
enableRowSelection: true,
|
||||||
enableRowHeaderSelection: false,
|
enableRowHeaderSelection: false,
|
||||||
multiSelect: false
|
multiSelect: false,
|
||||||
}
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
$scope.gridOptions.data =
|
||||||
|
($rootScope.client && $rootScope.client.torrents) || [];
|
||||||
|
$scope.$applyAsync();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// On row selection, set the selectedTorrent
|
||||||
$scope.gridOptions.onRegisterApi = function (gridApi) {
|
$scope.gridOptions.onRegisterApi = function (gridApi) {
|
||||||
$scope.gridApi = gridApi
|
$scope.gridApi = gridApi;
|
||||||
gridApi.selection.on.rowSelectionChanged($scope, function (row) {
|
gridApi.selection.on.rowSelectionChanged($scope, function (row) {
|
||||||
if (!row.isSelected && $rootScope.selectedTorrent && $rootScope.selectedTorrent.infoHash === row.entity.infoHash) {
|
if (row.isSelected) {
|
||||||
$rootScope.selectedTorrent = null
|
$rootScope.selectedTorrent = row.entity;
|
||||||
} else {
|
} else {
|
||||||
$rootScope.selectedTorrent = row.entity
|
$rootScope.selectedTorrent = null;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
},
|
||||||
// If there's a magnet in the URL (ex: torrent.html#/magnet-link)
|
]);
|
||||||
if ($location.hash() !== '') {
|
|
||||||
$rootScope.client.processing = true
|
|
||||||
setTimeout(function () {
|
|
||||||
dbg(`Adding ${$location.hash()}`)
|
|
||||||
$rootScope.addMagnet($location.hash())
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
// Download View Controller
|
|
||||||
app.controller('DownloadCtrl', [
|
|
||||||
'$scope',
|
|
||||||
'$rootScope',
|
|
||||||
'$http',
|
|
||||||
'$log',
|
|
||||||
'$location',
|
|
||||||
'ngNotify',
|
|
||||||
function ($scope, $rootScope, $http, $log, $location, ngNotify) {
|
|
||||||
ngNotify.config({
|
|
||||||
duration: 5000,
|
|
||||||
html: true
|
|
||||||
})
|
|
||||||
|
|
||||||
|
// DownloadCtrl / ViewCtrl are minimal in this example.
|
||||||
|
app.controller("DownloadCtrl", [
|
||||||
|
"$scope",
|
||||||
|
"$rootScope",
|
||||||
|
"$location",
|
||||||
|
function ($scope, $rootScope, $location) {
|
||||||
$scope.addMagnet = function () {
|
$scope.addMagnet = function () {
|
||||||
$rootScope.addMagnet($scope.torrentInput)
|
$rootScope.addMagnet($scope.torrentInput);
|
||||||
$scope.torrentInput = ''
|
$scope.torrentInput = "";
|
||||||
}
|
};
|
||||||
|
if ($location.hash()) {
|
||||||
if ($location.hash() !== '') {
|
$rootScope.addMagnet($location.hash());
|
||||||
$rootScope.client.processing = true
|
|
||||||
setTimeout(function () {
|
|
||||||
dbg(`Adding ${$location.hash()}`)
|
|
||||||
$rootScope.addMagnet($location.hash())
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
// Stream/View Controller
|
|
||||||
app.controller('ViewCtrl', [
|
|
||||||
'$scope',
|
|
||||||
'$rootScope',
|
|
||||||
'$http',
|
|
||||||
'$log',
|
|
||||||
'$location',
|
|
||||||
'ngNotify',
|
|
||||||
function ($scope, $rootScope, $http, $log, $location, ngNotify) {
|
|
||||||
ngNotify.config({
|
|
||||||
duration: 2000,
|
|
||||||
html: true
|
|
||||||
})
|
|
||||||
|
|
||||||
function onTorrent (torrent) {
|
|
||||||
// Adjust viewer styling
|
|
||||||
$rootScope.viewerStyle = {
|
|
||||||
'margin-top': '-20px',
|
|
||||||
'text-align': 'center'
|
|
||||||
}
|
|
||||||
dbg(torrent.magnetURI)
|
|
||||||
torrent.safeTorrentFileURL = torrent.torrentFileBlobURL
|
|
||||||
torrent.fileName = `${torrent.name}.torrent`
|
|
||||||
$rootScope.selectedTorrent = torrent
|
|
||||||
$rootScope.client.processing = false
|
|
||||||
dbg('Received metadata', torrent)
|
|
||||||
ngNotify.set(`Received ${torrent.name} metadata`)
|
|
||||||
|
|
||||||
// Append each file to #viewer
|
|
||||||
torrent.files.forEach(function (file) {
|
|
||||||
file.appendTo('#viewer')
|
|
||||||
file.getBlobURL(function (err, url) {
|
|
||||||
if (err) throw err
|
|
||||||
file.url = url
|
|
||||||
dbg('Done ', file)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
torrent.on('done', function () {
|
|
||||||
dbg('Done', torrent)
|
|
||||||
})
|
|
||||||
torrent.on('wire', function (wire, addr) {
|
|
||||||
dbg(`Wire ${addr}`, torrent)
|
|
||||||
})
|
|
||||||
torrent.on('error', er)
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
app.controller("ViewCtrl", [
|
||||||
|
"$scope",
|
||||||
|
"$rootScope",
|
||||||
|
"$location",
|
||||||
|
function ($scope, $rootScope, $location) {
|
||||||
$scope.addMagnet = function () {
|
$scope.addMagnet = function () {
|
||||||
$rootScope.addMagnet($scope.torrentInput, onTorrent)
|
$rootScope.addMagnet($scope.torrentInput);
|
||||||
$scope.torrentInput = ''
|
$scope.torrentInput = "";
|
||||||
|
};
|
||||||
|
if ($location.hash()) {
|
||||||
|
$rootScope.addMagnet($location.hash());
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
// If there's a magnet in the URL
|
// Angular filters for size, progress, etc.
|
||||||
if ($location.hash() !== '') {
|
app.filter("pbytes", function () {
|
||||||
$rootScope.client.processing = true
|
return function (num, speed) {
|
||||||
setTimeout(function () {
|
if (isNaN(num)) return "";
|
||||||
dbg(`Adding ${$location.hash()}`)
|
if (num < 1) return speed ? "" : "0 B";
|
||||||
$rootScope.addMagnet($location.hash(), onTorrent)
|
const units = ["B", "kB", "MB", "GB", "TB"];
|
||||||
}, 0)
|
const exponent = Math.min(Math.floor(Math.log(num) / Math.log(1000)), 4);
|
||||||
}
|
const val = (num / Math.pow(1000, exponent)).toFixed(1) * 1;
|
||||||
}
|
return `${val} ${units[exponent]}${speed ? "/s" : ""}`;
|
||||||
])
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Custom Angular filters
|
app.filter("progress", function () {
|
||||||
app.filter('html', [
|
return function (val) {
|
||||||
'$sce',
|
if (typeof val !== "number") return "";
|
||||||
|
return (val * 100).toFixed(1) + "%";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
app.filter("html", [
|
||||||
|
"$sce",
|
||||||
function ($sce) {
|
function ($sce) {
|
||||||
return function (input) {
|
return function (input) {
|
||||||
return $sce.trustAsHtml(input)
|
return $sce.trustAsHtml(input);
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
])
|
]);
|
||||||
|
|
||||||
app.filter('pbytes', function () {
|
app.filter("humanTime", function () {
|
||||||
return function (num, speed) {
|
|
||||||
if (isNaN(num)) return ''
|
|
||||||
if (num < 1) return speed ? '' : '0 B'
|
|
||||||
|
|
||||||
const units = ['B', 'kB', 'MB', 'GB', 'TB']
|
|
||||||
const exponent = Math.min(Math.floor(Math.log(num) / 6.907755278982137), 8)
|
|
||||||
const val = (num / Math.pow(1000, exponent)).toFixed(1) * 1
|
|
||||||
const unit = units[exponent]
|
|
||||||
return `${val} ${unit}${speed ? '/s' : ''}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.filter('humanTime', function () {
|
|
||||||
return function (millis) {
|
return function (millis) {
|
||||||
if (millis < 1000) return ''
|
if (millis < 1000) return "";
|
||||||
const remaining = moment.duration(millis).humanize()
|
const duration = moment.duration(millis).humanize();
|
||||||
return remaining.charAt(0).toUpperCase() + remaining.slice(1)
|
return duration.charAt(0).toUpperCase() + duration.slice(1);
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
app.filter('progress', function () {
|
|
||||||
return function (num) {
|
|
||||||
return `${(100 * num).toFixed(1)}%`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
@@ -40,10 +40,11 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bitvid-logo {
|
.bitvid-logo {
|
||||||
height: 4rem;
|
height: 6rem;
|
||||||
width: auto;
|
width: auto;
|
||||||
/* Adjust spacing around the logo if needed */
|
/* Adjust spacing around the logo if needed */
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
|
margin-top: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer styling */
|
/* Footer styling */
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
<!-- Scripts: webtorrent, angular, etc. -->
|
<!-- Scripts: webtorrent, angular, etc. -->
|
||||||
<script src="bundle.min.js"></script>
|
<script src="bundle.min.js"></script>
|
||||||
|
<!--<script src="https://cdn.jsdelivr.net/combine/npm/webtorrent/webtorrent.min.js,npm/moment@2,npm/angular@1.5/angular.min.js,npm/angular-route@1.5/angular-route.min.js,npm/angular-sanitize@1.5/angular-sanitize.min.js,npm/angular-ui-grid@3/ui-grid.min.js,gh/matowens/ng-notify/dist/ng-notify.min.js,npm/ng-file-upload@12.2.13/dist/ng-file-upload.min.js"></script>-->
|
||||||
|
|
||||||
<!-- External styles -->
|
<!-- External styles -->
|
||||||
<link
|
<link
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
|
<!-- MAIN CONTENT AREA -->
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<!-- Input row for adding or opening torrents -->
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="four columns">
|
<div class="four columns">
|
||||||
<input
|
<input
|
||||||
@@ -11,7 +13,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="two columns download-button">
|
<div class="two columns download-button">
|
||||||
<!-- Download button in red -->
|
<!-- "Download" button for adding a magnet/torrent -->
|
||||||
<button
|
<button
|
||||||
ng-click="addMagnet()"
|
ng-click="addMagnet()"
|
||||||
ng-disabled="!torrentInput.length || $root.disabled"
|
ng-disabled="!torrentInput.length || $root.disabled"
|
||||||
@@ -22,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="three columns">
|
<div class="three columns">
|
||||||
<!-- Open torrent file button in red -->
|
<!-- "Open torrent file" button -->
|
||||||
<button
|
<button
|
||||||
type="file"
|
type="file"
|
||||||
ngf-select="$root.openTorrentFile($file)"
|
ngf-select="$root.openTorrentFile($file)"
|
||||||
@@ -34,11 +36,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="three columns u-pull-right">
|
<div class="three columns u-pull-right">
|
||||||
<!-- Seed files button in red -->
|
<!-- "Seed files" button -->
|
||||||
<button
|
<button
|
||||||
class="button button-danger u-pull-right"
|
class="button button-danger u-pull-right"
|
||||||
ngf-select="$root.seedFiles($files)"
|
ngf-select="$root.seedFiles($files)"
|
||||||
multiple=""
|
multiple="true"
|
||||||
ng-disabled="$root.disabled"
|
ng-disabled="$root.disabled"
|
||||||
>
|
>
|
||||||
<i class="fa fa-upload"></i> Seed files
|
<i class="fa fa-upload"></i> Seed files
|
||||||
@@ -46,6 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ui-grid for active torrents (Name, Progress, Speeds, Peers, Ratio) -->
|
||||||
<div
|
<div
|
||||||
class="row grid"
|
class="row grid"
|
||||||
ui-grid="gridOptions"
|
ui-grid="gridOptions"
|
||||||
@@ -53,52 +56,65 @@
|
|||||||
ui-grid-selection="ui-grid-selection"
|
ui-grid-selection="ui-grid-selection"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
<!-- Selected torrent details: Pause/Resume, Download, Remove -->
|
||||||
<div class="row" ng-if="selectedTorrent">
|
<div class="row" ng-if="selectedTorrent">
|
||||||
<div class="six columns" style="overflow: auto">
|
<div class="six columns" style="overflow: auto">
|
||||||
<h5>
|
<h5>
|
||||||
{{$root.selectedTorrent.name}}
|
{{ selectedTorrent.name }}
|
||||||
<!-- Pause/Resume -->
|
|
||||||
|
<!-- Pause/Resume buttons -->
|
||||||
<button
|
<button
|
||||||
ng-if="!$root.selectedTorrent.paused"
|
ng-if="!selectedTorrent.paused"
|
||||||
ng-click="$root.selectedTorrent.pause()"
|
ng-click="selectedTorrent.pause()"
|
||||||
>
|
>
|
||||||
<i class="fa fa-pause"></i> Pause
|
<i class="fa fa-pause"></i> Pause
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
ng-if="$root.selectedTorrent.paused"
|
ng-if="selectedTorrent.paused"
|
||||||
ng-click="$root.selectedTorrent.resume()"
|
ng-click="selectedTorrent.resume()"
|
||||||
>
|
>
|
||||||
<i class="fa fa-play"></i> Resume
|
<i class="fa fa-play"></i> Resume
|
||||||
</button>
|
</button>
|
||||||
<!-- Remove torrent button in red -->
|
|
||||||
|
<!-- Download ALL files (only if progress == 1) -->
|
||||||
<button
|
<button
|
||||||
class="button button-danger"
|
class="button button-danger"
|
||||||
ng-click="$root.selectedTorrent.destroy($root.destroyedTorrent)"
|
ng-click="$root.downloadAll(selectedTorrent)"
|
||||||
|
ng-disabled="selectedTorrent.progress < 1"
|
||||||
|
>
|
||||||
|
<i class="fa fa-download"></i> Download
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Remove button -->
|
||||||
|
<button
|
||||||
|
class="button button-danger"
|
||||||
|
ng-click="selectedTorrent.destroy($root.destroyedTorrent)"
|
||||||
>
|
>
|
||||||
<i class="fa fa-times"></i> Remove
|
<i class="fa fa-times"></i> Remove
|
||||||
</button>
|
</button>
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<h6>Share</h6>
|
<h6>Share</h6>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a ng-href="#{{$root.selectedTorrent.infoHash}}" target="_blank"
|
<!-- Copy magnet button -->
|
||||||
>bitvid</a
|
<button
|
||||||
|
class="button button-small"
|
||||||
|
ng-click="$root.copyMagnetURI(selectedTorrent)"
|
||||||
>
|
>
|
||||||
|
Copy Magnet
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a ng-href="{{$root.selectedTorrent.magnetURI}}" target="_blank"
|
<!-- Download .torrent file button -->
|
||||||
>Magnet URI</a
|
<button
|
||||||
|
class="button button-small"
|
||||||
|
ng-click="$root.saveTorrentFile(selectedTorrent)"
|
||||||
>
|
>
|
||||||
|
Download .torrent
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li><strong>Hash:</strong> {{ selectedTorrent.infoHash }}</li>
|
||||||
<a
|
|
||||||
ng-href="{{$root.selectedTorrent.safeTorrentFileURL}}"
|
|
||||||
target="_self"
|
|
||||||
download="{{$root.selectedTorrent.fileName}}"
|
|
||||||
>.torrent</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li><strong>Hash: </strong>{{$root.selectedTorrent.infoHash}}</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -113,24 +129,20 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="files" ng-repeat="file in $root.selectedTorrent.files">
|
<tr class="files" ng-repeat="file in selectedTorrent.files">
|
||||||
<!-- If file not finished, just show the name -->
|
<!-- If file isn't finished, just show the name. -->
|
||||||
<td ng-hide="file.done">{{file.name}}</td>
|
<td ng-hide="file.done">{{ file.name }}</td>
|
||||||
<!-- If file is done, show link to download -->
|
<!-- If file is done, show direct link to download it. -->
|
||||||
<td ng-show="file.done">
|
<td ng-show="file.done">
|
||||||
<a
|
<a ng-href="{{ file.url }}" download="{{ file.name }}">
|
||||||
ng-href="{{file.url}}"
|
{{ file.name }}
|
||||||
download="{{file.name}}"
|
</a>
|
||||||
target="_self"
|
|
||||||
ng-show="file.done"
|
|
||||||
>{{file.name}}</a
|
|
||||||
>
|
|
||||||
</td>
|
</td>
|
||||||
<td>{{file.length | pbytes}}</td>
|
<td>{{ file.length | pbytes }}</td>
|
||||||
<td>
|
<td>
|
||||||
<select
|
<select
|
||||||
class="no-margin"
|
class="no-margin"
|
||||||
name="{{file.name}}Priority"
|
name="{{ file.name }}Priority"
|
||||||
ng-model="file.priority"
|
ng-model="file.priority"
|
||||||
ng-init="file.priority = '0'"
|
ng-init="file.priority = '0'"
|
||||||
ng-change="$root.changePriority(file)"
|
ng-change="$root.changePriority(file)"
|
||||||
@@ -148,9 +160,13 @@
|
|||||||
|
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<strong>
|
<strong>
|
||||||
Client Stats: ↓ {{$root.client.downloadSpeed | pbytes}}/s · ↑
|
Client Stats: ↓ {{ client.downloadSpeed | pbytes }}/s · ↑ {{
|
||||||
{{$root.client.uploadSpeed | pbytes}}/s · Ratio: {{$root.client.ratio |
|
client.uploadSpeed | pbytes }}/s · Ratio: {{ client.ratio | number:2 }}
|
||||||
number:2}}
|
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Processing Spinner -->
|
||||||
|
<div class="spinner" ng-show="client.processing">
|
||||||
|
<i class="fa fa-spinner fa-spin spinner-icon"></i>
|
||||||
|
</div>
|
||||||
|
Reference in New Issue
Block a user