copying folders

This commit is contained in:
nakst 2021-09-01 11:46:10 +01:00
parent 5d9a6f72dc
commit 787e51b372
11 changed files with 207 additions and 104 deletions

View File

@ -152,21 +152,81 @@ void CommandCopy(Instance *instance, EsElement *, EsCommand *) {
}
}
EsError CommandPasteFile(String source, String destinationBase, void **copyBuffer, String *_destination = nullptr) {
if (PathHasPrefix(destinationBase, source)) {
return ES_ERROR_TARGET_WITHIN_SOURCE;
}
String name = PathGetName(source);
String destination = StringAllocateAndFormat("%s%z%s", STRFMT(destinationBase), PathHasTrailingSlash(destinationBase) ? "" : "/", STRFMT(name));
if (StringEquals(PathGetParent(source), destinationBase)) {
destination.allocated += 32;
destination.text = (char *) EsHeapReallocate(destination.text, destination.allocated, false);
size_t bytes = EsPathFindUniqueName(destination.text, destination.bytes, destination.allocated);
if (bytes) {
destination.bytes = destination.allocated = bytes;
} else {
destination.allocated = destination.bytes;
StringDestroy(&destination);
return ES_ERROR_FILE_ALREADY_EXISTS;
}
}
EsPrint("Copying %s -> %s...\n", STRFMT(source), STRFMT(destination));
EsError error = EsFileCopy(STRING(source), STRING(destination), copyBuffer);
if (error == ES_ERROR_INCORRECT_NODE_TYPE) {
EsDirectoryChild *buffer;
ptrdiff_t childCount = EsDirectoryEnumerateChildren(STRING(source), &buffer);
if (ES_CHECK_ERROR(childCount)) {
error = (EsError) childCount;
} else {
error = EsPathCreate(STRING(destination), ES_NODE_DIRECTORY, false);
if (error == ES_SUCCESS) {
for (intptr_t i = 0; i < childCount && error == ES_SUCCESS; i++) {
String childSourcePath = StringAllocateAndFormat("%s%z%s", STRFMT(source),
PathHasTrailingSlash(source) ? "" : "/", buffer[i].nameBytes, buffer[i].name);
error = CommandPasteFile(childSourcePath, destination, copyBuffer);
StringDestroy(&childSourcePath);
}
}
EsHeapFree(buffer);
}
}
if (error == ES_SUCCESS) {
FolderFileUpdatedAtPath(destination, nullptr);
}
if (_destination && error == ES_SUCCESS) {
*_destination = destination;
} else {
StringDestroy(&destination);
}
return error;
}
void CommandPaste(Instance *instance, EsElement *, EsCommand *) {
if (EsClipboardHasFormat(ES_CLIPBOARD_PRIMARY, ES_CLIPBOARD_FORMAT_PATH_LIST)) {
// TODO Background task.
// TODO Renaming.
// TODO Recursing into folders.
// TODO Reporting errors properly.
// 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 Selecting *all* pasted files.
// TODO Update parent folders after copy complete.
// TODO Undo.
void *copyBuffer = nullptr;
size_t bytes;
char *pathList = EsClipboardReadText(ES_CLIPBOARD_PRIMARY, &bytes);
Array<String> itemsToSelect = {};
if (pathList) {
const char *position = pathList;
@ -175,29 +235,18 @@ void CommandPaste(Instance *instance, EsElement *, EsCommand *) {
if (!newline) break;
String source = StringFromLiteralWithSize(position, newline - position);
String name = PathGetName(source);
String destination = StringAllocateAndFormat("%s%s", STRFMT(instance->folder->path), STRFMT(name));
EsError error = EsFileCopy(STRING(source), STRING(destination), &copyBuffer);
String destination;
if (error == ES_SUCCESS) {
EsMutexAcquire(&instance->folder->modifyEntriesMutex);
EsAssert(instance->folder->doneInitialEnumeration);
EsDirectoryChild directoryChild;
if (EsPathQueryInformation(STRING(destination), &directoryChild)) {
FolderAddEntryAndUpdateInstances(instance->folder, STRING(name), &directoryChild, instance);
} else {
// File must have been deleted by the time we got here!
}
EsMutexRelease(&instance->folder->modifyEntriesMutex);
} else {
if (ES_SUCCESS != CommandPasteFile(source, instance->folder->path, &copyBuffer, &destination)) {
goto encounteredError;
}
String destinationItem = StringDuplicate(PathGetName(destination));
itemsToSelect.Add(destinationItem);
StringDestroy(&destination);
position += source.bytes + 1;
bytes -= source.bytes + 1;
StringDestroy(&destination);
}
} else {
encounteredError:;
@ -205,8 +254,23 @@ void CommandPaste(Instance *instance, EsElement *, EsCommand *) {
EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, point.x, point.y, INTERFACE_STRING(CommonAnnouncementPasteErrorOther));
}
size_t pathSectionCount = PathCountSections(instance->folder->path);
for (uintptr_t i = 0; i < pathSectionCount - 1; i++) {
String parent = PathGetParent(instance->folder->path, i + 1);
FolderFileUpdatedAtPath(parent, nullptr);
}
EsListViewSelectNone(instance->list);
for (uintptr_t i = 0; i < itemsToSelect.Length(); i++) {
InstanceSelectByName(instance, itemsToSelect[i], true, i == itemsToSelect.Length() - 1);
StringDestroy(&itemsToSelect[i]);
}
EsHeapFree(pathList);
EsHeapFree(copyBuffer);
itemsToSelect.Free();
} else {
// TODO Paste the data into a new file.
}

View File

@ -426,6 +426,29 @@ void FolderAddEntryAndUpdateInstances(Folder *folder, const char *name, size_t n
}
}
void FolderFileUpdatedAtPath(String path, Instance *instance) {
path = PathRemoveTrailingSlash(path);
String file = PathGetName(path);
String folder = PathGetParent(path);
EsDirectoryChild information = {};
if (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) {
FolderAddEntryAndUpdateInstances(loadedFolders[i], file.text, file.bytes, &information, instance);
}
EsMutexRelease(&loadedFolders[i]->modifyEntriesMutex);
}
}
}
uint64_t FolderRemoveEntryAndUpdateInstances(Folder *folder, const char *name, size_t nameBytes) {
FolderEntry *entry = (FolderEntry *) HashTableGetLong(&folder->entries, name, nameBytes);
uint64_t id = 0;

View File

@ -230,6 +230,7 @@ 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);
@ -549,25 +550,7 @@ void _start() {
size_t pathSectionCount = PathCountSections(fullPath);
for (uintptr_t i = 0; i < pathSectionCount; i++) {
String path = PathGetParent(fullPath, i + 1);
String file = PathGetSection(fullPath, i + 1);
String folder = PathGetParent(path);
EsDirectoryChild information = {};
if (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) {
FolderAddEntryAndUpdateInstances(loadedFolders[i], file.text, file.bytes, &information, nullptr);
}
EsMutexRelease(&loadedFolders[i]->modifyEntriesMutex);
}
}
FolderFileUpdatedAtPath(PathGetParent(fullPath, i + 1), nullptr);
}
EsHandleClose(message->user.context1.u);

View File

@ -176,7 +176,24 @@ String PathGetParent(String string) {
return result;
}
bool PathHasTrailingSlash(String path) {
return path.bytes && path.text[path.bytes - 1] == '/';
}
String PathRemoveTrailingSlash(String path) {
if (PathHasTrailingSlash(path)) path.bytes--;
return path;
}
String PathGetName(String path) {
intptr_t i = path.bytes - 2;
while (i >= 0 && path.text[i] != '/') i--;
path.text += i + 1, path.bytes -= i + 1;
return path;
}
bool PathHasPrefix(String path, String prefix) {
prefix = PathRemoveTrailingSlash(prefix);
return StringStartsWith(path, prefix) && path.bytes > prefix.bytes && path.text[prefix.bytes] == '/';
}
@ -191,18 +208,3 @@ bool PathReplacePrefix(String *knownPath, String oldPath, String newPath) {
return false;
}
String PathRemoveTrailingSlash(String path) {
if (path.bytes && path.text[path.bytes - 1] == '/') {
path.bytes--;
}
return path;
}
String PathGetName(String path) {
intptr_t i = path.bytes - 2;
while (i >= 0 && path.text[i] != '/') i--;
path.text += i + 1, path.bytes -= i + 1;
return path;
}

View File

@ -324,6 +324,16 @@ ES_MACRO_SORT(InstanceSortListContents, ListEntry, {
result = InstanceCompareFolderEntries(_left->entry, _right->entry, context);
}, uint16_t);
void InstanceSelectByName(Instance *instance, String name, bool addToExistingSelection, bool focusItem) {
for (uintptr_t i = 0; i < instance->listContents.Length(); i++) {
if (0 == EsStringCompareRaw(STRING(instance->listContents[i].entry->GetName()), STRING(name))) {
EsListViewSelect(instance->list, 0, i, addToExistingSelection);
if (focusItem) EsListViewFocusItem(instance->list, 0, i);
break;
}
}
}
void InstanceAddContents(Instance *instance, HashTable *newEntries) {
// Call with the message mutex acquired.
@ -352,14 +362,7 @@ void InstanceAddContents(Instance *instance, HashTable *newEntries) {
EsListViewInsert(instance->list, 0, 0, instance->listContents.Length());
if (instance->delayedFocusItem.bytes) {
for (uintptr_t i = 0; i < instance->listContents.Length(); i++) {
if (0 == EsStringCompareRaw(STRING(instance->listContents[i].entry->GetName()), STRING(instance->delayedFocusItem))) {
EsListViewSelect(instance->list, 0, i);
EsListViewFocusItem(instance->list, 0, i);
break;
}
}
InstanceSelectByName(instance, instance->delayedFocusItem, false, true);
StringDestroy(&instance->delayedFocusItem);
}
}
@ -696,7 +699,7 @@ int ListItemMessage(EsElement *element, EsMessage *message) {
int ListCallback(EsElement *element, EsMessage *message) {
Instance *instance = element->instance;
if (message->type == ES_MSG_FOCUSED_START) {
if (message->type == ES_MSG_FOCUSED_START || message->type == ES_MSG_PRIMARY_CLIPBOARD_UPDATED) {
InstanceUpdateItemSelectionCountCommands(instance);
return 0;
} else if (message->type == ES_MSG_FOCUSED_END) {

View File

@ -2900,8 +2900,8 @@ void ScrollPane::Refresh() {
EsRectangle bounds = parent->GetBounds();
if (bar[0]) ScrollbarSetMeasurements(bar[0], bounds.r - fixedViewport[0], contentWidth);
if (bar[1]) ScrollbarSetMeasurements(bar[1], bounds.b - fixedViewport[1], contentHeight);
if (bar[0]) ScrollbarSetMeasurements(bar[0], bounds.r - fixedViewport[0], contentWidth - fixedViewport[0]);
if (bar[1]) ScrollbarSetMeasurements(bar[1], bounds.b - fixedViewport[1], contentHeight - fixedViewport[1]);
SetPosition(0, position[0], true);
SetPosition(1, position[1], true);

View File

@ -2,6 +2,7 @@
// TODO Consistent int64_t/intptr_t.
// TODO Drag and drop.
// TODO GetFirstIndex/GetLastIndex assume that every group is non-empty.
// TODO Sticking to top/bottom scroll when inserting/removing space.
struct ListViewItemElement : EsElement {
uintptr_t index; // Index into the visible items array.
@ -482,6 +483,22 @@ struct EsListView : EsElement {
}
void Populate() {
EsPrint("--- Before Populate() ---\n");
EsPrint("Scroll: %i\n", (int) (scroll.position[1] - currentStyle->insets.t));
for (uintptr_t i = 0; i < visibleItems.Length(); i++) {
EsMessage m = { ES_MSG_LIST_VIEW_GET_CONTENT };
uint8_t _buffer[512];
EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) };
m.getContent.buffer = &buffer;
m.getContent.index = visibleItems[i].index;
m.getContent.group = visibleItems[i].group;
EsMessageSend(this, &m);
EsPrint("%d: %d '%s' at %i\n", i, visibleItems[i].index, buffer.position, _buffer, visibleItems[i].element->offsetY - GetListBounds().t);
}
EsPrint("------\n");
// TODO Keep one item before and after the viewport, so tab traversal on custom elements works.
// TODO Always keep an item if it has FOCUS_WITHIN.
// - But maybe we shouldn't allow focusable elements in a list view.
@ -533,7 +550,7 @@ struct EsListView : EsElement {
: visibleItem->element->offsetY - contentBounds.t;
if (position < expectedPosition - 1 || position > expectedPosition + 1) {
EsPrint("Item in unexpected position: expected %d, got %d; index %d, scroll %d.\n",
EsPrint("Item in unexpected position: got %d, should have been %d; index %d, scroll %d.\n",
expectedPosition, position, visibleItem->index, scroll);
EsAssert(false);
}
@ -726,33 +743,19 @@ struct EsListView : EsElement {
return;
}
int64_t currentScroll = (flags & ES_LIST_VIEW_HORIZONTAL) ? scroll.position[0] : scroll.position[1];
int64_t scrollLimit = (flags & ES_LIST_VIEW_HORIZONTAL) ? scroll.limit[0] : scroll.limit[1];
totalSize += space;
if (((beforeItem == 0 && currentScroll) || currentScroll == scrollLimit) && firstLayout && space > 0 && scrollLimit) {
scroll.Refresh();
for (uintptr_t i = beforeItem; i < visibleItems.Length(); i++) {
ListViewItem *item = &visibleItems[i];
if (flags & ES_LIST_VIEW_HORIZONTAL) {
scroll.SetX(scroll.position[0] + space, false);
item->element->offsetX += space;
} else {
scroll.SetY(scroll.position[1] + space, false);
item->element->offsetY += space;
}
} else {
for (uintptr_t i = beforeItem; i < visibleItems.Length(); i++) {
ListViewItem *item = &visibleItems[i];
if (flags & ES_LIST_VIEW_HORIZONTAL) {
item->element->offsetX += space;
} else {
item->element->offsetY += space;
}
}
scroll.Refresh();
}
scroll.Refresh();
EsElementUpdateContentSize(this);
}
@ -1575,7 +1578,7 @@ struct EsListView : EsElement {
if (message->type == ES_MSG_GET_WIDTH || message->type == ES_MSG_GET_HEIGHT) {
if (flags & ES_LIST_VIEW_HORIZONTAL) {
message->measure.width = totalSize + currentStyle->insets.l + currentStyle->insets.r;
message->measure.width = totalSize + currentStyle->insets.l + currentStyle->insets.r;
} else {
message->measure.height = totalSize + currentStyle->insets.t + currentStyle->insets.b;
@ -2305,10 +2308,20 @@ bool EsListViewGetFocusedItem(EsListView *view, EsListViewIndex *group, EsListVi
return view->hasFocusedItem;
}
void EsListViewSelect(EsListView *view, EsListViewIndex group, EsListViewIndex index) {
void EsListViewSelectNone(EsListView *view) {
EsMessageMutexCheck();
view->Select(-1, 0, false, false, false);
}
void EsListViewSelect(EsListView *view, EsListViewIndex group, EsListViewIndex index, bool addToExistingSelection) {
EsMessageMutexCheck();
view->Select(group, index, false, false, false);
if (addToExistingSelection) {
view->SetSelected(group, index, group, index, true, false);
view->UpdateVisibleItemsSelectionState();
} else {
view->Select(group, index, false, false, false);
}
}
void EsListViewSetEmptyMessage(EsListView *view, const char *message, ptrdiff_t messageBytes) {

View File

@ -2422,7 +2422,8 @@ function void EsListViewEnumerateVisibleItems(EsListView *view, EsListViewEnumer
function void EsListViewSetColumns(EsListView *view, EsListViewColumn *columns, size_t columnCount);
function void EsListViewSetEmptyMessage(EsListView *view, STRING message = BLANK_STRING);
function void EsListViewSetMaximumItemsPerBand(EsListView *view, int maximumItemsPerBand);
function void EsListViewSelect(EsListView *view, EsListViewIndex group, EsListViewIndex index);
function void EsListViewSelectNone(EsListView *view);
function void EsListViewSelect(EsListView *view, EsListViewIndex group, EsListViewIndex index, bool addToExistingSelection = false);
function void EsListViewFocusItem(EsListView *view, EsListViewIndex group, EsListViewIndex index);
function bool EsListViewGetFocusedItem(EsListView *view, EsListViewIndex *group, EsListViewIndex *index); // Returns false if not item was focused.
function void EsListViewInvalidateContent(EsListView *view, EsListViewIndex group, EsListViewIndex index);

View File

@ -235,25 +235,32 @@ EsError EsFileCopy(const char *source, ptrdiff_t sourceBytes, const char *destin
EsError error = ES_SUCCESS;
EsFileInformation sourceFile = EsFileOpen(source, sourceBytes, ES_FILE_READ | ES_NODE_FILE | ES_NODE_FAIL_IF_NOT_FOUND);
EsFileInformation destinationFile = EsFileOpen(destination, destinationBytes, ES_FILE_WRITE_EXCLUSIVE | ES_NODE_FILE | ES_NODE_FAIL_IF_FOUND);
if (sourceFile.error == ES_SUCCESS && destinationFile.error == ES_SUCCESS) {
error = EsFileResize(destinationFile.handle, sourceFile.size);
if (sourceFile.error == ES_SUCCESS) {
EsFileInformation destinationFile = EsFileOpen(destination, destinationBytes, ES_FILE_WRITE_EXCLUSIVE | ES_NODE_FILE | ES_NODE_FAIL_IF_FOUND);
if (error == ES_SUCCESS) {
for (uintptr_t i = 0; i < sourceFile.size; i += copyBufferBytes) {
size_t bytesRead = EsFileReadSync(sourceFile.handle, i, copyBufferBytes, copyBuffer);
if (ES_CHECK_ERROR(bytesRead)) { error = bytesRead; break; }
size_t bytesWritten = EsFileWriteSync(destinationFile.handle, i, bytesRead, copyBuffer);
if (ES_CHECK_ERROR(bytesWritten)) { error = bytesWritten; break; }
if (destinationFile.error == ES_SUCCESS) {
error = EsFileResize(destinationFile.handle, sourceFile.size);
if (error == ES_SUCCESS) {
for (uintptr_t i = 0; i < sourceFile.size; i += copyBufferBytes) {
size_t bytesRead = EsFileReadSync(sourceFile.handle, i, copyBufferBytes, copyBuffer);
if (ES_CHECK_ERROR(bytesRead)) { error = bytesRead; break; }
size_t bytesWritten = EsFileWriteSync(destinationFile.handle, i, bytesRead, copyBuffer);
if (ES_CHECK_ERROR(bytesWritten)) { error = bytesWritten; break; }
}
}
EsHandleClose(destinationFile.handle);
} else {
error = destinationFile.error;
}
EsHandleClose(sourceFile.handle);
} else {
error = sourceFile.error == ES_SUCCESS ? destinationFile.error : sourceFile.error;
error = sourceFile.error;
}
if (sourceFile.error == ES_SUCCESS) EsHandleClose(sourceFile.handle);
if (destinationFile.error == ES_SUCCESS) EsHandleClose(destinationFile.handle);
if (!_copyBuffer) EsHeapFree(copyBuffer);
return error;
}

View File

@ -1606,13 +1606,17 @@ void EnterDebugger() {
#ifndef KERNEL
size_t EsPathFindUniqueName(char *buffer, size_t originalBytes, size_t bufferBytes) {
// TODO Check that this runs in a reasonable amount of time when all files are already present.
if (originalBytes && buffer[originalBytes - 1] == '/') {
originalBytes--;
}
size_t extensionPoint = originalBytes;
for (uintptr_t i = 0; i < originalBytes; i++) {
if (buffer[i] == '.') {
extensionPoint = i;
} else if (buffer[i] == '/') {
extensionPoint = originalBytes;
}
}
@ -1625,6 +1629,8 @@ size_t EsPathFindUniqueName(char *buffer, size_t originalBytes, size_t bufferByt
uintptr_t attempt = 2;
// TODO Check that this runs in a reasonable amount of time when all files are already present.
while (attempt < 1000) {
size_t length = EsStringFormat(buffer2, bufferBytes, "%s %d%s", extensionPoint, buffer,
attempt, originalBytes - extensionPoint, buffer + extensionPoint);

View File

@ -430,3 +430,4 @@ EsListViewFixedItemGetSelected=428
EsClipboardHasFormat=429
EsClipboardHasData=430
EsFileCopy=431
EsListViewSelectNone=432