diff --git a/debian/control b/debian/control
index 4e56185..f56e60f 100644
--- a/debian/control
+++ b/debian/control
@@ -15,7 +15,8 @@ Depends: ${misc:Depends},
gir1.2-glib-2.0,
gir1.2-gtk-3.0,
gir1.2-notify-0.7,
- gir1.2-pango-1.0
+ gir1.2-pango-1.0,
+ python3-paramiko
Recommends: gvfs-fuse,
libglib2.0-bin
Description: My Computer, shows general information about your computer.
diff --git a/po/files b/po/files
index 414622e..0f6c8e9 100644
--- a/po/files
+++ b/po/files
@@ -1,3 +1,4 @@
ui/MainWindow.glade
src/MainWindow.py
src/UserSettings.py
+src/Scansftp.py
\ No newline at end of file
diff --git a/po/tr.po b/po/tr.po
index 2a259ab..c64051c 100644
--- a/po/tr.po
+++ b/po/tr.po
@@ -512,3 +512,15 @@ msgstr "# not: görünmesini sağlamak için kare işareti kaldırın"
#~ msgid "Website"
#~ msgstr "Website"
+
+#: ui/MainWindow.glade:2248
+msgid "Find the servers connected to the network:"
+msgstr "Ağa bağlı sunucuları bulun:"
+
+#: ui/MainWindow.glade:2248
+msgid "Scanning the SFTP servers on the network..."
+msgstr "Ağdaki SFTP sunucuları taranıyor..."
+
+#: ui/MainWindow.glade:2344
+msgid "Scan"
+msgstr "Tara"
diff --git a/setup.py b/setup.py
index ea70c31..77c8ec7 100644
--- a/setup.py
+++ b/setup.py
@@ -45,6 +45,7 @@ def create_mo_files():
"src/DiskManager.py",
"src/Unmount.py",
"src/UserSettings.py",
+ "src/Scansftp.py",
"src/__version__"
]),
("/usr/share/pardus/pardus-mycomputer/ui",
diff --git a/src/MainWindow.py b/src/MainWindow.py
index efb9a3b..3afb520 100644
--- a/src/MainWindow.py
+++ b/src/MainWindow.py
@@ -14,6 +14,9 @@
from UserSettings import UserSettings
+import threading
+import Scansftp # sftp scan function library
+
import locale
from locale import gettext as _
@@ -213,6 +216,10 @@ def UI(str):
self.box_user_domain_pass = UI("box_user_domain_pass")
self.box_anonym = UI("box_anonym")
self.btn_mount_connect = UI("btn_mount_connect")
+ self.btn_sftp_scan = UI("btn_sftp_scan") # sftp scan button
+ self.sftp_listbox = UI("sftp_listbox") # sftp list box
+ self.sftp_stack = UI("sftp_stack") # sftp stack panel
+ self.scrolledwindow = UI("scrolledwindow")
self.mount_password_options = UI("mount_password_options")
self.mount_anonym_options = UI("mount_anonym_options")
self.popover_connect = UI("popover_connect")
@@ -2103,6 +2110,55 @@ def remove_from_recent_clicked(self, button):
self.UserSettings.removeRecentServer(button.name)
+
+
+ # sftp scan function
+ def on_sftp_scan(self, button):
+ self.sftp_stack.set_visible_child_name('page1')
+ self.btn_sftp_scan.set_sensitive(False)
+ # Start background thread
+ threading.Thread(target=self.scan_process, daemon=True).start()
+
+ def scan_process(self):
+ try:
+ netaddress = Scansftp.get_local_network()
+ scanned_devices = Scansftp.scan_devices(netaddress)
+ except Exception as e:
+ return
+ GLib.idle_add(self.sftp_listboxadd, scanned_devices)
+
+ def sftp_listboxadd(self, scanned_devices):
+ # Update the list
+ self.sftp_listbox.foreach(lambda w: self.sftp_listbox.remove(w))
+ self.sftp_listbox.set_hexpand(True)
+ self.sftp_listbox.set_vexpand(True)
+ for d in scanned_devices:
+ row = self.make_sftp_listbox_button(d, on_clicked=self.server_entry_settext)
+ self.sftp_listbox.add(row)
+
+ self.sftp_stack.set_visible_child_name('page0')
+ self.sftp_listbox.show_all()
+ self.btn_sftp_scan.set_sensitive(True)
+
+ def make_sftp_listbox_button(self, label_text, on_clicked=None):
+ # Create row
+ row = Gtk.ListBoxRow()
+
+ # Create a button. You can edit it using align/halign
+ btn = Gtk.Button(label=label_text)
+ btn.set_hexpand(True)
+ btn.set_halign(Gtk.Align.FILL)
+
+ if on_clicked is not None:
+ btn.connect("clicked", on_clicked)
+ row.add(btn)
+ return row
+
+ def server_entry_settext(self, button):
+ ip = button.get_label().strip()
+ self.entry_addr.set_text("sftp://"+ip)
+
+
def on_btn_mount_connect_clicked(self, button, from_saved=False, saved_uri="", from_places=False):
def get_uri_name(source_object):
try:
diff --git a/src/Scansftp.py b/src/Scansftp.py
new file mode 100644
index 0000000..187dd17
--- /dev/null
+++ b/src/Scansftp.py
@@ -0,0 +1,102 @@
+import subprocess
+import socket
+import ipaddress
+from concurrent.futures import ThreadPoolExecutor, as_completed
+import time
+import paramiko
+import netifaces
+
+# a function that retrieves the network address of the connected interface
+def get_local_network():
+ # Find the interface used by the default gateway
+ gateway_info = netifaces.gateways()
+ interface = gateway_info['default'][netifaces.AF_INET][1]
+
+ # Interface IP and netmask information
+ addr_info = netifaces.ifaddresses(interface)[netifaces.AF_INET][0]
+
+ ip_addr = addr_info['addr']
+ netmask = addr_info['netmask']
+
+ # Create a network address
+ network = ipaddress.IPv4Network(f"{ip_addr}/{netmask}", strict=False)
+
+ return str(network)
+
+
+def get_ip_neigh():
+ ips = set()
+ try:
+ out = subprocess.check_output(["ip", "neigh"]).decode()
+ for line in out.splitlines():
+ parts = line.split()
+ if len(parts) > 0:
+ ip = parts[0]
+ if ip.count(".") == 3: # IPv4 basic filter
+ ips.add(ip)
+ except Exception as e:
+ print("ip neigh error:", e)
+
+ return ips
+
+
+def check_ssh_sftp(host, port=22, timeout=3):
+ # Checking if the SSH port is open
+ try:
+ sock = socket.create_connection((host, port), timeout=timeout)
+ sock.close()
+ except (socket.timeout, ConnectionRefusedError, OSError):
+ return False
+
+ # Sending a fake credential results in an AuthenticationException → This means SFTP exists
+ transport = paramiko.Transport((host, port))
+ transport.banner_timeout = timeout
+
+ try:
+ transport.connect(
+ username='__probe__',
+ password='__probe__'
+ )
+ return True
+
+ except paramiko.AuthenticationException:
+ return True # an authentication error means the SFTP service is active
+
+ except paramiko.SSHException:
+ return False # SSH handshake failed
+
+ except Exception:
+ return False
+
+# scan devices
+def scan_devices(network, max_threads=30, delay=0.02):
+ net = ipaddress.ip_network(network, strict=False)
+ results = []
+
+ def worker(ip):
+ ip = str(ip)
+ if check_ssh_sftp(ip): # this address is being checked to see if it is an SSH server
+ return ip
+ return None
+
+ with ThreadPoolExecutor(max_workers=max_threads) as executor:
+ futures = []
+
+ for ip in net.hosts():
+ futures.append(executor.submit(worker, ip))
+ time.sleep(delay)
+
+ for f in as_completed(futures):
+ res = f.result()
+ if res:
+ results.append(res)
+
+ print(results)
+ return results
+
+
+#netaddress = get_local_network()
+#hosts = scan(netaddress)
+#for h in hosts:
+# print(h)
+
diff --git a/ui/MainWindow.glade b/ui/MainWindow.glade
index 249ce41..abf549b 100644
--- a/ui/MainWindow.glade
+++ b/ui/MainWindow.glade
@@ -2223,6 +2223,144 @@ Fatih Altun <fatih.altun@pardus.org.tr>
1
+
+
+
+ False
+ True
+ 2
+
+
+
+
+
+ False
+ True
+ 3
+
+