Skip to content

Data loss: cancelling the Windows progress dialog during a move drag from Android to PC deletes the source but leaves no copy at the destination #317

@liqi0816

Description

@liqi0816

Summary

When moving a file from an Android device to the PC via drag & drop (i.e. a drag with the Move effect, not Copy), the file is first pulled into a local temp folder and then handed off to Windows to write to the chosen destination. If the user clicks Cancel on the Windows progress dialog, the destination never receives the file, but the source file on the Android device is deleted anyway.

The result is data loss: the file is neither at the PC destination nor on the Android source. Recovering it requires technical skills (and may not be possible at all — see below).

Steps to reproduce

  1. Connect an Android device and browse to a file in ADB Explorer.
  2. Drag the file out to a PC location (e.g. Explorer window or Desktop) using the Move effect (hold Shift while dragging, or drop on a target that requests move).
  3. While the Windows progress dialog is showing, click Cancel.

Expected

The source file remains on the Android device (since the move did not complete). Nothing is lost.

Actual

  • The destination does not receive the file.
  • The source file on the Android device is deleted.
  • A partial/leftover copy may be sitting in a temp folder, but it is not guaranteed to survive (see "Why recovery is unreliable").

Recommendations

  1. Do not delete the Android source until the transfer to the final destination is actually confirmed complete. Deletion should be tied to the destination succeeding, not to an intermediate temp copy finishing.
  2. Never automatically wipe files that are left in limbo. Given how sensitive file moves are, if something goes wrong the leftover temp copy is the user's last chance at recovery. Cleanup of leftover files should not run proactively (e.g. at the start of the next drag) in a way that destroys data from a previously failed transfer. Let the OS reclaim temp space instead, or only ever delete files the app itself just successfully transferred.

Technical root-cause analysis

Note: Everything below this line — the code-level analysis, file/line references, and any suggested code — was produced with AI assistance based on reading the source. It should be reviewed by a maintainer before acting on it. Code snippets are illustrative direction, not tested patches.

How the move-drag actually works

For an Android→PC drag, ADB Explorer is the drag source. It does not copy a temp file to the destination itself — it hands Windows Explorer a read-only stream and lets Explorer write the destination. The data is produced lazily per file in FileClass.PrepareDescriptors (ADB Explorer/Models/File/FileClass.cs):

  • A FileSyncOperation.PullFile operation is created to pull Android → TempDragPath.
  • fileOp.PropertyChanged += PullOperation_PropertyChanged; subscribes the source-deletion handler.
  • The descriptor's Stream lambda queues the pull, waits for it to finish, then returns a COM stream over the temp file via NativeMethods.GetComStreamFromFile(...).

Defect 1 — source is deleted when the pull-to-temp finishes, not when the destination write succeeds

FileClass.PullOperation_PropertyChanged (ADB Explorer/Models/File/FileClass.cs, approx. lines 456–490):

if (op.Status is FileOperation.OperationStatus.Completed)
{
    if (op.VFDO.CurrentEffect.HasFlag(DragDropEffects.Move))
    {
        // Delete file from device
        ShellFileOperation.SilentDelete(op.Device, op.FilePath.FullPath);   // <-- source deleted here
        ...
    }
    op.VFDO = null;
}

Here op is the pull operation (Android → temp). The moment that pull completes, the Android source is deleted. Nothing checks whether Explorer ever finished — or even started — writing the temp data to the real destination (no inspection of PerformedDropEffect, PasteSucceeded, or the destination file). So cancelling the destination write after the pull has completed leaves the source already gone.

This only affects the Move effect. DropSource.GiveFeedback in VirtualFileDataObject.cs downgrades the default to Copy unless Shift is held, and Copy never deletes the source — so copy-drags are safe.

Defect 2 — proactive temp cleanup destroys limbo files

In the happy path the temp file cleans itself up: NativeMethods.GetComStreamFromFile (ADB Explorer/Services/AppInfra/NativeMethods/DragDropNative.cs, ~line 183) opens the temp file with STGM_DELETEONRELEASE, so Windows deletes it when Explorer releases the stream. No explicit clear is needed for a successful transfer.

The explicit CopyPasteService.ClearTempFolder() (Directory.Delete(TempDragPath, true)) therefore only ever deletes files that did not self-delete — i.e. the limbo files left by a cancelled/failed move. It runs at the start of every new transfer:

  • VirtualFileDataObject.PrepareTransfer(files, …)
  • VirtualFileDataObject.PrepareTransfer(packages, …)
  • CopyPasteService.AcceptDataObject (the IsVirtual branch)

So the next drag operation wipes whatever the previous failed move left behind.

Why recovery is unreliable

STGM_DELETEONRELEASE can also remove the limbo copy: if Explorer requested the stream (pull ran → source already deleted) and the user then cancels, Explorer may still release the stream, deleting the temp file too. In that case nothing is left to recover. So "the file survives in temp" is not guaranteed — which is why fixing the deletion ordering (Defect 1) matters more than the cleanup behavior (Defect 2).

Suggested direction (AI-generated, untested)

  1. Defect 1: Move the source deletion out of the pull-completion handler. Delete the Android source only after the drop is confirmed — e.g. when Explorer reports PerformedDropEffect == Move at the end of the async operation (IAsyncOperation.EndOperation / dwEffects in VirtualFileDataObject.cs), and/or after verifying the destination file exists and is complete.
  2. Defect 2: Remove (or tightly scope) the proactive ClearTempFolder() wipes so they cannot delete data from a previous, unfinished transfer. Rely on STGM_DELETEONRELEASE for happy-path cleanup and the OS for reclaiming abandoned temp space.

Relevant files

  • ADB Explorer/Models/File/FileClass.csPrepareDescriptors, PullOperation_PropertyChanged
  • ADB Explorer/Services/AppInfra/LowLevel/VirtualFileDataObject.csPrepareTransfer, DropSource.GiveFeedback, IAsyncOperation members
  • ADB Explorer/Services/AppInfra/NativeMethods/DragDropNative.csGetComStreamFromFile (STGM_DELETEONRELEASE)
  • ADB Explorer/Services/AppInfra/CopyPasteService.csClearTempFolder and its call sites

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingfixedThis is fixed in main, but not in release

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions