Browse Source

Add ability to rename torrent files from the WebUI

Renaming folders is not yet supported. Closes #8892.
adaptive-webui-19844
Thomas Piccirello 5 years ago
parent
commit
2bce9f6179
  1. 43
      src/webui/api/torrentscontroller.cpp
  2. 1
      src/webui/api/torrentscontroller.h
  3. 3
      src/webui/www/private/index.html
  4. 90
      src/webui/www/private/rename_file.html
  5. 34
      src/webui/www/private/scripts/prop-files.js
  6. 1
      src/webui/www/webui.qrc

43
src/webui/api/torrentscontroller.cpp

@ -1154,3 +1154,46 @@ void TorrentsController::tagsAction()
result << tag; result << tag;
setResult(result); setResult(result);
} }
void TorrentsController::renameFileAction()
{
requireParams({"hash", "id", "name"});
const QString hash = params()["hash"];
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (!torrent)
throw APIError(APIErrorType::NotFound);
QString newName = params()["name"].trimmed();
if (newName.isEmpty())
throw APIError(APIErrorType::BadParams, tr("Name cannot be empty"));
if (!Utils::Fs::isValidFileSystemName(newName))
throw APIError(APIErrorType::Conflict, tr("Name is not valid"));
if (newName.endsWith(QB_EXT))
newName.chop(QB_EXT.size());
bool ok = false;
const int fileIndex = params()["id"].toInt(&ok);
if (!ok || (fileIndex < 0) || (fileIndex >= torrent->filesCount()))
throw APIError(APIErrorType::Conflict, tr("ID is not valid"));
const QString oldFileName = torrent->fileName(fileIndex);
const QString oldFilePath = torrent->filePath(fileIndex);
const bool useFilenameExt = BitTorrent::Session::instance()->isAppendExtensionEnabled()
&& (torrent->filesProgress()[fileIndex] != 1);
const QString newFileName = (newName + (useFilenameExt ? QB_EXT : QString()));
const QString newFilePath = (oldFilePath.leftRef(oldFilePath.size() - oldFileName.size()) + newFileName);
if (oldFileName == newFileName)
return;
// check if new name is already used
for (int i = 0; i < torrent->filesCount(); ++i) {
if (i == fileIndex) continue;
if (Utils::Fs::sameFileNames(torrent->filePath(i), newFilePath))
throw APIError(APIErrorType::Conflict, tr("Name is already in use"));
}
torrent->renameFile(fileIndex, newFilePath);
}

1
src/webui/api/torrentscontroller.h

@ -83,4 +83,5 @@ private slots:
void setForceStartAction(); void setForceStartAction();
void toggleSequentialDownloadAction(); void toggleSequentialDownloadAction();
void toggleFirstLastPiecePrioAction(); void toggleFirstLastPiecePrioAction();
void renameFileAction();
}; };

3
src/webui/www/private/index.html

@ -197,7 +197,8 @@
<li class="separator"><a href="#banPeer"><img src="images/qbt-theme/user-group-delete.svg" alt="QBT_TR(Ban peer permanently)QBT_TR[CONTEXT=PeerListWidget]"/> QBT_TR(Ban peer permanently)QBT_TR[CONTEXT=PeerListWidget]</a></li> <li class="separator"><a href="#banPeer"><img src="images/qbt-theme/user-group-delete.svg" alt="QBT_TR(Ban peer permanently)QBT_TR[CONTEXT=PeerListWidget]"/> QBT_TR(Ban peer permanently)QBT_TR[CONTEXT=PeerListWidget]</a></li>
</ul> </ul>
<ul id="torrentFilesMenu" class="contextMenu"> <ul id="torrentFilesMenu" class="contextMenu">
<li> <li><a href="#Rename"><img src="images/qbt-theme/edit-rename.svg" alt="QBT_TR(Rename...)QBT_TR[CONTEXT=PropertiesWidget]"/> QBT_TR(Rename...)QBT_TR[CONTEXT=PropertiesWidget]</a></li>
<li class="separator">
<a href="#FilePrio" class="arrow-right"><span style="display: inline-block; width: 16px;"></span> QBT_TR(Priority)QBT_TR[CONTEXT=PropertiesWidget]</a> <a href="#FilePrio" class="arrow-right"><span style="display: inline-block; width: 16px;"></span> QBT_TR(Priority)QBT_TR[CONTEXT=PropertiesWidget]</a>
<ul> <ul>
<li><a href="#FilePrioIgnore"><span style="display: inline-block; width: 16px;"></span> QBT_TR(Do not download)QBT_TR[CONTEXT=PropListDelegate]</a></li> <li><a href="#FilePrioIgnore"><span style="display: inline-block; width: 16px;"></span> QBT_TR(Do not download)QBT_TR[CONTEXT=PropListDelegate]</a></li>

90
src/webui/www/private/rename_file.html

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="${LANG}">
<head>
<meta charset="UTF-8" />
<title>QBT_TR(Renaming)QBT_TR[CONTEXT=TorrentContentTreeView]</title>
<link rel="stylesheet" href="css/style.css?v=${CACHEID}" type="text/css" />
<script src="scripts/lib/mootools-1.2-core-yc.js"></script>
<script src="scripts/lib/mootools-1.2-more.js"></script>
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
<script>
'use strict';
new Keyboard({
defaultEventType: 'keydown',
events: {
'Enter': function(event) {
$('renameButton').click();
event.preventDefault();
},
'Escape': function(event) {
window.parent.closeWindows();
event.preventDefault();
},
'Esc': function(event) {
window.parent.closeWindows();
event.preventDefault();
}
}
}).activate();
window.addEvent('domready', function() {
const hash = new URI().getData('hash');
const name = new URI().getData('name');
const id = new URI().getData('id');
if (!hash || !name || !id) return;
const decodedName = decodeURIComponent(name);
$('rename').value = decodedName;
$('rename').focus();
$('rename').setSelectionRange(0, decodedName.indexOf('.'));
$('renameButton').addEvent('click', function(e) {
new Event(e).stop();
// check field
const newName = $('rename').value.trim();
if (newName === '') {
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
return;
}
if (newName === name) {
alert('QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]');
return;
}
$('renameButton').disabled = true;
new Request({
url: 'api/v2/torrents/renameFile',
method: 'post',
data: {
hash: hash,
id: id,
name: newName
},
onSuccess: function() {
window.parent.closeWindows();
},
onFailure: function() {
alert('QBT_TR(Failed to update name)QBT_TR[CONTEXT=HttpServer]');
$('renameButton').disabled = false;
}
}).send();
});
});
</script>
</head>
<body>
<div style="padding: 10px 10px 0px 10px;">
<p style="font-weight: bold;">QBT_TR(New name:)QBT_TR[CONTEXT=TorrentContentTreeView]</p>
<input type="text" id="rename" value="" maxlength="100" style="width: 220px;" />
<div style="text-align: center; padding-top: 10px;">
<input type="button" value="QBT_TR(Save)QBT_TR[CONTEXT=HttpServer]" id="renameButton" />
</div>
</div>
</body>
</html>

34
src/webui/www/private/scripts/prop-files.js

@ -520,6 +520,33 @@ window.qBittorrent.PropFiles = (function() {
targets: '#torrentFilesTableDiv tr', targets: '#torrentFilesTableDiv tr',
menu: 'torrentFilesMenu', menu: 'torrentFilesMenu',
actions: { actions: {
Rename: function(element, ref) {
const hash = torrentsTable.getCurrentTorrentHash();
if (!hash) return;
const rowId = torrentFilesTable.selectedRowsIds()[0];
if (!rowId) return;
const row = torrentFilesTable.rows[rowId];
if (!row) return;
const node = torrentFilesTable.getNode(rowId);
if (node.isFolder) return;
const name = row.full_data.name;
const fileId = row.full_data.fileId;
new MochaUI.Window({
id: 'renamePage',
title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TorrentContentTreeView]",
loadMethod: 'iframe',
contentURL: 'rename_file.html?hash=' + hash + '&id=' + fileId + '&name=' + encodeURIComponent(name),
scrollbars: false,
resizable: false,
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 250,
height: 100
});
},
FilePrioIgnore: function(element, ref) { FilePrioIgnore: function(element, ref) {
filesPriorityMenuClicked(FilePriority.Ignored); filesPriorityMenuClicked(FilePriority.Ignored);
@ -543,6 +570,13 @@ window.qBittorrent.PropFiles = (function() {
this.hideItem('FilePrio'); this.hideItem('FilePrio');
else else
this.showItem('FilePrio'); this.showItem('FilePrio');
const rowId = torrentFilesTable.selectedRowsIds()[0];
const node = torrentFilesTable.getNode(rowId);
if (node.isFolder)
this.hideItem('Rename');
else
this.showItem('Rename');
} }
}); });

1
src/webui/www/webui.qrc

@ -17,6 +17,7 @@
<file>private/newcategory.html</file> <file>private/newcategory.html</file>
<file>private/newtag.html</file> <file>private/newtag.html</file>
<file>private/rename.html</file> <file>private/rename.html</file>
<file>private/rename_file.html</file>
<file>private/scripts/client.js</file> <file>private/scripts/client.js</file>
<file>private/scripts/contextmenu.js</file> <file>private/scripts/contextmenu.js</file>
<file>private/scripts/download.js</file> <file>private/scripts/download.js</file>

Loading…
Cancel
Save