Skip to content

Commit 3e04af0

Browse files
committed
[core] Always create empty files when starting a torrent
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.
1 parent 82160bf commit 3e04af0

File tree

3 files changed

+51
-5
lines changed

3 files changed

+51
-5
lines changed

src/MonoTorrent.Client/MonoTorrent.Client.Modes/StartingMode.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
using MonoTorrent.BEncoding;
3737
using MonoTorrent.Logging;
3838

39+
using ReusableTasks;
40+
3941
namespace MonoTorrent.Client.Modes
4042
{
4143
class StartingMode : Mode
@@ -62,6 +64,8 @@ public async Task WaitForStartingToComplete ()
6264
throw new TorrentException ("Torrents with no metadata must use 'MetadataMode', not 'StartingMode'.");
6365

6466
try {
67+
// If the torrent contains any files of length 0, ensure we create them now.
68+
await CreateEmptyFiles ();
6569
await VerifyHashState ();
6670
Cancellation.Token.ThrowIfCancellationRequested ();
6771
Manager.PieceManager.Initialise ();
@@ -123,6 +127,23 @@ public async Task WaitForStartingToComplete ()
123127
await Manager.LocalPeerAnnounceAsync ();
124128
}
125129

130+
async ReusableTask CreateEmptyFiles ()
131+
{
132+
foreach (var file in Manager.Files) {
133+
if (file.Length == 0) {
134+
var fileInfo = new FileInfo (file.FullPath);
135+
if (fileInfo.Exists && fileInfo.Length == 0)
136+
continue;
137+
138+
await MainLoop.SwitchToThreadpool ();
139+
Directory.CreateDirectory (Path.GetDirectoryName (file.FullPath)!);
140+
// Ensure file on disk is always 0 bytes, as it's supposed to be.
141+
using (var stream = File.OpenWrite (file.FullPath))
142+
stream.SetLength (0);
143+
}
144+
}
145+
}
146+
126147
async void SendAnnounces ()
127148
{
128149
try {
@@ -156,12 +177,9 @@ async Task TryLoadV2HashesFromCache()
156177
async Task VerifyHashState ()
157178
{
158179
// If we do not have metadata or the torrent needs a hash check, fast exit.
159-
if (!Manager.HasMetadata || !Manager.HashChecked)
180+
if (!Manager.HashChecked)
160181
return;
161182

162-
// FIXME: I should really just ensure that zero length files always exist on disk. If the first file is
163-
// a zero length file and someone deletes it after the first piece has been written to disk, it will
164-
// never be recreated. If the downloaded data requires this file to exist, we have an issue.
165183
foreach (ITorrentManagerFile file in Manager.Files) {
166184
if (!file.BitField.AllFalse && file.Length > 0) {
167185
if (!await DiskManager.CheckFileExistsAsync (file)) {

src/MonoTorrent.Client/MonoTorrent.Client/Managers/TorrentManager.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,10 +670,13 @@ internal void SetMetadata (Torrent torrent)
670670
}
671671

672672
var currentPath = File.Exists (downloadCompleteFullPath) ? downloadCompleteFullPath : downloadIncompleteFullPath;
673-
return new TorrentFileInfo (file, currentPath) {
673+
var torrentFileInfo = new TorrentFileInfo (file, currentPath) {
674674
DownloadCompleteFullPath = downloadCompleteFullPath,
675675
DownloadIncompleteFullPath = downloadIncompleteFullPath
676676
};
677+
if (file.Length == 0)
678+
torrentFileInfo.BitField[0] = true;
679+
return torrentFileInfo;
677680
}).Cast<ITorrentManagerFile> ().ToList ().AsReadOnly ();
678681

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

946952
// If we're only hashing 1 piece then we can start moving files now. This occurs when a torrent

src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client/ClientEngineTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,28 @@ public async Task SaveRestoreState_OneTorrentFile_NoContainingDirectory ()
370370
}
371371
}
372372

373+
[Test]
374+
public async Task StartAsyncAlwaysCreatesEmptyFiles ()
375+
{
376+
var files = TorrentFile.Create (Constants.BlockSize * 4, 0, 1, 2, 3);
377+
using var writer = new TestWriter ();
378+
using var rig = TestRig.CreateMultiFile (files, Constants.BlockSize * 4, writer);
379+
380+
for (int i = 0; i < 2; i++) {
381+
var downloadingState = rig.Manager.WaitForState (TorrentState.Downloading);
382+
var stoppedState = rig.Manager.WaitForState (TorrentState.Stopped);
383+
384+
await rig.Manager.StartAsync ();
385+
Assert.IsTrue (downloadingState.Wait (5000), "Started");
386+
Assert.IsTrue (File.Exists (rig.Manager.Files[0].FullPath));
387+
Assert.IsTrue (rig.Manager.Files[0].BitField.AllTrue);
388+
389+
await rig.Manager.StopAsync ();
390+
Assert.IsTrue (stoppedState.Wait (5000), "Stopped");
391+
File.Delete (rig.Manager.Files[0].FullPath);
392+
}
393+
}
394+
373395
[Test]
374396
public async Task StopTest ()
375397
{

0 commit comments

Comments
 (0)