mirror of https://gitlab.com/nakst/essence
682 lines
20 KiB
C++
682 lines
20 KiB
C++
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
#define ES_INSTANCE_TYPE Instance
|
|
#include <essence.h>
|
|
#include <shared/strings.cpp>
|
|
#include <shared/hash_table.cpp>
|
|
#include <shared/array.cpp>
|
|
#include <shared/arena.cpp>
|
|
|
|
// TODO Possible candidates for moving in the core API:
|
|
// - String/paths utils
|
|
// - Blocking task systems
|
|
|
|
// TODO Don't show modals if a folder can't be loaded.
|
|
// Instead, show a list view with an error message,
|
|
// and disable interactions.
|
|
|
|
#define SETTINGS_FILE "|Settings:/Default.ini"
|
|
|
|
#define ERROR_LOAD_FOLDER (1)
|
|
#define ERROR_NEW_FOLDER (2)
|
|
#define ERROR_RENAME_ITEM (3)
|
|
|
|
#define PLACES_VIEW_GROUP_BOOKMARKS (0)
|
|
#define PLACES_VIEW_GROUP_DRIVES (1)
|
|
|
|
#define MESSAGE_BLOCKING_TASK_COMPLETE ((EsMessageType) (ES_MSG_USER_START + 1))
|
|
#define MESSAGE_NON_BLOCKING_TASK_COMPLETE ((EsMessageType) (ES_MSG_USER_START + 2))
|
|
|
|
#define VIEW_DETAILS (0)
|
|
#define VIEW_TILES (1)
|
|
#define VIEW_THUMBNAILS (2)
|
|
|
|
const char *errorTypeStrings[] = {
|
|
interfaceString_FileManagerUnknownError,
|
|
interfaceString_FileManagerOpenFolderError,
|
|
interfaceString_FileManagerNewFolderError,
|
|
interfaceString_FileManagerRenameItemError,
|
|
};
|
|
|
|
#include "string.cpp"
|
|
|
|
#define COLUMN_NAME (0)
|
|
#define COLUMN_TYPE (1)
|
|
#define COLUMN_SIZE (2)
|
|
|
|
#define LOAD_FOLDER_BACK (1)
|
|
#define LOAD_FOLDER_FORWARD (2)
|
|
#define LOAD_FOLDER_START (3)
|
|
#define LOAD_FOLDER_REFRESH (4)
|
|
#define LOAD_FOLDER_NO_FOCUS (1 << 8)
|
|
|
|
struct FolderEntry {
|
|
// TODO Can this structure be made smaller?
|
|
uint16_t handles;
|
|
bool isFolder, sizeUnknown, guessedContentType;
|
|
uint8_t nameBytes, internalNameBytes; // 0 -> 256.
|
|
char *name, *internalName;
|
|
EsFileOffset size, previousSize;
|
|
uint64_t id;
|
|
EsUniqueIdentifier contentType;
|
|
|
|
inline String GetName() {
|
|
return { .text = name, .bytes = nameBytes ?: 256u, .allocated = nameBytes ?: 256u };
|
|
}
|
|
|
|
inline String GetInternalName() {
|
|
return { .text = internalName, .bytes = internalNameBytes ?: 256u, .allocated = internalNameBytes ?: 256u };
|
|
}
|
|
};
|
|
|
|
struct ListEntry {
|
|
FolderEntry *entry;
|
|
bool selected;
|
|
};
|
|
|
|
struct Task {
|
|
EsGeneric context, context2;
|
|
String string, string2;
|
|
uint64_t id;
|
|
EsError result;
|
|
const char *cDescription;
|
|
Instance *instance;
|
|
|
|
void (*callback)(Instance *instance, Task *task);
|
|
void (*then)(Instance *instance, Task *task); // Called on the main thread.
|
|
};
|
|
|
|
struct HistoryEntry {
|
|
String path;
|
|
String focusedItem;
|
|
};
|
|
|
|
struct Drive {
|
|
char *prefix;
|
|
size_t prefixBytes;
|
|
EsObjectID deviceID;
|
|
EsVolumeInformation information;
|
|
};
|
|
|
|
struct FolderViewSettings {
|
|
uint16_t sortColumn;
|
|
uint8_t viewType;
|
|
};
|
|
|
|
struct FolderViewSettingsEntry {
|
|
char path[160];
|
|
FolderViewSettings settings;
|
|
uint8_t pathBytes;
|
|
};
|
|
|
|
struct Thumbnail {
|
|
uint32_t *bits;
|
|
uint32_t width, height;
|
|
uintptr_t referenceCount;
|
|
uint32_t generatingTasksInProgress;
|
|
};
|
|
|
|
struct Instance : EsInstance {
|
|
// Interface elements.
|
|
|
|
EsListView *list;
|
|
EsListView *placesView;
|
|
EsTextbox *breadcrumbBar;
|
|
EsButton *newFolderButton;
|
|
EsTextDisplay *status;
|
|
EsDialog *blockingDialog;
|
|
|
|
union {
|
|
struct {
|
|
EsTextbox *textbox;
|
|
uintptr_t index;
|
|
} rename;
|
|
};
|
|
|
|
// Path and history.
|
|
|
|
String path;
|
|
Array<HistoryEntry> pathBackwardHistory;
|
|
Array<HistoryEntry> pathForwardHistory;
|
|
|
|
// Commands.
|
|
|
|
EsCommand commandGoBackwards, commandGoForwards, commandGoParent;
|
|
EsCommand commandNewFolder, commandRename;
|
|
EsCommand commandViewDetails, commandViewTiles, commandViewThumbnails;
|
|
EsCommand commandRefresh;
|
|
|
|
// Active folder.
|
|
|
|
struct Folder *folder;
|
|
|
|
// Sorted and filtered list contents.
|
|
|
|
Array<ListEntry> listContents;
|
|
|
|
size_t selectedItemCount;
|
|
EsFileOffset selectedItemsTotalSize;
|
|
|
|
String delayedFocusItem;
|
|
|
|
FolderViewSettings viewSettings;
|
|
|
|
// 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;
|
|
volatile bool blockingTaskInProgress, blockingTaskReachedTimeout;
|
|
volatile uint32_t blockingTaskID;
|
|
|
|
bool closed; // The window has been destroyed (or is about to be).
|
|
};
|
|
|
|
struct NamespaceHandler {
|
|
uint8_t type;
|
|
uint8_t rootContainerHandlerType;
|
|
|
|
bool canCut, canCopy, canPaste;
|
|
|
|
bool (*handlesPath)(String path);
|
|
|
|
uint32_t (*getFileType)(String path);
|
|
void (*getVisibleName)(EsBuffer *buffer, String path);
|
|
void (*getTotalSize)(Folder *folder); // Possibly called on the blocking task thread.
|
|
String (*getPathForChild)(Folder *folder, FolderEntry *entry);
|
|
void (*getDefaultViewSettings)(Folder *folder, FolderViewSettings *settings);
|
|
|
|
// Called on the blocking task thread:
|
|
EsError (*createChildFolder)(Folder *folder, String *name, bool findUniqueName); // Optional.
|
|
EsError (*renameItem)(Folder *folder, String oldName, String name); // Optional.
|
|
EsError (*enumerate)(Folder *folder);
|
|
};
|
|
|
|
struct Folder {
|
|
HashTable entries;
|
|
Arena entryArena;
|
|
|
|
Array<Instance *> attachedInstances; // NOTE Check Instance::closed is false before accessing the UI!
|
|
uintptr_t referenceCount;
|
|
|
|
String path;
|
|
bool recurse;
|
|
bool refreshing;
|
|
bool driveRemoved;
|
|
bool readOnly;
|
|
|
|
bool doneInitialEnumeration;
|
|
EsMutex modifyEntriesMutex;
|
|
|
|
EsFileOffset spaceTotal;
|
|
EsFileOffset spaceUsed;
|
|
|
|
NamespaceHandler *itemHandler, *containerHandler;
|
|
const char *cEmptyMessage;
|
|
};
|
|
|
|
void InstanceReportError(struct Instance *instance, int error, EsError code);
|
|
bool InstanceLoadFolder(Instance *instance, String path /* takes ownership */, int historyMode = 0);
|
|
void InstanceUpdateStatusString(Instance *instance);
|
|
void InstanceViewSettingsUpdated(Instance *instance);
|
|
void InstanceRefreshViewType(Instance *instance);
|
|
void InstanceFolderPathChanged(Instance *instance, bool fromLoadFolder);
|
|
void InstanceAddContents(struct Instance *instance, HashTable *newEntries);
|
|
void InstanceAddSingle(struct Instance *instance, ListEntry newEntry);
|
|
void InstanceRemoveContents(struct Instance *instance);
|
|
void InstanceSelectByName(Instance *instance, String name, bool addToExistingSelection, bool focusItem);
|
|
ListEntry InstanceRemoveSingle(Instance *instance, FolderEntry *folderEntry);
|
|
ListEntry *InstanceGetSelectedListEntry(Instance *instance);
|
|
void ListItemCreated(EsElement *element, uintptr_t index, bool fromFolderRename);
|
|
FolderEntry *FolderAddEntry(Folder *folder, const char *_name, size_t nameBytes, EsDirectoryChild *information, uint64_t id = 0);
|
|
void FolderAddEntries(Folder *folder, EsDirectoryChild *buffer, size_t entryCount);
|
|
uint32_t NamespaceDefaultGetFileType(String);
|
|
void ThumbnailGenerateIfNeeded(Folder *folder, FolderEntry *entry, bool fromFolderRename, bool modified);
|
|
|
|
Array<Drive> drives;
|
|
EsMutex drivesMutex;
|
|
|
|
Array<Instance *> instances; // NOTE Check Instance::closed is false before accessing the UI!
|
|
|
|
Array<String> bookmarks;
|
|
#define FOLDER_VIEW_SETTINGS_MAXIMUM_ENTRIES (10000)
|
|
Array<FolderViewSettingsEntry> folderViewSettings;
|
|
HashStore<uint64_t, Thumbnail> thumbnailCache;
|
|
|
|
Array<String> openDocuments;
|
|
|
|
// Styles.
|
|
|
|
const EsStyle styleFolderView = {
|
|
.inherit = ES_STYLE_LIST_VIEW,
|
|
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_MINIMUM_WIDTH | ES_THEME_METRICS_PREFERRED_WIDTH,
|
|
.preferredWidth = 200,
|
|
.minimumWidth = 150,
|
|
},
|
|
};
|
|
|
|
const EsStyle styleFolderViewTiled = {
|
|
.inherit = ES_STYLE_LIST_VIEW,
|
|
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_MINIMUM_WIDTH | ES_THEME_METRICS_PREFERRED_WIDTH | ES_THEME_METRICS_GAP_WRAP | ES_THEME_METRICS_GAP_MINOR,
|
|
.preferredWidth = 200,
|
|
.minimumWidth = 150,
|
|
.gapMinor = 5,
|
|
.gapWrap = 5,
|
|
},
|
|
};
|
|
|
|
const EsStyle styleFolderItemThumbnail = {
|
|
.inherit = ES_STYLE_LIST_ITEM_TILE,
|
|
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_PREFERRED_WIDTH | ES_THEME_METRICS_PREFERRED_HEIGHT
|
|
| ES_THEME_METRICS_ICON_SIZE | ES_THEME_METRICS_LAYOUT_VERTICAL | ES_THEME_METRICS_INSETS | ES_THEME_METRICS_TEXT_ALIGN,
|
|
.insets = ES_RECT_2(5, 10),
|
|
.preferredWidth = 170,
|
|
.preferredHeight = 150,
|
|
.textAlign = ES_TEXT_H_CENTER | ES_TEXT_V_CENTER | ES_TEXT_ELLIPSIS,
|
|
.iconSize = 80,
|
|
.layoutVertical = true,
|
|
},
|
|
};
|
|
|
|
const EsStyle stylePlacesView = {
|
|
.inherit = ES_STYLE_LIST_VIEW,
|
|
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_MINIMUM_WIDTH | ES_THEME_METRICS_PREFERRED_WIDTH | ES_THEME_METRICS_INSETS
|
|
| ES_THEME_METRICS_GAP_WRAP | ES_THEME_METRICS_GAP_MAJOR,
|
|
.insets = ES_RECT_1(8),
|
|
.preferredWidth = 200,
|
|
.minimumWidth = 150,
|
|
.gapMajor = 16,
|
|
.gapWrap = 16,
|
|
},
|
|
};
|
|
|
|
void BlockingTaskThread(EsGeneric _instance) {
|
|
Instance *instance = (Instance *) _instance.p;
|
|
instance->blockingTask.callback(instance, &instance->blockingTask);
|
|
EsMessage m = { MESSAGE_BLOCKING_TASK_COMPLETE };
|
|
m.user.context1.p = instance;
|
|
m.user.context2.u = instance->blockingTaskID;
|
|
EsMessagePost(nullptr, &m);
|
|
}
|
|
|
|
void BlockingTaskComplete(Instance *instance) {
|
|
EsAssert(instance->blockingTaskInProgress); // Task should have been in progress.
|
|
instance->blockingTaskInProgress = false;
|
|
|
|
if (instance->blockingTaskReachedTimeout && instance->blockingDialog) {
|
|
EsDialogClose(instance->blockingDialog);
|
|
}
|
|
|
|
instance->blockingDialog = nullptr;
|
|
Task *task = &instance->blockingTask;
|
|
|
|
if (task->then) {
|
|
task->then(instance, task);
|
|
}
|
|
|
|
EsInstanceCloseReference(instance);
|
|
}
|
|
|
|
void BlockingTaskQueue(Instance *instance, Task task) {
|
|
EsAssert(!instance->blockingTaskInProgress); // Cannot queue a new blocking task if the previous has not finished.
|
|
|
|
EsInstanceOpenReference(instance); // Don't destroy the instance while the task is in progress.
|
|
|
|
instance->blockingTask = task;
|
|
instance->blockingTaskInProgress = true;
|
|
|
|
EsThreadInformation thread;
|
|
EsThreadCreate(BlockingTaskThread, &thread, instance);
|
|
|
|
ptrdiff_t result = EsWait(&thread.handle, 1, BLOCKING_TASK_DIALOG_THRESHOLD_MS);
|
|
EsHandleClose(thread.handle);
|
|
|
|
if (result == ES_ERROR_TIMEOUT_REACHED) {
|
|
instance->blockingTaskReachedTimeout = true;
|
|
instance->blockingDialog = EsDialogShow(instance->window, task.cDescription, -1,
|
|
INTERFACE_STRING(FileManagerOngoingTaskDescription), ES_ICON_TOOLS_TIMER_SYMBOLIC);
|
|
// TODO Progress bar; cancelling tasks.
|
|
} else {
|
|
instance->blockingTaskReachedTimeout = false;
|
|
BlockingTaskComplete(instance);
|
|
instance->blockingTaskID++; // Prevent the task being completed twice.
|
|
}
|
|
}
|
|
|
|
void NonBlockingTaskWrapper(EsGeneric _task) {
|
|
Task *task = (Task *) _task.p;
|
|
task->callback(nullptr, task);
|
|
EsMessage m = { MESSAGE_NON_BLOCKING_TASK_COMPLETE };
|
|
m.user.context2.p = task;
|
|
EsMessagePost(nullptr, &m);
|
|
}
|
|
|
|
void NonBlockingTaskQueue(Task _task) {
|
|
// NOTE We can't store instances in tasks on the non-blocking queue thread,
|
|
// because the instances might be destroyed while the task is in progress!
|
|
Task *task = (Task *) EsHeapAllocate(sizeof(Task), false);
|
|
EsMemoryCopy(task, &_task, sizeof(Task));
|
|
EsWorkQueue(NonBlockingTaskWrapper, task);
|
|
}
|
|
|
|
void NonBlockingTaskComplete(EsMessage *message) {
|
|
Task *task = (Task *) message->user.context2.p;
|
|
if (task->then) task->then(nullptr, task);
|
|
EsHeapFree(task);
|
|
}
|
|
|
|
void ConfigurationSave() {
|
|
EsBuffer buffer = {};
|
|
buffer.canGrow = true;
|
|
|
|
EsBufferFormat(&buffer, "[bookmarks]\n");
|
|
|
|
for (uintptr_t i = 0; i < bookmarks.Length(); i++) {
|
|
EsBufferFormat(&buffer, "=%s\n", STRFMT(bookmarks[i]));
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < folderViewSettings.Length(); i++) {
|
|
FolderViewSettingsEntry *entry = &folderViewSettings[i];
|
|
EsBufferFormat(&buffer, "\n[folder]\npath=%z\nsort_column=%d\nview_type=%d\n",
|
|
entry->path, entry->settings.sortColumn, entry->settings.viewType);
|
|
}
|
|
|
|
EsFileWriteAll(EsLiteral(SETTINGS_FILE), buffer.out, buffer.position);
|
|
EsHeapFree(buffer.out);
|
|
}
|
|
|
|
#include "type_database.cpp"
|
|
#include "folder.cpp"
|
|
#include "commands.cpp"
|
|
#include "ui.cpp"
|
|
|
|
void DriveRefreshFolders(bool removed, const char *prefix, size_t prefixBytes) {
|
|
Array<Folder *> foldersToRefresh = {};
|
|
|
|
for (uintptr_t i = 0; i < loadedFolders.Length(); i++) {
|
|
Folder *folder = loadedFolders[i];
|
|
|
|
if (folder->itemHandler->type == NAMESPACE_HANDLER_DRIVES_PAGE || StringStartsWith(folder->path, StringFromLiteralWithSize(prefix, prefixBytes))) {
|
|
foldersToRefresh.Add(folder);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < foldersToRefresh.Length(); i++) {
|
|
foldersToRefresh[i]->driveRemoved = removed;
|
|
FolderRefresh(foldersToRefresh[i]);
|
|
}
|
|
|
|
foldersToRefresh.Free();
|
|
}
|
|
|
|
void DriveRemove(EsObjectID deviceID) {
|
|
char *prefix = nullptr;
|
|
size_t prefixBytes = 0;
|
|
EsMutexAcquire(&drivesMutex);
|
|
bool found = false;
|
|
|
|
for (uintptr_t index = 0; index < drives.Length(); index++) {
|
|
if (drives[index].deviceID == deviceID) {
|
|
prefix = drives[index].prefix;
|
|
prefixBytes = drives[index].prefixBytes;
|
|
drives.Delete(index);
|
|
found = true;
|
|
|
|
for (uintptr_t i = 0; i < instances.Length(); i++) {
|
|
if (instances[i]->closed) continue;
|
|
EsListViewRemove(instances[i]->placesView, PLACES_VIEW_GROUP_DRIVES, index, 1);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
EsAssert(found);
|
|
EsMutexRelease(&drivesMutex);
|
|
DriveRefreshFolders(true, prefix, prefixBytes);
|
|
EsHeapFree(prefix);
|
|
}
|
|
|
|
void DriveAdd(EsHandle deviceHandle, EsObjectID deviceID) {
|
|
// TODO Proper prefix allocation algorithm for drives.
|
|
static int nextPrefix = 1;
|
|
char prefix[16];
|
|
bool isBootFileSystem = EsDeviceControl(deviceHandle, ES_DEVICE_CONTROL_FS_IS_BOOT, 0, nullptr) == 1;
|
|
size_t prefixBytes = EsStringFormat(prefix, sizeof(prefix), "%fd:", ES_STRING_FORMAT_SIMPLE, isBootFileSystem ? 0 : nextPrefix);
|
|
if (!isBootFileSystem) nextPrefix++;
|
|
|
|
EsMutexAcquire(&drivesMutex);
|
|
|
|
Drive drive = {};
|
|
drive.prefix = (char *) EsHeapAllocate(prefixBytes, false);
|
|
EsMemoryCopy(drive.prefix, prefix, prefixBytes);
|
|
drive.prefixBytes = prefixBytes;
|
|
drive.deviceID = deviceID;
|
|
EsMountPointAdd(prefix, prefixBytes, deviceHandle);
|
|
EsMountPointGetVolumeInformation(prefix, prefixBytes, &drive.information);
|
|
drives.Add(drive);
|
|
|
|
for (uintptr_t i = 0; i < instances.Length(); i++) {
|
|
if (instances[i]->closed) continue;
|
|
EsListViewInsert(instances[i]->placesView, PLACES_VIEW_GROUP_DRIVES, drives.Length(), 1);
|
|
}
|
|
|
|
EsMutexRelease(&drivesMutex);
|
|
DriveRefreshFolders(false, prefix, prefixBytes);
|
|
}
|
|
|
|
void LoadSettings() {
|
|
EsINIState state = { (char *) EsFileReadAll(EsLiteral(SETTINGS_FILE), &state.bytes) };
|
|
FolderViewSettings *folder = nullptr;
|
|
|
|
while (EsINIParse(&state)) {
|
|
if (state.value && 0 == EsStringCompareRaw(state.section, state.sectionBytes, EsLiteral("bookmarks"))) {
|
|
String string = {};
|
|
string.text = state.value, string.bytes = state.valueBytes;
|
|
BookmarkAdd(string, false);
|
|
} else if (0 == EsStringCompareRaw(state.section, state.sectionBytes, EsLiteral("folder"))) {
|
|
if (0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("path"))) {
|
|
if (state.keyBytes < sizeof(folderViewSettings[0].path)) {
|
|
FolderViewSettingsEntry *entry = folderViewSettings.Add();
|
|
EsMemoryCopy(entry->path, state.value, state.valueBytes);
|
|
entry->pathBytes = state.valueBytes;
|
|
folder = &entry->settings;
|
|
}
|
|
} else if (folder && 0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("sort_column"))) {
|
|
folder->sortColumn = EsIntegerParse(state.value, state.valueBytes);
|
|
} else if (folder && 0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("view_type"))) {
|
|
folder->viewType = EsIntegerParse(state.value, state.valueBytes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int InstanceCallback(Instance *instance, EsMessage *message) {
|
|
if (message->type == ES_MSG_INSTANCE_CLOSE) {
|
|
EsAssert(!instance->closed);
|
|
instance->closed = true;
|
|
instance->list = nullptr;
|
|
instance->placesView = nullptr;
|
|
instance->breadcrumbBar = nullptr;
|
|
instance->newFolderButton = nullptr;
|
|
instance->status = nullptr;
|
|
instance->blockingDialog = nullptr;
|
|
} else if (message->type == ES_MSG_INSTANCE_DESTROY) {
|
|
EsApplicationStartupRequest request = EsInstanceGetStartupRequest(instance);
|
|
|
|
if (request.flags & ES_APPLICATION_STARTUP_BACKGROUND_SERVICE) {
|
|
// No cleanup to do.
|
|
} else {
|
|
InstanceDestroy(instance);
|
|
instances.FindAndDeleteSwap(instance, true);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void _start() {
|
|
_init();
|
|
|
|
AddKnownFileTypes();
|
|
LoadSettings();
|
|
|
|
// Enumerate drives.
|
|
|
|
size_t deviceCount;
|
|
EsMessageDevice *devices = EsDeviceEnumerate(&deviceCount);
|
|
for (uintptr_t i = 0; i < deviceCount; i++) if (devices[i].type == ES_DEVICE_FILE_SYSTEM) DriveAdd(devices[i].handle, devices[i].id);
|
|
EsHeapFree(devices);
|
|
|
|
// Process messages.
|
|
|
|
while (true) {
|
|
EsMessage *message = EsMessageReceive();
|
|
|
|
if (message->type == ES_MSG_INSTANCE_CREATE) {
|
|
Instance *instance = EsInstanceCreate(message, INTERFACE_STRING(FileManagerTitle));
|
|
instance->callback = InstanceCallback;
|
|
|
|
EsApplicationStartupRequest request = EsInstanceGetStartupRequest(instance);
|
|
|
|
if (request.flags & ES_APPLICATION_STARTUP_BACKGROUND_SERVICE) {
|
|
// Keep the instance alive so that we always receive PATH_MOVED messages and such.
|
|
} else {
|
|
instances.Add(instance);
|
|
InstanceCreateUI(instance);
|
|
}
|
|
} else if (message->type == ES_MSG_APPLICATION_EXIT) {
|
|
#ifdef DEBUG_BUILD
|
|
for (uintptr_t i = 0; i < drives.Length(); i++) {
|
|
EsHeapFree(drives[i].prefix);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < bookmarks.Length(); i++) {
|
|
StringDestroy(&bookmarks[i]);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < thumbnailCache.Count(); i++) {
|
|
EsHeapFree(thumbnailCache[i].bits);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < loadedFolders.Length(); i++) {
|
|
FolderDestroy(loadedFolders[i]);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < openDocuments.Length(); i++) {
|
|
StringDestroy(&openDocuments[i]);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < knownFileTypes.Length(); i++) {
|
|
knownFileTypes[i].applicationEntries.Free();
|
|
}
|
|
|
|
EsAssert(!instances.Length());
|
|
EsHeapFree(fileTypesBuffer.out);
|
|
|
|
openDocuments.Free();
|
|
bookmarks.Free();
|
|
drives.Free();
|
|
folderViewSettings.Free();
|
|
foldersWithNoAttachedInstances.Free();
|
|
instances.Free();
|
|
knownFileTypes.Free();
|
|
knownFileTypesByExtension.Free();
|
|
loadedFolders.Free();
|
|
thumbnailCache.Free();
|
|
#endif
|
|
} else if (message->type == ES_MSG_DEVICE_CONNECTED && message->device.type == ES_DEVICE_FILE_SYSTEM) {
|
|
DriveAdd(message->device.handle, message->device.id);
|
|
} else if (message->type == ES_MSG_DEVICE_DISCONNECTED && message->device.type == ES_DEVICE_FILE_SYSTEM) {
|
|
DriveRemove(message->device.id);
|
|
} else if (message->type == ES_MSG_FILE_MANAGER_FILE_MODIFIED) {
|
|
char *_path = (char *) EsHeapAllocate(message->user.context2.u, false);
|
|
EsConstantBufferRead(message->user.context1.u, _path);
|
|
String fullPath = StringFromLiteralWithSize(_path, message->user.context2.u);
|
|
size_t pathSectionCount = PathCountSections(fullPath);
|
|
|
|
for (uintptr_t i = 0; i < pathSectionCount; i++) {
|
|
FolderFileUpdatedAtPath(PathGetParent(fullPath, i + 1), nullptr, true);
|
|
}
|
|
|
|
EsHandleClose(message->user.context1.u);
|
|
EsHeapFree(_path);
|
|
} else if (message->type == ES_MSG_FILE_MANAGER_PATH_MOVED) {
|
|
char *data = (char *) EsHeapAllocate(message->user.context2.u, false);
|
|
uintptr_t *bytes = (uintptr_t *) data;
|
|
char *paths = data + sizeof(size_t) * 2;
|
|
EsConstantBufferRead(message->user.context1.u, data);
|
|
String oldPath = StringFromLiteralWithSize(paths, bytes[0]);
|
|
String newPath = StringFromLiteralWithSize(paths + bytes[0], bytes[1]);
|
|
size_t pathSectionCount;
|
|
|
|
if (oldPath.bytes) {
|
|
FolderPathMoved(oldPath, newPath, false);
|
|
} else {
|
|
FolderRefreshAllFrom(newPath);
|
|
}
|
|
|
|
pathSectionCount = PathCountSections(oldPath);
|
|
|
|
for (uintptr_t i = 0; i < pathSectionCount; i++) {
|
|
FolderFileUpdatedAtPath(PathGetParent(oldPath, i + 1), nullptr);
|
|
}
|
|
|
|
pathSectionCount = PathCountSections(newPath);
|
|
|
|
for (uintptr_t i = 0; i < pathSectionCount; i++) {
|
|
FolderFileUpdatedAtPath(PathGetParent(newPath, i + 1), nullptr);
|
|
}
|
|
|
|
EsHeapFree(data);
|
|
} else if (message->type == ES_MSG_FILE_MANAGER_DOCUMENT_UPDATE) {
|
|
EsBuffer buffer = { .canGrow = true };
|
|
_EsOpenDocumentEnumerate(&buffer);
|
|
|
|
size_t count = 0;
|
|
EsBufferReadInto(&buffer, &count, sizeof(size_t));
|
|
|
|
for (uintptr_t i = 0; i < openDocuments.Length(); i++) {
|
|
StringDestroy(&openDocuments[i]);
|
|
}
|
|
|
|
openDocuments.Free();
|
|
|
|
for (uintptr_t i = 0; i < count; i++) {
|
|
size_t pathBytes = 0;
|
|
EsBufferReadInto(&buffer, &pathBytes, sizeof(size_t));
|
|
char *path = (char *) EsHeapAllocate(pathBytes, false);
|
|
EsBufferReadInto(&buffer, path, pathBytes);
|
|
String string = { .text = path, .bytes = pathBytes, .allocated = pathBytes };
|
|
openDocuments.Add(string);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < instances.Length(); i++) {
|
|
if (instances[i]->closed) continue;
|
|
EsListViewInvalidateAll(instances[i]->list);
|
|
}
|
|
|
|
EsHeapFree(buffer.out);
|
|
EsAssert(!buffer.error);
|
|
} else if (message->type == MESSAGE_BLOCKING_TASK_COMPLETE) {
|
|
Instance *instance = (Instance *) message->user.context1.p;
|
|
if (message->user.context2.u == instance->blockingTaskID) BlockingTaskComplete(instance);
|
|
} else if (message->type == MESSAGE_NON_BLOCKING_TASK_COMPLETE) {
|
|
NonBlockingTaskComplete(message);
|
|
}
|
|
}
|
|
}
|