introduce CommandPasteTask and EsUserTaskStart

This commit is contained in:
nakst 2021-09-02 17:42:41 +01:00
parent e8d219033e
commit ce01df7c59
9 changed files with 163 additions and 79 deletions

View File

@ -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<PasteOperation> pasteOperations = {};
EsError error = ES_SUCCESS;
bool move = flags & ES_CLIPBOARD_ADD_LAZY_CUT;
String destinationBase = StringDuplicate(instance->folder->path);
Array<PasteOperation> 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, &copyBuffer, 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, &copyBuffer, 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.
}

View File

@ -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];

View File

@ -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;

View File

@ -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] == '/';

View File

@ -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);

View File

@ -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?

View File

@ -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);

View File

@ -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.

View File

@ -432,3 +432,4 @@ EsClipboardHasData=430
EsFileCopy=431
EsListViewSelectNone=432
EsElementIsFocused=433
EsUserTaskStart=434