Skip to content

Commit

Permalink
Do not create files marked 'DoNotDownload'
Browse files Browse the repository at this point in the history
This works exactly how you'd expect for BitTorrent v2 torrents. For
BitTorrent v1 torrents we still need to create files when a piece we
need to download spans across two, or more, files marked
'DoNotDownload'.

I've low interest in implementing support for placeholder files when
this situation occurs.
  • Loading branch information
alanmcgovern committed Aug 18, 2024
1 parent 548daed commit ac10de8
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 4 deletions.
11 changes: 10 additions & 1 deletion src/MonoTorrent.Client/MonoTorrent.Client.Modes/StartingMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public async Task WaitForStartingToComplete ()

async ReusableTask CreateOrTruncateFiles ()
{
foreach (TorrentFileInfo file in Manager.Files) {
foreach (TorrentFileInfo file in Manager.Files.Where (t => t.Priority != Priority.DoNotDownload)) {
var maybeLength = await DiskManager.GetLengthAsync (file).ConfigureAwait (false);
// If the file doesn't exist, create it.
if (!maybeLength.HasValue)
Expand All @@ -171,6 +171,15 @@ async ReusableTask CreateOrTruncateFiles ()
file.BitField[0] = true;
}

// Then check if any 'DoNotDownload' file overlaps with a file we are downloading.
var downloadingFiles = Manager.Files.Where (t => t.Priority != Priority.DoNotDownload).ToArray ();
foreach (var ignoredFile in Manager.Files.Where (t => t.Priority == Priority.DoNotDownload)) {
foreach (var downloading in downloadingFiles) {
if (ignoredFile.Overlaps (downloading))
await DiskManager.CreateAsync (ignoredFile, Settings.FileCreationOptions);
}
}

// After potentially creating or truncating files, refresh the state.
await Manager.RefreshAllFilesCorrectLengthAsync ();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,35 @@ public async Task SetFilePriorityAsync (ITorrentManagerFile file, Priority prior
if (!Files.Contains (file))
throw new ArgumentNullException (nameof (file), "The file is not part of this torrent");

// No change
// No change - bail out
if (priority == file.Priority)
return;

await ClientEngine.MainLoop;

if (Engine == null)
throw new InvalidOperationException ("This torrent manager has been removed from it's ClientEngine");

// If the old priority, or new priority, is 'DoNotDownload' then the selector needs to be refreshed
bool needsToUpdateSelector = file.Priority == Priority.DoNotDownload || priority == Priority.DoNotDownload;
var oldPriority = file.Priority;
((TorrentFileInfo) file).Priority = priority;

if (oldPriority == Priority.DoNotDownload && !(await Engine.DiskManager.CheckFileExistsAsync (file))) {
// Always create the file the user requested to download
await Engine.DiskManager.CreateAsync (file, Engine.Settings.FileCreationOptions);
}

if (oldPriority == Priority.DoNotDownload && file.Length > 0) {
// Look for any file which are still marked DoNotDownload but also overlap this file.
// We need to create those ones too because if there are three 400kB files and the
// piece length is 512kB, and the first file is set to 'DoNotDownload', then we still
// need to create it as we'll download the first 512kB under bittorrent v1.
foreach (var maybeCreateFile in Files.Where (t => t.Priority == Priority.DoNotDownload && t.Length > 0 && t != file)) {
// If this file overlaps, create it!
if (maybeCreateFile.Overlaps(file) && !(await Engine.DiskManager.CheckFileExistsAsync (maybeCreateFile)))
await Engine.DiskManager.CreateAsync (maybeCreateFile, Engine.Settings.FileCreationOptions);
}
}

if (needsToUpdateSelector) {
// If we change the priority of a file we need to figure out which files are marked
Expand All @@ -111,6 +130,7 @@ public async Task SetFilePriorityAsync (ITorrentManagerFile file, Priority prior
}
}

((TorrentFileInfo) file).Priority = priority;
Mode.HandleFilePriorityChanged (file, oldPriority);
}

Expand Down Expand Up @@ -1099,7 +1119,7 @@ internal async Task LoadFastResumeAsync (FastResume data, bool skipStateCheckFor
internal async ReusableTask RefreshAllFilesCorrectLengthAsync ()
{
var allFilesCorrectLength = true;
foreach (TorrentFileInfo file in Files) {
foreach (TorrentFileInfo file in Files.Where (t => t.Priority != Priority.DoNotDownload)) {
var maybeLength = await Engine!.DiskManager.GetLengthAsync (file);

// Empty files aren't stored in fast resume data because it's as easy to just check if they exist on disk.
Expand Down
6 changes: 6 additions & 0 deletions src/MonoTorrent/MonoTorrent/ITorrentFileInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,11 @@ public static class ITorrentFileInfoExtensions
{
public static long BytesDownloaded (this ITorrentManagerFile info)
=> (long) (info.BitField.PercentComplete * info.Length / 100.0);

public static bool Overlaps (this ITorrentManagerFile self, ITorrentManagerFile file)
=> self.Length > 0 &&
file.Length > 0 &&
self.StartPieceIndex <= file.EndPieceIndex &&
file.StartPieceIndex <= self.EndPieceIndex;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,66 @@ public async Task StartAsyncAlwaysCreatesEmptyFiles ()
}
}

[Test]
public async Task StartAsync_DoesNotCreateDoNotDownloadPriority ()
{
using var writer = new TestWriter ();
var files = TorrentFile.Create (Constants.BlockSize * 4, 0, 1, 2, 3);
using var accessor = TempDir.Create ();
using var rig = TestRig.CreateMultiFile (files, Constants.BlockSize * 4, writer, baseDirectory: accessor.Path);

foreach (var file in rig.Manager.Files)
await rig.Manager.SetFilePriorityAsync (file, Priority.DoNotDownload);

await rig.Manager.StartAsync ();

foreach (var file in rig.Manager.Files)
Assert.IsFalse (await writer.ExistsAsync (file));
}

[Test]
public async Task StartAsync_CreatesAllImplicatedFiles ()
{
using var writer = new TestWriter ();
var files = TorrentFile.Create (Constants.BlockSize * 4, 0, 1, Constants.BlockSize * 4, 3);
using var accessor = TempDir.Create ();
using var rig = TestRig.CreateMultiFile (files, Constants.BlockSize * 4, writer, baseDirectory: accessor.Path);

foreach (var file in rig.Manager.Files)
await rig.Manager.SetFilePriorityAsync (file, file.Length == 1 ? Priority.Normal : Priority.DoNotDownload);

await rig.Manager.StartAsync ();
await rig.Manager.WaitForState (TorrentState.Downloading);

Assert.IsFalse (await writer.ExistsAsync (rig.Manager.Files[0]));
Assert.IsTrue (await writer.ExistsAsync (rig.Manager.Files[1]));
Assert.IsTrue (await writer.ExistsAsync (rig.Manager.Files[2]));
Assert.IsFalse (await writer.ExistsAsync (rig.Manager.Files[3]));
}

[Test]
public async Task StartAsync_SetPriorityCreatesAllImplicatedFiles ()
{
using var writer = new TestWriter ();
var files = TorrentFile.Create (Constants.BlockSize * 4, 0, 1, Constants.BlockSize * 4, Constants.BlockSize * 4);
using var accessor = TempDir.Create ();
using var rig = TestRig.CreateMultiFile (files, Constants.BlockSize * 4, writer, baseDirectory: accessor.Path);

foreach (var file in rig.Manager.Files)
await rig.Manager.SetFilePriorityAsync (file, Priority.DoNotDownload);

await rig.Manager.StartAsync ();

await rig.Manager.SetFilePriorityAsync (rig.Manager.Files[0], Priority.Normal);
Assert.IsTrue (await writer.ExistsAsync (rig.Manager.Files[0]));
Assert.IsFalse (await writer.ExistsAsync (rig.Manager.Files[1]));

await rig.Manager.SetFilePriorityAsync (rig.Manager.Files[1], Priority.Normal);
Assert.IsTrue (await writer.ExistsAsync (rig.Manager.Files[1]));
Assert.IsTrue (await writer.ExistsAsync (rig.Manager.Files[2]));
Assert.IsFalse (await writer.ExistsAsync (rig.Manager.Files[3]));
}

[Test]
public async Task StopTest ()
{
Expand Down

0 comments on commit ac10de8

Please sign in to comment.