-
Notifications
You must be signed in to change notification settings - Fork 0
Additional task #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Roman-A113
wants to merge
20
commits into
master
Choose a base branch
from
additional-task
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
27bd878
add gui
Roman-A113 527d6cd
add progressbar
Roman-A113 eecf89d
add elements column
Roman-A113 cf283ed
correct columns
Roman-A113 a90a37a
minor changes
Roman-A113 657ed1d
minor changes
Roman-A113 b2553bd
minor changes
Roman-A113 8a4a5d5
add sorters
Roman-A113 3a0f324
change name
Roman-A113 e7890da
refactor PR#1
Roman-A113 5f3b78c
add arrows
Roman-A113 60c3d2c
add extension frame
Roman-A113 cf30983
correct linters
Roman-A113 2149ec0
cut init to methods
Roman-A113 2cf49bc
chane field names
Roman-A113 6f37155
correct linters
Roman-A113 895ad6c
decompose class
Roman-A113 37b4325
correct braces
Roman-A113 f16c756
minor changes
Roman-A113 0c6e417
add Column class
Roman-A113 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| import threading | ||
| import tkinter as tk | ||
| from collections import defaultdict | ||
| from functools import partial | ||
| from pathlib import Path | ||
| from tkinter import filedialog, messagebox | ||
|
|
||
| from grouper import group_paths | ||
| from models import PathInfo | ||
| from scanner import scan_all_directories | ||
| from ui_builder import COLUMNS, UIBuilder | ||
| from utils import convert_size, convert_time | ||
|
|
||
|
|
||
| class DiskUsageGUI: | ||
| def __init__(self, root: tk.Tk) -> None: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. почему все публичное? |
||
| self._root = root | ||
| self._root.title("Disk Usage Analyzer") | ||
| self._root.geometry("1000x700") | ||
|
|
||
| self._init_state() | ||
|
|
||
| self._btn_select = tk.Button(root, text="Выбрать папку", command=self._select_folder) | ||
| self._btn_select.pack(pady=10) | ||
|
|
||
| self._status_var = tk.StringVar() | ||
| self._status_label = tk.Label(root, textvariable=self._status_var, fg="gray") | ||
| self._status_label.pack() | ||
|
|
||
| main_pane = tk.PanedWindow(root, orient=tk.HORIZONTAL, sashrelief=tk.RAISED, sashwidth=6) | ||
| main_pane.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) | ||
|
|
||
| self._tree = UIBuilder.create_directory_tree(main_pane) | ||
|
|
||
| self._ext_tree = UIBuilder.create_extension_tree(main_pane) | ||
|
|
||
| self._setup_tree_sorting() | ||
|
|
||
| root.after(100, lambda: main_pane.sash_place(0, 700, 0)) | ||
|
|
||
| def _init_state(self) -> None: | ||
| """Инициализация внутренних данных приложения.""" | ||
| self._paths_info: defaultdict[Path, PathInfo] = defaultdict() | ||
| self._paths_ids: defaultdict[Path, str] = defaultdict() | ||
| self._graph: defaultdict[Path, list[PathInfo]] = defaultdict(list) | ||
| self._folder: str = "" | ||
| self._current_root: Path | None = None | ||
| self._sorter_parameter: tuple[str, bool] = (COLUMNS[1].id, True) | ||
|
|
||
| def _setup_tree_sorting(self) -> None: | ||
| """Подключает обработчики сортировки к заголовкам дерева.""" | ||
| for column in COLUMNS: | ||
| self._tree.heading(column.id, command=partial(self._on_header_click, column.id)) | ||
|
|
||
| def _update_heading_texts(self) -> None: | ||
| """Обновляет текст заголовка (добавляет стрелочки)""" | ||
| for column in COLUMNS: | ||
| if column.id == self._sorter_parameter[0]: | ||
| arrow = " ▼" if self._sorter_parameter[1] else " ▲" | ||
| self._tree.heading(column.id, text=column.text + arrow) | ||
| else: | ||
| self._tree.heading(column.id, text=column.text) | ||
|
|
||
| def _on_header_click(self, parameter: str) -> None: | ||
| """Обрабатывает события нажатия на заголовок""" | ||
| if not self._graph: | ||
| return | ||
| self._tree.delete(*self._tree.get_children()) | ||
| self._paths_ids.clear() | ||
| if parameter == self._sorter_parameter[0]: | ||
| self._sorter_parameter = (self._sorter_parameter[0], self._sorter_parameter[1] ^ True) | ||
| else: | ||
| self._sorter_parameter = (parameter, False) | ||
|
|
||
| self._sort_graph() | ||
| self._root.after(0, self._show_results, Path(self._folder).resolve(), list(self._paths_info.values())) | ||
|
|
||
| def _select_folder(self) -> None: | ||
| """Выбирает папку для сканирования""" | ||
| self._folder = filedialog.askdirectory(title="Выберите папку") | ||
| if self._folder: | ||
| self._start_analysis(self._folder) | ||
|
|
||
| def _progress_callback(self, count: int) -> None: | ||
| """Прогресс-бар операции сканирования""" | ||
| self._root.after(0, lambda: self._status_var.set(f"Сканирование: {count} элементов")) | ||
|
|
||
| def _start_analysis(self, folder: str) -> None: | ||
| """Запускает сканирование выбранной директории""" | ||
| self._status_var.set("Сканирование: 0 элементов") | ||
| self._btn_select.config(state="disabled") | ||
| self._tree.delete(*self._tree.get_children()) | ||
| self._ext_tree.delete(*self._ext_tree.get_children()) | ||
|
|
||
| self._paths_ids.clear() | ||
|
|
||
| thread = threading.Thread(target=self._analyze, args=(folder,), daemon=True) | ||
| thread.start() | ||
|
|
||
| def _analyze(self, folder: str) -> None: | ||
| """Анализирует выбранную директорию""" | ||
| try: | ||
| root_path = Path(folder).resolve() | ||
| if not root_path.exists(): | ||
| raise FileNotFoundError(f"Путь не существует: {root_path}") | ||
|
|
||
| self._paths_info = scan_all_directories(root_path, self._progress_callback) | ||
|
|
||
| self._graph = self._create_graph(root_path, list(self._paths_info.values())) | ||
|
|
||
| self.ext_stats = sorted( | ||
| group_paths(self._paths_info, "extension").items(), key=lambda x: x[1][COLUMNS[1].id], reverse=True | ||
| ) | ||
|
|
||
| self._sorter_parameter = (COLUMNS[1].id, True) | ||
| self._sort_graph() | ||
| self._root.after(0, self._show_results, root_path, list(self._paths_info.values())) | ||
|
|
||
| except Exception as e: | ||
| self._root.after(0, self._show_error, str(e)) | ||
|
|
||
| def _show_error(self, msg: str) -> None: | ||
| """Выводит сообщение об ошибке""" | ||
| messagebox.showerror("Ошибка", msg) | ||
| self._status_var.set("Ошибка") | ||
| self._btn_select.config(state="normal") | ||
|
|
||
| def _sort_graph(self) -> None: | ||
| """Сортирует граф""" | ||
| for node in self._graph: | ||
| match self._sorter_parameter[0]: | ||
| case "#0": | ||
| self._graph[node].sort(key=lambda x: x.path.name, reverse=self._sorter_parameter[1]) | ||
| case "size": | ||
| self._graph[node].sort(key=lambda x: x.size, reverse=self._sorter_parameter[1]) | ||
| case "files_count": | ||
| self._graph[node].sort(key=lambda x: x.files_count, reverse=self._sorter_parameter[1]) | ||
| case "last_change_time": | ||
| self._graph[node].sort(key=lambda x: x.last_change_time, reverse=self._sorter_parameter[1]) | ||
| self._update_heading_texts() | ||
|
|
||
| def _insert_items(self, parent_path: Path, parent_tree_id: str) -> None: | ||
| """Добавляет новую директорию в дерево""" | ||
| children = self._graph.get(parent_path, []) | ||
|
|
||
| for child in children: | ||
| name = child.path.name | ||
| size_str = convert_size(child.size) | ||
| files_count = child.files_count | ||
| last_change_time = convert_time(child.last_change_time) | ||
|
|
||
| child_id = self._tree.insert( | ||
| parent_tree_id, "end", text=name, values=(size_str, files_count, last_change_time) | ||
| ) | ||
|
|
||
| self._paths_ids[child.path] = child_id | ||
| if child.is_dir: | ||
| self._insert_items(child.path, child_id) | ||
|
|
||
| def _update_extension_stats(self) -> None: | ||
| """Обновляет таблицу расширений.""" | ||
| if not self._paths_info: | ||
| return | ||
|
|
||
| for ext, data in self.ext_stats: | ||
| self._ext_tree.insert("", "end", values=(ext, data["count"], convert_size(data[COLUMNS[1].id]))) | ||
|
|
||
| def _show_results(self, root_path: Path, display_items: list[PathInfo]) -> None: | ||
| """Выводит на экран все дерево директорий.""" | ||
| self._current_root = root_path | ||
| self._status_var.set(f"Просканировано: {len(display_items)} элементов") | ||
| self._btn_select.config(state="normal") | ||
|
|
||
| self._paths_ids.clear() | ||
| root_name = root_path.name | ||
| root_item = self._tree.insert( | ||
| "", | ||
| "end", | ||
| text=root_name, | ||
| values=( | ||
| convert_size(self._paths_info[root_path].size), | ||
| self._paths_info[root_path].files_count, | ||
| convert_time(self._paths_info[root_path].last_change_time), | ||
| ), | ||
| ) | ||
| self._paths_ids[root_path] = root_item | ||
|
|
||
| self._insert_items(root_path, root_item) | ||
| self._tree.item(root_item, open=True) | ||
| self._update_extension_stats() | ||
|
|
||
| def _create_graph(self, root_path: Path, display_items: list[PathInfo]) -> defaultdict[Path, list[PathInfo]]: | ||
| """Создает граф вложенности всех директорий.""" | ||
| graph = defaultdict(list) | ||
| for item in display_items: | ||
| if item.path == root_path: | ||
| continue | ||
| parent = item.path.parent | ||
| graph[parent].append(item) | ||
| return graph | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| root = tk.Tk() | ||
| app = DiskUsageGUI(root) | ||
| root.mainloop() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
а мб немного декомпозировать класс?