From 0f750b9d899632897398e21daa8d347d94ff84f2 Mon Sep 17 00:00:00 2001 From: kqjy Date: Thu, 5 Feb 2026 22:56:00 +0800 Subject: [PATCH] Optimize object browser for large listings on slow networks --- app/compression.py | 13 ++++++++++-- app/ui.py | 1 + static/js/bucket-detail-main.js | 36 +++++++++++++++++++++++++++++---- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/app/compression.py b/app/compression.py index a0bed7c..28e6899 100644 --- a/app/compression.py +++ b/app/compression.py @@ -36,10 +36,11 @@ class GzipMiddleware: content_type = None content_length = None should_compress = False + is_streaming = False exc_info_holder = [None] def custom_start_response(status: str, headers: List[Tuple[str, str]], exc_info=None): - nonlocal response_started, status_code, response_headers, content_type, content_length, should_compress + nonlocal response_started, status_code, response_headers, content_type, content_length, should_compress, is_streaming response_started = True status_code = int(status.split(' ', 1)[0]) response_headers = list(headers) @@ -54,6 +55,9 @@ class GzipMiddleware: elif name_lower == 'content-encoding': should_compress = False return start_response(status, headers, exc_info) + elif name_lower == 'x-stream-response': + is_streaming = True + return start_response(status, headers, exc_info) if content_type and content_type in COMPRESSIBLE_MIMES: if content_length is None or content_length >= self.min_size: @@ -61,7 +65,12 @@ class GzipMiddleware: return None - response_body = b''.join(self.app(environ, custom_start_response)) + app_iter = self.app(environ, custom_start_response) + + if is_streaming: + return app_iter + + response_body = b''.join(app_iter) if not response_started: return [response_body] diff --git a/app/ui.py b/app/ui.py index 8287259..461d006 100644 --- a/app/ui.py +++ b/app/ui.py @@ -711,6 +711,7 @@ def stream_bucket_objects(bucket_name: str): headers={ 'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no', + 'X-Stream-Response': 'true', } ) diff --git a/static/js/bucket-detail-main.js b/static/js/bucket-detail-main.js index 97d047d..48dfbcd 100644 --- a/static/js/bucket-detail-main.js +++ b/static/js/bucket-detail-main.js @@ -182,6 +182,9 @@ let visibleItems = []; let renderedRange = { start: 0, end: 0 }; + let memoizedVisibleItems = null; + let memoizedInputs = { objectCount: -1, prefix: null, filterTerm: null }; + const createObjectRow = (obj, displayKey = null) => { const tr = document.createElement('tr'); tr.dataset.objectRow = ''; @@ -340,7 +343,21 @@ } }; - const computeVisibleItems = () => { + const computeVisibleItems = (forceRecompute = false) => { + const currentInputs = { + objectCount: allObjects.length, + prefix: currentPrefix, + filterTerm: currentFilterTerm + }; + + if (!forceRecompute && + memoizedVisibleItems !== null && + memoizedInputs.objectCount === currentInputs.objectCount && + memoizedInputs.prefix === currentInputs.prefix && + memoizedInputs.filterTerm === currentInputs.filterTerm) { + return memoizedVisibleItems; + } + const items = []; const folders = new Set(); @@ -381,6 +398,8 @@ return aKey.localeCompare(bKey); }); + memoizedVisibleItems = items; + memoizedInputs = currentInputs; return items; }; @@ -533,6 +552,8 @@ loadedObjectCount = 0; totalObjectCount = 0; allObjects = []; + memoizedVisibleItems = null; + memoizedInputs = { objectCount: -1, prefix: null, filterTerm: null }; pendingStreamObjects = []; streamAbortController = new AbortController(); @@ -643,6 +664,8 @@ loadedObjectCount = 0; totalObjectCount = 0; allObjects = []; + memoizedVisibleItems = null; + memoizedInputs = { objectCount: -1, prefix: null, filterTerm: null }; } if (append && loadMoreSpinner) { @@ -985,13 +1008,15 @@ }; const navigateToFolder = (prefix) => { + if (streamAbortController) { + streamAbortController.abort(); + streamAbortController = null; + } + currentPrefix = prefix; if (scrollContainer) scrollContainer.scrollTop = 0; - refreshVirtualList(); - renderBreadcrumb(prefix); - selectedRows.clear(); if (typeof updateBulkDeleteState === 'function') { @@ -1001,6 +1026,9 @@ if (previewPanel) previewPanel.classList.add('d-none'); if (previewEmpty) previewEmpty.classList.remove('d-none'); activeRow = null; + + isLoadingObjects = false; + loadObjects(false); }; const renderObjectsView = () => {