|
| 1 | +import sys |
| 2 | +import os |
| 3 | +import time |
| 4 | +from pathlib import Path |
| 5 | +import tkinter as tk |
| 6 | +from tkinter import filedialog, messagebox |
| 7 | +import ttkbootstrap as tb |
| 8 | +from PIL import Image, ImageTk |
| 9 | +import subprocess |
| 10 | +import PyPDF2 |
| 11 | + |
| 12 | +# =================== APP CONFIG =================== |
| 13 | +APP_NAME = "Mate Desktop Search Pro" |
| 14 | +APP_VERSION = "2.0.0" |
| 15 | +APP_AUTHOR = "Mate Technologies" |
| 16 | + |
| 17 | +def resource_path(name): |
| 18 | + base = getattr(sys, "_MEIPASS", Path(__file__).parent) |
| 19 | + return Path(base) / name |
| 20 | + |
| 21 | +# ================= APP ================= |
| 22 | + |
| 23 | +app = tk.Tk() |
| 24 | +style = tb.Style(theme="superhero") |
| 25 | + |
| 26 | +app.title(f"{APP_NAME} {APP_VERSION}") |
| 27 | +app.geometry("1200x650") |
| 28 | + |
| 29 | +# ================= VARIABLES ================= |
| 30 | + |
| 31 | +indexed_files = [] |
| 32 | +recent_files = [] |
| 33 | + |
| 34 | +current_folder = tk.StringVar(value="") |
| 35 | +search_query = tk.StringVar(value="") |
| 36 | +status_text = tk.StringVar(value="Idle") |
| 37 | +file_filter = tk.StringVar(value="All") |
| 38 | +sort_mode = tk.StringVar(value="Name") |
| 39 | + |
| 40 | +preview_img = None |
| 41 | + |
| 42 | +FILTERS = { |
| 43 | + "All": [], |
| 44 | + "Images": [".png",".jpg",".jpeg",".gif"], |
| 45 | + "Docs": [".txt",".pdf",".docx"], |
| 46 | + "Videos": [".mp4",".mov",".avi"] |
| 47 | +} |
| 48 | + |
| 49 | +# ================= FUNCTIONS ================= |
| 50 | + |
| 51 | +def select_folder(): |
| 52 | + folder = filedialog.askdirectory() |
| 53 | + if folder: |
| 54 | + current_folder.set(folder) |
| 55 | + index_folder(folder) |
| 56 | + |
| 57 | +def index_folder(folder): |
| 58 | + indexed_files.clear() |
| 59 | + |
| 60 | + for root, dirs, files in os.walk(folder): |
| 61 | + for f in files: |
| 62 | + indexed_files.append(os.path.join(root,f)) |
| 63 | + |
| 64 | + status_text.set(f"Indexed {len(indexed_files)} files") |
| 65 | + update_results() |
| 66 | + |
| 67 | +def file_matches(path): |
| 68 | + ext = Path(path).suffix.lower() |
| 69 | + allowed = FILTERS[file_filter.get()] |
| 70 | + return not allowed or ext in allowed |
| 71 | + |
| 72 | +def search_content(path, query): |
| 73 | + try: |
| 74 | + if path.endswith(".txt"): |
| 75 | + return query in open(path,errors="ignore").read().lower() |
| 76 | + |
| 77 | + if path.endswith(".pdf"): |
| 78 | + reader = PyPDF2.PdfReader(path) |
| 79 | + for p in reader.pages[:3]: |
| 80 | + if query in p.extract_text().lower(): |
| 81 | + return True |
| 82 | + except: |
| 83 | + pass |
| 84 | + return False |
| 85 | + |
| 86 | +def update_results(*args): |
| 87 | + q = search_query.get().lower() |
| 88 | + results.delete(0,tk.END) |
| 89 | + |
| 90 | + matches = [] |
| 91 | + |
| 92 | + for f in indexed_files: |
| 93 | + if not file_matches(f): |
| 94 | + continue |
| 95 | + |
| 96 | + name_match = q in os.path.basename(f).lower() |
| 97 | + content_match = q and search_content(f,q) |
| 98 | + |
| 99 | + if not q or name_match or content_match: |
| 100 | + matches.append(f) |
| 101 | + |
| 102 | + if sort_mode.get()=="Size": |
| 103 | + matches.sort(key=lambda x: os.path.getsize(x)) |
| 104 | + elif sort_mode.get()=="Date": |
| 105 | + matches.sort(key=lambda x: os.path.getmtime(x)) |
| 106 | + else: |
| 107 | + matches.sort() |
| 108 | + |
| 109 | + for m in matches[:500]: |
| 110 | + results.insert(tk.END,m) |
| 111 | + |
| 112 | + status_text.set(f"{len(matches)} result(s)") |
| 113 | + |
| 114 | +def open_selected(event=None): |
| 115 | + sel = results.curselection() |
| 116 | + if not sel: return |
| 117 | + path = results.get(sel[0]) |
| 118 | + |
| 119 | + recent_files.append(path) |
| 120 | + if len(recent_files)>20: |
| 121 | + recent_files.pop(0) |
| 122 | + |
| 123 | + try: |
| 124 | + if sys.platform.startswith("win"): |
| 125 | + os.startfile(path) |
| 126 | + elif sys.platform.startswith("darwin"): |
| 127 | + subprocess.call(["open", path]) |
| 128 | + else: |
| 129 | + subprocess.call(["xdg-open", path]) |
| 130 | + except Exception as e: |
| 131 | + messagebox.showerror("Error",str(e)) |
| 132 | + |
| 133 | +def show_preview(event=None): |
| 134 | + global preview_img |
| 135 | + sel = results.curselection() |
| 136 | + if not sel: return |
| 137 | + |
| 138 | + path = results.get(sel[0]) |
| 139 | + ext = Path(path).suffix.lower() |
| 140 | + |
| 141 | + preview.delete("all") |
| 142 | + |
| 143 | + try: |
| 144 | + if ext in [".png",".jpg",".jpeg",".gif"]: |
| 145 | + img = Image.open(path) |
| 146 | + img.thumbnail((250,250)) |
| 147 | + preview_img = ImageTk.PhotoImage(img) |
| 148 | + preview.create_image(0,0,anchor="nw",image=preview_img) |
| 149 | + |
| 150 | + elif ext==".txt": |
| 151 | + txt = open(path,errors="ignore").read()[:1500] |
| 152 | + preview.create_text(5,5,anchor="nw",fill="white",text=txt) |
| 153 | + |
| 154 | + else: |
| 155 | + info = f"{Path(path).name}\n\nSize: {os.path.getsize(path)//1024} KB\nModified: {time.ctime(os.path.getmtime(path))}" |
| 156 | + preview.create_text(5,5,anchor="nw",fill="white",text=info) |
| 157 | + |
| 158 | + except: |
| 159 | + pass |
| 160 | + |
| 161 | +def show_recent(): |
| 162 | + results.delete(0,tk.END) |
| 163 | + for f in reversed(recent_files): |
| 164 | + results.insert(tk.END,f) |
| 165 | + |
| 166 | +def show_about(): |
| 167 | + messagebox.showinfo( |
| 168 | + APP_NAME, |
| 169 | + f"{APP_NAME} {APP_VERSION}\n\n" |
| 170 | + "Advanced desktop search engine.\n\n" |
| 171 | + "• Content search TXT/PDF\n" |
| 172 | + "• Filters\n" |
| 173 | + "• Preview\n" |
| 174 | + "• Sorting\n" |
| 175 | + "• Recent files\n\n" |
| 176 | + "Mate Technologies" |
| 177 | + ) |
| 178 | + |
| 179 | +# ================= UI ================= |
| 180 | + |
| 181 | +tb.Label(app,text=APP_NAME,font=("Segoe UI",18,"bold")).pack(pady=5) |
| 182 | + |
| 183 | +main = tb.Frame(app) |
| 184 | +main.pack(fill="both",expand=True,padx=10,pady=10) |
| 185 | + |
| 186 | +# Left |
| 187 | +left = tb.Frame(main) |
| 188 | +left.pack(side="left",fill="both",expand=True) |
| 189 | + |
| 190 | +results = tk.Listbox(left,bg="#222",fg="white") |
| 191 | +results.pack(fill="both",expand=True) |
| 192 | +results.bind("<Double-Button-1>",open_selected) |
| 193 | +results.bind("<<ListboxSelect>>",show_preview) |
| 194 | + |
| 195 | +# Right |
| 196 | +right = tb.Frame(main,width=320) |
| 197 | +right.pack(side="right",fill="y",padx=5) |
| 198 | + |
| 199 | +tb.Entry(right,textvariable=current_folder).pack(fill="x") |
| 200 | +tb.Button(right,text="Select Folder",bootstyle="primary",command=select_folder).pack(fill="x",pady=3) |
| 201 | + |
| 202 | +tb.Entry(right,textvariable=search_query).pack(fill="x") |
| 203 | +search_query.trace_add("write",update_results) |
| 204 | + |
| 205 | +tb.Combobox(right,values=list(FILTERS.keys()),textvariable=file_filter).pack(fill="x",pady=2) |
| 206 | +file_filter.trace_add("write",update_results) |
| 207 | + |
| 208 | +tb.Combobox(right,values=["Name","Size","Date"],textvariable=sort_mode).pack(fill="x",pady=2) |
| 209 | +sort_mode.trace_add("write",update_results) |
| 210 | + |
| 211 | +tb.Button(right,text="Recent Files",bootstyle="info",command=show_recent).pack(fill="x",pady=3) |
| 212 | +tb.Button(right,text="Open",bootstyle="success",command=open_selected).pack(fill="x",pady=3) |
| 213 | +tb.Button(right,text="About",bootstyle="secondary",command=show_about).pack(fill="x",pady=3) |
| 214 | + |
| 215 | +preview = tk.Canvas(right,width=250,height=250,bg="#333") |
| 216 | +preview.pack(pady=5) |
| 217 | + |
| 218 | +tb.Label(right,textvariable=status_text).pack() |
| 219 | + |
| 220 | +# ================= RUN ================= |
| 221 | +app.mainloop() |
0 commit comments