Skip to content

Commit f36d4bc

Browse files
authored
Merge pull request #115 from Anil-Akdeniz/master
Added Filecat
2 parents bb9b35a + db15065 commit f36d4bc

File tree

7 files changed

+347
-0
lines changed

7 files changed

+347
-0
lines changed

Projects/filecat/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# FileCat - FTP Client / Filezilla/CyberDuck clone project with Python
2+
3+
FileCat is a simple FTP client application built with Python and Tkinter. It allows users to connect to FTP/SFTP servers, browse remote directories, download files, and upload files or folders to the server (if permission granted). It provides a user-friendly interface for managing files and directories on remote servers.
4+
5+
## Features
6+
7+
- **Connect to FTP/SFTP Servers:** Enter server address, username, password, and port to connect to FTP/SFTP servers.
8+
- **Browse Remote Directories:** Navigate through remote directories to view files and folders.
9+
- **Download Files:** Double-click on a file to download it to the local system.
10+
- **Upload Files/Folders:** Drag and drop files or folders to upload them to the server (if permission granted).
11+
- **Remember Credentials:** Option to remember server credentials for future connections.
12+
13+
## Libraries Used
14+
15+
- **tkinter:** Standard Python interface to the Tk GUI toolkit.
16+
- **ftplib:** Standard Python library for FTP protocol.
17+
- **configparser:** Standard Python library for working with configuration files.
18+
- **os:** Standard Python library for interacting with the operating system.
19+
20+
## Usage
21+
22+
- Launch the application.
23+
- Enter the server address, username, password, and port.
24+
- Click on the "Connect" button to connect to the server.
25+
- Navigate through remote directories using the file explorer.
26+
- Double-click on a file to download it to the local system.
27+
- Upload files to the server (if permission granted).
28+
- Click on the "Disconnect" button to disconnect from the server.
29+
30+
## Classes
31+
32+
### filecat.py
33+
34+
FileCatApp Class:
35+
- __init__(self, master): Initializes the main application window, including all widgets and their layout.
36+
- connect(self): Connects to the FTP server using the provided credentials and updates the file explorer.
37+
- disconnect(self): Disconnects from the FTP server and clears the file explorer.
38+
- upload_file(self): Opens a file dialog to select a file for uploading to the current directory on the server.
39+
40+
41+
### file_explorer.py
42+
43+
FileExplorer Class:
44+
- __init__(self, master): Initializes the file explorer frame, including the tree view for displaying files and folders.
45+
- update_treeview(self, files): Updates the tree view with the provided list of files and directories.
46+
- change_directory(self, event=None): Changes the current directory to the specified path and updates the tree view.
47+
- on_double_click(self, event): Handles double-click events on the tree view, either changing the directory or downloading a file.
48+
- download_file(self, filename, server_address): Downloads the specified file from the current directory to the local system.
49+
- up_directory(self): Moves up one directory level in the file explorer.

Projects/filecat/app.ico

3.93 KB
Binary file not shown.

Projects/filecat/download_icon.png

270 Bytes
Loading

Projects/filecat/file_explorer.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import tkinter as tk
2+
from tkinter import ttk, messagebox, PhotoImage
3+
import ftplib
4+
import os
5+
from datetime import datetime
6+
7+
class FileExplorer(tk.Frame):
8+
def __init__(self, master, app):
9+
super().__init__(master)
10+
self.app = app # Save reference to FileCatApp instance
11+
self.ftp = None
12+
self.current_path = tk.StringVar()
13+
14+
self.url_entry = tk.Entry(self)
15+
self.url_entry.pack(fill=tk.X)
16+
self.url_entry.bind("<Return>", self.change_directory)
17+
18+
self.tree = ttk.Treeview(self)
19+
self.tree["columns"] = ("size", "type")
20+
self.tree.heading("#0", text="Name", anchor=tk.W)
21+
self.tree.heading("size", text="Size", anchor=tk.W)
22+
self.tree.heading("type", text="Type", anchor=tk.W)
23+
24+
self.tree.column("size", stretch=tk.YES)
25+
self.tree.column("type", stretch=tk.YES)
26+
self.url_frame = tk.Frame(self)
27+
self.url_frame.pack(fill=tk.X)
28+
self.tree.pack(expand=True, fill=tk.BOTH)
29+
icon_path = os.path.join(os.path.dirname(__file__), "folder_icon.png")
30+
self.folder_icon = PhotoImage(file=icon_path)
31+
up_icon = PhotoImage(file="up_icon.png")
32+
self.up_button = tk.Button(self.url_frame, image=up_icon, command=self.up_directory)
33+
self.up_button.image = up_icon
34+
self.up_button.pack(side=tk.RIGHT, padx=5, pady=5)
35+
self.tree.bind("<Double-1>", self.on_double_click)
36+
37+
# Add delete button
38+
self.delete_button = tk.Button(self.url_frame, text="Delete", command=self.delete_item)
39+
self.delete_button.pack(side=tk.RIGHT, padx=5, pady=5)
40+
41+
# Add reconnect button
42+
self.reconnect_button = tk.Button(self.url_frame, text="Reconnect", command=self.reconnect)
43+
self.reconnect_button.pack(side=tk.RIGHT, padx=5, pady=5)
44+
45+
# Add upload button next to delete button
46+
self.app.upload_button = tk.Button(self.url_frame, text="Upload", command=self.app.upload, state=tk.DISABLED)
47+
self.app.upload_button.pack(side=tk.RIGHT, padx=5, pady=5)
48+
49+
def update_treeview(self, files):
50+
self.tree.delete(*self.tree.get_children())
51+
self.tree["columns"] = ("size", "type", "last_modified")
52+
self.tree.heading("size", text="Size", anchor=tk.W)
53+
self.tree.heading("type", text="Type", anchor=tk.W)
54+
self.tree.heading("last_modified", text="Last Modified", anchor=tk.W)
55+
56+
directories = []
57+
files_list = []
58+
59+
for name, details in files:
60+
if details['type'] == 'dir':
61+
directories.append((name, details))
62+
else:
63+
files_list.append((name, details))
64+
65+
for name, details in directories:
66+
last_modified = self._format_last_modified(details.get('modify', ''))
67+
self.tree.insert("", "end", text=name, open=False, image=self.folder_icon,
68+
values=("", "Directory", last_modified))
69+
70+
for name, details in files_list:
71+
size_kb = int(details.get('size', 0)) / 1024
72+
last_modified = self._format_last_modified(details.get('modify', ''))
73+
self.tree.insert("", "end", values=(f"{size_kb:.2f} KB", "File", last_modified), text=name)
74+
75+
def delete_item(self):
76+
item_id = self.tree.selection()[0]
77+
item = self.tree.item(item_id)
78+
item_text = item["text"]
79+
80+
if messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete '{item_text}'?"):
81+
try:
82+
if item["values"][1] == "File":
83+
self.ftp.delete(os.path.join(self.current_path.get(), item_text))
84+
elif "directory" in item["tags"]:
85+
self.ftp.rmd(os.path.join(self.current_path.get(), item_text))
86+
self.change_directory()
87+
except ftplib.error_perm as e:
88+
messagebox.showerror("Error", str(e))
89+
90+
def reconnect(self):
91+
self.app.reconnect() # Use the FileCatApp instance to call connect method
92+
93+
def _format_last_modified(self, raw_timestamp):
94+
try:
95+
timestamp = datetime.strptime(raw_timestamp, "%Y%m%d%H%M%S")
96+
formatted_date = timestamp.strftime("%Y-%m-%d %H:%M:%S")
97+
return formatted_date
98+
except ValueError:
99+
return ""
100+
101+
def change_directory(self, event=None):
102+
new_path = self.url_entry.get().strip()
103+
104+
new_path = new_path.replace("\\", "/")
105+
106+
if not new_path.startswith("/"):
107+
new_path = "/" + new_path
108+
109+
try:
110+
files = self.ftp.mlsd(new_path)
111+
files = [(name, details) for name, details in files if name not in ['.', '..']]
112+
113+
self.current_path.set(new_path)
114+
self.update_treeview(files)
115+
except ftplib.error_perm as e:
116+
messagebox.showerror("Error", str(e))
117+
118+
def on_double_click(self, event):
119+
item_id = self.tree.focus()
120+
item = self.tree.item(item_id)
121+
item_text = item["text"]
122+
item_type = item["values"][1]
123+
124+
if item_text != "":
125+
if item_type == "Directory":
126+
self.url_entry.delete(0, tk.END)
127+
new_path = os.path.join(self.current_path.get(), item_text)
128+
self.url_entry.insert(0, new_path)
129+
self.change_directory()
130+
elif item_type == "File":
131+
self.download_file(item_text)
132+
133+
def up_directory(self):
134+
current_path = self.url_entry.get().strip()
135+
if current_path != "/":
136+
parent_path = os.path.dirname(current_path)
137+
self.url_entry.delete(0, tk.END)
138+
self.url_entry.insert(0, parent_path)
139+
self.change_directory()
140+
141+
def download_file(self, filename):
142+
remote_filename = os.path.join(self.current_path.get(), filename)
143+
remote_filename = remote_filename.replace("\\", "/")
144+
145+
downloads_folder = os.path.join(os.getcwd(), "downloads")
146+
if not os.path.exists(downloads_folder):
147+
os.makedirs(downloads_folder)
148+
149+
local_filename = os.path.join(downloads_folder, filename)
150+
151+
with open(local_filename, 'wb') as local_file:
152+
try:
153+
self.ftp.retrbinary('RETR ' + remote_filename, local_file.write)
154+
messagebox.showinfo("Download", f"{filename} downloaded successfully.")
155+
except ftplib.error_perm as e:
156+
messagebox.showerror("Download Error", f"Failed to download {filename}: {str(e)}")

Projects/filecat/filecat.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import tkinter as tk
2+
from tkinter import ttk, messagebox
3+
import ftplib
4+
import configparser
5+
import tkinter.filedialog as filedialog
6+
import os
7+
8+
from file_explorer import FileExplorer
9+
10+
class FileCatApp:
11+
def __init__(self, master):
12+
self.master = master
13+
self.master.title("FileCat - FTP Client")
14+
self.master.geometry("800x600")
15+
16+
self.connection_frame = tk.Frame(self.master)
17+
self.connection_frame.pack(fill=tk.X)
18+
self.upload_button = tk.Button(self.connection_frame, text="Upload", command=self.upload, state=tk.DISABLED)
19+
20+
tk.Label(self.connection_frame, text="Server Address:").grid(row=0, column=0, padx=5, pady=5)
21+
self.server_address_entry = tk.Entry(self.connection_frame)
22+
self.server_address_entry.grid(row=0, column=1, padx=5, pady=5)
23+
24+
tk.Label(self.connection_frame, text="Username:").grid(row=0, column=2, padx=5, pady=5)
25+
self.username_entry = tk.Entry(self.connection_frame)
26+
self.username_entry.grid(row=0, column=3, padx=5, pady=5)
27+
28+
tk.Label(self.connection_frame, text="Password:").grid(row=1, column=0, padx=5, pady=5)
29+
self.password_entry = tk.Entry(self.connection_frame, show="*")
30+
self.password_entry.grid(row=1, column=1, padx=5, pady=5)
31+
32+
tk.Label(self.connection_frame, text="Port:").grid(row=1, column=2, padx=5, pady=5)
33+
self.port_entry = tk.Entry(self.connection_frame)
34+
self.port_entry.grid(row=1, column=3, padx=5, pady=5)
35+
36+
self.remember_var = tk.IntVar(value=0)
37+
self.remember_checkbox = tk.Checkbutton(self.connection_frame, text="Remember", variable=self.remember_var)
38+
self.remember_checkbox.grid(row=0, column=4, rowspan=2, padx=5, pady=5)
39+
40+
self.connect_button = tk.Button(self.connection_frame, text="Connect", command=self.connect)
41+
self.connect_button.grid(row=0, column=5, padx=5, pady=5)
42+
43+
self.disconnect_button = tk.Button(self.connection_frame, text="Disconnect", command=self.disconnect, state=tk.DISABLED)
44+
self.disconnect_button.grid(row=0, column=6, padx=5, pady=5)
45+
46+
self.file_explorer = FileExplorer(self.master, self)
47+
self.file_explorer.pack(expand=True, fill=tk.BOTH)
48+
49+
self.config = configparser.ConfigParser()
50+
self.config.read("credentials.ini")
51+
if "Credentials" in self.config:
52+
self.server_address_entry.insert(0, self.config["Credentials"].get("ServerAddress", ""))
53+
self.username_entry.insert(0, self.config["Credentials"].get("Username", ""))
54+
self.password_entry.insert(0, self.config["Credentials"].get("Password", ""))
55+
self.port_entry.insert(0, self.config["Credentials"].get("Port", ""))
56+
57+
def upload(self):
58+
file_path = filedialog.askopenfilename()
59+
if file_path:
60+
self.upload_file(file_path)
61+
62+
def reconnect(self):
63+
self.disconnect()
64+
self.connect()
65+
66+
def connect(self):
67+
server_address = self.server_address_entry.get()
68+
username = self.username_entry.get()
69+
password = self.password_entry.get()
70+
port_entry = self.port_entry.get()
71+
72+
if port_entry:
73+
try:
74+
port = int(port_entry)
75+
except ValueError:
76+
messagebox.showerror("Connection Error", "Invalid port number.")
77+
return
78+
else:
79+
port = 21
80+
81+
try:
82+
ftp = ftplib.FTP()
83+
ftp.connect(server_address, port)
84+
ftp.login(username, password)
85+
86+
self.file_explorer.ftp = ftp
87+
88+
root_dir = "/"
89+
self.file_explorer.url_entry.insert(0, root_dir)
90+
self.file_explorer.change_directory()
91+
self.upload_button.config(state=tk.NORMAL)
92+
93+
self.disconnect_button.config(state=tk.NORMAL)
94+
self.connect_button.config(state=tk.DISABLED)
95+
96+
if self.remember_var.get() == 1:
97+
self.config["Credentials"] = {
98+
"ServerAddress": server_address,
99+
"Username": username,
100+
"Password": password,
101+
"Port": port
102+
}
103+
with open("credentials.ini", "w") as configfile:
104+
self.config.write(configfile)
105+
106+
except ftplib.all_errors as e:
107+
messagebox.showerror("Connection Error", str(e))
108+
109+
def disconnect(self):
110+
self.upload_button.config(state=tk.DISABLED)
111+
if self.file_explorer.ftp:
112+
self.file_explorer.ftp.quit()
113+
self.file_explorer.ftp = None
114+
115+
self.connect_button.config(state=tk.NORMAL)
116+
self.disconnect_button.config(state=tk.DISABLED)
117+
118+
self.file_explorer.update_treeview([])
119+
120+
self.file_explorer.url_entry.delete(0, tk.END)
121+
122+
else:
123+
messagebox.showinfo("Info", "Not connected to any server.")
124+
125+
def upload_file(self, file_path):
126+
file_name = os.path.basename(file_path)
127+
128+
try:
129+
with open(file_path, 'rb') as file:
130+
self.file_explorer.ftp.storbinary('STOR ' + file_name, file)
131+
messagebox.showinfo("Upload", f"{file_name} uploaded successfully.")
132+
except Exception as e:
133+
messagebox.showerror("Upload Error", f"Failed to upload {file_name}: {str(e)}")
134+
135+
def main():
136+
root = tk.Tk()
137+
app = FileCatApp(root)
138+
root.iconbitmap("app.ico")
139+
root.mainloop()
140+
141+
if __name__ == "__main__":
142+
main()

Projects/filecat/folder_icon.png

1000 Bytes
Loading

Projects/filecat/up_icon.png

1.13 KB
Loading

0 commit comments

Comments
 (0)