Skip to content

Commit

Permalink
[core] Always create empty files when starting a torrent
Browse files Browse the repository at this point in the history
Empty files are always created on disk as part of starting
the torrent. This simplifies some of the internal logic as
nothing else needs to care about creating empty files after
a torrent is started.
  • Loading branch information
alanmcgovern committed Oct 31, 2022
1 parent 82160bf commit 3e04af0
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 5 deletions.
26 changes: 22 additions & 4 deletions src/MonoTorrent.Client/MonoTorrent.Client.Modes/StartingMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
using MonoTorrent.BEncoding;
using MonoTorrent.Logging;

using ReusableTasks;

namespace MonoTorrent.Client.Modes
{
class StartingMode : Mode
Expand All @@ -62,6 +64,8 @@ public async Task WaitForStartingToComplete ()
throw new TorrentException ("Torrents with no metadata must use 'MetadataMode', not 'StartingMode'.");

try {
// If the torrent contains any files of length 0, ensure we create them now.
await CreateEmptyFiles ();
await VerifyHashState ();
Cancellation.Token.ThrowIfCancellationRequested ();
Manager.PieceManager.Initialise ();
Expand Down Expand Up @@ -123,6 +127,23 @@ public async Task WaitForStartingToComplete ()
await Manager.LocalPeerAnnounceAsync ();
}

async ReusableTask CreateEmptyFiles ()
{
foreach (var file in Manager.Files) {
if (file.Length == 0) {
var fileInfo = new FileInfo (file.FullPath);
if (fileInfo.Exists && fileInfo.Length == 0)
continue;

await MainLoop.SwitchToThreadpool ();
Directory.CreateDirectory (Path.GetDirectoryName (file.FullPath)!);
// Ensure file on disk is always 0 bytes, as it's supposed to be.
using (var stream = File.OpenWrite (file.FullPath))
stream.SetLength (0);
}
}
}

async void SendAnnounces ()
{
try {
Expand Down Expand Up @@ -156,12 +177,9 @@ async Task TryLoadV2HashesFromCache()
async Task VerifyHashState ()
{
// If we do not have metadata or the torrent needs a hash check, fast exit.
if (!Manager.HasMetadata || !Manager.HashChecked)
if (!Manager.HashChecked)
return;

// FIXME: I should really just ensure that zero length files always exist on disk. If the first file is
// a zero length file and someone deletes it after the first piece has been written to disk, it will
// never be recreated. If the downloaded data requires this file to exist, we have an issue.
foreach (ITorrentManagerFile file in Manager.Files) {
if (!file.BitField.AllFalse && file.Length > 0) {
if (!await DiskManager.CheckFileExistsAsync (file)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -670,10 +670,13 @@ internal void SetMetadata (Torrent torrent)
}

var currentPath = File.Exists (downloadCompleteFullPath) ? downloadCompleteFullPath : downloadIncompleteFullPath;
return new TorrentFileInfo (file, currentPath) {
var torrentFileInfo = new TorrentFileInfo (file, currentPath) {
DownloadCompleteFullPath = downloadCompleteFullPath,
DownloadIncompleteFullPath = downloadIncompleteFullPath
};
if (file.Length == 0)
torrentFileInfo.BitField[0] = true;
return torrentFileInfo;
}).Cast<ITorrentManagerFile> ().ToList ().AsReadOnly ();

PieceHashes = Torrent.CreatePieceHashes ();
Expand Down Expand Up @@ -941,6 +944,9 @@ internal void OnPieceHashed (int index, bool hashPassed, int piecesHashed, int t
var files = Files;
var fileIndex = files.FindFileByPieceIndex (index);
for (int i = fileIndex; i < files.Count && files[i].StartPieceIndex <= index; i++) {
// Empty files always have all bits set to 'true' as they're treated as being downloaded as soon as they exist on disk.
if (files[i].Length == 0)
continue;
((TorrentFileInfo) files[i]).BitField[index - files[i].StartPieceIndex] = hashPassed;

// If we're only hashing 1 piece then we can start moving files now. This occurs when a torrent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,28 @@ public async Task SaveRestoreState_OneTorrentFile_NoContainingDirectory ()
}
}

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

for (int i = 0; i < 2; i++) {
var downloadingState = rig.Manager.WaitForState (TorrentState.Downloading);
var stoppedState = rig.Manager.WaitForState (TorrentState.Stopped);

await rig.Manager.StartAsync ();
Assert.IsTrue (downloadingState.Wait (5000), "Started");
Assert.IsTrue (File.Exists (rig.Manager.Files[0].FullPath));
Assert.IsTrue (rig.Manager.Files[0].BitField.AllTrue);

await rig.Manager.StopAsync ();
Assert.IsTrue (stoppedState.Wait (5000), "Stopped");
File.Delete (rig.Manager.Files[0].FullPath);
}
}

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

0 comments on commit 3e04af0

Please sign in to comment.