FileSharer Development Notes 8 — What a ‘Download Complete’ Log Entry Actually Means

FileSharer Development Notes · 08 / 10

Three developers follow event cards to find the stage where a transfer stopped.
AI illustration · Three developers follow event cards to find the stage where a transfer stopped.

When someone says they did not receive a file, replying that your screen says ‘complete’ does not solve the problem. You need to know which stage completed: accepting the request, finishing the server’s writes, or saving the file on the recipient’s device.

A diagram of how AccessLogger sends events to the log file and the interface. The addresses and filenames shown are examples; the original access_log.txt was not read.
A diagram of how AccessLogger sends events to the log file and the interface. The addresses and filenames shown are examples; the original access_log.txt was not read.

FileSharer records share-page views, file downloads, ZIP transfers, PIN failures, interruptions, and tunnel reconnections as distinct actions. For this article, I examined the source that produces those records, rather than opening operational logs, to establish what each field and event means.

Sending one event to both the file and the interface

AccessLogger.Log first creates an AccessLogEntry. It contains the timestamp, display IP, hostname, actual socket IP, action, target, share ID, User-Agent, and details. The method then appends the entry to the file and notifies the interface through the EntryLogged event.

This structure lets the file log and the interface use the same event model. However, the file-writing code catches exceptions internally and returns. The interface event can still fire even if the file write fails, so seeing an entry on screen does not guarantee that it was successfully saved to disk. Exposing a save failure as a separate status is one possible improvement.

Before an entry is written in tab-separated form, Clean replaces tabs and line breaks with spaces. This reduces the chance that a single event appears to span multiple lines merely because its target name contains a line break. It does not replace validation for every other output format, such as HTML or spreadsheets.

Why keep two IP addresses?

For a request that passes through a proxy, the TCP socket’s peer may differ from the address you want to display as the actual client. The code has both Ip and SocketIp. Preserving both makes it easier to trace the stage at which each value was determined.

Running the original ResolveClientIp with sample headers produced the following results. The addresses below are documentation examples.

Input Return value
X-Forwarded-For: 192.0.2.10, 192.0.2.11 192.0.2.10
No forwarding header; socket IP 127.0.0.1 127.0.0.1

The function gives priority to the first forwarded address, but does not itself check whether the request came from a proxy whose forwarding headers should be trusted. Its return value must therefore not be treated as a verified user identity. The design needs a trust boundary that accepts forwarding headers only through known relay paths and uses the socket address otherwise.

Hostname lookup runs in the background so that it does not hold up download requests. The first event may contain an empty hostname, while later events may use a cached name. Care is also needed when treating an IP address and a name as a simple identification of one person.

The server finishing its writes is different from the recipient saving the file

ServeFile reads the file, writes it to a stream, and accumulates the number of bytes sent. It records a completion event after the loop, or an interruption event if an exception occurs. For a partial request, it also adds a description indicating that the requested range was sent.

Here, completion is an observation made by the server’s transfer loop. It is not an acknowledgment that the recipient has finished saving the file to disk, can reopen it, or has verified a matching hash. In particular, if a read returns 0—for example, because the source file becomes shorter during the transfer—the loop can end and reach the completion log. Adding a completion condition that checks whether sent equals the expected length is a potential follow-up improvement.

The operational interface would therefore be more accurate if it described the observed fact as ‘server transfer ended’ and added ‘receipt confirmed’ only when there was a separate verification. Clear names reduce the chance that users and support staff use the same term for different stages.

Showing only recent entries can still mean reading the entire file

LoadRecent(max) displays only a recent subset of lines, but internally it uses ReadAllLines to read the entire file before selecting the range at the end. As the log grows, reducing the maximum number of displayed rows alone does not reduce the cost of reading it.

For long-term use, options to consider include splitting files by date or size, defining a retention period, and reading only the most recent portion. These are improvement directions derived from the source, not a set of features all present in the current implementation.

Good logs distinguish facts from interpretations

Client IP addresses, filenames, and hostnames can contain personal or business information. No actual logs were used in this article or its diagram. Artificially constructed events are sufficient to explain the processing flow in a public example.

One useful aspect of FileSharer’s logging is its separation of events into multiple stages. Adding precise information about whether logging succeeded, how much to trust the source, and what constitutes completion would make those records more useful for explaining failures.

Source references
FileSharer.cs:100–158 AccessLogger.Log / TryGetHostName · FileSharer.cs:193–261 Clean / Append / LoadRecent · FileSharer.cs:5051–5064 ResolveClientIp · FileSharer.cs:5324–5379 Transfer logging
Proposed improvements are distinguished from implemented behavior.

 

댓글