Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix failed downloads (file coordination) #2467

Merged
merged 27 commits into from
Apr 2, 2024
Merged

Conversation

mallexxx
Copy link
Collaborator

@mallexxx mallexxx commented Mar 21, 2024

Task/Issue URL: https://app.asana.com/0/1199178362774117/1201330245678289/f
Notarized build: https://github.com/duckduckgo/macos-browser/actions/runs/8388376828/artifacts/1349521698
CC: @ayoy

Description:

  • File Presenter implementation for file coordination and file moving/removal detection
  • Downloads refactored to use File Presenters instead of URLs

Steps to test this PR:

  • Validate both sandboxed and non-sandboxed builds, preferably in RELEASE/REVIEW
  • Clean app data before testing to reset download settings; Delete the app from /Applications; delete the app rom System Settings -> Files and Folders
  1. Test regular downloads: quick ones (like https://hexfiend.com/) and long (like openoffice.org) - validate both saving in default location (~/Downloads), choosing a custom location (like ~/Desktop; ~/Downloads/DuckDuckGo), or always requesting location;
  2. Validate files are not overwritten when using a set location;
  3. Validate files are correctly overwritten when always requesting location and choosing an existing files
  4. Start a long download (e.g. openoffice); click Download again, choose the 1st downloaded file as a location for the second download, confirm overwrite: fist download should stop and disappear from the Downloads panel, the new one should be added
  5. set a breakpoint in the setupTemporaryDownloadURL method at return, remove the item replacement directory (po itemReplacementDirectory; rm /tmp/path/to/the/dir) manually and continue – this should lead to download failure
  6. test download cancellation by clicking "x" on a .duckload file in Finder/Dock; by clicking "x" in the Downloads panel - download should stay in the panel with "cancelled" state
  7. Validate retrying cancelled downloads - they should resume with the same progress, displaying progress in Finder
  8. test download cancellation by removing a .duckload file in Finder (to Trash or by Cmd+Shift+Del); by rm filename.duckload, rm destinationFileName.zip; by overwriting a download file (.duckload or the target one) from another app - removed downloads should disappear from the downloads panel
  9. Cancel some downloads - .duckload and target files should stay in ~/Downloads; Click "clear" in the Downloads panel - both .duckload and target files should be removed
  10. Validate removing a completed download file - removing the file from the Downloads panel
  11. Turn off wi-fi during a download - downloads should fail showing an error and Retry button for resumable downloads and no retry button for non-resumable downloads (like when Downloading All from a shared google photos album)
  12. Validate retrying failed downloads work
  13. Session restoration should bring back both failed and completed downloads; If a file was removed when the app was not running - the download should be removed from Downloads;
    14..duckload and target files should be removed if one of them was removed - for non-complete downloads
  14. Open File Location should highlight the file in Finder both right after download completion or after session restoration; after hiding&showing the Downloads panel
  15. Open File (double click) should not work for non-completed downloads; should work for completed downloads right after download completion and after session restoration
  16. Old downloads should be removed after 3 days including the .duckload and target file for non-completed download, but not remove a completed download file
  17. When renaming/moving a .duckload file, target download file should be also renamed (but not moved), download should continue, the rename should be displayed in the Downloads panel. If there‘s an existing file with the target name (i.e. renaming myload.duckload to test.duckload - myload.zip should be renamed to test.zip but if there‘s already a test.zip file present, the target file should remain myload.zip. This won‘t work when the sandboxed build downloads a file to a user-selected non-Downloads location - in this case the download should go-on with an old target name.
  18. If a target file was renamed during download (mv myload.zip test.zip) - the name change should be reflected in the downloads panel, the download should complete successfully to the new location

Internal references:

Pull Request Review Checklist
Software Engineering Expectations
Technical Design Template
Pull Request Documentation

Copy link
Contributor

github-actions bot commented Mar 21, 2024

Warnings
⚠️ PR has more than 500 lines of code changing. Consider splitting into smaller PRs if possible.

Generated by 🚫 dangerJS against b357e49

@mallexxx mallexxx requested a review from tomasstrba March 22, 2024 05:43
@github-actions github-actions bot added the bot: not in app board Added by automation for pull requests with tasks not added to macOS App Board Asana project label Mar 22, 2024
Copy link
Contributor

@tomasstrba tomasstrba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great engineering, @mallexxx! 👏
In major refactoring like this, I’d recommend to use tech designs more. It can help with the review and also later in case someone else has to fix/enhance the functionality.

I tested all kinds of scenarios and it worked very well 👏 :
✔️ Tested simultanous downloads of files
✔️ Tested custom download location
✔️ Tested asking for download destination on every
❔ Couldn’t remove the item replacement directory because: Operation not permitted. Not sure if it's a big deal
✔️ Tested download cancellation
✔️ Tested download resuming after the cancelation
✔️ Tested download cancellation by removing a .duckload file in Finder
✔️ Tested cancelation and resuming with wifi problems
✔️ Validated downloads list is properly restored after the app restart
✔️ Validated downloads list is refreshed according to the file system changes when the app is turned off
✔️ Validated opening files from the download list
✔️ Validated renaming of the .duckload file

There are couple of issues I experienced:

  1. After clicking "clear" in the Downloads panel, .duckload files were not removed from Desktop (custom download location)
  2. Removing a completed download file from Downloads panel didn't remove it from the file system. (Not really sure if this is the requirement, just assuming from testing steps)
  3. It often takes very long to see .duckload being renamed to the final name. Sometimes even 10 seconds
  4. Experienced following assertion when clicking multiple times on the download link
Screenshot 2024-03-22 at 9 03 42 AM 5) Experienced following assertion when choosing a disk as the destination for download Screenshot 2024-03-22 at 9 06 23 AM

And I have 2 product suggestions we can bring to Asana:

  1. Does it make sense to rephrase “Open Downloads Folder” in case of custom downloads folder is set?
  2. The download list truncates the file name with "...". Should it be able to show the full filename, at least when the item is selected or on hover?

modified = Date()
}
}

var destinationURL: URL? {
var destinationBookmarkData: Data? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that it won't be trivial to rename this across the whole PR, but I'd recommend to avoid using 'bookmark' word in this context, because it might be very confusing when looking at the codebase later after the merge. Would destinationPersistedData, tempFilePersistedURLData, etc, work better?

Copy link
Collaborator Author

@mallexxx mallexxx Mar 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I‘d stick with the API naming (i.e. bookmarkData), we could use URLBookmarkData or FileBookmarkData if it‘s better

DuckDuckGo/FileDownload/Model/FileDownloadManager.swift Outdated Show resolved Hide resolved
delegate?.presentedItemDidMove(to: newURL)
}

final func accommodatePresentedItemDeletion() async throws {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is async necessary here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really

completionHandler: @escaping (URL?, UTType?) -> Void)
func fileDownloadTask(_ task: WebKitDownloadTask, didFinishWith result: Result<URL, FileDownloadError>)
func fileDownloadTaskNeedsDestinationURL(_ task: WebKitDownloadTask, suggestedFilename: String, suggestedFileType: UTType?) async -> (URL?, UTType?)
func fileDownloadTask(_ task: WebKitDownloadTask, didFinishWith result: Result<Void, FileDownloadError>)
}

/// WKDownload wrapper managing Finder File Progress and coordinating file URLs
final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebKitDownloadTask class is quite complex and covers a wide range of functionalities. Is it possible to break it down into smaller components?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I‘ve moved out the progress/fileProgress from it, otherwise it‘s now mainly related to file coordination

@mallexxx
Copy link
Collaborator Author

TBD: test on macOS 11

@mallexxx mallexxx marked this pull request as ready for review March 22, 2024 13:44
@mallexxx
Copy link
Collaborator Author

Fixed the issues we‘ve discussed including the "duckload" download corner case,

After clicking "clear" in the Downloads panel, .duckload files were not removed from Desktop (custom download location)

  • actually I couldn‘t reproduce this one, is there some special steps?

Removing a completed download file from Downloads panel didn't remove it from the file system.

  • it shouldn‘t delete completed files

It often takes very long to see .duckload being renamed to the final name. Sometimes even 10 seconds

  • can you provide more detailed description for this one?

Experienced following assertion when clicking multiple times on the download link

  • out of scope for now

Experienced following assertion when choosing a disk as the destination for download

  • should be fixed now. although it wouldn‘t display any error for it but let‘s handle it separately

Does it make sense to rephrase “Open Downloads Folder” in case of custom downloads folder is set?

  • Probably it doesn‘t since the behavior of the button may vary - if there‘s only files downloaded to custom directories - it will try to locate the file instead of opening a "downloads" dir

The download list truncates the file name with "...". Should it be able to show the full filename, at least when the item is selected or on hover?

  • it displays a tooltip with a full name

@github-actions github-actions bot removed the bot: not in app board Added by automation for pull requests with tasks not added to macOS App Board Asana project label Mar 22, 2024
Copy link
Contributor

@tomasstrba tomasstrba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

> actually I couldn‘t reproduce this one, is there some special steps?
As discussed on MM, the behavior is expected.

> It often takes very long to see .duckload being renamed to the final name. Sometimes even 10 seconds
I don't experience this anymore. In the previous testing, a download finished correctly and I could see it completed in the Download panel. But in Finder, I saw .duckload file and it took ~10 seconds until it changed.

I performed the testing again on macOS 14, both sandboxed and non-sandboxed app. Everything works great! 💯 Job job, Alex 👏

Two last comments from me:

  • Please don't forget to perform smoke tests on macOS 11

  • One more product suggestion is to keep Downloads panel button in the stack after the manual activation for at least a minute. It was slightly annoying to activate it through the menu all the time.

@mallexxx mallexxx merged commit 6ee57a4 into main Apr 2, 2024
17 checks passed
@mallexxx mallexxx deleted the alex/fix-failed-downloads branch April 2, 2024 08:23
mallexxx added a commit that referenced this pull request Apr 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants