Skip to content

Commit aa985d7

Browse files
committed
update db and fix remains
1 parent 0fe57f3 commit aa985d7

File tree

4 files changed

+169
-32
lines changed

4 files changed

+169
-32
lines changed

Assignment1/client.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ def _handle_peer(self, peer_socket, peer_address):
7777
peer_socket.close()
7878
logging.info(f"[{thread_name}] Closed connection with peer {peer_address}")
7979

80-
def _do_publish(self, lname, fname):
80+
def _do_publish(self, lname, fname, allow_overwrite=False):
8181
if not os.path.exists(lname):
8282
logging.error(f"File {lname} does not exist.")
83-
return
83+
raise FileNotFoundError(f"File {lname} does not exist.")
8484
file_size = os.path.getsize(lname)
8585
last_modified = datetime.utcfromtimestamp(os.path.getmtime(lname)).isoformat() + "Z"
8686
source_ext = os.path.splitext(lname)[1]
@@ -93,12 +93,17 @@ def _do_publish(self, lname, fname):
9393
'fname': fname,
9494
'file_size': file_size,
9595
'last_modified': last_modified,
96+
'allow_overwrite': allow_overwrite,
9697
}
97-
if protocol.send_message(self.server_socket, publish_message):
98-
response = protocol.receive_message(self.server_socket)
99-
logging.info(f"Publish response: {response}")
100-
else:
98+
if not protocol.send_message(self.server_socket, publish_message):
10199
logging.error("Failed to send publish message.")
100+
raise RuntimeError("Failed to send publish message.")
101+
response = protocol.receive_message(self.server_socket)
102+
if response is None:
103+
logging.error("No publish response received from server.")
104+
raise RuntimeError("No response received after publish request.")
105+
logging.info(f"Publish response: {response}")
106+
return response
102107

103108
def _download_from_peer(self, chosen_peer, fname_to_save):
104109
logging.info("Starting download from peer...")

Assignment1/client_ui.py

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,11 @@ def disconnect(self):
117117

118118
logging.info("Client disconnected.")
119119

120-
def publish(self, local_path, alias):
120+
def publish(self, local_path, alias, allow_overwrite=False):
121121
if not self.connected or not self.client:
122122
raise RuntimeError("Client is not connected.")
123123
with self._socket_lock:
124-
self.client._do_publish(local_path, alias)
124+
return self.client._do_publish(local_path, alias, allow_overwrite=allow_overwrite)
125125

126126
def fetch_peer_list(self, fname):
127127
if not self.connected or not self.client:
@@ -412,18 +412,70 @@ def publish_file(self):
412412

413413
threading.Thread(
414414
target=self._publish_task,
415-
args=(local_path, alias),
415+
args=(local_path, alias, False),
416416
daemon=True,
417417
).start()
418418

419-
def _publish_task(self, local_path, alias):
419+
def _publish_task(self, local_path, alias, allow_overwrite):
420420
try:
421-
self.controller.publish(local_path, alias)
421+
response = self.controller.publish(local_path, alias, allow_overwrite=allow_overwrite)
422422
except Exception as exc:
423423
logging.error("Publish failed: %s", exc)
424424
self.root.after(0, lambda: messagebox.showerror("Publish error", str(exc)))
425425
return
426-
logging.info("Publish request sent for %s -> alias '%s'", local_path, alias)
426+
logging.info(
427+
"Publish response received for %s -> alias '%s': %s",
428+
local_path,
429+
alias,
430+
response,
431+
)
432+
self.root.after(
433+
0,
434+
lambda: self._handle_publish_response(local_path, alias, response, allow_overwrite),
435+
)
436+
437+
def _handle_publish_response(self, local_path, alias, response, allow_overwrite):
438+
status = (response or {}).get("status")
439+
message = response.get("message") if isinstance(response, dict) else None
440+
if not isinstance(response, dict):
441+
messagebox.showerror("Publish error", "Unexpected response from server.")
442+
return
443+
444+
if status == "conflict" and not allow_overwrite:
445+
existing_path = response.get("existing_lname") or "unknown location"
446+
prompt = (
447+
"Alias '{alias}' is already published for this client.\n\n"
448+
"Existing path: {existing}\n"
449+
"New path: {new}\n\n"
450+
"Do you want to overwrite the previous file entry?"
451+
).format(alias=alias, existing=existing_path, new=local_path)
452+
overwrite = messagebox.askyesno("Overwrite alias?", prompt)
453+
if overwrite:
454+
logging.info("User confirmed overwrite for alias '%s'.", alias)
455+
threading.Thread(
456+
target=self._publish_task,
457+
args=(local_path, alias, True),
458+
daemon=True,
459+
).start()
460+
else:
461+
logging.info("User declined to overwrite alias '%s'.", alias)
462+
messagebox.showinfo("Publish", f"Publish cancelled for alias '{alias}'.")
463+
return
464+
465+
if status in ("created", "updated"):
466+
title = "Publish" if status == "created" else "Publish Updated"
467+
messagebox.showinfo(title, message or f"Alias '{alias}' published successfully.")
468+
return
469+
470+
if status == "unchanged":
471+
messagebox.showinfo("Publish", message or f"Alias '{alias}' is already up to date.")
472+
return
473+
474+
if status == "error":
475+
messagebox.showerror("Publish error", message or f"Failed to publish '{alias}'.")
476+
return
477+
478+
messagebox.showinfo("Publish", message or f"Alias '{alias}' publish result: {status}")
427479

428480
def fetch_file(self):
429481
fname = self.fetch_name_var.get().strip()
@@ -537,11 +589,11 @@ def _show_peer_selection(self, fname, peer_list):
537589
listbox.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
538590

539591
for idx, peer in enumerate(peer_list, start=1):
540-
hostname = peer.get("hostname") or "Unknown"
541-
original_name = os.path.basename(peer.get("lname") or fname)
592+
client_label = peer.get("hostname") or peer.get("ip") or "Unknown client"
593+
size_label = self._format_file_size(peer.get("file_size"))
542594
listbox.insert(
543595
tk.END,
544-
f"{idx}. {hostname} ({original_name})",
596+
f"{idx}. {client_label} ({size_label})",
545597
)
546598

547599
button_frame = tk.Frame(dialog)
@@ -600,7 +652,7 @@ def on_cancel():
600652
all_button = tk.Button(button_frame, text="Download All", command=on_select_all, bg=PASTEL_BUTTON)
601653
all_button.grid(row=0, column=1, padx=5, sticky="ew")
602654

603-
custom_button = tk.Button(button_frame, text="Custom...", command=on_custom, bg=PASTEL_BUTTON)
655+
custom_button = tk.Button(button_frame, text="Custom", command=on_custom, bg=PASTEL_BUTTON)
604656
custom_button.grid(row=0, column=2, padx=5, sticky="ew")
605657

606658
cancel_button = tk.Button(button_frame, text="Cancel", command=on_cancel, bg=PASTEL_BUTTON)
@@ -668,6 +720,20 @@ def _on_multi_download_finished(self, successes, failures):
668720
messagebox.showinfo("Fetch summary", summary)
669721
self.fetch_button.config(state=tk.NORMAL)
670722

723+
def _format_file_size(self, size_value):
724+
try:
725+
size = int(size_value)
726+
except (TypeError, ValueError):
727+
return "unknown size"
728+
units = ["B", "KB", "MB", "GB", "TB"]
729+
for unit in units:
730+
if size < 1024 or unit == "TB":
731+
if unit == "B":
732+
return f"{size} {unit}"
733+
return f"{size:.1f} {unit}"
734+
size /= 1024
735+
return f"{size:.1f} TB"
736+
671737
def _get_preferred_filename(self, peer_info, fallback_name):
672738
original = peer_info.get("lname")
673739
if original:

Assignment1/database.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,14 @@ def _ensure_schema(self) -> None:
5454
last_modified TEXT
5555
);
5656
"""
57+
drop_legacy_index_stmt = "DROP INDEX IF EXISTS uq_file_index_unique_peer;"
5758
create_index_stmt = """
58-
CREATE UNIQUE INDEX IF NOT EXISTS uq_file_index_unique_peer
59-
ON file_index (fname, hostname, ip, port, file_size, last_modified);
59+
CREATE UNIQUE INDEX IF NOT EXISTS uq_file_index_alias_per_peer
60+
ON file_index (fname, hostname, ip, port);
6061
"""
6162
with self._connect() as conn, conn.cursor() as cur:
6263
cur.execute(create_table_stmt)
64+
cur.execute(drop_legacy_index_stmt)
6365
cur.execute(create_index_stmt)
6466
logging.info("Database schema verified.")
6567

@@ -85,18 +87,36 @@ def list_peers_for_file(self, fname: str) -> List[Dict[str, object]]:
8587
rows = cur.fetchall()
8688
return list(rows)
8789

88-
def register_file(self, entry: Dict[str, object]) -> bool:
90+
def get_entry(self, fname: str, hostname: str, ip: str, port: int) -> Optional[Dict[str, object]]:
91+
query = """
92+
SELECT fname, hostname, ip, port, lname, file_size, last_modified
93+
FROM file_index
94+
WHERE fname = %s AND hostname = %s AND ip = %s AND port = %s
95+
LIMIT 1
96+
"""
97+
with self._connect() as conn, conn.cursor(cursor_factory=RealDictCursor) as cur:
98+
cur.execute(query, (fname, hostname, ip, port))
99+
row = cur.fetchone()
100+
return dict(row) if row else None
101+
102+
def register_file(self, entry: Dict[str, object]) -> str:
89103
insert_stmt = """
90104
INSERT INTO file_index (fname, hostname, ip, port, lname, file_size, last_modified)
91105
VALUES (%(fname)s, %(hostname)s, %(ip)s, %(port)s, %(lname)s, %(file_size)s, %(last_modified)s)
92-
ON CONFLICT (fname, hostname, ip, port, file_size, last_modified)
93-
DO NOTHING
94-
RETURNING id
106+
ON CONFLICT (fname, hostname, ip, port)
107+
DO UPDATE SET
108+
lname = EXCLUDED.lname,
109+
file_size = EXCLUDED.file_size,
110+
last_modified = EXCLUDED.last_modified
111+
RETURNING id, xmax = 0 AS inserted
95112
"""
96113
with self._connect() as conn, conn.cursor() as cur:
97114
cur.execute(insert_stmt, entry)
98-
inserted = cur.fetchone()
99-
return inserted is not None
115+
result = cur.fetchone()
116+
if not result:
117+
return "none"
118+
_, inserted = result
119+
return "inserted" if inserted else "updated"
100120

101121
def delete_entries_for_peer(self, hostname: str, ip: str, port: int) -> Dict[str, int]:
102122
delete_stmt = """

Assignment1/server.py

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def handle_client(self, client_socket: socket.socket, client_address: tuple[str,
7070
if action == "publish":
7171
lname = message.get("lname")
7272
fname = message.get("fname")
73+
allow_overwrite = bool(message.get("allow_overwrite"))
7374
if not lname or not fname:
7475
response = {"status": "error", "message": "Missing lname or fname"}
7576
else:
@@ -82,15 +83,60 @@ def handle_client(self, client_socket: socket.socket, client_address: tuple[str,
8283
"last_modified": message.get("last_modified"),
8384
"fname": fname,
8485
}
85-
inserted = self.db.register_file(peer_info)
86-
if inserted:
87-
logging.info("[%s] Client %s publishing file %s", thread_name, client_address, fname)
88-
response = {"status": "success", "message": f"File {fname} published successfully"}
89-
else:
90-
logging.warning(
91-
"[%s] File '%s' already published with same metadata by %s", thread_name, fname, client_address
86+
existing_entry = None
87+
if client_hostname and client_ip and client_p2p_port:
88+
existing_entry = self.db.get_entry(fname, client_hostname, client_ip, client_p2p_port)
89+
90+
if existing_entry:
91+
same_file_path = existing_entry.get("lname") == lname
92+
metadata_matches = (
93+
same_file_path
94+
and existing_entry.get("file_size") == peer_info["file_size"]
95+
and existing_entry.get("last_modified") == peer_info["last_modified"]
9296
)
93-
response = {"status": "exists", "message": f"File {fname} already registered with same metadata"}
97+
if metadata_matches:
98+
logging.info(
99+
"[%s] Client %s attempted to republish %s with unchanged metadata",
100+
thread_name,
101+
client_address,
102+
fname,
103+
)
104+
response = {
105+
"status": "unchanged",
106+
"message": f"File {fname} is already up to date for this client.",
107+
}
108+
elif not same_file_path and not allow_overwrite:
109+
logging.info(
110+
"[%s] Client %s publish conflict on alias %s (existing path %s, new path %s)",
111+
thread_name,
112+
client_address,
113+
fname,
114+
existing_entry.get("lname"),
115+
lname,
116+
)
117+
response = {
118+
"status": "conflict",
119+
"message": f"Alias '{fname}' is already published for this client.",
120+
"existing_lname": existing_entry.get("lname"),
121+
}
122+
else:
123+
result = self.db.register_file(peer_info)
124+
logging.info(
125+
"[%s] Client %s overwrote alias %s with path %s",
126+
thread_name,
127+
client_address,
128+
fname,
129+
lname,
130+
)
131+
response = {
132+
"status": "updated",
133+
"message": f"File {fname} metadata updated.",
134+
"result": result,
135+
}
136+
else:
137+
result = self.db.register_file(peer_info)
138+
logging.info("[%s] Client %s publishing new file %s", thread_name, client_address, fname)
139+
response = {"status": "created", "message": f"File {fname} published successfully", "result": result}
94140
protocol.send_message(client_socket, response)
95141

96142
elif action == "fetch":

0 commit comments

Comments
 (0)