diff --git a/Common/File/DiskFree.cpp b/Common/File/DiskFree.cpp index 5e3cc55204..7caf6216eb 100644 --- a/Common/File/DiskFree.cpp +++ b/Common/File/DiskFree.cpp @@ -22,6 +22,7 @@ #include "Common/File/Path.h" #include "Common/File/AndroidStorage.h" #include "Common/Data/Encoding/Utf8.h" +#include "Common/TimeUtil.h" #if PPSSPP_PLATFORM(UWP) #include "UWP/UWPHelpers/StorageManager.h" @@ -33,8 +34,7 @@ bool free_disk_space(const Path &path, int64_t &space) { #if PPSSPP_PLATFORM(UWP) if (GetDriveFreeSpace(path, space)) { return true; - } - else + } else #endif if (GetDiskFreeSpaceExW(path.ToWString().c_str(), &free, nullptr, nullptr)) { space = free.QuadPart; diff --git a/Common/File/Path.cpp b/Common/File/Path.cpp index dc6d9e92ee..ab9dbbf552 100644 --- a/Common/File/Path.cpp +++ b/Common/File/Path.cpp @@ -251,7 +251,7 @@ bool Path::StartsWith(const Path &other) const { return startsWith(path_, other.path_); } -bool Path::StartsWithGlobal(const Path &other) const { +bool Path::StartsWithGlobalAndNotEqual(const Path &other) const { if (other.empty()) { return true; } @@ -262,9 +262,14 @@ bool Path::StartsWithGlobal(const Path &other) const { if (type_ == PathType::CONTENT_URI) { AndroidContentURI a(path_); AndroidContentURI b(other.path_); - return startsWith(a.GetLastPart(), b.GetLastPart()); + std::string aLast = a.GetLastPart(); + std::string bLast = b.GetLastPart(); + if (aLast == bLast) { + return false; + } + return startsWith(aLast, bLast); } - return StartsWith(other); + return *this != other && StartsWith(other); } const std::string &Path::ToString() const { diff --git a/Common/File/Path.h b/Common/File/Path.h index f649a899c1..5d437a0bcb 100644 --- a/Common/File/Path.h +++ b/Common/File/Path.h @@ -126,8 +126,8 @@ public: // WARNING: This can return wrong results if two ContentURI have different root paths. bool StartsWith(const Path &other) const; - // This handles ContentURI with different paths "correctly". - bool StartsWithGlobal(const Path &other) const; + // This handles ContentURI with different paths "correctly". Very specific use case. + bool StartsWithGlobalAndNotEqual(const Path &other) const; bool operator <(const Path &other) const { return path_ < other.path_; diff --git a/Common/Thread/Promise.h b/Common/Thread/Promise.h index 4b57a3a386..19103f15e5 100644 --- a/Common/Thread/Promise.h +++ b/Common/Thread/Promise.h @@ -44,13 +44,28 @@ public: void Run() override { T value = fun_(); - tx_->Send(value); + if (!cancelled_) { + tx_->Send(value); + } else { + INFO_LOG(Log::System, "PromiseTask ended after cancellation"); + } + } + + bool Cancellable() const override { + return true; + } + + void Cancel() override { + INFO_LOG(Log::System, "PromiseTask cancelled"); + cancelled_ = true; } std::function fun_; Mailbox *tx_; const TaskType type_; const TaskPriority priority_; + + std::atomic cancelled_{}; }; // Represents pending or actual data. @@ -70,6 +85,7 @@ public: PromiseTask *task = new PromiseTask(fun, mailbox, taskType, taskPriority); threadman->EnqueueTask(task); + promise->task_ = task; return promise; } @@ -89,8 +105,8 @@ public: // Allow an empty promise to spawn, too, in case we want to delay it. void SpawnEmpty(ThreadManager *threadman, std::function fun, TaskType taskType, TaskPriority taskPriority = TaskPriority::NORMAL) { - PromiseTask *task = new PromiseTask(fun, rx_, taskType, taskPriority); - threadman->EnqueueTask(task); + task_ = new PromiseTask(fun, rx_, taskType, taskPriority); + threadman->EnqueueTask(task_); } ~Promise() { @@ -110,10 +126,12 @@ public: if (ready_) { return data_; } else { + _dbg_assert_(rx_); if (rx_->Poll(&data_)) { rx_->Release(); rx_ = nullptr; ready_ = true; + task_ = nullptr; return data_; } else { return nullptr; @@ -128,10 +146,12 @@ public: if (ready_) { return data_; } else { + _dbg_assert_(rx_); data_ = rx_->Wait(); rx_->Release(); rx_ = nullptr; ready_ = true; + task_ = nullptr; return data_; } } @@ -141,6 +161,17 @@ public: rx_->Send(data); } + void Cancel() { + std::lock_guard guard(readyMutex_); + if (!ready_) { + _dbg_assert_(task_); + ready_ = true; + task_->Cancel(); + rx_->Release(); + rx_ = nullptr; + } + } + private: Promise() {} @@ -150,4 +181,5 @@ private: std::mutex readyMutex_; Mailbox *rx_ = nullptr; uint32_t sentinel_ = 0xffc0ffee; + PromiseTask *task_ = nullptr; }; diff --git a/Common/Thread/ThreadManager.h b/Common/Thread/ThreadManager.h index c4b2e7a50d..6afa564e85 100644 --- a/Common/Thread/ThreadManager.h +++ b/Common/Thread/ThreadManager.h @@ -26,7 +26,7 @@ public: virtual TaskType Type() const = 0; virtual TaskPriority Priority() const = 0; virtual void Run() = 0; - virtual bool Cancellable() { return false; } + virtual bool Cancellable() const { return false; } virtual void Cancel() {} virtual uint64_t id() { return 0; } virtual void Release() { delete this; } diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index 3563de60d1..31a197407e 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -460,24 +460,36 @@ ConfirmMemstickMoveScreen::ConfirmMemstickMoveScreen(const Path &newMemstickFold : newMemstickFolder_(newMemstickFolder), initialSetup_(initialSetup), progressReporter_() { const Path &oldMemstickFolder = g_Config.memStickDirectory; existingFilesInNewFolder_ = FolderSeemsToBeUsed(newMemstickFolder); - folderConflict_ = newMemstickFolder.StartsWithGlobal(oldMemstickFolder); + if (!oldMemstickFolder.empty()) { + folderConflict_ = newMemstickFolder != oldMemstickFolder && newMemstickFolder.StartsWithGlobalAndNotEqual(oldMemstickFolder); + } else { + folderConflict_ = false; + } + INFO_LOG(Log::System, "Old: '%s'", oldMemstickFolder.c_str()); + INFO_LOG(Log::System, "New: '%s'", newMemstickFolder.c_str()); + + // TODO: If you reinstall the app and start by selecting a subfolder of the PSP folder, we don't detect and warn about that - + // we can only warn if there's a known previous folder :( + if (initialSetup_) { moveData_ = false; } } ConfirmMemstickMoveScreen::~ConfirmMemstickMoveScreen() { + // We should no longer end up blocking here since the back button is disabled until the tasks are done. if (moveDataTask_) { INFO_LOG(Log::System, "Move Data task still running, blocking on it"); moveDataTask_->BlockUntilReady(); delete moveDataTask_; } + // These we just cancel / leak. if (oldSpaceTask_) { - oldSpaceTask_->BlockUntilReady(); + oldSpaceTask_->Cancel(); delete oldSpaceTask_; } if (newSpaceTask_) { - newSpaceTask_->BlockUntilReady(); + newSpaceTask_->Cancel(); delete newSpaceTask_; } } @@ -504,11 +516,14 @@ void ConfirmMemstickMoveScreen::CreateViews() { } leftColumn->Add(new TextView(newMemstickFolder_.ToVisualString(), ALIGN_LEFT, false)); - newFreeSpaceView_ = leftColumn->Add(new TextView(ms->T("Free space"), ALIGN_LEFT, false)); + // TODO: Add spinner + newFreeSpaceView_ = leftColumn->Add(new TextView(ApplySafeSubstitutions("%1: ...", ms->T("Free space")), ALIGN_LEFT, false)); - newSpaceTask_ = Promise::Spawn(&g_threadManager, [&]() -> SpaceResult * { + Path newMemstickFolder = newMemstickFolder_; + newSpaceTask_ = Promise::Spawn(&g_threadManager, [newMemstickFolder]() -> SpaceResult * { int64_t freeSpaceNew; - free_disk_space(newMemstickFolder_, freeSpaceNew); + INFO_LOG(Log::System, "Computing free space in %s", newMemstickFolder.c_str()); + free_disk_space(newMemstickFolder, freeSpaceNew); return new SpaceResult{ freeSpaceNew }; }, TaskType::IO_BLOCKING, TaskPriority::HIGH); @@ -528,15 +543,16 @@ void ConfirmMemstickMoveScreen::CreateViews() { } if (!oldMemstickFolder.empty()) { - oldSpaceTask_ = Promise::Spawn(&g_threadManager, [&]() -> SpaceResult * { + oldSpaceTask_ = Promise::Spawn(&g_threadManager, [oldMemstickFolder]() -> SpaceResult * { int64_t freeSpaceOld; + INFO_LOG(Log::System, "Computing free space in %s", oldMemstickFolder.c_str()); free_disk_space(oldMemstickFolder, freeSpaceOld); return new SpaceResult{ freeSpaceOld }; }, TaskType::IO_BLOCKING, TaskPriority::HIGH); rightColumn->Add(new TextView(std::string(ms->T("Current")) + ":", ALIGN_LEFT, false)); rightColumn->Add(new TextView(oldMemstickFolder.ToVisualString(), ALIGN_LEFT, false)); - oldFreeSpaceView_ = rightColumn->Add(new TextView(ms->T("Free space"), ALIGN_LEFT, false)); + oldFreeSpaceView_ = rightColumn->Add(new TextView(ApplySafeSubstitutions("%1: ...", ms->T("Free space")), ALIGN_LEFT, false)); } if (moveDataTask_) { @@ -556,14 +572,6 @@ void ConfirmMemstickMoveScreen::CreateViews() { if (moveDataTask_ && !moveDataTask_->Poll()) { return UI::EVENT_DONE; } - if (newSpaceTask_ && !newSpaceTask_->Poll()) { - // TODO: we should detach/cancel the task somehow instead.. - return UI::EVENT_DONE; - } - if (oldSpaceTask_ && !oldSpaceTask_->Poll()) { - // TODO: we should detach/cancel the task somehow instead.. - return UI::EVENT_DONE; - } return UIScreen::OnBack(params); }); } diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index 1871b2705f..56cad6fae6 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -2,6 +2,7 @@ package org.ppsspp.ppsspp; import android.annotation.TargetApi; import android.app.AlertDialog; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; @@ -9,6 +10,8 @@ import android.os.Bundle; import android.os.Environment; import android.os.Looper; import android.os.ParcelFileDescriptor; +import android.os.StatFs; +import android.os.storage.StorageVolume; import android.provider.MediaStore; import android.util.Log; import android.system.StructStatVfs; @@ -20,6 +23,7 @@ import android.provider.DocumentsContract; import androidx.documentfile.provider.DocumentFile; import java.util.ArrayList; +import java.util.List; import java.util.UUID; import java.io.File; @@ -469,14 +473,13 @@ public class PpssppActivity extends NativeActivity { // The example in Android documentation uses this.getFilesDir for path. // There's also a way to beg the OS for more space, which might clear caches, but // let's just not bother with that for now. + // NOTE: This is really super slow! @TargetApi(Build.VERSION_CODES.M) - - public long contentUriGetFreeStorageSpace(String fileName) { + public long contentUriGetFreeStorageSpaceSlow(Uri uri) { try { - Uri uri = Uri.parse(fileName); ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r"); if (pfd == null) { - Log.w(TAG, "Failed to get free storage space from URI: " + fileName); + Log.w(TAG, "Failed to get free storage space from URI: " + uri.toString()); return -1; } StructStatVfs stats = Os.fstatvfs(pfd.getFileDescriptor()); @@ -490,6 +493,22 @@ public class PpssppActivity extends NativeActivity { return -1; } } + + public long contentUriGetFreeStorageSpace(String str) { + Uri uri = Uri.parse(str); + if (uri == null) { + Log.e(TAG, "Failed to parse uri " + str); + return -1; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return contentUriGetFreeStorageSpaceSlow(uri); + } + + // Too early Android version + return -1; + } + @TargetApi(Build.VERSION_CODES.O) public long filePathGetFreeStorageSpace(String filePath) { try {