mirror of https://gitlab.com/nakst/essence
625 lines
19 KiB
C++
625 lines
19 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 NAMESPACE_HANDLER_FILE_SYSTEM (1)
|
|
#define NAMESPACE_HANDLER_DRIVES_PAGE (2)
|
|
#define NAMESPACE_HANDLER_ROOT (3) // Acts as a container handler where needed.
|
|
#define NAMESPACE_HANDLER_INVALID (4) // For when a folder does not exist.
|
|
|
|
Array<Folder *> loadedFolders;
|
|
|
|
#define MAXIMUM_FOLDERS_WITH_NO_ATTACHED_INSTANCES (20)
|
|
Array<Folder *> foldersWithNoAttachedInstances;
|
|
|
|
/////////////////////////////////
|
|
|
|
bool FSDirHandlesPath(String path) {
|
|
EsDirectoryChild information;
|
|
|
|
if (ES_SUCCESS == EsPathQueryInformation(STRING(path), &information)) {
|
|
return information.type == ES_NODE_DIRECTORY;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
EsError FSDirCreateChildFolder(Folder *folder, String *name, bool findUniqueName) {
|
|
size_t pathBytes;
|
|
char *path;
|
|
|
|
if (findUniqueName) {
|
|
const char *extra = " ";
|
|
path = EsStringAllocateAndFormat(&pathBytes, "%s%s%z", STRFMT(folder->path), STRFMT((*name)), extra);
|
|
pathBytes = EsPathFindUniqueName(path, pathBytes - EsCStringLength(extra), pathBytes);
|
|
StringDestroy(name);
|
|
*name = StringAllocateAndFormat("%s", pathBytes - folder->path.bytes, path + folder->path.bytes);
|
|
} else {
|
|
path = EsStringAllocateAndFormat(&pathBytes, "%s%s", STRFMT(folder->path), STRFMT((*name)));
|
|
}
|
|
|
|
EsError error = EsPathCreate(path, pathBytes, ES_NODE_DIRECTORY, false);
|
|
EsHeapFree(path);
|
|
return error;
|
|
}
|
|
|
|
EsError FSDirRenameItem(Folder *folder, String oldName, String newName) {
|
|
size_t oldPathBytes;
|
|
char *oldPath = EsStringAllocateAndFormat(&oldPathBytes, "%s%s", STRFMT(folder->path), STRFMT(oldName));
|
|
|
|
size_t newPathBytes;
|
|
char *newPath = EsStringAllocateAndFormat(&newPathBytes, "%s%s", STRFMT(folder->path), STRFMT(newName));
|
|
|
|
EsError error = EsPathMove(oldPath, oldPathBytes, newPath, newPathBytes);
|
|
|
|
EsHeapFree(oldPath);
|
|
EsHeapFree(newPath);
|
|
|
|
return error;
|
|
};
|
|
|
|
EsError FSDirEnumerate(Folder *folder) {
|
|
// TODO Recurse mode.
|
|
|
|
EsNodeType type;
|
|
|
|
if (!EsPathExists(STRING(folder->path), &type) || type != ES_NODE_DIRECTORY) {
|
|
return ES_ERROR_FILE_DOES_NOT_EXIST;
|
|
}
|
|
|
|
EsVolumeInformation volume;
|
|
folder->readOnly = true;
|
|
|
|
if (EsMountPointGetVolumeInformation(STRING(folder->path), &volume)) {
|
|
folder->readOnly = volume.flags & ES_VOLUME_READ_ONLY;
|
|
}
|
|
|
|
uintptr_t _entryCount;
|
|
EsError error;
|
|
EsDirectoryChild *buffer = EsDirectoryEnumerate(STRING(folder->path), &_entryCount, &error);
|
|
|
|
if (error == ES_SUCCESS) {
|
|
for (uintptr_t i = 0; i < _entryCount; i++) {
|
|
FolderAddEntry(folder, buffer[i].name, buffer[i].nameBytes, &buffer[i]);
|
|
}
|
|
}
|
|
|
|
EsHeapFree(buffer);
|
|
return error;
|
|
}
|
|
|
|
void FSDirGetTotalSize(Folder *folder) {
|
|
EsDirectoryChild information;
|
|
|
|
if (ES_SUCCESS == EsPathQueryInformation(STRING(folder->path), &information)) {
|
|
folder->spaceUsed = information.fileSize;
|
|
folder->spaceTotal = 0;
|
|
}
|
|
}
|
|
|
|
String FSDirGetPathForChild(Folder *folder, FolderEntry *entry) {
|
|
String item = entry->GetInternalName();
|
|
return StringAllocateAndFormat("%s%s%z", STRFMT(folder->path), STRFMT(item), entry->isFolder ? "/" : "");
|
|
}
|
|
|
|
/////////////////////////////////
|
|
|
|
bool DrivesPageHandlesPath(String path) {
|
|
return StringEquals(path, StringFromLiteralWithSize(INTERFACE_STRING(FileManagerDrivesPage)));
|
|
}
|
|
|
|
uint32_t DrivesPageGetFileType(String path) {
|
|
path = PathRemoveTrailingSlash(path);
|
|
String string = PathGetSection(path, PathCountSections(path));
|
|
EsVolumeInformation volume;
|
|
|
|
if (EsMountPointGetVolumeInformation(STRING(string), &volume)) {
|
|
if (volume.driveType == ES_DRIVE_TYPE_HDD ) return KNOWN_FILE_TYPE_DRIVE_HDD;
|
|
if (volume.driveType == ES_DRIVE_TYPE_SSD ) return KNOWN_FILE_TYPE_DRIVE_SSD;
|
|
if (volume.driveType == ES_DRIVE_TYPE_CDROM ) return KNOWN_FILE_TYPE_DRIVE_CDROM;
|
|
if (volume.driveType == ES_DRIVE_TYPE_USB_MASS_STORAGE) return KNOWN_FILE_TYPE_DRIVE_USB_MASS_STORAGE;
|
|
return KNOWN_FILE_TYPE_DRIVE_HDD;
|
|
}
|
|
|
|
return KNOWN_FILE_TYPE_DRIVE_HDD;
|
|
}
|
|
|
|
void DrivesPageGetTotalSize(Folder *folder) {
|
|
EsVolumeInformation information;
|
|
|
|
if (EsMountPointGetVolumeInformation(STRING(folder->path), &information)) {
|
|
folder->spaceUsed = information.spaceUsed;
|
|
folder->spaceTotal = information.spaceTotal;
|
|
}
|
|
}
|
|
|
|
void DrivesPageGetVisibleName(EsBuffer *buffer, String path) {
|
|
path = PathRemoveTrailingSlash(path);
|
|
String string = PathGetSection(path, PathCountSections(path));
|
|
EsVolumeInformation volume;
|
|
|
|
if (EsMountPointGetVolumeInformation(STRING(string), &volume)) {
|
|
EsBufferFormat(buffer, "%s", volume.labelBytes, volume.label);
|
|
return;
|
|
}
|
|
|
|
EsBufferFormat(buffer, "%s", STRFMT(string));
|
|
}
|
|
|
|
EsError DrivesPageEnumerate(Folder *folder) {
|
|
EsMutexAcquire(&drivesMutex);
|
|
EsDirectoryChild information = {};
|
|
information.type = ES_NODE_DIRECTORY;
|
|
|
|
for (uintptr_t i = 0; i < drives.Length(); i++) {
|
|
Drive *drive = &drives[i];
|
|
information.fileSize = drive->information.spaceTotal;
|
|
FolderAddEntry(folder, drive->prefix, drive->prefixBytes, &information);
|
|
}
|
|
|
|
EsMutexRelease(&drivesMutex);
|
|
return ES_SUCCESS;
|
|
}
|
|
|
|
String DrivesPageGetPathForChild(Folder *, FolderEntry *entry) {
|
|
String item = entry->GetInternalName();
|
|
return StringAllocateAndFormat("%s/", STRFMT(item));
|
|
}
|
|
|
|
void DrivesPageGetDefaultViewSettings(Folder *, FolderViewSettings *settings) {
|
|
settings->viewType = VIEW_TILES;
|
|
}
|
|
|
|
/////////////////////////////////
|
|
|
|
uint32_t NamespaceDefaultGetFileType(String) {
|
|
return KNOWN_FILE_TYPE_DIRECTORY;
|
|
}
|
|
|
|
void NamespaceDefaultGetVisibleName(EsBuffer *buffer, String path) {
|
|
String string = PathGetSection(path, -1);
|
|
EsBufferFormat(buffer, "%s", STRFMT(string));
|
|
}
|
|
|
|
uint32_t NamespaceRootGetFileType(String path) {
|
|
if (StringEquals(path, StringFromLiteralWithSize(INTERFACE_STRING(FileManagerDrivesPage)))) {
|
|
return KNOWN_FILE_TYPE_DRIVES_PAGE;
|
|
} else {
|
|
return KNOWN_FILE_TYPE_DIRECTORY;
|
|
}
|
|
}
|
|
|
|
void NamespaceRootGetVisibleName(EsBuffer *buffer, String path) {
|
|
EsBufferFormat(buffer, "%s", STRFMT(StringSlice(path, 0, path.bytes - 1)));
|
|
}
|
|
|
|
EsError NamespaceInvalidEnumerate(Folder *folder) {
|
|
folder->cEmptyMessage = folder->driveRemoved ? interfaceString_FileManagerInvalidDrive : interfaceString_FileManagerInvalidPath;
|
|
return ES_SUCCESS;
|
|
}
|
|
|
|
NamespaceHandler namespaceHandlers[] = {
|
|
{
|
|
.type = NAMESPACE_HANDLER_DRIVES_PAGE,
|
|
.rootContainerHandlerType = NAMESPACE_HANDLER_ROOT,
|
|
.handlesPath = DrivesPageHandlesPath,
|
|
.getFileType = DrivesPageGetFileType,
|
|
.getVisibleName = DrivesPageGetVisibleName,
|
|
.getTotalSize = DrivesPageGetTotalSize,
|
|
.getPathForChild = DrivesPageGetPathForChild,
|
|
.getDefaultViewSettings = DrivesPageGetDefaultViewSettings,
|
|
.enumerate = DrivesPageEnumerate,
|
|
},
|
|
|
|
{
|
|
.type = NAMESPACE_HANDLER_FILE_SYSTEM,
|
|
.rootContainerHandlerType = NAMESPACE_HANDLER_DRIVES_PAGE,
|
|
.canCut = true,
|
|
.canCopy = true,
|
|
.canPaste = true,
|
|
.handlesPath = FSDirHandlesPath,
|
|
.getFileType = NamespaceDefaultGetFileType,
|
|
.getVisibleName = NamespaceDefaultGetVisibleName,
|
|
.getTotalSize = FSDirGetTotalSize,
|
|
.getPathForChild = FSDirGetPathForChild,
|
|
.createChildFolder = FSDirCreateChildFolder,
|
|
.renameItem = FSDirRenameItem,
|
|
.enumerate = FSDirEnumerate,
|
|
},
|
|
|
|
{
|
|
.type = NAMESPACE_HANDLER_ROOT,
|
|
.handlesPath = [] (String) { return false; },
|
|
.getFileType = NamespaceRootGetFileType,
|
|
.getVisibleName = NamespaceRootGetVisibleName,
|
|
},
|
|
|
|
{
|
|
.type = NAMESPACE_HANDLER_INVALID,
|
|
.rootContainerHandlerType = NAMESPACE_HANDLER_ROOT,
|
|
.handlesPath = [] (String) { return true; },
|
|
.getFileType = NamespaceDefaultGetFileType,
|
|
.getVisibleName = NamespaceDefaultGetVisibleName,
|
|
.enumerate = NamespaceInvalidEnumerate,
|
|
},
|
|
};
|
|
|
|
void NamespaceFindHandlersForPath(NamespaceHandler **itemHandler, NamespaceHandler **containerHandler, String path) {
|
|
String pathContainer = PathGetParent(path);
|
|
*itemHandler = *containerHandler = nullptr;
|
|
|
|
for (uintptr_t i = 0; i < sizeof(namespaceHandlers) / sizeof(namespaceHandlers[0]); i++) {
|
|
if (!(*itemHandler) && namespaceHandlers[i].handlesPath(path)) {
|
|
*itemHandler = &namespaceHandlers[i];
|
|
}
|
|
|
|
if (pathContainer.bytes && !(*containerHandler) && namespaceHandlers[i].handlesPath(pathContainer)) {
|
|
*containerHandler = &namespaceHandlers[i];
|
|
}
|
|
}
|
|
|
|
if (!pathContainer.bytes && (*itemHandler)) {
|
|
for (uintptr_t i = 0; i < sizeof(namespaceHandlers) / sizeof(namespaceHandlers[0]); i++) {
|
|
if (namespaceHandlers[i].type == (*itemHandler)->rootContainerHandlerType) {
|
|
*containerHandler = &namespaceHandlers[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EsAssert(*itemHandler && *containerHandler); // NAMESPACE_HANDLER_INVALID should handle failure cases.
|
|
}
|
|
|
|
uint32_t NamespaceGetIcon(String path) {
|
|
NamespaceHandler *itemHandler, *containerHandler;
|
|
NamespaceFindHandlersForPath(&itemHandler, &containerHandler, path);
|
|
|
|
if (containerHandler) {
|
|
return knownFileTypes[containerHandler->getFileType(path)].iconID;
|
|
} else {
|
|
return ES_ICON_FOLDER;
|
|
}
|
|
}
|
|
|
|
void NamespaceGetVisibleName(EsBuffer *buffer, String path) {
|
|
NamespaceHandler *itemHandler, *containerHandler;
|
|
NamespaceFindHandlersForPath(&itemHandler, &containerHandler, path);
|
|
|
|
if (containerHandler) {
|
|
containerHandler->getVisibleName(buffer, path);
|
|
} else {
|
|
String string = PathGetSection(path, -1);
|
|
EsBufferFormat(buffer, "%s", STRFMT(string));
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////
|
|
|
|
void BookmarkAdd(String path, bool saveConfiguration = true) {
|
|
bookmarks.Add(StringDuplicate(path));
|
|
if (saveConfiguration) ConfigurationSave();
|
|
|
|
for (uintptr_t i = 0; i < instances.Length(); i++) {
|
|
if (instances[i]->closed) continue;
|
|
EsListViewInsert(instances[i]->placesView, PLACES_VIEW_GROUP_BOOKMARKS, bookmarks.Length(), 1);
|
|
}
|
|
}
|
|
|
|
void BookmarkRemove(String path) {
|
|
for (uintptr_t i = 0; i < bookmarks.Length(); i++) {
|
|
if (0 == EsStringCompareRaw(STRING(bookmarks[i]), STRING(path))) {
|
|
bookmarks.Delete(i);
|
|
|
|
for (uintptr_t i = 0; i < instances.Length(); i++) {
|
|
if (instances[i]->closed) continue;
|
|
EsListViewRemove(instances[i]->placesView, PLACES_VIEW_GROUP_BOOKMARKS, i + 1, 1);
|
|
}
|
|
|
|
ConfigurationSave();
|
|
return;
|
|
}
|
|
}
|
|
|
|
EsAssert(false);
|
|
}
|
|
|
|
/////////////////////////////////
|
|
|
|
void FolderEntryCloseHandle(Folder *folder, FolderEntry *entry) {
|
|
entry->handles--;
|
|
|
|
if (!entry->handles) {
|
|
if (entry->name != entry->internalName) {
|
|
EsHeapFree(entry->internalName);
|
|
}
|
|
|
|
EsHeapFree(entry->name);
|
|
ArenaFree(&folder->entryArena, entry);
|
|
}
|
|
}
|
|
|
|
void FolderDestroy(Folder *folder) {
|
|
for (uintptr_t i = 0; i < folder->entries.slotCount; i++) {
|
|
if (folder->entries.slots[i].key.used) {
|
|
FolderEntryCloseHandle(folder, (FolderEntry *) folder->entries.slots[i].value);
|
|
}
|
|
}
|
|
|
|
StringDestroy(&folder->path);
|
|
folder->attachedInstances.Free();
|
|
HashTableFree(&folder->entries, false);
|
|
EsMutexDestroy(&folder->modifyEntriesMutex);
|
|
EsHeapFree(folder);
|
|
}
|
|
|
|
void FolderDetachInstance(Instance *instance) {
|
|
Folder *folder = instance->folder;
|
|
if (!folder) return;
|
|
instance->folder = nullptr;
|
|
folder->attachedInstances.FindAndDeleteSwap(instance, false /* in case the instance stopped loading the folder before it was ready */);
|
|
EsAssert(folder->referenceCount);
|
|
folder->referenceCount--;
|
|
|
|
if (!folder->referenceCount) {
|
|
if (folder->refreshing) {
|
|
loadedFolders.FindAndDeleteSwap(folder, true);
|
|
FolderDestroy(folder);
|
|
} else {
|
|
EsAssert(!folder->attachedInstances.Length());
|
|
foldersWithNoAttachedInstances.Add(folder);
|
|
|
|
if (foldersWithNoAttachedInstances.Length() > MAXIMUM_FOLDERS_WITH_NO_ATTACHED_INSTANCES) {
|
|
Folder *leastRecentlyUsed = foldersWithNoAttachedInstances[0];
|
|
loadedFolders.FindAndDeleteSwap(leastRecentlyUsed, true);
|
|
foldersWithNoAttachedInstances.Delete(0);
|
|
FolderDestroy(leastRecentlyUsed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FolderEntry *FolderAddEntry(Folder *folder, const char *_name, size_t nameBytes, EsDirectoryChild *information, uint64_t id) {
|
|
char *name = (char *) EsHeapAllocate(nameBytes + 1, false);
|
|
name[nameBytes] = 0;
|
|
EsMemoryCopy(name, _name, nameBytes);
|
|
|
|
FolderEntry *entry = (FolderEntry *) HashTableGetLong(&folder->entries, name, nameBytes);
|
|
|
|
if (!entry) {
|
|
entry = (FolderEntry *) ArenaAllocate(&folder->entryArena, true);
|
|
HashTablePutLong(&folder->entries, name, nameBytes, entry, false);
|
|
|
|
static uint64_t nextEntryID = 1;
|
|
entry->id = id ?: __sync_fetch_and_add(&nextEntryID, 1);
|
|
entry->handles = 1;
|
|
entry->name = name;
|
|
entry->nameBytes = nameBytes;
|
|
entry->extensionOffset = PathGetExtension(entry->GetName()).text - name;
|
|
entry->internalName = name;
|
|
entry->internalNameBytes = nameBytes;
|
|
|
|
if (folder->itemHandler->getVisibleName != NamespaceDefaultGetVisibleName) {
|
|
String path = StringAllocateAndFormat("%s%s", STRFMT(folder->path), STRFMT(entry->GetName()));
|
|
uint8_t _buffer[256];
|
|
EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) };
|
|
folder->itemHandler->getVisibleName(&buffer, path);
|
|
entry->nameBytes = buffer.position;
|
|
entry->name = (char *) EsHeapAllocate(buffer.position, false);
|
|
EsMemoryCopy(entry->name, _buffer, buffer.position);
|
|
StringDestroy(&path);
|
|
}
|
|
} else {
|
|
EsHeapFree(name);
|
|
name = entry->name;
|
|
entry->previousSize = entry->size;
|
|
}
|
|
|
|
entry->size = information->fileSize;
|
|
entry->isFolder = information->type == ES_NODE_DIRECTORY;
|
|
entry->sizeUnknown = entry->isFolder && information->directoryChildren == ES_DIRECTORY_CHILDREN_UNKNOWN;
|
|
|
|
return entry;
|
|
}
|
|
|
|
void FolderAddEntryAndUpdateInstances(Folder *folder, const char *name, size_t nameBytes,
|
|
EsDirectoryChild *information, Instance *selectItem, uint64_t id = 0) {
|
|
if (folder->containerHandler->getTotalSize) {
|
|
folder->containerHandler->getTotalSize(folder);
|
|
}
|
|
|
|
FolderEntry *entry = FolderAddEntry(folder, name, nameBytes, information, id);
|
|
ListEntry listEntry = { .entry = entry };
|
|
|
|
ThumbnailGenerateIfNeeded(folder, entry, false, true /* modified */);
|
|
|
|
for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) {
|
|
Instance *instance = folder->attachedInstances[i];
|
|
if (instance->closed) continue;
|
|
listEntry.selected = instance == selectItem;
|
|
InstanceAddSingle(instance, listEntry);
|
|
InstanceUpdateStatusString(instance);
|
|
}
|
|
}
|
|
|
|
uint64_t FolderRemoveEntryAndUpdateInstances(Folder *folder, const char *name, size_t nameBytes) {
|
|
FolderEntry *entry = (FolderEntry *) HashTableGetLong(&folder->entries, name, nameBytes);
|
|
uint64_t id = 0;
|
|
|
|
if (entry) {
|
|
for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) {
|
|
if (folder->attachedInstances[i]->closed) continue;
|
|
InstanceRemoveSingle(folder->attachedInstances[i], entry);
|
|
}
|
|
|
|
id = entry->id;
|
|
HashTableDeleteLong(&folder->entries, name, nameBytes);
|
|
FolderEntryCloseHandle(folder, entry);
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
void FolderFileUpdatedAtPath(String path, Instance *instance) {
|
|
path = PathRemoveTrailingSlash(path);
|
|
String file = PathGetName(path);
|
|
String folder = PathGetParent(path);
|
|
EsDirectoryChild information = {};
|
|
bool add = ES_SUCCESS == EsPathQueryInformation(STRING(path), &information);
|
|
|
|
for (uintptr_t i = 0; i < loadedFolders.Length(); i++) {
|
|
if (loadedFolders[i]->itemHandler->type != NAMESPACE_HANDLER_FILE_SYSTEM) continue;
|
|
if (EsStringCompareRaw(STRING(loadedFolders[i]->path), STRING(folder))) continue;
|
|
|
|
EsMutexAcquire(&loadedFolders[i]->modifyEntriesMutex);
|
|
|
|
if (loadedFolders[i]->doneInitialEnumeration) {
|
|
if (add) {
|
|
FolderAddEntryAndUpdateInstances(loadedFolders[i], file.text, file.bytes, &information, instance);
|
|
} else {
|
|
FolderRemoveEntryAndUpdateInstances(loadedFolders[i], file.text, file.bytes);
|
|
}
|
|
}
|
|
|
|
EsMutexRelease(&loadedFolders[i]->modifyEntriesMutex);
|
|
}
|
|
}
|
|
|
|
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];
|
|
|
|
if (PathReplacePrefix(&folder->path, oldPath, newPath)) {
|
|
for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) {
|
|
if (folder->attachedInstances[i]->closed) continue;
|
|
InstanceFolderPathChanged(folder->attachedInstances[i], false);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < openDocuments.Length(); i++) {
|
|
PathReplacePrefix(&openDocuments[i], oldPath, newPath);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < bookmarks.Length(); i++) {
|
|
PathReplacePrefix(&bookmarks[i], oldPath, newPath);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < instances.Length(); i++) {
|
|
if (instances[i]->closed) continue;
|
|
EsListViewInvalidateAll(instances[i]->placesView);
|
|
|
|
for (uintptr_t j = 0; j < instances[i]->pathBackwardHistory.Length(); j++) {
|
|
HistoryEntry *entry = &instances[i]->pathBackwardHistory[j];
|
|
PathReplacePrefix(&entry->path, oldPath, newPath);
|
|
}
|
|
|
|
for (uintptr_t j = 0; j < instances[i]->pathForwardHistory.Length(); j++) {
|
|
HistoryEntry *entry = &instances[i]->pathForwardHistory[j];
|
|
PathReplacePrefix(&entry->path, oldPath, newPath);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < folderViewSettings.Length(); i++) {
|
|
String knownPath = StringFromLiteralWithSize(folderViewSettings[i].path, folderViewSettings[i].pathBytes);
|
|
|
|
if (knownPath.bytes - oldPath.bytes + newPath.bytes >= sizeof(folderViewSettings[i].path)) {
|
|
continue;
|
|
}
|
|
|
|
if (!PathHasPrefix(knownPath, oldPath)) {
|
|
continue;
|
|
}
|
|
|
|
String after = StringSlice(knownPath, oldPath.bytes, -1);
|
|
folderViewSettings[i].pathBytes = EsStringFormat(folderViewSettings[i].path, sizeof(folderViewSettings[i].path),
|
|
"%s%s", STRFMT(newPath), STRFMT(after));
|
|
}
|
|
|
|
if (saveConfiguration) {
|
|
ConfigurationSave();
|
|
}
|
|
}
|
|
|
|
void FolderAttachInstance(Instance *instance, String path, bool recurse) {
|
|
Folder *folder = nullptr;
|
|
bool driveRemoved = false;
|
|
|
|
// Check if we've already loaded the folder.
|
|
|
|
for (uintptr_t i = 0; i < loadedFolders.Length(); i++) {
|
|
if (StringEquals(loadedFolders[i]->path, path) && loadedFolders[i]->recurse == recurse) {
|
|
if (!loadedFolders[i]->refreshing) {
|
|
folder = loadedFolders[i];
|
|
goto success;
|
|
} else {
|
|
driveRemoved = loadedFolders[i]->driveRemoved;
|
|
}
|
|
}
|
|
}
|
|
|
|
folder = (Folder *) EsHeapAllocate(sizeof(Folder), true);
|
|
folder->path = StringDuplicate(path);
|
|
folder->recurse = recurse;
|
|
NamespaceFindHandlersForPath(&folder->itemHandler, &folder->containerHandler, path);
|
|
folder->cEmptyMessage = interfaceString_FileManagerEmptyFolderView;
|
|
folder->driveRemoved = driveRemoved;
|
|
ArenaInitialise(&folder->entryArena, 1048576, sizeof(FolderEntry));
|
|
loadedFolders.Add(folder);
|
|
|
|
success:;
|
|
|
|
foldersWithNoAttachedInstances.FindAndDeleteSwap(folder, false);
|
|
folder->referenceCount++;
|
|
FolderDetachInstance(instance);
|
|
instance->folder = folder;
|
|
}
|
|
|
|
void FolderRefresh(Folder *folder) {
|
|
if (folder->refreshing) {
|
|
return;
|
|
}
|
|
|
|
if (!folder->attachedInstances.Length()) {
|
|
// The folder does not have any attached instances, so just destroy it.
|
|
loadedFolders.FindAndDeleteSwap(folder, true);
|
|
foldersWithNoAttachedInstances.FindAndDeleteSwap(folder, true);
|
|
FolderDestroy(folder);
|
|
return;
|
|
}
|
|
|
|
folder->refreshing = true;
|
|
|
|
Array<Instance *> instancesToRefresh = {};
|
|
|
|
for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) {
|
|
if (folder->attachedInstances[i]->closed) continue;
|
|
instancesToRefresh.Add(folder->attachedInstances[i]);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < instancesToRefresh.Length(); i++) {
|
|
InstanceLoadFolder(instancesToRefresh[i], StringDuplicate(folder->path), LOAD_FOLDER_REFRESH);
|
|
}
|
|
|
|
instancesToRefresh.Free();
|
|
}
|
|
|
|
void FolderRefreshAllFrom(String prefix) {
|
|
Array<Folder *> foldersToRefresh = {};
|
|
|
|
for (uintptr_t i = 0; i < loadedFolders.Length(); i++) {
|
|
if (!loadedFolders[i]->refreshing
|
|
&& loadedFolders[i]->itemHandler->type == NAMESPACE_HANDLER_FILE_SYSTEM
|
|
&& PathHasPrefix(loadedFolders[i]->path, prefix)) {
|
|
foldersToRefresh.Add(loadedFolders[i]);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < foldersToRefresh.Length(); i++) {
|
|
FolderRefresh(foldersToRefresh[i]);
|
|
}
|
|
|
|
foldersToRefresh.Free();
|
|
}
|