├── .github └── workflows │ └── python-publish.yml ├── .gitmodules ├── LICENSE ├── MANIFEST.in ├── README.md ├── disk_treemap ├── Everything64.dll ├── Everything_license.txt ├── __init__.py ├── main.py ├── scan_everything.py ├── scan_fs.py └── scan_s3.py ├── optional-requirements.txt ├── requirements.txt └── setup.py /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | submodules: recursive 19 | - name: Set up Python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: '3.x' 23 | - uses: actions/setup-node@v1 24 | with: 25 | node-version: 12 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install setuptools wheel twine 30 | - name: Build and publish 31 | env: 32 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 33 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 34 | run: | 35 | python setup.py build_static 36 | python setup.py sdist bdist_wheel 37 | twine upload dist/* 38 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "disk_treemap/static"] 2 | path = disk_treemap/static 3 | url = https://github.com/exzhawk/disk_treemap_static.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Epix Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include disk_treemap/static/dist/webapp * 2 | include disk_treemap/Everything64.dll disk_treemap/Everything_license.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # disk_treemap 2 | 3 | Just another disk usage analyzer with treemap GUI. 4 | 5 | ## Pros 6 | 7 | * Written in Python. Easy to run, modify and extend. Cross platform support. (Tested on Linux, Windows, and Android with 8 | Termux.) 9 | * B/S structure. You can run the scanner on a remote machine, and view treemap via a browser on a local machine 10 | * Support save/load a treemap. You can run the scanner on a machine, then copy result to another one and view it. 11 | * Support S3 and S3 compatible object storage service 12 | * Support using [*Everything by voidtools*](https://www.voidtools.com/) to speedup scanning extraordinarily. 13 | 14 | ## Cons 15 | 16 | * The web base frontend may suffer from performance issue if the treemap is too large. 17 | 18 | # Installation 19 | 20 | ## Install via pip package installer 21 | 22 | ```shell 23 | pip install disk_treemap 24 | ``` 25 | 26 | ## Build and install from source 27 | 28 | **dependencies:** 29 | * npm: `npm` must be in `PATH` to build static. Make sure NodeJS and npm is installed and in `PATH`. 30 | 31 | ```shell 32 | # clone the repository 33 | git clone 34 | git submodule update --recursive 35 | # build static 36 | python3 setup.py build_static 37 | 38 | #build wheel 39 | python3 setup.py bdist_wheel 40 | #install built wheel 41 | pip install dist/disk_treemap-1.0.0-py3-none-any.whl # change the filename 42 | ``` 43 | 44 | # Usage 45 | 46 | ``` 47 | usage: disk-treemap [-h] [--size-tree-path SIZE_TREE_PATH] [--overwrite] 48 | [--scan-only] [--host HOST] [--port PORT] [--compression] 49 | [--endpoint-url ENDPOINT_URL] [--follow-links] 50 | [--follow-mounts] [--everything] 51 | [paths ...] 52 | 53 | positional arguments: 54 | paths path(s) to scan. If multiple paths is provided, they 55 | will be show in root side by side. S3 or compatible 56 | object storage service is supported by a "s3://" 57 | prefixed URI 58 | 59 | optional arguments: 60 | -h, --help show this help message and exit 61 | --size-tree-path SIZE_TREE_PATH, --size_tree_path SIZE_TREE_PATH, -f SIZE_TREE_PATH 62 | path to save scan result as a JSON file 63 | --overwrite, -o overwrite existed JSON file. default to False 64 | --scan-only, --scan_only, -s 65 | scan and save JSON file but do not start web server. 66 | default to False 67 | --host HOST, -H HOST listening host of the web server 68 | --port PORT, -p PORT listening port of the web server. default to 8000 69 | --compression, -c enable compression of web server. require 70 | flask_compress to operate. default to False 71 | --endpoint-url ENDPOINT_URL 72 | custom endpoint url, only affects S3 73 | --follow-links, --follow_links 74 | follow symlinks 75 | --follow-mounts, --follow_mounts 76 | follow mounts 77 | --everything use Everything by voidtools to speedup scanning. The 78 | result will be absolute path. Everything must be 79 | running and only x64 version is supported. 80 | ``` 81 | 82 | You may also use the module directly: `python -m disk_treemap.main`. Same arguments apply. 83 | 84 | A `size_tree.json` will be generated in the current directory. It contains file tree and file size information. Keep it 85 | safe! 86 | 87 | # Typical Usage 88 | 89 | **Analyze an ordinary computer** 90 | 91 | 1. Run `disk-treemap ` 92 | 93 | 1. After `listening 127.0.0.1:8000` appearing, open browser and navigate to http://127.0.0.1:8000 . 94 | 95 | **Analyze a Windows computer with Everything x64 installed** 96 | 97 | 1. Make sure Everything x64 is running. Get it from https://www.voidtools.com/ if it's not installed. It's free. 98 | 99 | 1. Run `disk-treemap --everything ` 100 | 101 | 1. After `listening 127.0.0.1:8000` appearing, open browser and navigate to http://127.0.0.1:8000 . 102 | 103 | **Analyze a remote Linux server, view on the local machine** 104 | 105 | 1. Run `disk-treemap --host 0.0.0.0`. 106 | 107 | If bandwidth between the server and the local machine is limited, try to install optional dependencies and append `--compression` to command above to enable compression. 108 | 109 | 1. After `listening 0.0.0.0:8000` appearing, open browser on the local machine and navigate to http: 110 | //:8000 . 111 | 112 | **Analyze a remote Linux server without external accessible IP, view on the local machine** 113 | 114 | 1. Run `disk-treemap --scan_only` 115 | 116 | 1. After process exit without error. There should be a file named `size_tree.json` in the current directory. Copy the 117 | file to local machine using `rsync` or other tools. 118 | 119 | 1. Run `disk-treemap` in the directory where the copied file located. 120 | 121 | 1. After `listening 127.0.0.1:8000` appearing, open browser and navigate to http://127.0.0.1:8000 . 122 | 123 | **Install and analyze an Android phone with Termux** 124 | 125 | 1. Install Termux https://termux.com/ 126 | 127 | 1. Install Python and pip in Termux https://wiki.termux.com/wiki/Python 128 | 129 | 1. Install disk_treemap via pip: `pip install disk_treemap` 130 | 131 | 1. Just do analyze as your phone is an ordinary computer. 132 | 133 | # TODO 134 | 135 | * Provide more visualization. Icicle/flame, sunburst maybe. 136 | 137 | * Use NTFS USN Journal to speed up scanning on Windows. 138 | -------------------------------------------------------------------------------- /disk_treemap/Everything64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exzhawk/disk_treemap/2c393f698074388857aae19fc86eb5bbba7bd8fb/disk_treemap/Everything64.dll -------------------------------------------------------------------------------- /disk_treemap/Everything_license.txt: -------------------------------------------------------------------------------- 1 | Everything 2 | 3 | Copyright (C) 2018 David Carpenter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | 23 | Perl-Compatible Regular Expressions 24 | 25 | Copyright (c) 1997-2012 University of Cambridge 26 | 27 | Redistribution and use in source and binary forms, with or without 28 | modification, are permitted provided that the following conditions are met: 29 | 30 | * Redistributions of source code must retain the above copyright notice, 31 | this list of conditions and the following disclaimer. 32 | 33 | * Redistributions in binary form must reproduce the above copyright 34 | notice, this list of conditions and the following disclaimer in the 35 | documentation and/or other materials provided with the distribution. 36 | 37 | * Neither the name of the University of Cambridge nor the names of its 38 | contributors may be used to endorse or promote products derived from 39 | this software without specific prior written permission. 40 | 41 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 42 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 43 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 44 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 45 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 46 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 47 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 48 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 49 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 50 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 51 | POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /disk_treemap/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # Author: Epix 3 | 4 | __version__ = '1.0.10' 5 | -------------------------------------------------------------------------------- /disk_treemap/main.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # Author: Epix 3 | import argparse 4 | import json 5 | # import ujson as json 6 | import os 7 | import sys 8 | from pathlib import Path 9 | 10 | from flask import Flask, send_file, jsonify 11 | 12 | 13 | def start_server(size_tree_file_path, host, port, compression): 14 | app = Flask(__name__) 15 | base_dir = Path(__file__).absolute().parent / 'static' / 'dist' / 'webapp' 16 | app.root_path = str(base_dir) 17 | if compression: 18 | from flask_compress import Compress 19 | Compress(app) 20 | 21 | @app.route('/') 22 | def index(): 23 | return send_file('index.html') 24 | 25 | @app.route('/') 26 | def static_files(path): 27 | return send_file(path) 28 | 29 | @app.route('/size_tree.json') 30 | def size_tree(): 31 | return send_file(size_tree_file_path, max_age=-1) 32 | 33 | @app.route('/info') 34 | def get_info(): 35 | info = { 36 | 'sep': os.path.sep 37 | } 38 | return jsonify(info) 39 | 40 | print('listening {}:{}'.format(host, port)) 41 | app.run(host, port) 42 | 43 | 44 | def scan_paths(root_paths, size_tree_file_path, args): 45 | all_size_tree = {} 46 | for root_path in root_paths: 47 | if root_path.startswith('s3://'): 48 | if root_path.endswith('/'): 49 | root_path = root_path[:-1] 50 | from .scan_s3 import scan_size_tree 51 | size_tree = scan_size_tree(root_path, args.endpoint_url) 52 | else: 53 | root_path = str(Path(root_path)) 54 | if args.everything: 55 | from .scan_everything import scan_size_tree 56 | size_tree = scan_size_tree(root_path) 57 | if not size_tree: 58 | from .scan_fs import scan_size_tree 59 | size_tree = scan_size_tree(root_path, args.follow_links, args.follow_mounts) 60 | else: 61 | from .scan_fs import scan_size_tree 62 | size_tree = scan_size_tree(root_path, args.follow_links, args.follow_mounts) 63 | all_size_tree.update(size_tree) 64 | with open(size_tree_file_path, 'w') as f: 65 | json.dump(all_size_tree, f) 66 | print('scanning complete.') 67 | 68 | 69 | def main(): 70 | parser = argparse.ArgumentParser() 71 | parser.add_argument('paths', nargs='*', 72 | help='path(s) to scan. ' 73 | 'If multiple paths is provided, they will be show in root side by side. ' 74 | 'S3 or compatible object storage service is supported by a "s3://" prefixed URI') 75 | parser.add_argument('--size-tree-path', '--size_tree_path', '-f', default='size_tree.json', 76 | help='path to save scan result as a JSON file') 77 | parser.add_argument('--overwrite', '-o', action='store_true', 78 | help='overwrite existed JSON file. default to False') 79 | parser.add_argument('--scan-only', '--scan_only', '-s', action='store_true', 80 | help='scan and save JSON file but do not start web server. default to False') 81 | parser.add_argument('--host', '-H', default='127.0.0.1', 82 | help='listening host of the web server') 83 | parser.add_argument('--port', '-p', default=8000, type=int, 84 | help='listening port of the web server. default to 8000') 85 | parser.add_argument('--compression', '-c', action='store_true', 86 | help='enable compression of web server. require flask_compress to operate. default to False') 87 | parser.add_argument('--endpoint-url', help='custom endpoint url, only affects S3') 88 | parser.add_argument('--follow-links', '--follow_links', action='store_true', 89 | help='follow symlinks') 90 | parser.add_argument('--follow-mounts', '--follow_mounts', action='store_true', 91 | help='follow mounts') 92 | parser.add_argument('--everything', action='store_true', 93 | help='use Everything by voidtools to speedup scanning. The result will be absolute path. ' 94 | 'Everything must be running and only x64 version is supported.') 95 | # parser.add_argument('--mlocate', nargs='?', const=True, default=False, 96 | # help='use mlocate to speedup scanning. you may specify location of mlocate.db file') 97 | args = parser.parse_args() 98 | root_paths = args.paths 99 | size_tree_file_path = os.path.abspath(args.size_tree_path) 100 | if os.path.exists(size_tree_file_path): 101 | if args.overwrite: 102 | scan_paths(root_paths, size_tree_file_path, args) 103 | else: 104 | print('{} exists. Skip scanning process.'.format(args.size_tree_path)) 105 | else: 106 | if len(root_paths) == 0: 107 | print('nothing to scan and nothing to show. exiting.') 108 | return -1 109 | else: 110 | scan_paths(root_paths, size_tree_file_path, args) 111 | 112 | if not args.scan_only: 113 | start_server(size_tree_file_path=size_tree_file_path, host=args.host, port=args.port, 114 | compression=args.compression) 115 | return 0 116 | 117 | 118 | if __name__ == '__main__': 119 | sys.exit(main()) 120 | -------------------------------------------------------------------------------- /disk_treemap/scan_everything.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # Author: Epix 3 | import ctypes 4 | import os 5 | from pathlib import Path, PureWindowsPath 6 | 7 | from tqdm import tqdm 8 | 9 | # copied from https://www.voidtools.com/support/everything/sdk/python/ 10 | 11 | # defines 12 | EVERYTHING_REQUEST_FILE_NAME = 0x00000001 13 | EVERYTHING_REQUEST_PATH = 0x00000002 14 | EVERYTHING_REQUEST_SIZE = 0x00000010 15 | EVERYTHING_REQUEST_ATTRIBUTES = 0x00000100 16 | 17 | # dll imports 18 | try: 19 | everything_dll = ctypes.WinDLL(str(Path(__file__).parent.resolve() / "Everything64.dll")) 20 | except: 21 | raise RuntimeError('Load DLL failed. Only Windows x64 is supported') 22 | everything_dll.Everything_GetResultSize.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_ulonglong)] 23 | everything_dll.Everything_GetResultFileNameW.argtypes = [ctypes.c_int] 24 | everything_dll.Everything_GetResultFileNameW.restype = ctypes.c_wchar_p 25 | 26 | 27 | def scan(top_path): 28 | prefix_len = len(top_path.rstrip(os.sep)) + 1 29 | if os.name == 'nt': 30 | if ':' in top_path: 31 | prefix_len -= 1 # backslash after drive letter and colon is missing under windows 32 | 33 | # setup search 34 | everything_dll.Everything_SetSearchW(f'"{top_path}"') 35 | everything_dll.Everything_SetRequestFlags( 36 | EVERYTHING_REQUEST_FILE_NAME | EVERYTHING_REQUEST_PATH | EVERYTHING_REQUEST_SIZE) 37 | 38 | # execute the query 39 | everything_dll.Everything_QueryW(1) 40 | 41 | # get the number of results 42 | num_results = everything_dll.Everything_GetNumResults() 43 | 44 | # create buffers 45 | filename = ctypes.create_unicode_buffer(260) 46 | file_size = ctypes.c_ulonglong(1) 47 | 48 | directory_size = 2 ** 64 - 1 49 | 50 | # show results 51 | for i in range(num_results): 52 | everything_dll.Everything_GetResultFullPathNameW(i, filename, 260) 53 | everything_dll.Everything_GetResultSize(i, file_size) 54 | if file_size.value == directory_size: # no way to judge if it's a directory 55 | continue 56 | yield ctypes.wstring_at(filename)[prefix_len:], file_size.value 57 | 58 | 59 | def scan_size_tree(root_path): 60 | root_path = str(PureWindowsPath(Path(root_path).absolute())) 61 | size_tree = dict() 62 | for path, size in tqdm(scan(root_path), desc=root_path): 63 | *directories, filename = path.lstrip(os.sep).split(os.sep) 64 | directories.insert(0, root_path) 65 | current_size_tree = size_tree 66 | for directory in directories: 67 | if directory not in current_size_tree: 68 | current_size_tree[directory] = dict() 69 | current_size_tree = current_size_tree[directory] 70 | current_size_tree[filename] = size 71 | return size_tree 72 | -------------------------------------------------------------------------------- /disk_treemap/scan_fs.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # Author: Epix 3 | import os 4 | 5 | from tqdm import tqdm 6 | 7 | 8 | def scan(top_path, follow_links, follow_mounts): 9 | prefix_len = len(top_path.rstrip(os.sep)) + 1 10 | if os.name == 'nt': 11 | if ':' in top_path: 12 | prefix_len -= 1 # backslash after drive letter and colon is missing under windows 13 | 14 | for root, dirs, filenames in os.walk(top_path, followlinks=follow_links): 15 | if not follow_mounts: 16 | dirs[:] = filter(lambda d: not os.path.ismount(os.path.join(root, d)), dirs) 17 | try: 18 | for filename in filenames: 19 | file_path = os.path.join(root, filename) 20 | yield file_path[prefix_len:], os.path.getsize(file_path) 21 | except (FileNotFoundError, PermissionError, OSError): 22 | pass 23 | 24 | 25 | def scan_size_tree(root_path, follow_links, follow_mounts): 26 | size_tree = dict() 27 | for path, size in tqdm(scan(root_path, follow_links, follow_mounts), desc=root_path): 28 | *directories, filename = path.lstrip(os.sep).split(os.sep) 29 | directories.insert(0, root_path) 30 | current_size_tree = size_tree 31 | for directory in directories: 32 | if directory not in current_size_tree: 33 | current_size_tree[directory] = dict() 34 | current_size_tree = current_size_tree[directory] 35 | current_size_tree[filename] = size 36 | return size_tree 37 | -------------------------------------------------------------------------------- /disk_treemap/scan_s3.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Epix 3 | from functools import lru_cache 4 | 5 | import boto3 6 | from tqdm import tqdm 7 | 8 | 9 | @lru_cache(maxsize=1) 10 | def get_s3_client2(endpoint_url): 11 | return boto3.client('s3', endpoint_url=endpoint_url) 12 | 13 | 14 | def walk_with_size(s3_url, endpoint_url): 15 | s3_url = s3_url + '/' 16 | bucket, prefix = s3_url[5:].split('/', 1) 17 | s3_client = get_s3_client2(endpoint_url) 18 | paginator = s3_client.get_paginator('list_objects_v2') 19 | response_iterator = paginator.paginate(Bucket=bucket, Prefix=prefix) 20 | for response in response_iterator: 21 | yield [(f['Key'], f['Size']) for f in response.get('Contents', ())] 22 | 23 | 24 | def scan(top_path, endpoint_url): 25 | for files in walk_with_size(top_path, endpoint_url): 26 | for filename, size in files: 27 | yield filename, size 28 | 29 | 30 | def scan_size_tree(root_path, endpoint_url): 31 | object_prefix_same = set() 32 | size_tree = dict() 33 | for path, size in tqdm(scan(root_path, endpoint_url), desc=root_path): 34 | *directories, filename = path.lstrip('/').split('/') 35 | directories.insert(0, root_path) 36 | current_size_tree = size_tree 37 | skip_flag = False 38 | for index, directory in enumerate(directories): 39 | if isinstance(current_size_tree, int): 40 | same_path = '/'.join(directories[:index]) 41 | if same_path not in object_prefix_same: 42 | object_prefix_same.add(same_path) 43 | tqdm.write('s3 path: {} is both object and prefix'.format(same_path)) 44 | skip_flag = True 45 | break 46 | if directory not in current_size_tree: 47 | current_size_tree[directory] = dict() 48 | current_size_tree = current_size_tree[directory] 49 | if skip_flag: 50 | continue 51 | if isinstance(current_size_tree, int): 52 | same_path = '/'.join(directories) 53 | if same_path not in object_prefix_same: 54 | object_prefix_same.add(same_path) 55 | tqdm.write('s3 path: {} is both object and prefix'.format(same_path)) 56 | continue 57 | current_size_tree[filename] = size 58 | return size_tree 59 | -------------------------------------------------------------------------------- /optional-requirements.txt: -------------------------------------------------------------------------------- 1 | flask-compress -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | tqdm 3 | flask-compress 4 | boto3 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import subprocess 4 | 5 | import setuptools 6 | from setuptools import setup 7 | from disk_treemap import __version__ 8 | 9 | 10 | class BuildStatic(setuptools.Command): 11 | user_options = [] 12 | 13 | def initialize_options(self): 14 | pass 15 | 16 | def finalize_options(self): 17 | pass 18 | 19 | def run(self): 20 | cwd = 'disk_treemap/static' 21 | subprocess.call(['npm', 'install'], cwd=cwd) 22 | subprocess.call(['npm', 'run', 'build', '--', '--prod'], cwd=cwd) 23 | 24 | 25 | with open('README.md', 'r', encoding="utf-8") as fh: 26 | long_description = fh.read() 27 | 28 | setup( 29 | name='disk_treemap', 30 | version=__version__, 31 | author='Epix Zhang', 32 | packages=['disk_treemap'], 33 | description='Just another disk usage analyzer with treemap GUI.', 34 | long_description=long_description, 35 | long_description_content_type="text/markdown", 36 | url='https://github.com/exzhawk/disk_treemap', 37 | classifiers=[ 38 | "Programming Language :: Python :: 3", 39 | "License :: OSI Approved :: MIT License", 40 | "Operating System :: OS Independent", 41 | "Development Status :: 5 - Production/Stable", 42 | "Environment :: Console", 43 | "Environment :: Web Environment", 44 | "Framework :: Flask", 45 | "Intended Audience :: End Users/Desktop", 46 | "Intended Audience :: System Administrators", 47 | "Programming Language :: JavaScript", 48 | "Topic :: Desktop Environment :: File Managers", 49 | "Topic :: Scientific/Engineering :: Visualization", 50 | "Topic :: System :: Filesystems", 51 | "Topic :: Utilities", 52 | ], 53 | python_requires='>=3.6', 54 | license='MIT', 55 | zip_safe=True, 56 | include_package_data=True, 57 | cmdclass={ 58 | 'build_static': BuildStatic, 59 | }, 60 | install_requires=[ 61 | 'flask>=2.0', 62 | 'tqdm', 63 | 'flask-compress', 64 | 'boto3', 65 | ], 66 | entry_points={ 67 | 'console_scripts': [ 68 | 'disk-treemap=disk_treemap.main:main', 69 | ] 70 | } 71 | ) 72 | --------------------------------------------------------------------------------