From ce01df7c591c3941e2a66bba1f85166d2c6105dd Mon Sep 17 00:00:00 2001 From: nakst <> Date: Thu, 2 Sep 2021 17:42:41 +0100 Subject: [PATCH] introduce CommandPasteTask and EsUserTaskStart --- apps/file_manager/commands.cpp | 156 +++++++++++++++++++-------------- apps/file_manager/folder.cpp | 4 +- apps/file_manager/main.cpp | 8 +- apps/file_manager/string.cpp | 7 ++ apps/file_manager/ui.cpp | 2 + desktop/api.cpp | 44 +++++++++- desktop/desktop.cpp | 15 ++-- desktop/os.header | 5 +- util/api_table.ini | 1 + 9 files changed, 163 insertions(+), 79 deletions(-) diff --git a/apps/file_manager/commands.cpp b/apps/file_manager/commands.cpp index 9988ea3..93670dc 100644 --- a/apps/file_manager/commands.cpp +++ b/apps/file_manager/commands.cpp @@ -62,7 +62,7 @@ void CommandRename(Instance *instance, EsElement *, EsCommand *) { size_t oldPathBytes; char *oldPath = EsStringAllocateAndFormat(&oldPathBytes, "%s%s", STRFMT(instance->folder->path), STRFMT(task->string2)); - FolderPathMoved(instance, { .text = oldPath, .bytes = oldPathBytes }, { .text = newPath, .bytes = newPathBytes }, true); + FolderPathMoved({ .text = oldPath, .bytes = oldPathBytes }, { .text = newPath, .bytes = newPathBytes }, true); EsDirectoryChild information = {}; EsPathQueryInformation(newPath, newPathBytes, &information); @@ -229,11 +229,10 @@ EsError CommandPasteFile(String source, String destinationBase, void **copyBuffe } if (error == ES_SUCCESS) { - if (move) { - FolderFileUpdatedAtPath(source, nullptr); - } - + EsMessageMutexAcquire(); + if (move) FolderFileUpdatedAtPath(source, nullptr); FolderFileUpdatedAtPath(destination, nullptr); + EsMessageMutexRelease(); } done:; @@ -251,62 +250,58 @@ struct PasteOperation { String source, destination; }; -void CommandPaste(Instance *instance, EsElement *, EsCommand *) { - if (EsClipboardHasFormat(ES_CLIPBOARD_PRIMARY, ES_CLIPBOARD_FORMAT_PATH_LIST)) { - // TODO Background task. - // TODO Reporting errors properly. Ask to retry or cancel. - // TODO If the destination file already exists, ask to replace, skip, rename or cancel. - // TODO Other namespace handlers. - // TODO Undo. +struct PasteTask { + // Input: + String destinationBase; + bool move; + char *pathList; + size_t pathListBytes; +}; - void *copyBuffer = nullptr; +void CommandPasteTask(EsGeneric _task) { + // TODO Background task. + // TODO Reporting errors properly. Ask to retry or cancel. + // TODO If the destination file already exists, ask to replace, skip, rename or cancel. + // TODO Other namespace handlers. + // TODO Undo. - size_t bytes; - uint32_t flags; - char *pathList = EsClipboardReadText(ES_CLIPBOARD_PRIMARY, &bytes, &flags); + PasteTask *task = (PasteTask *) _task.p; + Array pasteOperations = {}; + EsError error = ES_SUCCESS; - bool move = flags & ES_CLIPBOARD_ADD_LAZY_CUT; - String destinationBase = StringDuplicate(instance->folder->path); - Array pasteOperations = {}; + void *copyBuffer = nullptr; + const char *position = task->pathList; - bool success = true; + while (task->pathListBytes) { + const char *newline = (const char *) EsCRTmemchr(position, '\n', task->pathListBytes); + if (!newline) break; - if (pathList) { - const char *position = pathList; + String source = StringFromLiteralWithSize(position, newline - position); + String destination; + error = CommandPasteFile(source, task->destinationBase, ©Buffer, task->move, &destination); + if (error != ES_SUCCESS) break; - while (bytes) { - const char *newline = (const char *) EsCRTmemchr(position, '\n', bytes); - if (!newline) break; + PasteOperation operation = { .source = StringDuplicate(source), .destination = destination }; + pasteOperations.Add(operation); - String source = StringFromLiteralWithSize(position, newline - position); - String destination; + position += source.bytes + 1; + task->pathListBytes -= source.bytes + 1; + } - if (ES_SUCCESS != CommandPasteFile(source, destinationBase, ©Buffer, move, &destination)) { - goto encounteredError; - } + EsMessageMutexAcquire(); - PasteOperation operation = { .source = StringDuplicate(source), .destination = destination }; - pasteOperations.Add(operation); + size_t pathSectionCount = PathCountSections(task->destinationBase); + FolderFileUpdatedAtPath(PathGetDrive(task->destinationBase), nullptr); - position += source.bytes + 1; - bytes -= source.bytes + 1; - } - } else { - encounteredError:; - EsPoint point = EsListViewGetAnnouncementPointForSelection(instance->list); - EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, point.x, point.y, INTERFACE_STRING(CommonAnnouncementPasteErrorOther)); - success = false; - } - - size_t pathSectionCount = PathCountSections(destinationBase); - - for (uintptr_t i = 0; i < pathSectionCount; i++) { - String parent = PathGetParent(destinationBase, i + 1); - FolderFileUpdatedAtPath(parent, nullptr); - } + for (uintptr_t i = 0; i < pathSectionCount; i++) { + String parent = PathGetParent(task->destinationBase, i + 1); + FolderFileUpdatedAtPath(parent, nullptr); + } + if (task->move) { if (pasteOperations.Length()) { size_t pathSectionCount = PathCountSections(pasteOperations[0].source); + FolderFileUpdatedAtPath(PathGetDrive(pasteOperations[0].source), nullptr); for (uintptr_t i = 0; i < pathSectionCount; i++) { String parent = PathGetParent(pasteOperations[0].source, i + 1); @@ -314,28 +309,61 @@ void CommandPaste(Instance *instance, EsElement *, EsCommand *) { } } - if (success) { - EsListViewSelectNone(instance->list); - } - for (uintptr_t i = 0; i < pasteOperations.Length(); i++) { - if (success) { - InstanceSelectByName(instance, PathGetName(pasteOperations[i].destination), true, i == pasteOperations.Length() - 1); + FolderPathMoved(pasteOperations[i].source, pasteOperations[i].destination, i == pasteOperations.Length() - 1); + } + } - if (move) { - // TODO We must do this regardless of whether the instance has been destroyed during the operation. - FolderPathMoved(instance, pasteOperations[i].source, pasteOperations[i].destination, i == pasteOperations.Length() - 1); + for (uintptr_t i = 0; i < instances.Length(); i++) { + Instance *instance = instances[i]; + + if (instance->issuedPasteTask == task) { + instance->issuedPasteTask = nullptr; + + if (error != ES_SUCCESS) { + EsPoint point = EsListViewGetAnnouncementPointForSelection(instance->list); + EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, point.x, point.y, INTERFACE_STRING(CommonAnnouncementPasteErrorOther)); + } else { + EsListViewSelectNone(instance->list); + + for (uintptr_t i = 0; i < pasteOperations.Length(); i++) { + String name = PathRemoveTrailingSlash(PathGetName(pasteOperations[i].destination)); + InstanceSelectByName(instance, name, true, i == pasteOperations.Length() - 1); } } - - StringDestroy(&pasteOperations[i].source); - StringDestroy(&pasteOperations[i].destination); } + } - EsHeapFree(pathList); - EsHeapFree(copyBuffer); - StringDestroy(&destinationBase); - pasteOperations.Free(); + EsMessageMutexRelease(); + + for (uintptr_t i = 0; i < pasteOperations.Length(); i++) { + StringDestroy(&pasteOperations[i].source); + StringDestroy(&pasteOperations[i].destination); + } + + pasteOperations.Free(); + EsHeapFree(copyBuffer); + EsHeapFree(task->pathList); + StringDestroy(&task->destinationBase); + EsHeapFree(task); +} + +void CommandPaste(Instance *instance, EsElement *, EsCommand *) { + if (EsClipboardHasFormat(ES_CLIPBOARD_PRIMARY, ES_CLIPBOARD_FORMAT_PATH_LIST)) { + PasteTask *task = (PasteTask *) EsHeapAllocate(sizeof(PasteTask), true); + uint32_t flags; + task->pathList = EsClipboardReadText(ES_CLIPBOARD_PRIMARY, &task->pathListBytes, &flags); + task->move = flags & ES_CLIPBOARD_ADD_LAZY_CUT; + task->destinationBase = StringDuplicate(instance->folder->path); + instance->issuedPasteTask = task; + + if (ES_SUCCESS != EsUserTaskStart(CommandPasteTask, task)) { + EsPoint point = EsListViewGetAnnouncementPointForSelection(instance->list); + EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, point.x, point.y, INTERFACE_STRING(CommonAnnouncementPasteErrorOther)); + EsHeapFree(task->pathList); + StringDestroy(&task->destinationBase); + EsHeapFree(task); + } } else { // TODO Paste the data into a new file. } diff --git a/apps/file_manager/folder.cpp b/apps/file_manager/folder.cpp index 74d5681..a92ea9f 100644 --- a/apps/file_manager/folder.cpp +++ b/apps/file_manager/folder.cpp @@ -469,8 +469,8 @@ void FolderFileUpdatedAtPath(String path, Instance *instance) { } } -void FolderPathMoved(Instance *instance, String oldPath, String newPath, bool saveConfiguration) { - _EsPathAnnouncePathMoved(instance, STRING(oldPath), STRING(newPath)); +void FolderPathMoved(String oldPath, String newPath, bool saveConfiguration) { + _EsPathAnnouncePathMoved(STRING(oldPath), STRING(newPath)); for (uintptr_t i = 0; i < loadedFolders.Length(); i++) { Folder *folder = loadedFolders[i]; diff --git a/apps/file_manager/main.cpp b/apps/file_manager/main.cpp index 4eaafae..e4104ce 100644 --- a/apps/file_manager/main.cpp +++ b/apps/file_manager/main.cpp @@ -168,9 +168,11 @@ struct Instance : EsInstance { FolderViewSettings viewSettings; - // Blocking task thread. - // Tasks that block the use of the instance, - // but display progress and can be (optionally) cancelled. + // Asynchronous tasks. + + struct PasteTask *issuedPasteTask; + + // Tasks that block the use of the instance, but display progress and can be (optionally) cancelled. // Shows the dialog after some threshold. #define BLOCKING_TASK_DIALOG_THRESHOLD_MS (100) Task blockingTask; diff --git a/apps/file_manager/string.cpp b/apps/file_manager/string.cpp index 06fc845..962b3f3 100644 --- a/apps/file_manager/string.cpp +++ b/apps/file_manager/string.cpp @@ -192,6 +192,13 @@ String PathGetName(String path) { return path; } +String PathGetDrive(String path) { + uintptr_t i = 0; + while (i < path.bytes && path.text[i] != '/') i++; + path.bytes = i; + return path; +} + bool PathHasPrefix(String path, String prefix) { prefix = PathRemoveTrailingSlash(prefix); return StringStartsWith(path, prefix) && path.bytes > prefix.bytes && path.text[prefix.bytes] == '/'; diff --git a/apps/file_manager/ui.cpp b/apps/file_manager/ui.cpp index 3550b6d..a14deb1 100644 --- a/apps/file_manager/ui.cpp +++ b/apps/file_manager/ui.cpp @@ -29,6 +29,8 @@ bool InstanceLoadFolder(Instance *instance, String path /* takes ownership */, i return true; } + instance->issuedPasteTask = nullptr; + InstanceRemoveContents(instance); FolderAttachInstance(instance, path, false); StringDestroy(&path); diff --git a/desktop/api.cpp b/desktop/api.cpp index 0666a54..5ae2a96 100644 --- a/desktop/api.cpp +++ b/desktop/api.cpp @@ -133,6 +133,8 @@ struct { uintptr_t performanceTimerStackCount; ThreadLocalStorage firstThreadLocalStorage; + + size_t openInstanceCount; // Also counts user tasks. } api; ptrdiff_t tlsStorageOffset; @@ -244,6 +246,7 @@ MountPoint *NodeFindMountPoint(const char *prefix, size_t prefixBytes) { bool EsMountPointGetVolumeInformation(const char *prefix, size_t prefixBytes, EsVolumeInformation *information) { MountPoint *mountPoint = NodeFindMountPoint(prefix, prefixBytes); if (!mountPoint) return false; + EsSyscall(ES_SYSCALL_VOLUME_GET_INFORMATION, mountPoint->base, (uintptr_t) &mountPoint->information, 0, 0); EsMemoryCopy(information, &mountPoint->information, sizeof(EsVolumeInformation)); return true; } @@ -513,7 +516,7 @@ int EsMessageSend(EsElement *element, EsMessage *message) { return response; } -void _EsPathAnnouncePathMoved(EsInstance *instance, const char *oldPath, ptrdiff_t oldPathBytes, const char *newPath, ptrdiff_t newPathBytes) { +void _EsPathAnnouncePathMoved(const char *oldPath, ptrdiff_t oldPathBytes, const char *newPath, ptrdiff_t newPathBytes) { if (oldPathBytes == -1) oldPathBytes = EsCStringLength(oldPath); if (newPathBytes == -1) newPathBytes = EsCStringLength(newPath); size_t bufferBytes = 1 + sizeof(uintptr_t) * 2 + oldPathBytes + newPathBytes; @@ -523,7 +526,7 @@ void _EsPathAnnouncePathMoved(EsInstance *instance, const char *oldPath, ptrdiff EsMemoryCopy(buffer + 1 + sizeof(uintptr_t), &newPathBytes, sizeof(uintptr_t)); EsMemoryCopy(buffer + 1 + sizeof(uintptr_t) * 2, oldPath, oldPathBytes); EsMemoryCopy(buffer + 1 + sizeof(uintptr_t) * 2 + oldPathBytes, newPath, newPathBytes); - MessageDesktop(buffer, bufferBytes, instance->window->handle); + MessageDesktop(buffer, bufferBytes); EsHeapFree(buffer); } @@ -675,6 +678,7 @@ EsInstance *_EsInstanceCreate(size_t bytes, EsMessage *message, const char *appl APIInstance *apiInstance = InstanceSetup(instance); apiInstance->applicationName = applicationName; apiInstance->applicationNameBytes = applicationNameBytes; + api.openInstanceCount++; if (message && message->createInstance.data != ES_INVALID_HANDLE && message->createInstance.dataBytes > 1) { apiInstance->startupInformation = (EsApplicationStartupInformation *) EsHeapAllocate(message->createInstance.dataBytes, false); @@ -781,9 +785,12 @@ EsMessage *EsMessageReceive() { } else if (message.message.type == ES_MSG_APPLICATION_EXIT) { EsProcessTerminateCurrent(); } else if (message.message.type == ES_MSG_INSTANCE_DESTROY) { + api.openInstanceCount--; + APIInstance *instance = (APIInstance *) message.message.instanceDestroy.instance->_private; - if (instance->startupInformation && (instance->startupInformation->flags & ES_APPLICATION_STARTUP_SINGLE_INSTANCE_IN_PROCESS)) { + if (instance->startupInformation && (instance->startupInformation->flags & ES_APPLICATION_STARTUP_SINGLE_INSTANCE_IN_PROCESS) + && !api.openInstanceCount) { EsMessage m = { ES_MSG_APPLICATION_EXIT }; EsMessagePost(nullptr, &m); } @@ -1540,6 +1547,37 @@ const void *EsEmbeddedFileGet(const char *_name, ptrdiff_t nameBytes, size_t *by return nullptr; } +struct UserTask { + EsUserTaskCallbackFunction callback; + EsGeneric data; +}; + +void UserTaskThread(EsGeneric _task) { + UserTask *task = (UserTask *) _task.p; + task->callback(task->data); + EsMessageMutexAcquire(); + api.openInstanceCount--; + // TODO Send ES_MSG_APPLICATION_EXIT if needed. + // TODO Tell Desktop the task is complete. + EsMessageMutexRelease(); + EsHeapFree(task); +} + +EsError EsUserTaskStart(EsUserTaskCallbackFunction callback, EsGeneric data) { + EsMessageMutexCheck(); + UserTask *task = (UserTask *) EsHeapAllocate(sizeof(UserTask), true); + if (!task) return ES_ERROR_INSUFFICIENT_RESOURCES; + task->callback = callback; + task->data = data; + // TODO Tell Desktop about the task. (This'll also prevent it sending ES_MSG_APPLICATION_EXIT in single process mode.) + api.openInstanceCount++; + EsThreadInformation information; + EsError error = EsThreadCreate(UserTaskThread, &information, task); + if (error == ES_SUCCESS) EsHandleClose(information.handle); + else EsHeapFree(task); + return error; +} + void TimersThread(EsGeneric) { // TODO Maybe terminate this thread after ~10 seconds of no timers? diff --git a/desktop/desktop.cpp b/desktop/desktop.cpp index 8bbe113..245f55a 100644 --- a/desktop/desktop.cpp +++ b/desktop/desktop.cpp @@ -1442,8 +1442,9 @@ void ApplicationInstanceRequestSave(ApplicationInstance *instance, const char *n EsMessagePostRemote(instance->processHandle, &m); } -void InstanceAnnouncePathMoved(ApplicationInstance *fromInstance, const uint8_t *buffer, size_t embedWindowMessageBytes) { +void InstanceAnnouncePathMoved(InstalledApplication *fromApplication, const uint8_t *buffer, size_t embedWindowMessageBytes) { // TODO Update the location of installed applications and other things in the configuration. + // TODO Replace fromApplication with something better. uintptr_t oldPathBytes, newPathBytes; EsMemoryCopy(&oldPathBytes, buffer + 1, sizeof(uintptr_t)); @@ -1491,7 +1492,7 @@ void InstanceAnnouncePathMoved(ApplicationInstance *fromInstance, const uint8_t ApplicationInstance *instance = desktop.allApplicationInstances[i]; if (instance->documentID != documentID) continue; - if (instance->application == fromInstance->application) continue; + if (instance->application == fromApplication) continue; if (!instance->processHandle) continue; EsMessage m = { ES_MSG_INSTANCE_DOCUMENT_RENAMED }; @@ -2038,6 +2039,12 @@ void DesktopMessage2(EsMessage *message, uint8_t *buffer, EsBuffer *pipe) { if (application && (application->permissions & APPLICATION_PERMISSION_VIEW_FILE_TYPES)) { ConfigurationWriteSectionsToBuffer("file_type", nullptr, false, pipe); } + } else if (buffer[0] == DESKTOP_MSG_ANNOUNCE_PATH_MOVED && message->desktop.bytes > 1 + sizeof(uintptr_t) * 2) { + InstalledApplication *application = ApplicationFindByPID(message->desktop.processID); + + if (application && (application->permissions & APPLICATION_PERMISSION_ALL_FILES)) { + InstanceAnnouncePathMoved(application, buffer, message->desktop.bytes); + } } else if (!instance) { // ------------------------------------------------- // | Messages below here require a valid instance. | @@ -2072,10 +2079,6 @@ void DesktopMessage2(EsMessage *message, uint8_t *buffer, EsBuffer *pipe) { startupInformation.filePathBytes = document->pathBytes; ApplicationInstanceCreate(desktop.fileManager->id, &startupInformation, instance->tab->container); } - } else if (buffer[0] == DESKTOP_MSG_ANNOUNCE_PATH_MOVED - && (instance->application->permissions & APPLICATION_PERMISSION_ALL_FILES) - && message->desktop.bytes > 1 + sizeof(uintptr_t) * 2) { - InstanceAnnouncePathMoved(instance, buffer, message->desktop.bytes); } else if (buffer[0] == DESKTOP_MSG_RUN_TEMPORARY_APPLICATION) { if (instance->application && (instance->application->permissions & APPLICATION_PERMISSION_RUN_TEMPORARY_APPLICATION)) { InstalledApplication *application = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true); diff --git a/desktop/os.header b/desktop/os.header index ff28905..6f6666e 100644 --- a/desktop/os.header +++ b/desktop/os.header @@ -1887,6 +1887,7 @@ function_pointer void EsUndoCallback(const void *item, EsUndoManager *manager, E function_pointer void EsMountPointEnumerationCallbackFunction(const char *prefix, size_t prefixBytes, EsGeneric context); function_pointer void EsListViewEnumerateVisibleItemsCallbackFunction(EsListView *view, EsElement *item, uint32_t group, EsGeneric index); function_pointer void EsFontEnumerationCallbackFunction(const EsFontInformation *information, EsGeneric context); +function_pointer void EsUserTaskCallbackFunction(EsGeneric data); // System. @@ -1958,7 +1959,7 @@ function void *EsFileStoreMap(EsFileStore *file, size_t *fileSize, uint32_t flag // Requires permission_all_files. function bool EsMountPointGetVolumeInformation(const char *prefix, size_t prefixBytes, EsVolumeInformation *information); // Returns false if the mount point does not exist. function void EsMountPointEnumerate(EsMountPointEnumerationCallbackFunction callback, EsGeneric context); -function void _EsPathAnnouncePathMoved(EsInstance *instance, STRING oldPath, STRING newPath); +function void _EsPathAnnouncePathMoved(STRING oldPath, STRING newPath); // Processes and threads. @@ -2264,6 +2265,8 @@ function const EsApplicationStartupInformation *EsInstanceGetStartupInformation( function void EsInstanceOpenComplete(EsMessage *message, bool success, STRING errorText = BLANK_STRING); function void EsInstanceSaveComplete(EsMessage *message, bool success); +function EsError EsUserTaskStart(EsUserTaskCallbackFunction callback, EsGeneric data); + // Message processing. function size_t EsMessageGetInputText(EsMessage *message, char *buffer); // The buffer should be 64 bytes in size. diff --git a/util/api_table.ini b/util/api_table.ini index 4d4235b..993029d 100644 --- a/util/api_table.ini +++ b/util/api_table.ini @@ -432,3 +432,4 @@ EsClipboardHasData=430 EsFileCopy=431 EsListViewSelectNone=432 EsElementIsFocused=433 +EsUserTaskStart=434