aird 0.4.23.dev12__tar.gz → 0.4.23.dev17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/MANIFEST.in +1 -0
- {aird-0.4.23.dev12/aird.egg-info → aird-0.4.23.dev17}/PKG-INFO +1 -1
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/constants/__init__.py +42 -0
- aird-0.4.23.dev17/aird/core/folder_size.py +101 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/core/mmap_handler.py +67 -26
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/api_handlers.py +53 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/base_handler.py +1 -1
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/transfer_ws_handlers.py +33 -15
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/view_handlers.py +16 -3
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/main.py +2 -2
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/css/app.css +1 -1
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/browse/app.js +69 -228
- aird-0.4.23.dev17/aird/static/js/download-manager.js +151 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/file-transfer-ws.js +40 -3
- aird-0.4.23.dev17/aird/static/js/folder-size-scan.js +188 -0
- aird-0.4.23.dev17/aird/static/js/transfer-tracker.js +315 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/_app_nav_header.html +32 -3
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/_bg_canvas.html +1 -1
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/admin.html +1 -1
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/admin_audit.html +4 -4
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/admin_ldap.html +3 -3
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/admin_login.html +4 -4
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/admin_network_shares.html +3 -3
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/admin_policies.html +4 -4
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/admin_tags.html +4 -4
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/admin_user_attributes.html +4 -4
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/admin_users.html +3 -3
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/browse.html +14 -12
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/directory.html +3 -3
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/edit.html +4 -4
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/error.html +3 -3
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/file.html +4 -4
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/ldap_config_create.html +97 -97
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/ldap_config_edit.html +99 -99
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/login.html +4 -4
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/media_view.html +3 -3
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/p2p_transfer.html +11 -11
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/profile.html +3 -3
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/share.html +2 -2
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/shared_list.html +3 -7
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/super_search.html +3 -3
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/tagged_files.html +32 -10
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/token_verification.html +3 -3
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/user_create.html +75 -75
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/user_edit.html +116 -116
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/utils/util.py +26 -5
- {aird-0.4.23.dev12 → aird-0.4.23.dev17/aird.egg-info}/PKG-INFO +1 -1
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird.egg-info/SOURCES.txt +1 -1
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/setup.py +1 -1
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_folder_size.py +7 -1
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_mmap_handler.py +42 -4
- aird-0.4.23.dev17/tests/test_wheel_static_assets.py +112 -0
- aird-0.4.23.dev12/aird/core/folder_size.py +0 -51
- aird-0.4.23.dev12/aird/handlers/folder_size_ws_handlers.py +0 -193
- aird-0.4.23.dev12/aird/static/js/download-manager.js +0 -309
- aird-0.4.23.dev12/aird/static/js/folder-size-scan.js +0 -115
- aird-0.4.23.dev12/tests/test_wheel_static_assets.py +0 -48
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/LICENSE +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/README.md +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/__init__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/__main__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/app_context.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/cli/__init__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/cli/__main__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/cli/authelia.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/cli/config.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/cli/main.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/cli/session.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/cloud/__init__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/config.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/constants/admin.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/constants/file_ops.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/constants/input_limits.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/constants/media.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/core/__init__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/core/events.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/core/file_operations.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/core/filter_expression.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/core/input_validation.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/core/security.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/core/share_root.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/core/websocket_manager.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/core/zip_download.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/database/__init__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/database/db.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/database/feature_flags.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/database/ldap.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/__init__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/audit.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/config.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/favorites.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/network_shares.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/policies.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/policy_decisions.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/policy_seeds.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/quota.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/resource_tags.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/schema.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/shares.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/user_attributes.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/db/users.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/domain/__init__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/domain/contracts.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/domain/models.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/email/__init__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/email/brevo.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/email/resolve.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/__init__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/abac_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/admin_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/auth_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/constants.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/file_op_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/health_handler.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/p2p_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/handlers/share_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/network_share_manager.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/server_runtime.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/__init__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/audit_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/config_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/email_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/email_subscriber.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/event_subscribers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/favorites_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/network_share_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/p2p_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/policy_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/quota_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/share_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/tag_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/services/user_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/sql_identifiers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/favicon.png +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/favicon.svg +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/img/logo-icon.png +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/img/logo-mark.svg +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/img/logo-text.png +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/img/logo.png +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/aird-core.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/bg-canvas.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/common/command-palette.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/components/folder-picker.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/feature-flags-live.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/login-ui.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/media-view.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/p2p/app.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/p2p/mediator.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/p2p/qr-adapter.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/p2p/signaling-service.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/p2p/state-machine.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/p2p/transfer-service.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/pages/p2p-page.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/pages/super-search.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/share/app.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/theme.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/vendor/pdf.min.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/vendor/pdf.worker.min.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/static/js/vendor/qrcode-browser.js +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/_admin_tabs.html +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/_theme_early.html +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/templates/_theme_login_corner.html +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird/utils/__init__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird.egg-info/dependency_links.txt +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird.egg-info/entry_points.txt +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird.egg-info/requires.txt +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/aird.egg-info/top_level.txt +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/setup.cfg +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/__init__.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/conftest.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/handler_helpers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_admin_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_api_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_architecture_conformance.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_auth_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_auth_handlers_extended.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_base_handler.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_base_handler_pep.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_cli.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_cloud.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_config.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_core_file_operations.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_database_db.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_database_feature_flags.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_database_ldap.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_database_shares.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_database_users.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_database_users_hashing.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_db.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_email_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_file_op_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_filter_expression.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_main.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_multi_user.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_network_shares.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_p2p_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_password_hashing.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_policy_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_rate_limit.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_security.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_security_comprehensive.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_server_runtime.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_share_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_share_ownership.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_super_search_handler.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_tag_service.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_transfer_ws_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_util.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_view_handlers.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_websocket_manager.py +0 -0
- {aird-0.4.23.dev12 → aird-0.4.23.dev17}/tests/test_zip_download.py +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
7
8
|
from aird.cloud import CloudManager
|
|
@@ -100,6 +101,8 @@ UPLOAD_ALLOWED_EXTENSIONS = set(ALLOWED_UPLOAD_EXTENSIONS)
|
|
|
100
101
|
# Mmap constants
|
|
101
102
|
MMAP_MIN_SIZE = 1 * 1024 * 1024 # 1 MB
|
|
102
103
|
CHUNK_SIZE = 64 * 1024 # 64 KB
|
|
104
|
+
# WebSocket binary frame size (stay under Cloudflare ~1 MiB message limit)
|
|
105
|
+
WS_TRANSFER_FRAME_BYTES = 768 * 1024
|
|
103
106
|
|
|
104
107
|
# Network share manager (set at startup)
|
|
105
108
|
NETWORK_SHARE_MANAGER = None
|
|
@@ -141,3 +144,42 @@ def _read_app_version() -> str:
|
|
|
141
144
|
|
|
142
145
|
|
|
143
146
|
APP_VERSION = _read_app_version()
|
|
147
|
+
|
|
148
|
+
_UI_PACKAGE_SUFFIXES = frozenset(
|
|
149
|
+
{".html", ".css", ".js", ".png", ".ico", ".svg", ".jpg", ".jpeg", ".webp", ".gif"}
|
|
150
|
+
)
|
|
151
|
+
_PKG_ROOT = Path(__file__).resolve().parent.parent
|
|
152
|
+
_static_version_cache: tuple[float, str] | None = None
|
|
153
|
+
_STATIC_VERSION_TTL = 2.0
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _ui_fingerprint() -> str:
|
|
157
|
+
"""Hex fingerprint from latest mtime among shipped UI package-data files."""
|
|
158
|
+
latest = 0
|
|
159
|
+
for sub in ("static", "templates"):
|
|
160
|
+
base = _PKG_ROOT / sub
|
|
161
|
+
if not base.is_dir():
|
|
162
|
+
continue
|
|
163
|
+
for path in base.rglob("*"):
|
|
164
|
+
if not path.is_file():
|
|
165
|
+
continue
|
|
166
|
+
if path.suffix.lower() not in _UI_PACKAGE_SUFFIXES:
|
|
167
|
+
continue
|
|
168
|
+
try:
|
|
169
|
+
latest = max(latest, path.stat().st_mtime_ns)
|
|
170
|
+
except OSError:
|
|
171
|
+
pass
|
|
172
|
+
return format(latest, "x")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_static_version() -> str:
|
|
176
|
+
"""Cache-bust query value: package version plus UI file fingerprint."""
|
|
177
|
+
import time
|
|
178
|
+
|
|
179
|
+
global _static_version_cache
|
|
180
|
+
now = time.monotonic()
|
|
181
|
+
if _static_version_cache and now - _static_version_cache[0] < _STATIC_VERSION_TTL:
|
|
182
|
+
return _static_version_cache[1]
|
|
183
|
+
version = f"{APP_VERSION}-{_ui_fingerprint()}"
|
|
184
|
+
_static_version_cache = (now, version)
|
|
185
|
+
return version
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Incremental folder size calculation (sum of file sizes)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Files processed per batch before yielding back to the event loop.
|
|
9
|
+
FOLDER_SIZE_BATCH_FILES = 250
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FolderSizeWalker:
|
|
13
|
+
"""Walk a directory tree in batches without loading all paths at once."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, root_abspath: str) -> None:
|
|
16
|
+
self._walk_gen = os.walk(root_abspath)
|
|
17
|
+
self._current_dir: str | None = None
|
|
18
|
+
self._pending_files: list[str] = []
|
|
19
|
+
self.total_bytes = 0
|
|
20
|
+
self.file_count = 0
|
|
21
|
+
self.done = False
|
|
22
|
+
|
|
23
|
+
def step(self, batch_size: int = FOLDER_SIZE_BATCH_FILES) -> tuple[int, int, bool]:
|
|
24
|
+
"""Process up to *batch_size* files. Returns (total_bytes, file_count, done)."""
|
|
25
|
+
if self.done:
|
|
26
|
+
return self.total_bytes, self.file_count, True
|
|
27
|
+
|
|
28
|
+
processed = 0
|
|
29
|
+
while processed < batch_size and not self.done:
|
|
30
|
+
if not self._pending_files:
|
|
31
|
+
try:
|
|
32
|
+
self._current_dir, _dirnames, filenames = next(self._walk_gen)
|
|
33
|
+
self._pending_files = list(filenames)
|
|
34
|
+
except StopIteration:
|
|
35
|
+
self.done = True
|
|
36
|
+
break
|
|
37
|
+
|
|
38
|
+
while self._pending_files and processed < batch_size:
|
|
39
|
+
fname = self._pending_files.pop()
|
|
40
|
+
if not self._current_dir:
|
|
41
|
+
continue
|
|
42
|
+
fpath = os.path.join(self._current_dir, fname)
|
|
43
|
+
try:
|
|
44
|
+
if os.path.isfile(fpath):
|
|
45
|
+
self.total_bytes += os.path.getsize(fpath)
|
|
46
|
+
except OSError:
|
|
47
|
+
pass
|
|
48
|
+
self.file_count += 1
|
|
49
|
+
processed += 1
|
|
50
|
+
|
|
51
|
+
return self.total_bytes, self.file_count, self.done
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def norm_rel_path(rel_path: str) -> str:
|
|
55
|
+
return rel_path.replace("\\", "/").strip().strip("/")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def resolve_folder_abspath(user_root: str, rel_path: str) -> str | None:
|
|
59
|
+
"""Return absolute folder path or None if invalid / not a directory."""
|
|
60
|
+
from aird.core.security import is_within_root
|
|
61
|
+
|
|
62
|
+
rel = norm_rel_path(rel_path)
|
|
63
|
+
if not rel or ".." in rel.split("/"):
|
|
64
|
+
return None
|
|
65
|
+
abs_path = os.path.abspath(os.path.join(user_root, rel))
|
|
66
|
+
if not is_within_root(abs_path, user_root) or not os.path.isdir(abs_path):
|
|
67
|
+
return None
|
|
68
|
+
return abs_path
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def compute_folder_size(root_abspath: str) -> tuple[int, int]:
|
|
72
|
+
"""Sum file sizes under *root_abspath*. Returns (total_bytes, file_count)."""
|
|
73
|
+
walker = FolderSizeWalker(root_abspath)
|
|
74
|
+
while not walker.done:
|
|
75
|
+
walker.step(FOLDER_SIZE_BATCH_FILES)
|
|
76
|
+
return walker.total_bytes, walker.file_count
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def norm_rel_path(rel_path: str) -> str:
|
|
80
|
+
return rel_path.replace("\\", "/").strip().strip("/")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def resolve_folder_abspath(user_root: str, rel_path: str) -> str | None:
|
|
84
|
+
"""Return absolute folder path or None if invalid / not a directory."""
|
|
85
|
+
from aird.core.security import is_within_root
|
|
86
|
+
|
|
87
|
+
rel = norm_rel_path(rel_path)
|
|
88
|
+
if not rel or ".." in rel.split("/"):
|
|
89
|
+
return None
|
|
90
|
+
abs_path = os.path.abspath(os.path.join(user_root, rel))
|
|
91
|
+
if not is_within_root(abs_path, user_root) or not os.path.isdir(abs_path):
|
|
92
|
+
return None
|
|
93
|
+
return abs_path
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def compute_folder_size(root_abspath: str) -> tuple[int, int]:
|
|
97
|
+
"""Sum file sizes under *root_abspath*. Returns (total_bytes, file_count)."""
|
|
98
|
+
walker = FolderSizeWalker(root_abspath)
|
|
99
|
+
while not walker.done:
|
|
100
|
+
walker.step(FOLDER_SIZE_BATCH_FILES)
|
|
101
|
+
return walker.total_bytes, walker.file_count
|
|
@@ -25,20 +25,50 @@ def _read_chunks_sync(
|
|
|
25
25
|
return chunks
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
class _SyncChunkReader:
|
|
29
|
+
"""Read one chunk at a time from disk (mmap or plain file)."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
file_path: str,
|
|
34
|
+
start: int,
|
|
35
|
+
end: int | None,
|
|
36
|
+
file_size: int,
|
|
37
|
+
chunk_size: int,
|
|
38
|
+
use_mmap: bool,
|
|
39
|
+
) -> None:
|
|
40
|
+
self._file_path = file_path
|
|
41
|
+
self._pos = start
|
|
42
|
+
self._end = min(end if end is not None else file_size - 1, file_size - 1)
|
|
43
|
+
self._chunk_size = chunk_size
|
|
44
|
+
self._use_mmap = use_mmap
|
|
45
|
+
self._file = None
|
|
46
|
+
self._mm = None
|
|
47
|
+
|
|
48
|
+
def open(self) -> None:
|
|
49
|
+
self._file = open(self._file_path, "rb")
|
|
50
|
+
if self._use_mmap:
|
|
51
|
+
self._mm = mmap.mmap(self._file.fileno(), 0, access=mmap.ACCESS_READ)
|
|
52
|
+
|
|
53
|
+
def read_next(self) -> bytes | None:
|
|
54
|
+
if self._pos > self._end:
|
|
55
|
+
return None
|
|
56
|
+
chunk_end = min(self._pos + self._chunk_size, self._end + 1)
|
|
57
|
+
if self._mm is not None:
|
|
58
|
+
chunk = self._mm[self._pos : chunk_end]
|
|
59
|
+
else:
|
|
60
|
+
self._file.seek(self._pos)
|
|
61
|
+
chunk = self._file.read(chunk_end - self._pos)
|
|
62
|
+
self._pos = chunk_end
|
|
63
|
+
return chunk if chunk else None
|
|
64
|
+
|
|
65
|
+
def close(self) -> None:
|
|
66
|
+
if self._mm is not None:
|
|
67
|
+
self._mm.close()
|
|
68
|
+
self._mm = None
|
|
69
|
+
if self._file is not None:
|
|
70
|
+
self._file.close()
|
|
71
|
+
self._file = None
|
|
42
72
|
|
|
43
73
|
|
|
44
74
|
class MMapFileHandler:
|
|
@@ -53,12 +83,11 @@ class MMapFileHandler:
|
|
|
53
83
|
async def serve_file_chunk(
|
|
54
84
|
file_path: str, start: int = 0, end: int = None, chunk_size: int = CHUNK_SIZE
|
|
55
85
|
):
|
|
56
|
-
"""Serve file chunks
|
|
86
|
+
"""Serve file chunks; only one chunk is held in memory at a time."""
|
|
57
87
|
try:
|
|
58
88
|
file_size = await asyncio.to_thread(os.path.getsize, file_path)
|
|
59
89
|
|
|
60
90
|
if not MMapFileHandler.should_use_mmap(file_size):
|
|
61
|
-
# Use async file API for small files
|
|
62
91
|
remaining = (end - start + 1) if end is not None else file_size - start
|
|
63
92
|
async with aiofiles.open(file_path, "rb") as f:
|
|
64
93
|
await f.seek(start)
|
|
@@ -70,21 +99,33 @@ class MMapFileHandler:
|
|
|
70
99
|
remaining -= len(chunk)
|
|
71
100
|
return
|
|
72
101
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
_read_chunks_mmap, file_path, start, end, file_size, chunk_size
|
|
102
|
+
reader = _SyncChunkReader(
|
|
103
|
+
file_path, start, end, file_size, chunk_size, use_mmap=True
|
|
76
104
|
)
|
|
77
|
-
|
|
78
|
-
|
|
105
|
+
try:
|
|
106
|
+
await asyncio.to_thread(reader.open)
|
|
107
|
+
while True:
|
|
108
|
+
chunk = await asyncio.to_thread(reader.read_next)
|
|
109
|
+
if not chunk:
|
|
110
|
+
break
|
|
111
|
+
yield chunk
|
|
112
|
+
finally:
|
|
113
|
+
await asyncio.to_thread(reader.close)
|
|
79
114
|
|
|
80
115
|
except (OSError, ValueError):
|
|
81
|
-
# Fallback to traditional method on mmap errors (run in thread pool)
|
|
82
116
|
file_size = await asyncio.to_thread(os.path.getsize, file_path)
|
|
83
|
-
|
|
84
|
-
|
|
117
|
+
reader = _SyncChunkReader(
|
|
118
|
+
file_path, start, end, file_size, chunk_size, use_mmap=False
|
|
85
119
|
)
|
|
86
|
-
|
|
87
|
-
|
|
120
|
+
try:
|
|
121
|
+
await asyncio.to_thread(reader.open)
|
|
122
|
+
while True:
|
|
123
|
+
chunk = await asyncio.to_thread(reader.read_next)
|
|
124
|
+
if not chunk:
|
|
125
|
+
break
|
|
126
|
+
yield chunk
|
|
127
|
+
finally:
|
|
128
|
+
await asyncio.to_thread(reader.close)
|
|
88
129
|
|
|
89
130
|
@staticmethod
|
|
90
131
|
def find_line_offsets(file_path: str, max_lines: int = None) -> list[int]:
|
|
@@ -336,6 +336,59 @@ class FileStreamHandler(ManagedWebSocketMixin, tornado.websocket.WebSocketHandle
|
|
|
336
336
|
return is_valid_websocket_origin(self, origin)
|
|
337
337
|
|
|
338
338
|
|
|
339
|
+
class FolderSizeAPIHandler(BaseHandler):
|
|
340
|
+
"""GET /api/folder-size?path= — recursive folder byte total (on demand)."""
|
|
341
|
+
|
|
342
|
+
@tornado.web.authenticated
|
|
343
|
+
async def get(self):
|
|
344
|
+
from aird.core.folder_size import (
|
|
345
|
+
compute_folder_size,
|
|
346
|
+
norm_rel_path,
|
|
347
|
+
resolve_folder_abspath,
|
|
348
|
+
)
|
|
349
|
+
from aird.utils.util import format_size
|
|
350
|
+
|
|
351
|
+
path = self.get_argument("path", "").strip()
|
|
352
|
+
norm_path = norm_rel_path(path)
|
|
353
|
+
decision = self.check_access("file.list", resource_path=norm_path)
|
|
354
|
+
if decision is not None and decision.is_deny:
|
|
355
|
+
self.set_status(403)
|
|
356
|
+
self.set_header("Content-Type", "application/json")
|
|
357
|
+
self.write(json.dumps({"error": decision.reason or "Access denied"}))
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
user_root = get_user_root(self)
|
|
361
|
+
abs_path = resolve_folder_abspath(user_root, path)
|
|
362
|
+
if not abs_path:
|
|
363
|
+
self.set_status(404)
|
|
364
|
+
self.set_header("Content-Type", "application/json")
|
|
365
|
+
self.write(json.dumps({"error": "Folder not found"}))
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
try:
|
|
369
|
+
total_bytes, file_count = await asyncio.to_thread(
|
|
370
|
+
compute_folder_size, abs_path
|
|
371
|
+
)
|
|
372
|
+
except Exception:
|
|
373
|
+
logging.exception("Folder size calculation failed for %s", norm_path)
|
|
374
|
+
self.set_status(500)
|
|
375
|
+
self.set_header("Content-Type", "application/json")
|
|
376
|
+
self.write(json.dumps({"error": "Folder size calculation failed"}))
|
|
377
|
+
return
|
|
378
|
+
|
|
379
|
+
self.set_header("Content-Type", "application/json")
|
|
380
|
+
self.write(
|
|
381
|
+
json.dumps(
|
|
382
|
+
{
|
|
383
|
+
"path": norm_path,
|
|
384
|
+
"bytes": total_bytes,
|
|
385
|
+
"files": file_count,
|
|
386
|
+
"size_str": format_size(total_bytes),
|
|
387
|
+
}
|
|
388
|
+
)
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
339
392
|
class FileListAPIHandler(BaseHandler):
|
|
340
393
|
@tornado.web.authenticated
|
|
341
394
|
@require_action("file.list", resource_arg="path")
|
|
@@ -897,7 +897,7 @@ class BaseHandler(tornado.web.RequestHandler):
|
|
|
897
897
|
namespace.setdefault("nav_title", "")
|
|
898
898
|
namespace.setdefault("show_admin_link", False)
|
|
899
899
|
namespace.setdefault("ldap_enabled", self.settings.get("ldap_server") is not None)
|
|
900
|
-
namespace.setdefault("static_version", constants_module.
|
|
900
|
+
namespace.setdefault("static_version", constants_module.get_static_version())
|
|
901
901
|
return namespace
|
|
902
902
|
|
|
903
903
|
def get_current_user(self):
|
|
@@ -21,6 +21,7 @@ from aird.constants.file_ops import (
|
|
|
21
21
|
FILE_UPLOAD_DISABLED,
|
|
22
22
|
UPLOAD_SAVE_FAILED,
|
|
23
23
|
)
|
|
24
|
+
from aird.constants import WS_TRANSFER_FRAME_BYTES
|
|
24
25
|
from aird.core.mmap_handler import MMapFileHandler
|
|
25
26
|
from aird.core.security import is_valid_websocket_origin, is_within_root
|
|
26
27
|
from aird.handlers.base_handler import (
|
|
@@ -108,7 +109,8 @@ class FileTransferWebSocketHandler(
|
|
|
108
109
|
super().__init__(application, request, **kwargs)
|
|
109
110
|
self._upload: dict | None = None
|
|
110
111
|
self._upload_buffer: deque[bytes] = deque()
|
|
111
|
-
self.
|
|
112
|
+
self._upload_buffer_event = asyncio.Event()
|
|
113
|
+
self._upload_writer_done = False
|
|
112
114
|
self._upload_writer_task: asyncio.Task | None = None
|
|
113
115
|
self._cancelled = False
|
|
114
116
|
self._download_task: asyncio.Task | None = None
|
|
@@ -199,6 +201,9 @@ class FileTransferWebSocketHandler(
|
|
|
199
201
|
"temp_path": temp_path,
|
|
200
202
|
"aiofile": aiofile,
|
|
201
203
|
}
|
|
204
|
+
self._upload_writer_done = False
|
|
205
|
+
self._upload_buffer_event.clear()
|
|
206
|
+
self._upload_writer_task = asyncio.create_task(self._upload_writer_loop())
|
|
202
207
|
await self._send_json({"type": "upload_started", "total_size": total_size})
|
|
203
208
|
|
|
204
209
|
async def _handle_upload_binary(self, data: bytes) -> None:
|
|
@@ -212,31 +217,40 @@ class FileTransferWebSocketHandler(
|
|
|
212
217
|
return
|
|
213
218
|
|
|
214
219
|
self._upload_buffer.append(data)
|
|
215
|
-
|
|
216
|
-
self._upload_writing = True
|
|
217
|
-
self._upload_writer_task = asyncio.create_task(self._drain_upload_buffer())
|
|
220
|
+
self._upload_buffer_event.set()
|
|
218
221
|
|
|
219
|
-
async def
|
|
222
|
+
async def _upload_writer_loop(self) -> None:
|
|
223
|
+
"""Drain the upload buffer continuously until signalled to stop."""
|
|
220
224
|
try:
|
|
221
|
-
while
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
while True:
|
|
226
|
+
await self._upload_buffer_event.wait()
|
|
227
|
+
self._upload_buffer_event.clear()
|
|
228
|
+
while self._upload_buffer and self._upload:
|
|
229
|
+
chunk = self._upload_buffer.popleft()
|
|
230
|
+
await self._upload["aiofile"].write(chunk)
|
|
231
|
+
if self._upload:
|
|
232
|
+
await self._upload["aiofile"].flush()
|
|
233
|
+
if self._upload_writer_done:
|
|
234
|
+
return
|
|
235
|
+
except asyncio.CancelledError:
|
|
236
|
+
pass
|
|
237
|
+
except Exception:
|
|
238
|
+
logger.debug("WS upload writer loop failed", exc_info=True)
|
|
228
239
|
|
|
229
240
|
async def _finalize_upload_writer(self) -> None:
|
|
241
|
+
self._upload_writer_done = True
|
|
242
|
+
self._upload_buffer_event.set()
|
|
230
243
|
if self._upload_writer_task is not None:
|
|
231
244
|
try:
|
|
232
245
|
await self._upload_writer_task
|
|
233
246
|
except Exception:
|
|
234
|
-
|
|
247
|
+
logger.debug("WS upload writer await failed", exc_info=True)
|
|
248
|
+
self._upload_writer_task = None
|
|
235
249
|
if self._upload and self._upload.get("aiofile"):
|
|
236
250
|
try:
|
|
237
251
|
await self._upload["aiofile"].close()
|
|
238
252
|
except Exception:
|
|
239
|
-
|
|
253
|
+
logger.debug("WS upload file close failed", exc_info=True)
|
|
240
254
|
self._upload["aiofile"] = None
|
|
241
255
|
|
|
242
256
|
async def _abort_upload(self, message: str | None = None) -> None:
|
|
@@ -339,12 +353,16 @@ class FileTransferWebSocketHandler(
|
|
|
339
353
|
"filename": filename,
|
|
340
354
|
"content_type": content_type,
|
|
341
355
|
"size": file_size,
|
|
356
|
+
"chunk_size": WS_TRANSFER_FRAME_BYTES,
|
|
342
357
|
}
|
|
343
358
|
)
|
|
344
|
-
async for chunk in MMapFileHandler.serve_file_chunk(
|
|
359
|
+
async for chunk in MMapFileHandler.serve_file_chunk(
|
|
360
|
+
abs_path, chunk_size=WS_TRANSFER_FRAME_BYTES
|
|
361
|
+
):
|
|
345
362
|
if self._cancelled:
|
|
346
363
|
return
|
|
347
364
|
await self.write_message(chunk, binary=True)
|
|
365
|
+
await asyncio.sleep(0)
|
|
348
366
|
await self._send_json({"type": "download_end"})
|
|
349
367
|
except asyncio.CancelledError:
|
|
350
368
|
raise
|
|
@@ -506,18 +506,31 @@ class TaggedFilesHandler(BaseHandler):
|
|
|
506
506
|
patterns = [r["glob_pattern"] for r in rules if r.get("tag") == tag_name]
|
|
507
507
|
all_tags = sorted({r["tag"] for r in rules if r.get("tag")})
|
|
508
508
|
|
|
509
|
-
|
|
509
|
+
entries: list[dict] = []
|
|
510
|
+
file_count = 0
|
|
511
|
+
folder_count = 0
|
|
510
512
|
if patterns:
|
|
511
513
|
root = constants_module.ROOT_DIR
|
|
512
|
-
|
|
514
|
+
for path in get_files_by_tag_patterns(patterns, root):
|
|
515
|
+
is_dir = path.endswith("/")
|
|
516
|
+
if is_dir:
|
|
517
|
+
folder_count += 1
|
|
518
|
+
name = path.rstrip("/")
|
|
519
|
+
else:
|
|
520
|
+
file_count += 1
|
|
521
|
+
name = path
|
|
522
|
+
entries.append({"path": path, "name": name, "is_dir": is_dir})
|
|
513
523
|
|
|
514
524
|
self.render(
|
|
515
525
|
"tagged_files.html",
|
|
516
526
|
tag_name=tag_name,
|
|
517
527
|
patterns=patterns,
|
|
518
|
-
|
|
528
|
+
entries=entries,
|
|
529
|
+
file_count=file_count,
|
|
530
|
+
folder_count=folder_count,
|
|
519
531
|
all_tags=all_tags,
|
|
520
532
|
user=self.current_user,
|
|
533
|
+
get_file_icon=get_file_icon,
|
|
521
534
|
)
|
|
522
535
|
|
|
523
536
|
|
|
@@ -89,6 +89,7 @@ from aird.handlers.api_handlers import (
|
|
|
89
89
|
FeatureFlagAPIHandler,
|
|
90
90
|
FeatureFlagSocketHandler,
|
|
91
91
|
FileListAPIHandler,
|
|
92
|
+
FolderSizeAPIHandler,
|
|
92
93
|
FileStreamHandler,
|
|
93
94
|
ShareDetailsAPIHandler,
|
|
94
95
|
ShareDetailsByIdAPIHandler,
|
|
@@ -105,7 +106,6 @@ from aird.handlers.auth_handlers import (
|
|
|
105
106
|
MandatoryPasswordHandler,
|
|
106
107
|
ProfileHandler,
|
|
107
108
|
)
|
|
108
|
-
from aird.handlers.folder_size_ws_handlers import FolderSizeWebSocketHandler
|
|
109
109
|
from aird.handlers.transfer_ws_handlers import FileTransferWebSocketHandler
|
|
110
110
|
from aird.handlers.file_op_handlers import (
|
|
111
111
|
CloudUploadHandler,
|
|
@@ -237,7 +237,7 @@ def make_app(
|
|
|
237
237
|
(r"/ws/policy-decisions", PolicyDecisionsWebSocket),
|
|
238
238
|
(r"/stream/(.*)", FileStreamHandler),
|
|
239
239
|
(r"/ws/file-transfer", FileTransferWebSocketHandler),
|
|
240
|
-
(r"/
|
|
240
|
+
(r"/api/folder-size", FolderSizeAPIHandler),
|
|
241
241
|
(r"/features", FeatureFlagSocketHandler),
|
|
242
242
|
(r"/api/features", FeatureFlagAPIHandler),
|
|
243
243
|
(r"/upload", UploadHandler),
|