diff -Nru nicotine-3.1.0/debian/changelog nicotine-3.1.0/debian/changelog --- nicotine-3.1.0/debian/changelog 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/debian/changelog 2021-06-24 20:37:36.000000000 +0000 @@ -1,8 +1,8 @@ -nicotine (3.1.0-202106232035~ubuntu20.10.1) groovy; urgency=low +nicotine (3.1.0-202106242037~ubuntu20.10.1) groovy; urgency=low * Auto build. - -- Launchpad Package Builder Wed, 23 Jun 2021 20:35:17 +0000 + -- Launchpad Package Builder Thu, 24 Jun 2021 20:37:36 +0000 nicotine (3.1.0-dev1) hirsute; urgency=medium diff -Nru nicotine-3.1.0/debian/git-build-recipe.manifest nicotine-3.1.0/debian/git-build-recipe.manifest --- nicotine-3.1.0/debian/git-build-recipe.manifest 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/debian/git-build-recipe.manifest 2021-06-24 20:37:36.000000000 +0000 @@ -1,2 +1,2 @@ -# git-build-recipe format 0.4 deb-version {debupstream}-202106232035 -lp:nicotine+ git-commit:0a96baaa607b3ad3e1e13bba9628c5e3224b2395 +# git-build-recipe format 0.4 deb-version {debupstream}-202106242037 +lp:nicotine+ git-commit:877d5e3cfbd1db7dbd62df9864ae9b246f4a1bb2 diff -Nru nicotine-3.1.0/pynicotine/gtkgui/frame.py nicotine-3.1.0/pynicotine/gtkgui/frame.py --- nicotine-3.1.0/pynicotine/gtkgui/frame.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/gtkgui/frame.py 2021-06-24 20:37:36.000000000 +0000 @@ -49,9 +49,8 @@ from pynicotine.gtkgui.settingswindow import Settings from pynicotine.gtkgui.statistics import Statistics from pynicotine.gtkgui.uploads import Uploads -from pynicotine.gtkgui.userbrowse import UserBrowse -from pynicotine.gtkgui.userinfo import UserInfo -from pynicotine.gtkgui.userinfo import UserTabs +from pynicotine.gtkgui.userbrowse import UserBrowses +from pynicotine.gtkgui.userinfo import UserInfos from pynicotine.gtkgui.userlist import UserList from pynicotine.gtkgui.utils import append_line from pynicotine.gtkgui.utils import connect_key_press_event @@ -80,8 +79,6 @@ from pynicotine.utils import get_latest_version from pynicotine.utils import human_speed from pynicotine.utils import make_version -from pynicotine.utils import RestrictedUnpickler -from pynicotine.utils import unescape class NicotineFrame: @@ -228,8 +225,8 @@ self.uploads = Uploads(self, self.UploadsTabLabel) self.userlist = UserList(self) self.privatechats = PrivateChats(self) - self.userinfo = UserTabs(self, UserInfo, self.UserInfoNotebookRaw, self.UserInfoTabLabel, "userinfo") - self.userbrowse = UserTabs(self, UserBrowse, self.UserBrowseNotebookRaw, self.UserBrowseTabLabel, "userbrowse") + self.userinfo = UserInfos(self) + self.userbrowse = UserBrowses(self) """ Entry Completion """ @@ -422,10 +419,8 @@ self.uploads.server_login() self.downloads.server_login() self.privatechats.server_login() - self.userbrowse.server_login() - self.userinfo.server_login() - return (self.privatechats, self.chatrooms, self.userinfo, self.userbrowse) + return (self.privatechats, self.chatrooms) def init_spell_checker(self): @@ -648,11 +643,13 @@ self.np.disconnect() def on_away(self, *args): + self.np.away = not self.np.away config.sections["server"]["away"] = self.np.away self._apply_away_state() def _apply_away_state(self): + if not self.np.away: self.set_user_status(_("Online")) self.on_disable_auto_away() @@ -661,12 +658,12 @@ self.tray_icon.set_away(self.np.away) - self.np.queue.append(slskmessages.SetStatus(self.np.away and 1 or 2)) + self.np.request_set_status(self.np.away and 1 or 2) self.away_action.set_state(GLib.Variant.new_boolean(self.np.away)) self.privatechats.update_visuals() def on_check_privileges(self, *args): - self.np.queue.append(slskmessages.CheckPrivileges()) + self.np.request_check_privileges() def on_get_privileges(self, *args): import urllib.parse @@ -870,41 +867,11 @@ def on_buddy_rescan(self, *args, rebuild=False): self.np.shares.rescan_buddy_shares(rebuild) - def on_browse_public_shares(self, *args, folder=None): - """ Browse your own public shares """ - - login = config.sections["server"]["login"] - - # Deactivate if we only share with buddies - if config.sections["transfers"]["friendsonly"]: - msg = slskmessages.SharedFileList(None, {}) - else: - msg = self.np.shares.get_compressed_shares_message("normal") - - thread = threading.Thread(target=self.parse_local_shares, args=(login, msg, folder, "normal")) - thread.name = "LocalShareParser" - thread.daemon = True - thread.start() - - self.userbrowse.show_user(login, indeterminate_progress=True) + def on_browse_public_shares(self, *args): + self.np.userbrowse.browse_local_public_shares() - def on_browse_buddy_shares(self, *args, folder=None): - """ Browse your own buddy shares """ - - login = config.sections["server"]["login"] - - # Show public shares if we don't have specific shares for buddies - if not config.sections["transfers"]["enablebuddyshares"]: - msg = self.np.shares.get_compressed_shares_message("normal") - else: - msg = self.np.shares.get_compressed_shares_message("buddy") - - thread = threading.Thread(target=self.parse_local_shares, args=(login, msg, folder, "buddy")) - thread.name = "LocalBuddyShareParser" - thread.daemon = True - thread.start() - - self.userbrowse.show_user(login, indeterminate_progress=True) + def on_browse_buddy_shares(self, *args): + self.np.userbrowse.browse_local_buddy_shares() # Modes @@ -1969,11 +1936,6 @@ else: self.show_tab(tab_box) - """ Transfer Statistics """ - - def update_stat_value(self, stat_id, stat_value): - self.statistics.update_stat_value(stat_id, stat_value) - """ Search """ def on_settings_searches(self, *args): @@ -2012,119 +1974,36 @@ def on_get_user_info(self, widget, *args): - text = widget.get_text() + username = widget.get_text() - if not text: + if not username: return - self.local_user_info_request(text) + self.np.userinfo.request_user_info(username) clear_entry(widget) - def local_user_info_request(self, user): - msg = slskmessages.UserInfoRequest(None) - - # Hack for local userinfo requests, for extra security - if user == config.sections["server"]["login"]: - try: - if config.sections["userinfo"]["pic"] != "": - userpic = config.sections["userinfo"]["pic"] - if os.path.exists(userpic): - msg.has_pic = True - with open(userpic, 'rb') as f: - msg.pic = f.read() - else: - msg.has_pic = False - msg.pic = None - else: - msg.has_pic = False - msg.pic = None - except Exception: - msg.pic = None - - msg.descr = unescape(config.sections["userinfo"]["descr"]) - msg.totalupl = self.np.transfers.get_total_uploads_allowed() - msg.queuesize = self.np.transfers.get_upload_queue_size() - msg.slotsavail = self.np.transfers.allow_new_uploads() - ua = config.sections["transfers"]["remotedownloads"] - if ua: - msg.uploadallowed = config.sections["transfers"]["uploadallowed"] - else: - msg.uploadallowed = ua - - self.userinfo.show_user(user, msg=msg) - - else: - self.userinfo.show_user(user) - self.np.send_message_to_peer(user, msg) - """ User Browse """ - def browse_user(self, user, folder=None, local_shares_type=None): - """ Browse a user shares """ - - login = config.sections["server"]["login"] - - if user is not None: - if user == login: - if local_shares_type == "normal" or not config.sections["transfers"]["enablebuddyshares"]: - self.on_browse_public_shares(folder=folder) - else: - self.on_browse_buddy_shares(folder=folder) - else: - self.userbrowse.show_user(user, folder=folder) - - if self.userbrowse.is_new_request(user): - self.np.send_message_to_peer(user, slskmessages.GetSharedFileList(None)) - - def parse_local_shares(self, username, msg, folder=None, shares_type="normal"): - """ Parse our local shares list and show it in the UI """ - - built = msg.make_network_message() - msg.parse_network_message(built) - - indeterminate_progress = change_page = False - GLib.idle_add(self.userbrowse.show_user, username, msg.conn, msg, indeterminate_progress, - change_page, folder, shares_type) - def on_get_shares(self, widget, *args): - text = widget.get_text() + username = widget.get_text() - if not text: + if not username: return - self.browse_user(text) + self.np.userbrowse.browse_user(username) clear_entry(widget) def on_load_from_disk_selected(self, selected, data): - for share in selected: - try: - try: - # Try legacy format first - import bz2 - - with bz2.BZ2File(share) as sharefile: - mylist = RestrictedUnpickler(sharefile, encoding='utf-8').load() - - except Exception: - # Try new format + for filename in selected: + shares_list = self.np.userbrowse.get_shares_list_from_disk(filename) - with open(share, encoding="utf-8") as sharefile: - import json - mylist = json.load(sharefile) - - if not isinstance(mylist, (list, dict)): - raise TypeError("Bad data in file %(sharesdb)s" % {'sharesdb': share}) - - username = share.replace('\\', os.sep).split(os.sep)[-1] - self.userbrowse.show_user(username) - - if username in self.userbrowse.users: - self.userbrowse.users[username].load_shares(mylist) + if shares_list is None: + continue - except Exception as msg: - log.add(_("Loading Shares from disk failed: %(error)s"), {'error': msg}) + username = filename.replace('\\', os.sep).split(os.sep)[-1] + self.np.userbrowse.load_local_shares_list(username, shares_list) def on_load_from_disk(self, *args): diff -Nru nicotine-3.1.0/pynicotine/gtkgui/search.py nicotine-3.1.0/pynicotine/gtkgui/search.py --- nicotine-3.1.0/pynicotine/gtkgui/search.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/gtkgui/search.py 2021-06-24 20:37:36.000000000 +0000 @@ -32,7 +32,6 @@ from gi.repository import GObject from gi.repository import Gtk -from pynicotine import slskmessages from pynicotine.config import config from pynicotine.gtkgui.fileproperties import FileProperties from pynicotine.gtkgui.utils import connect_key_press_event @@ -1126,7 +1125,7 @@ folder = file[1].rsplit('\\', 1)[0] if user not in requested_users and folder not in requested_folders: - self.frame.browse_user(user, folder) + self.frame.np.userbrowse.browse_user(user, folder=folder) requested_users.add(user) requested_folders.add(folder) @@ -1222,8 +1221,7 @@ requested_folders[user][folder] = download_location - # First queue the visible search results - files = [] + visible_files = [] for row in self.all_data: # Find the wanted directory @@ -1234,22 +1232,10 @@ (counter, user, flag, immediatedl, h_speed, h_queue, directory, filename, h_size, h_bitrate, h_length, bitrate, fullpath, country, size, speed, queue, length, color) = row - files.append( + visible_files.append( (user, fullpath, destination, size.get_uint64(), bitrate.get_uint64(), length.get_uint64())) - if config.sections["transfers"]["reverseorder"]: - files.sort(key=lambda x: x[1], reverse=True) - - for file in files: - user, fullpath, destination, size, bitrate, length = file - - self.frame.np.transfers.get_file( - user, fullpath, destination, - size=size, bitrate=bitrate, length=length, checkduplicate=True - ) - - # Ask for the rest of the files in the folder - self.frame.np.send_message_to_peer(user, slskmessages.FolderContentsRequest(None, folder)) + self.frame.np.search.request_folder_download(user, folder, visible_files) def on_download_folders_to_selected(self, selected, data): self.on_download_folders(download_location=selected) diff -Nru nicotine-3.1.0/pynicotine/gtkgui/settingswindow.py nicotine-3.1.0/pynicotine/gtkgui/settingswindow.py --- nicotine-3.1.0/pynicotine/gtkgui/settingswindow.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/gtkgui/settingswindow.py 2021-06-24 20:37:36.000000000 +0000 @@ -34,7 +34,6 @@ from gi.repository import GObject from gi.repository import Gtk -from pynicotine import slskmessages from pynicotine.config import config from pynicotine.gtkgui.utils import append_line from pynicotine.gtkgui.utils import load_ui_elements @@ -172,7 +171,7 @@ self.on_change_password() return - self.frame.np.queue.append(slskmessages.ChangePassword(password)) + self.frame.np.request_change_password(password) def on_change_password(self, *args): diff -Nru nicotine-3.1.0/pynicotine/gtkgui/transferlist.py nicotine-3.1.0/pynicotine/gtkgui/transferlist.py --- nicotine-3.1.0/pynicotine/gtkgui/transferlist.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/gtkgui/transferlist.py 2021-06-24 20:37:36.000000000 +0000 @@ -923,7 +923,7 @@ folder = transfer.filename.rsplit('\\', 1)[0] if user not in requested_users and folder not in requested_folders: - self.frame.browse_user(user, folder) + self.frame.np.userbrowse.browse_user(user, folder=folder) requested_users.add(user) requested_folders.add(folder) diff -Nru nicotine-3.1.0/pynicotine/gtkgui/userbrowse.py nicotine-3.1.0/pynicotine/gtkgui/userbrowse.py --- nicotine-3.1.0/pynicotine/gtkgui/userbrowse.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/gtkgui/userbrowse.py 2021-06-24 20:37:36.000000000 +0000 @@ -25,6 +25,7 @@ from sys import maxsize +from gi.repository import GLib from gi.repository import GObject from gi.repository import Gtk @@ -39,6 +40,7 @@ from pynicotine.gtkgui.utils import open_file_path from pynicotine.gtkgui.utils import parse_accelerator from pynicotine.gtkgui.widgets.filechooser import choose_dir +from pynicotine.gtkgui.widgets.iconnotebook import IconNotebook from pynicotine.gtkgui.widgets.infobar import InfoBar from pynicotine.gtkgui.widgets.dialogs import entry_dialog from pynicotine.gtkgui.widgets.popupmenu import PopupMenu @@ -51,12 +53,104 @@ from pynicotine.utils import human_size +class UserBrowses(IconNotebook): + + def __init__(self, frame): + + self.frame = frame + self.pages = {} + + IconNotebook.__init__( + self, + self.frame.images, + tabclosers=config.sections["ui"]["tabclosers"], + show_hilite_image=config.sections["notifications"]["notification_tab_icons"], + reorderable=config.sections["ui"]["tab_reorderable"], + show_status_image=config.sections["ui"]["tab_status_icons"], + notebookraw=self.frame.UserBrowseNotebookRaw + ) + + def show_user(self, user, folder=None, local_shares_type=None, indeterminate_progress=False): + + if user not in self.pages: + self.save_columns() + + try: + status = self.frame.np.users[user].status + except Exception: + # Offline + status = 0 + + self.pages[user] = page = UserBrowse(self, user) + page.set_in_progress(indeterminate_progress) + + self.append_page(page.Main, user, page.on_close, status=status) + page.set_label(self.get_tab_label_inner(page.Main)) + + page = self.pages[user] + + page.indeterminate_progress = indeterminate_progress + page.local_shares_type = local_shares_type + page.queued_folder = folder + page.browse_queued_folder() + + self.set_current_page(self.page_num(page.Main)) + self.frame.change_main_page("userbrowse") + + def set_conn(self, user, conn): + if user in self.pages: + self.pages[user].conn = conn + + def show_connection_error(self, user): + if user in self.pages: + self.pages[user].show_connection_error() + + def get_user_status(self, msg): + + if msg.user in self.pages: + page = self.pages[msg.user] + self.set_user_status(page.Main, msg.user, msg.status) + + def _shared_file_list(self, user, msg): + if user in self.pages: + self.pages[user].shared_file_list(msg) + + def shared_file_list(self, user, msg): + # We can potentially arrive here from a different thread. Run in main thread. + GLib.idle_add(self._shared_file_list, user, msg) + + def update_gauge(self, msg): + + for page in self.pages.values(): + if page.conn == msg.conn.conn: + page.update_gauge(msg) + + def update_visuals(self): + for page in self.pages.values(): + page.update_visuals() + + def server_disconnect(self): + for user, page in self.pages.items(): + self.set_user_status(page.Main, user, 0) + + def save_columns(self): + """ Save the treeview state of the currently selected tab """ + + current_page = self.get_nth_page(self.get_current_page()) + + for page in self.pages.values(): + if page.Main == current_page: + page.save_columns() + break + + class UserBrowse: def __init__(self, userbrowses, user): self.userbrowses = userbrowses self.frame = userbrowses.frame + self.frame.np.userbrowse.add_user(user) # Build the window load_ui_elements(self, os.path.join(self.frame.gui_dir, "ui", "userbrowse.ui")) @@ -69,13 +163,9 @@ else: self.MainPaned.child_set_property(self.FolderPane, "resize", True) - # Monitor user online status - self.frame.np.watch_user(user) - self.user = user self.conn = None self.local_shares_type = None - self.refreshing = True # selected_folder is the current selected folder self.selected_folder = None @@ -453,16 +543,16 @@ return directory - def browse_folder(self, folder): - """ Browse a specific folder in the share """ + def browse_queued_folder(self): + """ Browse a queued folder in the share """ try: - iterator = self.directories[folder] + iterator = self.directories[self.queued_folder] except KeyError: # Folder not found return - if folder: + if self.queued_folder: sel = self.FolderTreeView.get_selection() sel.unselect_all() @@ -547,30 +637,11 @@ def save_columns(self): save_columns("user_browse", self.FileTreeView.get_columns()) - def show_user(self, msg, folder=None, indeterminate_progress=False, local_shares_type=None): - - self.set_in_progress(indeterminate_progress) - - if folder: - self.queued_folder = folder + def shared_file_list(self, msg): - # If this is our own share, remember if it's public or buddy - # (needed for refresh button) - if local_shares_type: - self.local_shares_type = local_shares_type - - """ Update the list model if: - 1. This is a new user browse tab - 2. We're refreshing the file list - 3. This is the list of our own shared files (local_shares_type set) - """ - if self.refreshing or local_shares_type: - if msg is None: - return - - self.make_new_model(msg.list) + self.make_new_model(msg.list) - if msg and not msg.list: + if not msg.list: self.info_bar.show_message( _("User's list of shared files is empty. Either the user is not sharing anything, " "or they are sharing files privately.") @@ -578,7 +649,7 @@ else: self.info_bar.set_visible(False) - self.browse_folder(self.queued_folder) + self.browse_queued_folder() self.set_finished() @@ -591,12 +662,6 @@ self.set_finished() - def load_shares(self, list): - self.make_new_model(list) - - def is_refreshing(self): - return self.refreshing - def set_in_progress(self, indeterminate_progress): if not indeterminate_progress: @@ -618,8 +683,6 @@ self.FileTreeView.set_sensitive(True) self.RefreshButton.set_sensitive(True) - self.refreshing = False - def update_gauge(self, msg): if msg.total == 0 or msg.bufferlen == 0: @@ -1017,13 +1080,13 @@ def on_refresh(self, *args): - self.refreshing = True self.info_bar.set_visible(False) self.FolderTreeView.set_sensitive(False) self.FileTreeView.set_sensitive(False) - self.frame.browse_user(self.user, local_shares_type=self.local_shares_type) + self.set_in_progress(self.indeterminate_progress) + self.frame.np.userbrowse.browse_user(self.user, local_shares_type=self.local_shares_type, new_request=True) def on_copy_folder_path(self, *args): self.copy_selected_path() @@ -1059,7 +1122,8 @@ self.user_popup.toggle_user_items() def on_close(self, *args): - del self.userbrowses.users[self.user] + del self.userbrowses.pages[self.user] + self.frame.np.userbrowse.remove_user(self.user) self.userbrowses.remove_page(self.Main) def on_close_all_tabs(self, *args): diff -Nru nicotine-3.1.0/pynicotine/gtkgui/userinfo.py nicotine-3.1.0/pynicotine/gtkgui/userinfo.py --- nicotine-3.1.0/pynicotine/gtkgui/userinfo.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/gtkgui/userinfo.py 2021-06-24 20:37:36.000000000 +0000 @@ -27,7 +27,6 @@ from gi.repository import GLib from gi.repository import Gtk -from pynicotine import slskmessages from pynicotine.config import config from pynicotine.gtkgui.utils import append_line from pynicotine.gtkgui.utils import load_ui_elements @@ -42,12 +41,12 @@ from pynicotine.utils import human_speed -# User Info and User Browse Notebooks -class UserTabs(IconNotebook): +class UserInfos(IconNotebook): - def __init__(self, frame, subwindow, notebookraw, tab_label, tab_name): + def __init__(self, frame): self.frame = frame + self.pages = {} IconNotebook.__init__( self, @@ -56,115 +55,64 @@ show_hilite_image=config.sections["notifications"]["notification_tab_icons"], reorderable=config.sections["ui"]["tab_reorderable"], show_status_image=config.sections["ui"]["tab_status_icons"], - notebookraw=notebookraw + notebookraw=self.frame.UserInfoNotebookRaw ) - self.subwindow = subwindow + def show_user(self, user): - self.users = {} - self.tab_label = tab_label - self.tab_name = tab_name - - def init_window(self, user): - - try: - status = self.frame.np.users[user].status - except Exception: - # Offline - status = 0 - - w = self.users[user] = self.subwindow(self, user) - self.append_page(w.Main, user, w.on_close, status=status) - w.set_label(self.get_tab_label_inner(w.Main)) - - def show_user(self, user, conn=None, msg=None, indeterminate_progress=False, - change_page=True, folder=None, local_shares_type=None): - - self.save_columns() - - if user in self.users: - self.users[user].conn = conn - - elif not change_page: - # This tab was closed, but we received a response. Don't reopen it. - return - - else: - self.init_window(user) - - self.users[user].show_user(msg, folder, indeterminate_progress, local_shares_type) - - if change_page: - self.set_current_page(self.page_num(self.users[user].Main)) - self.frame.change_main_page(self.tab_name) + if user not in self.pages: + try: + status = self.frame.np.users[user].status + except Exception: + # Offline + status = 0 + + self.pages[user] = page = UserInfo(self, user) + self.append_page(page.Main, user, page.on_close, status=status) + page.set_label(self.get_tab_label_inner(page.Main)) + + self.set_current_page(self.page_num(self.pages[user].Main)) + self.frame.change_main_page("userinfo") + + def set_conn(self, user, conn): + if user in self.pages: + self.pages[user].conn = conn def show_connection_error(self, user): - if user in self.users: - self.users[user].show_connection_error() - - def save_columns(self): - """ Save the treeview state of the currently selected tab """ - - current_page = self.get_nth_page(self.get_current_page()) - - for tab in self.users.values(): - if tab.Main == current_page: - tab.save_columns() - break + if user in self.pages: + self.pages[user].show_connection_error() def get_user_stats(self, msg): - - if msg.user in self.users: - tab = self.users[msg.user] - tab.speed.set_text(_("Speed: %s") % human_speed(msg.avgspeed)) - tab.filesshared.set_text(_("Files: %s") % humanize(msg.files)) - tab.dirsshared.set_text(_("Directories: %s") % humanize(msg.dirs)) + if msg.user in self.pages: + self.pages[msg.user].get_user_stats(msg) def get_user_status(self, msg): - if msg.user in self.users: - - tab = self.users[msg.user] - tab.status = msg.status - - self.set_user_status(tab.Main, msg.user, msg.status) - - def is_new_request(self, user): - - if user in self.users: - return self.users[user].is_refreshing() - - return True - - def show_interests(self, msg): - - if msg.user in self.users: - self.users[msg.user].show_interests(msg.likes, msg.hates) + if msg.user in self.pages: + page = self.pages[msg.user] + self.set_user_status(page.Main, msg.user, msg.status) + + def user_interests(self, msg): + if msg.user in self.pages: + self.pages[msg.user].user_interests(msg) + + def user_info_reply(self, user, msg): + if user in self.pages: + self.pages[user].user_info_reply(msg) def update_gauge(self, msg): - for i in self.users.values(): - if i.conn == msg.conn.conn: - i.update_gauge(msg) + for page in self.pages.values(): + if page.conn == msg.conn.conn: + page.update_gauge(msg) def update_visuals(self): - - for i in self.users.values(): - i.update_visuals() - - def server_login(self): - - for user in self.users: - # Get notified of user status - self.frame.np.watch_user(user) + for page in self.pages.values(): + page.update_visuals() def server_disconnect(self): - - for user in self.users: - tab = self.users[user] - tab.status = 0 - - self.set_user_status(tab.Main, user, tab.status) + for user, page in self.pages.items(): + self.set_user_status(page.Main, user, 0) class UserInfo: @@ -173,6 +121,7 @@ self.userinfos = userinfos self.frame = userinfos.frame + self.frame.np.userinfo.add_user(user) # Build the window load_ui_elements(self, os.path.join(self.frame.gui_dir, "ui", "userinfo.ui")) @@ -204,19 +153,12 @@ # GTK <3.24 self.ImageViewport.connect("scroll-event", self.on_scroll_event) - # Request user status, speed and number of shared files - self.frame.np.watch_user(user, force_update=True) - - # Request user interests - self.frame.np.queue.append(slskmessages.UserInterests(user)) - self.user = user self.conn = None self._descr = "" self.image_pixbuf = None self.zoom_factor = 5 self.actual_zoom = 0 - self.status = 0 self.hates_store = Gtk.ListStore(str) self.Hates.set_model(self.hates_store) @@ -279,15 +221,15 @@ for widget in list(self.__dict__.values()): update_widget_visuals(widget) - def show_interests(self, likes, hates): + def user_interests(self, msg): self.likes_store.clear() self.hates_store.clear() - for like in likes: + for like in msg.likes: self.likes_store.insert_with_valuesv(-1, self.like_column_numbers, [like]) - for hate in hates: + for hate in msg.hates: self.hates_store.insert_with_valuesv(-1, self.hate_column_numbers, [hate]) def save_columns(self): @@ -322,7 +264,30 @@ "error": str(e) }) - def show_user(self, msg, *args): + def get_user_stats(self, msg): + + self.speed.set_text(_("Speed: %s") % human_speed(msg.avgspeed)) + self.filesshared.set_text(_("Files: %s") % humanize(msg.files)) + self.dirsshared.set_text(_("Directories: %s") % humanize(msg.dirs)) + + def show_connection_error(self): + + self.info_bar.show_message( + _("Unable to request information from user. Either you both have a closed listening " + "port, the user is offline, or there's a temporary connectivity issue.") + ) + + self.set_finished() + + def set_finished(self): + + # Tab notification + self.frame.request_tab_icon(self.frame.UserInfoTabLabel) + self.userinfos.request_changed(self.Main) + + self.progressbar.set_fraction(1.0) + + def user_info_reply(self, msg): if msg is None: return @@ -362,23 +327,6 @@ self.info_bar.set_visible(False) self.set_finished() - def show_connection_error(self): - - self.info_bar.show_message( - _("Unable to request information from user. Either you both have a closed listening " - "port, the user is offline, or there's a temporary connectivity issue.") - ) - - self.set_finished() - - def set_finished(self): - - # Tab notification - self.frame.request_tab_icon(self.frame.UserInfoTabLabel) - self.userinfos.request_changed(self.Main) - - self.progressbar.set_fraction(1.0) - def update_gauge(self, msg): if msg.total == 0 or msg.bufferlen == 0: @@ -431,19 +379,20 @@ self.frame.change_main_page("private") def on_show_ip_address(self, *args): - self.frame.np.ip_requested.add(self.user) - self.frame.np.queue.append(slskmessages.GetPeerAddress(self.user)) + self.frame.np.request_ip_address(self.user) def on_refresh(self, *args): + self.info_bar.set_visible(False) self.progressbar.set_fraction(0.0) - self.frame.local_user_info_request(self.user) + + self.frame.np.userinfo.request_user_info(self.user) def on_browse_user(self, *args): - self.frame.browse_user(self.user) + self.frame.np.userbrowse.browse_user(self.user) def on_add_to_list(self, *args): - self.frame.np.userlist.add_to_list(self.user) + self.frame.np.userlist.add_user(self.user) def on_ban_user(self, *args): self.frame.np.network_filter.ban_user(self.user) @@ -562,7 +511,8 @@ gc.collect() def on_close(self, *args): - del self.userinfos.users[self.user] + del self.userinfos.pages[self.user] + self.frame.np.userinfo.remove_user(self.user) self.userinfos.remove_page(self.Main) def on_close_all_tabs(self, *args): diff -Nru nicotine-3.1.0/pynicotine/gtkgui/userlist.py nicotine-3.1.0/pynicotine/gtkgui/userlist.py --- nicotine-3.1.0/pynicotine/gtkgui/userlist.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/gtkgui/userlist.py 2021-06-24 20:37:36.000000000 +0000 @@ -239,13 +239,13 @@ def on_add_user(self, widget, *args): - text = widget.get_text() + username = widget.get_text() - if not text: + if not username: return widget.set_text("") - self.add_to_list(text) + self.frame.np.userlist.add_user(username) def update_visuals(self): @@ -420,7 +420,7 @@ self.usersmodel.set_value(iterator, 1, GObject.Value(GObject.TYPE_OBJECT, self.frame.get_flag_image(country))) self.usersmodel.set_value(iterator, 14, "flag_" + country) - def add_to_list(self, user): + def add_user(self, user): if user in self.user_iterators: return @@ -450,7 +450,6 @@ ) self.save_user_list() - self.frame.np.userlist.add_user(user) for widget in self.buddies_combo_entries: widget.append_text(user) @@ -458,7 +457,7 @@ if config.sections["words"]["buddies"]: self.frame.update_completions() - def remove_from_list(self, user): + def remove_user(self, user): if user in self.user_iterators: self.usersmodel.remove(self.user_iterators[user]) @@ -559,7 +558,7 @@ ) def on_remove_user(self, *args): - self.remove_from_list(self.popup_menu.get_user()) + self.frame.np.userlist.remove_user(self.popup_menu.get_user()) def server_disconnect(self): diff -Nru nicotine-3.1.0/pynicotine/gtkgui/utils.py nicotine-3.1.0/pynicotine/gtkgui/utils.py --- nicotine-3.1.0/pynicotine/gtkgui/utils.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/gtkgui/utils.py 2021-06-24 20:37:36.000000000 +0000 @@ -33,7 +33,6 @@ from gi.repository import Gtk from gi.repository import Pango -from pynicotine import slskmessages from pynicotine.config import config from pynicotine.logfacility import log from pynicotine.utils import execute_command @@ -197,8 +196,7 @@ user, file = urllib.parse.unquote(url[7:]).split("/", 1) if file[-1] == "/": - NICOTINE.np.send_message_to_peer( - user, slskmessages.FolderContentsRequest(None, file[:-1].replace("/", "\\"))) + NICOTINE.np.transfers.get_folder(user, file[:-1].replace("/", "\\")) else: NICOTINE.np.transfers.get_file(user, file.replace("/", "\\"), "") diff -Nru nicotine-3.1.0/pynicotine/gtkgui/widgets/popupmenu.py nicotine-3.1.0/pynicotine/gtkgui/widgets/popupmenu.py --- nicotine-3.1.0/pynicotine/gtkgui/widgets/popupmenu.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/gtkgui/widgets/popupmenu.py 2021-06-24 20:37:36.000000000 +0000 @@ -467,14 +467,13 @@ self.frame.change_main_page("private") def on_show_ip_address(self, *args): - self.frame.np.ip_requested.add(self.user) - self.frame.np.queue.append(slskmessages.GetPeerAddress(self.user)) + self.frame.np.request_ip_address(self.user) def on_get_user_info(self, *args): - self.frame.local_user_info_request(self.user) + self.frame.np.userinfo.request_user_info(self.user) def on_browse_user(self, *args): - self.frame.browse_user(self.user) + self.frame.np.userbrowse.browse_user(self.user) def on_private_room_add_user(self, *args): room = args[-1] @@ -498,9 +497,9 @@ return if state.get_boolean(): - self.frame.userlist.add_to_list(self.user) + self.frame.np.userlist.add_user(self.user) else: - self.frame.userlist.remove_from_list(self.user) + self.frame.np.userlist.remove_user(self.user) action.set_state(state) @@ -568,14 +567,14 @@ try: days = int(days) - self.frame.np.queue.append(slskmessages.GivePrivileges(self.user, days)) + self.frame.np.request_give_privileges(self.user, days) except ValueError: self.on_give_privileges(error=_("Please enter a whole number!")) def on_give_privileges(self, *args, error=None): - self.frame.np.queue.append(slskmessages.CheckPrivileges()) + self.frame.np.request_check_privileges() if self.frame.np.privileges_left is None: days = _("Unknown") diff -Nru nicotine-3.1.0/pynicotine/gtkgui/widgets/textentry.py nicotine-3.1.0/pynicotine/gtkgui/widgets/textentry.py --- nicotine-3.1.0/pynicotine/gtkgui/widgets/textentry.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/gtkgui/widgets/textentry.py 2021-06-24 20:37:36.000000000 +0000 @@ -273,18 +273,17 @@ elif cmd in ("/w", "/whois", "/info"): if arg_self: - self.frame.local_user_info_request(arg_self) + self.frame.np.userinfo.request_user_info(arg_self) self.frame.change_main_page("userinfo") elif cmd in ("/b", "/browse"): if arg_self: - self.frame.browse_user(arg_self) + self.frame.np.userbrowse.browse_user(arg_self) self.frame.change_main_page("userbrowse") elif cmd == "/ip": if arg_self: - self.frame.np.ip_requested.add(arg_self) - self.frame.np.queue.append(slskmessages.GetPeerAddress(arg_self)) + self.frame.np.request_ip_address(arg_self) elif cmd == "/pm": if args: @@ -344,11 +343,11 @@ elif cmd in ("/ad", "/add", "/buddy"): if args: - self.frame.userlist.add_to_list(args) + self.frame.np.userlist.add_user(args) elif cmd in ("/rem", "/unbuddy"): if args: - self.frame.userlist.remove_from_list(args) + self.frame.np.userlist.remove_user(args) elif cmd == "/ban": if args: diff -Nru nicotine-3.1.0/pynicotine/gtkgui/widgets/trayicon.py nicotine-3.1.0/pynicotine/gtkgui/widgets/trayicon.py --- nicotine-3.1.0/pynicotine/gtkgui/widgets/trayicon.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/gtkgui/widgets/trayicon.py 2021-06-24 20:37:36.000000000 +0000 @@ -24,7 +24,6 @@ from gi.repository import GLib from gi.repository import Gtk -from pynicotine import slskmessages from pynicotine.config import config from pynicotine.gtkgui.widgets.dialogs import entry_dialog @@ -160,7 +159,7 @@ return if user: - self.frame.local_user_info_request(user) + self.frame.np.userinfo.request_user_info(user) def on_get_a_users_info(self, *args): @@ -182,8 +181,7 @@ return if user: - self.frame.np.ip_requested.add(user) - self.frame.np.queue.append(slskmessages.GetPeerAddress(user)) + self.frame.np.request_ip_address(user) def on_get_a_users_ip(self, *args): @@ -205,7 +203,7 @@ return if user: - self.frame.browse_user(user) + self.frame.np.userbrowse.browse_user(user) def on_get_a_users_shares(self, *args): diff -Nru nicotine-3.1.0/pynicotine/plugins/plugindebugger/__init__.py nicotine-3.1.0/pynicotine/plugins/plugindebugger/__init__.py --- nicotine-3.1.0/pynicotine/plugins/plugindebugger/__init__.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/plugins/plugindebugger/__init__.py 2021-06-24 20:37:36.000000000 +0000 @@ -29,6 +29,9 @@ def LoadNotification(self): # noqa self.log('LoadNotification') + def PublicRoomMessageNotification(self, room, user, line): # noqa + self.log('PublicRoomMessageNotification room=%s, user=%s, line=%s' % (room, user, line)) + def IncomingPrivateChatEvent(self, user, line): # noqa self.log('IncomingPrivateChatEvent user=%s, line=%s' % (user, line)) @@ -86,5 +89,11 @@ def UserLeaveChatroomNotification(self, room, user): # noqa self.log('UserLeaveChatroomNotification, room=%s, user=%s' % (room, user,)) + def UploadQueuedNotification(self, user, virtualfile, realfile): # noqa + self.log('UploadQueuedNotification, user=%s, virtualfile=%s, realfile=%s' % (user, virtualfile, realfile)) + + def UserStatsNotification(self, user, stats): # noqa + self.log('UserStatsNotification, user=%s, stats=%s' % (user, stats)) + def ShutdownNotification(self): # noqa self.log('ShutdownNotification') diff -Nru nicotine-3.1.0/pynicotine/pynicotine.py nicotine-3.1.0/pynicotine/pynicotine.py --- nicotine-3.1.0/pynicotine/pynicotine.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/pynicotine.py 2021-06-24 20:37:36.000000000 +0000 @@ -49,6 +49,8 @@ from pynicotine.search import Search from pynicotine.shares import Shares from pynicotine.transfers import Statistics +from pynicotine.userbrowse import UserBrowse +from pynicotine.userinfo import UserInfo from pynicotine.userlist import UserList from pynicotine.utils import unescape @@ -119,6 +121,8 @@ self.search = None self.transfers = None self.interests = None + self.userbrowse = None + self.userinfo = None self.userlist = None self.pluginhandler = None self.now_playing = None @@ -128,8 +132,6 @@ self.chatrooms = None self.privatechat = None - self.userinfo = None - self.userbrowse = None self.shutdown = False self.manualdisconnect = False @@ -288,17 +290,19 @@ script_dir = os.path.dirname(__file__) self.geoip = GeoIP(os.path.join(script_dir, "geoip/ipcountrydb.bin")) + self.notifications = Notifications(config, ui_callback) self.network_filter = NetworkFilter(self, config, self.users, self.queue, self.geoip) + self.now_playing = NowPlaying(config) self.statistics = Statistics(config, ui_callback) + self.shares = Shares(self, config, self.queue, ui_callback) self.search = Search(self, config, self.queue, self.shares.share_dbs, ui_callback) self.transfers = transfers.Transfers(self, config, self.peerconns, self.queue, self.users, - self.network_callback, self.ui_callback, self.pluginhandler) + self.network_callback, ui_callback) self.interests = Interests(self, config, self.queue, ui_callback) + self.userbrowse = UserBrowse(self, config, ui_callback) + self.userinfo = UserInfo(self, config, self.queue, ui_callback) self.userlist = UserList(self, config, self.queue, ui_callback) - self.pluginhandler = PluginHandler(self, config) - self.now_playing = NowPlaying(config) - self.notifications = Notifications(config, ui_callback) self.add_upnp_portmapping() @@ -307,6 +311,8 @@ self.protothread = slskproto.SlskProtoThread( self.network_callback, self.queue, self.bindip, interface, self.port, port_range, self.network_filter, self) + self.pluginhandler = PluginHandler(self, config) + connect_ready = not config.need_config() if not connect_ready: @@ -345,7 +351,7 @@ self.protothread.server_connect() if self.active_server_conn is not None: - return + return True # Clear any potential messages queued up to this point (should not happen) while self.queue: @@ -359,7 +365,7 @@ "network interface and restart Nicotine+." ) log.add_important_error(message, self.protothread.interface) - return + return False valid_listen_port = self.protothread.validate_listen_port() @@ -376,7 +382,7 @@ " most operating systems with the exception of Windows." ) log.add_important_error(message) - return + return False server = config.sections["server"]["server"] log.add(_("Connecting to %(host)s:%(port)s"), {'host': server[0], 'port': server[1]}) @@ -386,6 +392,8 @@ self.servertimer.cancel() self.servertimer = None + return True + def disconnect(self): self.manualdisconnect = True self.queue.append(slskmessages.ConnClose(self.active_server_conn)) @@ -745,11 +753,11 @@ for j in peerconn.msgs: - if j.__class__ is slskmessages.UserInfoRequest and self.userinfo is not None: - self.userinfo.show_user(peerconn.username, conn=conn, change_page=False) + if j.__class__ is slskmessages.UserInfoRequest: + self.userinfo.set_conn(peerconn.username, conn) - elif j.__class__ is slskmessages.GetSharedFileList and self.userbrowse is not None: - self.userbrowse.show_user(peerconn.username, conn=conn, change_page=False) + elif j.__class__ is slskmessages.GetSharedFileList: + self.userbrowse.set_conn(peerconn.username, conn) j.conn = conn self.queue.append(j) @@ -852,10 +860,10 @@ elif i.__class__ is slskmessages.QueueUpload: self.transfers.get_cant_connect_queue_file(conn.username, i.file) - elif i.__class__ is slskmessages.GetSharedFileList and self.userbrowse is not None: + elif i.__class__ is slskmessages.GetSharedFileList: self.userbrowse.show_connection_error(conn.username) - elif i.__class__ is slskmessages.UserInfoRequest and self.userinfo is not None: + elif i.__class__ is slskmessages.UserInfoRequest: self.userinfo.show_connection_error(conn.username) def cant_connect_to_peer(self, msg): @@ -935,7 +943,7 @@ self.transfers.server_disconnect() - self.privatechat = self.chatrooms = self.userinfo = self.userbrowse = None + self.privatechat = self.chatrooms = None self.pluginhandler.server_disconnect_notification(userchoice) if self.ui_callback: @@ -1088,6 +1096,22 @@ log.add_msg_contents(msg) self.transfers.transfer_timeout(msg) + def request_change_password(self, password): + self.queue.append(slskmessages.ChangePassword(password)) + + def request_check_privileges(self): + self.queue.append(slskmessages.CheckPrivileges()) + + def request_give_privileges(self, user, days): + self.queue.append(slskmessages.GivePrivileges(user, days)) + + def request_ip_address(self, username): + self.ip_requested.add(username) + self.queue.append(slskmessages.GetPeerAddress(username)) + + def request_set_status(self, status): + self.queue.append(slskmessages.SetStatus(status)) + def watch_user(self, user, force_update=False): """ Tell the server we want to be notified of status/stat updates for a user """ @@ -1178,14 +1202,14 @@ log.add(_("Listening on port %i"), msg.port) def peer_transfer(self, msg): - if self.userinfo is not None and msg.msg is slskmessages.UserInfoReply: + + if msg.msg is slskmessages.UserInfoReply: self.userinfo.update_gauge(msg) - if self.userbrowse is not None and msg.msg is slskmessages.SharedFileList: + if msg.msg is slskmessages.SharedFileList: self.userbrowse.update_gauge(msg) def check_download_queue(self, msg): - log.add_msg_contents(msg) self.transfers.check_download_queue() @@ -1193,17 +1217,14 @@ self.transfers.check_upload_queue() def file_download(self, msg): - log.add_msg_contents(msg) self.transfers.file_download(msg) def file_upload(self, msg): - log.add_msg_contents(msg) self.transfers.file_upload(msg) def file_error(self, msg): - log.add_msg_contents(msg) self.transfers.file_error(msg) @@ -1238,10 +1259,12 @@ if msg.ip_address is not None: self.ipaddress = msg.ip_address + self.userbrowse.server_login() + self.userinfo.server_login() self.userlist.server_login() if self.ui_callback: - (self.privatechat, self.chatrooms, self.userinfo, self.userbrowse) = self.ui_callback.server_login() + self.privatechat, self.chatrooms = self.ui_callback.server_login() if msg.banner: log.add(msg.banner) @@ -1313,18 +1336,14 @@ self.transfers.remove_from_privileged(msg.user) self.interests.get_user_status(msg) - self.userlist.get_user_status(msg) self.transfers.get_user_status(msg) + self.userbrowse.get_user_status(msg) + self.userinfo.get_user_status(msg) + self.userlist.get_user_status(msg) if self.privatechat is not None: self.privatechat.get_user_status(msg) - if self.userinfo is not None: - self.userinfo.get_user_status(msg) - - if self.userbrowse is not None: - self.userbrowse.get_user_status(msg) - if self.chatrooms is not None: self.chatrooms.get_user_status(msg) @@ -1409,14 +1428,12 @@ self.speed = msg.avgspeed self.interests.get_user_stats(msg) + self.userinfo.get_user_stats(msg) self.userlist.get_user_stats(msg) if self.chatrooms is not None: self.chatrooms.get_user_stats(msg) - if self.userinfo is not None: - self.userinfo.get_user_stats(msg) - stats = { 'avgspeed': msg.avgspeed, 'uploadnum': msg.uploadnum, @@ -1449,9 +1466,7 @@ """ Server code: 57 """ log.add_msg_contents(msg) - - if self.userinfo is not None: - self.userinfo.show_interests(msg) + self.userinfo.user_interests(msg) def room_list(self, msg): """ Server code: 64 """ @@ -1805,10 +1820,9 @@ conn = msg.conn.conn for i in self.peerconns: - if i.conn is conn and self.userbrowse is not None: + if i.conn is conn: if i.username != config.sections["server"]["login"]: - indeterminate_progress = change_page = False - self.userbrowse.show_user(i.username, None, msg, indeterminate_progress, change_page) + self.userbrowse.shared_file_list(i.username, msg) break def file_search_result(self, msg): @@ -1927,11 +1941,10 @@ conn = msg.conn.conn for i in self.peerconns: - if i.conn is conn and self.userinfo is not None: + if i.conn is conn: # probably impossible to do this if i.username != config.sections["server"]["login"]: - indeterminate_progress = change_page = False - self.userinfo.show_user(i.username, None, msg, indeterminate_progress, change_page) + self.userinfo.user_info_reply(i.username, msg) break def p_message_user(self, msg): diff -Nru nicotine-3.1.0/pynicotine/search.py nicotine-3.1.0/pynicotine/search.py --- nicotine-3.1.0/pynicotine/search.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/search.py 2021-06-24 20:37:36.000000000 +0000 @@ -44,6 +44,23 @@ if hasattr(ui_callback, "searches"): self.ui_callback = ui_callback.searches + def request_folder_download(self, user, folder, visible_files): + + # First queue the visible search results + if self.config.sections["transfers"]["reverseorder"]: + visible_files.sort(key=lambda x: x[1], reverse=True) + + for file in visible_files: + user, fullpath, destination, size, bitrate, length = file + + self.np.transfers.get_file( + user, fullpath, destination, + size=size, bitrate=bitrate, length=length, checkduplicate=True + ) + + # Ask for the rest of the files in the folder + self.np.transfers.get_folder(user, folder) + """ Outgoing search requests """ def process_search_term(self, text, mode, room, user): diff -Nru nicotine-3.1.0/pynicotine/transfers.py nicotine-3.1.0/pynicotine/transfers.py --- nicotine-3.1.0/pynicotine/transfers.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/transfers.py 2021-06-24 20:37:36.000000000 +0000 @@ -109,7 +109,7 @@ """ This is the transfers manager """ def __init__(self, eventprocessor, config, peerconns, queue, users, - network_callback, ui_callback=None, pluginhandler=None): + network_callback, ui_callback=None): self.eventprocessor = eventprocessor self.config = config @@ -128,7 +128,6 @@ self.users = users self.network_callback = network_callback - self.pluginhandler = pluginhandler self.downloadsview = None self.uploadsview = None @@ -693,9 +692,7 @@ if self.uploadsview: self.uploadsview.update(newupload) - if self.pluginhandler: - self.pluginhandler.upload_queued_notification(user, msg.file, realpath) - + self.eventprocessor.pluginhandler.upload_queued_notification(user, msg.file, realpath) self.check_upload_queue() else: @@ -868,8 +865,7 @@ return slskmessages.TransferResponse(None, 0, reason=limitmsg, req=msg.req) # All checks passed, user can queue file! - if self.pluginhandler: - self.pluginhandler.upload_queued_notification(user, msg.file, realpath) + self.eventprocessor.pluginhandler.upload_queued_notification(user, msg.file, realpath) # Is user already downloading/negotiating a download? already_downloading = False @@ -1635,6 +1631,9 @@ self.transfer_file(0, user, filename, path, transfer, size, bitrate, length) + def get_folder(self, user, folder): + self.eventprocessor.send_message_to_peer(user, slskmessages.FolderContentsRequest(None, folder)) + def push_file(self, user, filename, path="", transfer=None, size=None, bitrate=None, length=None, locally_queued=False): self.transfer_file(1, user, filename, path, transfer, size, bitrate, length, locally_queued) @@ -2407,6 +2406,9 @@ self.ui_callback = ui_callback self.session_stats = {} + if hasattr(ui_callback, "statistics"): + self.ui_callback = ui_callback.statistics + for stat_id in self.config.defaults["statistics"]: self.session_stats[stat_id] = 0 diff -Nru nicotine-3.1.0/pynicotine/userbrowse.py nicotine-3.1.0/pynicotine/userbrowse.py --- nicotine-3.1.0/pynicotine/userbrowse.py 1970-01-01 00:00:00.000000000 +0000 +++ nicotine-3.1.0/pynicotine/userbrowse.py 2021-06-24 20:37:36.000000000 +0000 @@ -0,0 +1,171 @@ +# COPYRIGHT (C) 2020-2021 Nicotine+ Team +# +# GNU GENERAL PUBLIC LICENSE +# Version 3, 29 June 2007 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import threading + +from pynicotine import slskmessages +from pynicotine.logfacility import log +from pynicotine.utils import RestrictedUnpickler + + +class UserBrowse: + + def __init__(self, np, config, ui_callback=None): + + self.np = np + self.config = config + self.users = set() + self.ui_callback = None + + if hasattr(ui_callback, "userbrowse"): + self.ui_callback = ui_callback.userbrowse + + def server_login(self): + for user in self.users: + self.np.watch_user(user) # Get notified of user status + + def add_user(self, user): + self.np.watch_user(user, force_update=True) + self.users.add(user) + + def remove_user(self, user): + self.users.remove(user) + + def parse_local_shares(self, username, msg): + """ Parse a local shares list and show it in the UI """ + + built = msg.make_network_message() + msg.parse_network_message(built) + + self.shared_file_list(username, msg) + + def browse_local_public_shares(self): + """ Browse your own public shares """ + + login = self.config.sections["server"]["login"] + + # Deactivate if we only share with buddies + if self.config.sections["transfers"]["friendsonly"]: + msg = slskmessages.SharedFileList(None, {}) + else: + msg = self.np.shares.get_compressed_shares_message("normal") + + thread = threading.Thread(target=self.parse_local_shares, args=(login, msg)) + thread.name = "LocalShareParser" + thread.daemon = True + thread.start() + + if self.ui_callback: + self.ui_callback.show_user(login, local_shares_type="normal", indeterminate_progress=True) + + def browse_local_buddy_shares(self): + """ Browse your own buddy shares """ + + login = self.config.sections["server"]["login"] + + # Show public shares if we don't have specific shares for buddies + if not self.config.sections["transfers"]["enablebuddyshares"]: + msg = self.np.shares.get_compressed_shares_message("normal") + else: + msg = self.np.shares.get_compressed_shares_message("buddy") + + thread = threading.Thread(target=self.parse_local_shares, args=(login, msg)) + thread.name = "LocalBuddyShareParser" + thread.daemon = True + thread.start() + + if self.ui_callback: + self.ui_callback.show_user(login, local_shares_type="buddy", indeterminate_progress=True) + + def browse_user(self, username, folder=None, local_shares_type=None, new_request=False): + """ Browse a user's shares """ + + if not username: + return + + if username == self.config.sections["server"]["login"]: + if local_shares_type == "normal" or not self.config.sections["transfers"]["enablebuddyshares"]: + self.browse_local_public_shares() + return + + self.browse_local_buddy_shares() + return + + if username not in self.users or new_request: + self.np.send_message_to_peer(username, slskmessages.GetSharedFileList(None)) + + if self.ui_callback: + self.ui_callback.show_user(username, folder=folder) + + @staticmethod + def get_shares_list_from_disk(filename): + + try: + try: + # Try legacy format first + import bz2 + + with bz2.BZ2File(filename) as file_handle: + shares_list = RestrictedUnpickler(file_handle, encoding='utf-8').load() + + except Exception: + # Try new format + + with open(filename, encoding="utf-8") as file_handle: + import json + shares_list = json.load(file_handle) + + if not isinstance(shares_list, (list, dict)): + raise TypeError("Bad data in file %(sharesdb)s" % {'sharesdb': filename}) + + return shares_list + + except Exception as msg: + log.add(_("Loading Shares from disk failed: %(error)s"), {'error': msg}) + + return None + + def load_local_shares_list(self, username, shares_list): + + if self.ui_callback: + self.ui_callback.show_user(username) + + msg = slskmessages.GetSharedFileList(None) + msg.list = shares_list + + self.shared_file_list(username, msg) + + def show_connection_error(self, username): + if self.ui_callback: + self.ui_callback.show_connection_error(username) + + def set_conn(self, username, conn): + if self.ui_callback: + self.ui_callback.set_conn(username, conn) + + def get_user_status(self, msg): + if self.ui_callback: + self.ui_callback.get_user_status(msg) + + def shared_file_list(self, user, msg): + if self.ui_callback: + self.ui_callback.shared_file_list(user, msg) + + def update_gauge(self, msg): + if self.ui_callback: + self.ui_callback.update_gauge(msg) diff -Nru nicotine-3.1.0/pynicotine/userinfo.py nicotine-3.1.0/pynicotine/userinfo.py --- nicotine-3.1.0/pynicotine/userinfo.py 1970-01-01 00:00:00.000000000 +0000 +++ nicotine-3.1.0/pynicotine/userinfo.py 2021-06-24 20:37:36.000000000 +0000 @@ -0,0 +1,123 @@ +# COPYRIGHT (C) 2021 Nicotine+ Team +# +# GNU GENERAL PUBLIC LICENSE +# Version 3, 29 June 2007 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from pynicotine import slskmessages +from pynicotine.utils import unescape + + +class UserInfo: + + def __init__(self, np, config, queue, ui_callback=None): + + self.np = np + self.config = config + self.queue = queue + self.users = set() + self.ui_callback = None + + if hasattr(ui_callback, "userinfo"): + self.ui_callback = ui_callback.userinfo + + def server_login(self): + + # Get notified of user status + for user in self.users: + self.np.watch_user(user) + + def add_user(self, user): + + # Request user status, speed and number of shared files + self.np.watch_user(user, force_update=True) + + # Request user interests + self.queue.append(slskmessages.UserInterests(user)) + + self.users.add(user) + + def remove_user(self, user): + self.users.remove(user) + + def request_local_user_info(self, user, msg): + + try: + msg.has_pic = False + msg.pic = None + + userpic = self.config.sections["userinfo"]["pic"] + + if userpic and os.path.exists(userpic): + with open(userpic, 'rb') as file_handle: + msg.pic = file_handle.read() + + msg.has_pic = True + + except OSError: + msg.pic = None + + msg.descr = unescape(self.config.sections["userinfo"]["descr"]) + msg.totalupl = self.np.transfers.get_total_uploads_allowed() + msg.queuesize = self.np.transfers.get_upload_queue_size() + msg.slotsavail = self.np.transfers.allow_new_uploads() + msg.uploadallowed = self.config.sections["transfers"]["remotedownloads"] + + if msg.uploadallowed: + msg.uploadallowed = self.config.sections["transfers"]["uploadallowed"] + + self.user_info_reply(user, msg) + + def request_user_info(self, user): + + msg = slskmessages.UserInfoRequest(None) + + if self.ui_callback: + self.ui_callback.show_user(user) + + if user == self.config.sections["server"]["login"]: + self.request_local_user_info(user, msg) + else: + self.np.send_message_to_peer(user, msg) + + def set_conn(self, username, conn): + if self.ui_callback: + self.ui_callback.set_conn(username, conn) + + def show_connection_error(self, username): + if self.ui_callback: + self.ui_callback.show_connection_error(username) + + def get_user_stats(self, msg): + if self.ui_callback: + self.ui_callback.get_user_stats(msg) + + def get_user_status(self, msg): + if self.ui_callback: + self.ui_callback.get_user_status(msg) + + def update_gauge(self, msg): + if self.ui_callback: + self.ui_callback.update_gauge(msg) + + def user_info_reply(self, user, msg): + if self.ui_callback: + self.ui_callback.user_info_reply(user, msg) + + def user_interests(self, msg): + if self.ui_callback: + self.ui_callback.user_interests(msg) diff -Nru nicotine-3.1.0/pynicotine/userlist.py nicotine-3.1.0/pynicotine/userlist.py --- nicotine-3.1.0/pynicotine/userlist.py 2021-06-23 20:35:17.000000000 +0000 +++ nicotine-3.1.0/pynicotine/userlist.py 2021-06-24 20:37:36.000000000 +0000 @@ -38,18 +38,6 @@ user = str(row[0]) self.np.watch_user(user) - def get_user_status(self, msg): - if self.ui_callback: - self.ui_callback.get_user_status(msg) - - def set_user_country(self, user, country): - if self.ui_callback: - self.ui_callback.set_user_country(user, country) - - def get_user_stats(self, msg): - if self.ui_callback: - self.ui_callback.get_user_stats(msg) - def add_user(self, user): # Request user status, speed and number of shared files @@ -58,6 +46,25 @@ # Request user's IP address, so we can get the country self.queue.append(slskmessages.GetPeerAddress(user)) + if self.ui_callback: + self.ui_callback.add_user(user) + + def remove_user(self, user): + if self.ui_callback: + self.ui_callback.remove_user(user) + def save_user_list(self, user_list): self.config.sections["server"]["userlist"] = user_list self.config.write_configuration() + + def get_user_status(self, msg): + if self.ui_callback: + self.ui_callback.get_user_status(msg) + + def set_user_country(self, user, country): + if self.ui_callback: + self.ui_callback.set_user_country(user, country) + + def get_user_stats(self, msg): + if self.ui_callback: + self.ui_callback.get_user_stats(msg)