def setUp(self):

        config.data_dir = os.path.dirname(os.path.realpath(__file__))
        config.filename = os.path.join(config.data_dir, "temp_config")

        config.load_config()

        self.transfers = Transfers(Mock(), config, deque(), Mock())
        self.transfers.privileged_users = {"puser1", "puser2"}
    def setUp(self):

        config.data_dir = os.path.dirname(os.path.realpath(__file__))
        config.filename = os.path.join(config.data_dir, "temp_config")

        config.load_config()

        self.transfers = Transfers(Mock(), config, deque(), {}, Mock())
        self.transfers.server_login()
    def setUp(self):

        config.data_dir = os.path.dirname(os.path.realpath(__file__))
        config.filename = os.path.join(config.data_dir, "temp_config")

        config.load_config()

        self.transfers = Transfers(MagicMock(), config, deque(), Mock())
        self.transfers.init_transfers()
        self.transfers.server_login()
        self.transfers.allow_saving_transfers = False
class TransfersTest(unittest.TestCase):
    def setUp(self):

        config.data_dir = os.path.dirname(os.path.realpath(__file__))
        config.filename = os.path.join(config.data_dir, "temp_config")

        config.load_config()

        self.transfers = Transfers(Mock(), config, deque(), {}, Mock())
        self.transfers.server_login()

    def test_load_downloads(self):
        """ Test loading a downloads.json file """

        self.assertEqual(len(self.transfers.queue), 2)
        self.assertEqual(len(self.transfers.downloads), 13)

        transfer = self.transfers.downloads[0]

        self.assertEqual(transfer.user, "user13")
        self.assertEqual(transfer.filename, "Downloaded\\Song13.mp3")
        self.assertEqual(transfer.status, "Getting status")
        self.assertIsNone(transfer.size)
        self.assertIsNone(transfer.currentbytes)
        self.assertIsNone(transfer.bitrate)
        self.assertIsNone(transfer.length)

        transfer = self.transfers.downloads[12]

        self.assertEqual(transfer.user, "user1")
        self.assertEqual(transfer.filename, "Downloaded\\Song1.mp3")
        self.assertEqual(transfer.status, "Aborted")
        self.assertEqual(transfer.size, 10093741)
        self.assertEqual(transfer.currentbytes, 5000)
        self.assertEqual(transfer.bitrate, "320")
        self.assertEqual(transfer.length, "4:12")

    def test_save_downloads(self):
        """ Verify that the order of the download list at the end of the sesson
        is identical to the one we loaded. Ignore transfer 13, since its missing
        properties will be added at the end of the session. """

        self.transfers.abort_transfers()

        old_transfers = self.transfers.load_current_transfers_format(
            self.transfers.downloads_file_name)[:12]

        saved_transfers = self.transfers.get_downloads()[:12]
        self.assertEqual(old_transfers, saved_transfers)

    def test_load_uploads(self):
        """ Test loading a uploads.json file """

        # Only finished uploads are loaded, other types should never be stored
        self.assertEqual(len(self.transfers.uploads), 3)
        self.assertEqual(len(self.transfers.queue), 2)

        transfer = self.transfers.uploads[0]

        self.assertEqual(transfer.user, "user5")
        self.assertEqual(transfer.filename, "Junk\\Song5.mp3")
        self.assertEqual(transfer.status, "Finished")
        self.assertEqual(transfer.size, 11733776)
        self.assertEqual(transfer.currentbytes, 11733776)
        self.assertEqual(transfer.bitrate, "319")
        self.assertEqual(transfer.length, "4:53")

        transfer = self.transfers.uploads[2]

        self.assertEqual(transfer.user, "user3")
        self.assertEqual(transfer.filename, "Junk\\Song3.flac")
        self.assertEqual(transfer.status, "Finished")
        self.assertEqual(transfer.size, 27231044)
        self.assertEqual(transfer.currentbytes, 27231044)
        self.assertEqual(transfer.bitrate, "792")
        self.assertEqual(transfer.length, "4:28")

    def test_queue_download(self):
        """ Verify that new downloads are prepended to the list """

        self.transfers.get_file("newuser", "Hello\\Path\\File.mp3", "")
        transfer = self.transfers.downloads[0]

        self.assertEqual(transfer.user, "newuser")
        self.assertEqual(transfer.filename, "Hello\\Path\\File.mp3")
        self.assertEqual(transfer.path, "")

    def test_push_upload(self):
        """ Verify that new uploads are prepended to the list """

        self.transfers.push_file("newuser2", "Hello\\Upload\\File.mp3",
                                 "/home/test")
        self.transfers.push_file("newuser99", "Home\\None.mp3", "")
        transfer = self.transfers.uploads[1]

        self.assertEqual(transfer.user, "newuser2")
        self.assertEqual(transfer.filename, "Hello\\Upload\\File.mp3")
        self.assertEqual(transfer.path, "/home/test")
class GetUploadCandidateTest(unittest.TestCase):
    def setUp(self):

        config.data_dir = os.path.dirname(os.path.realpath(__file__))
        config.filename = os.path.join(config.data_dir, "temp_config")

        config.load_config()

        self.transfers = Transfers(Mock(), config, deque(), Mock())
        self.transfers.privileged_users = {"puser1", "puser2"}

    def add_transfers(self, users, status):

        transfer_list = []

        for user in users:
            filename = "%s/%i" % (user, len(self.transfers.uploads))
            transfer = Transfer(user=user, path=filename, status=status)

            transfer_list.append(transfer)
            self.transfers.append_upload(user, filename, transfer)

        return transfer_list

    def set_finished(self, transfer):

        transfer.status = "Finished"
        self.transfers.update_user_counter(transfer.user)
        self.transfers.uploads.remove(transfer)

    def consume_transfers(self, queued, in_progress, clear_first=False):
        """Call self.transfers.get_upload_candidate until no uploads are left.

        Transfers should be added to self.transfers in the desired starting
        states already.

        One in progress upload will be removed each time get_upload_candidate
        is called.

        `queued` and `in_progress` should contain the transfers in those states.

        `clear_first` indicates whether the upload candidate should be
        generated after the in progress upload is marked finished or before.

        All candidates received are returned in a list.
        """

        candidates = []
        none_count = 0  # prevent infinite loop in case of bug or bad test setup
        num_allowed_nones = 2

        while len(
                self.transfers.uploads) > 0 and none_count < num_allowed_nones:

            # "finish" one in progress transfer, if any
            if clear_first and in_progress:
                self.set_finished(in_progress.pop(0))

            candidate = self.transfers.get_upload_candidate()

            if not clear_first and in_progress:
                self.set_finished(in_progress.pop(0))

            if not candidate:
                none_count += 1
                print("none_count: %i" % none_count)

                if queued:
                    print(
                        "Found no transfer candidates out of %i queued uploads"
                        % len(queued))

                candidates.append(None)
                continue

            none_count = 0
            print("Transfer candidate: %s" % candidate.user)

            candidates.append(candidate)
            queued.remove(candidate)
            in_progress.append(candidate)

            candidate.status = "Getting status"

        return candidates

    def base_test(self,
                  queued,
                  in_progress,
                  expected,
                  round_robin=False,
                  clear_first=False):

        config.sections["transfers"]["fifoqueue"] = not round_robin

        queued_transfers = self.add_transfers(queued, status="Queued")
        in_progress_transfers = self.add_transfers(in_progress,
                                                   status="Getting status")

        candidates = self.consume_transfers(queued_transfers,
                                            in_progress_transfers,
                                            clear_first=clear_first)
        users = [
            transfer.user if transfer else None for transfer in candidates
        ]

        # `expected` should contain `None` in cases where there aren't
        # expected to be any queued users without existing in progress uploads
        self.assertEqual(users, expected)

    def test_round_robin_basic(self):

        self.base_test(
            queued=["user1", "user1", "user2", "user2", "user3", "user3"],
            in_progress=[],
            expected=[
                "user1", "user2", "user3", "user1", "user2", "user3", None
            ],
            round_robin=True)

    def test_round_robin_no_contention(self):

        self.base_test(
            queued=["user1", "user1", "user2", "user2", "user3", "user3"],
            in_progress=[],
            expected=[
                "user1", "user2", "user3", "user1", "user2", "user3", None
            ],
            round_robin=True,
            clear_first=True)

    def test_round_robin_one_user(self):

        self.base_test(queued=["user1", "user1"],
                       in_progress=[],
                       expected=["user1", None, "user1", None],
                       round_robin=True)

    def test_round_robin_returning_user(self):

        self.base_test(queued=[
            "user1", "user1", "user2", "user2", "user2", "user3", "user3",
            "user3", "user1", "user1"
        ],
                       in_progress=[],
                       expected=[
                           "user1", "user2", "user3", "user1", "user2",
                           "user3", "user1", "user2", "user3", "user1", None
                       ],
                       round_robin=True)

    def test_round_robin_in_progress(self):

        self.base_test(queued=["user1", "user1", "user2", "user2"],
                       in_progress=["user1"],
                       expected=["user2", "user1", "user2", "user1", None],
                       round_robin=True)

    def test_round_robin_privileged(self):

        self.base_test(
            queued=["user1", "user2", "puser1", "puser1", "puser2"],
            in_progress=[],
            expected=["puser1", "puser2", "puser1", "user1", "user2", None],
            round_robin=True)

    def test_fifo_basic(self):

        self.base_test(
            queued=["user1", "user1", "user2", "user2", "user3", "user3"],
            in_progress=[],
            expected=[
                "user1", "user2", "user1", "user2", "user3", None, "user3",
                None
            ])

    def test_fifo_robin_no_contention(self):

        self.base_test(
            queued=["user1", "user1", "user2", "user2", "user3", "user3"],
            in_progress=[],
            expected=[
                "user1", "user1", "user2", "user2", "user3", "user3", None
            ],
            clear_first=True)

    def test_fifo_one_user(self):

        self.base_test(queued=["user1", "user1"],
                       in_progress=[],
                       expected=["user1", None, "user1", None])

    def test_fifo_returning_user(self):

        self.base_test(queued=[
            "user1", "user1", "user2", "user2", "user2", "user3", "user3",
            "user3", "user1", "user1"
        ],
                       in_progress=[],
                       expected=[
                           "user1", "user2", "user1", "user2", "user3",
                           "user2", "user3", "user1", "user3", "user1", None
                       ])

    def test_fifo_in_progress(self):

        self.base_test(queued=["user1", "user1", "user2", "user2"],
                       in_progress=["user1"],
                       expected=["user2", "user1", "user2", "user1", None])

    def test_fifo_privileged(self):

        self.base_test(
            queued=["user1", "user2", "puser1", "puser1", "puser2"],
            in_progress=[],
            expected=["puser1", "puser2", "puser1", "user1", "user2", None])
class NicotineCore:
    """ NicotineCore contains handlers for various messages from the networking thread.
    This class links the networking thread and user interface. """
    def __init__(self, bindip, port):

        self.ui_callback = None
        self.network_callback = None
        self.network_filter = None
        self.statistics = None
        self.shares = None
        self.search = None
        self.transfers = None
        self.interests = None
        self.userbrowse = None
        self.userinfo = None
        self.userlist = None
        self.privatechats = None
        self.chatrooms = None
        self.pluginhandler = None
        self.now_playing = None
        self.protothread = None
        self.upnp = None
        self.geoip = None
        self.notifications = None

        # Handle Ctrl+C and "kill" exit gracefully
        for signal_type in (signal.SIGINT, signal.SIGTERM):
            signal.signal(signal_type, self.quit)

        self.bindip = bindip
        self.port = port

        self.shutdown = False
        self.away = False
        self.logged_in = False
        self.login_username = None  # Only present while logged in
        self.user_ip_address = None
        self.privileges_left = None
        self.ban_message = "You are banned from downloading my shared files. Ban message: \"%s\""

        self.queue = deque()
        self.user_statuses = {}
        self.watched_users = set()
        self.ip_requested = set()
        self.requested_info_times = {}
        self.requested_share_times = {}

        # Callback handlers for messages
        self.events = {
            slskmessages.ServerDisconnect: self.server_disconnect,
            slskmessages.Login: self.login,
            slskmessages.ChangePassword: self.change_password,
            slskmessages.MessageUser: self.message_user,
            slskmessages.PMessageUser: self.p_message_user,
            slskmessages.ExactFileSearch: self.dummy_message,
            slskmessages.RoomAdded: self.dummy_message,
            slskmessages.RoomRemoved: self.dummy_message,
            slskmessages.UserJoinedRoom: self.user_joined_room,
            slskmessages.SayChatroom: self.say_chat_room,
            slskmessages.JoinRoom: self.join_room,
            slskmessages.UserLeftRoom: self.user_left_room,
            slskmessages.CantCreateRoom: self.dummy_message,
            slskmessages.QueuedDownloads: self.dummy_message,
            slskmessages.GetPeerAddress: self.get_peer_address,
            slskmessages.UserInfoReply: self.user_info_reply,
            slskmessages.UserInfoRequest: self.user_info_request,
            slskmessages.PierceFireWall: self.dummy_message,
            slskmessages.ConnectToPeer: self.connect_to_peer,
            slskmessages.CantConnectToPeer: self.dummy_message,
            slskmessages.MessageProgress: self.message_progress,
            slskmessages.SharedFileList: self.shared_file_list,
            slskmessages.GetSharedFileList: self.get_shared_file_list,
            slskmessages.FileSearchRequest: self.dummy_message,
            slskmessages.FileSearchResult: self.file_search_result,
            slskmessages.GetUserStatus: self.get_user_status,
            slskmessages.GetUserStats: self.get_user_stats,
            slskmessages.Relogged: self.relogged,
            slskmessages.PeerInit: self.dummy_message,
            slskmessages.CheckDownloadQueue: self.check_download_queue,
            slskmessages.CheckUploadQueue: self.check_upload_queue,
            slskmessages.DownloadFile: self.file_download,
            slskmessages.UploadFile: self.file_upload,
            slskmessages.FileDownloadInit: self.file_download_init,
            slskmessages.FileUploadInit: self.file_upload_init,
            slskmessages.TransferRequest: self.transfer_request,
            slskmessages.TransferResponse: self.transfer_response,
            slskmessages.QueueUpload: self.queue_upload,
            slskmessages.UploadDenied: self.upload_denied,
            slskmessages.UploadFailed: self.upload_failed,
            slskmessages.PlaceInQueue: self.place_in_queue,
            slskmessages.DownloadFileError: self.download_file_error,
            slskmessages.UploadFileError: self.upload_file_error,
            slskmessages.DownloadConnClose: self.download_conn_close,
            slskmessages.UploadConnClose: self.upload_conn_close,
            slskmessages.FolderContentsResponse: self.folder_contents_response,
            slskmessages.FolderContentsRequest: self.folder_contents_request,
            slskmessages.RoomList: self.room_list,
            slskmessages.LeaveRoom: self.leave_room,
            slskmessages.GlobalUserList: self.dummy_message,
            slskmessages.AddUser: self.add_user,
            slskmessages.PrivilegedUsers: self.privileged_users,
            slskmessages.AddToPrivileged: self.add_to_privileged,
            slskmessages.CheckPrivileges: self.check_privileges,
            slskmessages.ServerPing: self.dummy_message,
            slskmessages.ParentMinSpeed: self.dummy_message,
            slskmessages.ParentSpeedRatio: self.dummy_message,
            slskmessages.ParentInactivityTimeout: self.dummy_message,
            slskmessages.SearchInactivityTimeout: self.dummy_message,
            slskmessages.MinParentsInCache: self.dummy_message,
            slskmessages.WishlistInterval: self.wishlist_interval,
            slskmessages.DistribAliveInterval: self.dummy_message,
            slskmessages.DistribChildDepth: self.dummy_message,
            slskmessages.DistribBranchLevel: self.dummy_message,
            slskmessages.DistribBranchRoot: self.dummy_message,
            slskmessages.AdminMessage: self.admin_message,
            slskmessages.TunneledMessage: self.dummy_message,
            slskmessages.PlaceholdUpload: self.dummy_message,
            slskmessages.PlaceInQueueRequest: self.place_in_queue_request,
            slskmessages.UploadQueueNotification: self.dummy_message,
            slskmessages.FileSearch: self.search_request,
            slskmessages.RoomSearch: self.search_request,
            slskmessages.UserSearch: self.search_request,
            slskmessages.RelatedSearch: self.dummy_message,
            slskmessages.PossibleParents: self.dummy_message,
            slskmessages.DistribAlive: self.dummy_message,
            slskmessages.DistribSearch: self.distrib_search,
            slskmessages.ResetDistributed: self.dummy_message,
            slskmessages.ServerTimeout: self.server_timeout,
            slskmessages.TransferTimeout: self.transfer_timeout,
            slskmessages.SetConnectionStats: self.set_connection_stats,
            slskmessages.GlobalRecommendations: self.global_recommendations,
            slskmessages.Recommendations: self.recommendations,
            slskmessages.ItemRecommendations: self.item_recommendations,
            slskmessages.SimilarUsers: self.similar_users,
            slskmessages.ItemSimilarUsers: self.item_similar_users,
            slskmessages.UserInterests: self.user_interests,
            slskmessages.RoomTickerState: self.room_ticker_state,
            slskmessages.RoomTickerAdd: self.room_ticker_add,
            slskmessages.RoomTickerRemove: self.room_ticker_remove,
            slskmessages.UserPrivileged: self.dummy_message,
            slskmessages.AckNotifyPrivileges: self.dummy_message,
            slskmessages.NotifyPrivileges: self.dummy_message,
            slskmessages.PrivateRoomUsers: self.private_room_users,
            slskmessages.PrivateRoomOwned: self.private_room_owned,
            slskmessages.PrivateRoomAddUser: self.private_room_add_user,
            slskmessages.PrivateRoomRemoveUser: self.private_room_remove_user,
            slskmessages.PrivateRoomAdded: self.private_room_added,
            slskmessages.PrivateRoomRemoved: self.private_room_removed,
            slskmessages.PrivateRoomDisown: self.private_room_disown,
            slskmessages.PrivateRoomToggle: self.private_room_toggle,
            slskmessages.PrivateRoomSomething: self.dummy_message,
            slskmessages.PrivateRoomOperatorAdded:
            self.private_room_operator_added,
            slskmessages.PrivateRoomOperatorRemoved:
            self.private_room_operator_removed,
            slskmessages.PrivateRoomAddOperator:
            self.private_room_add_operator,
            slskmessages.PrivateRoomRemoveOperator:
            self.private_room_remove_operator,
            slskmessages.PublicRoomMessage: self.public_room_message,
            slskmessages.ShowConnectionErrorMessage:
            self.show_connection_error_message,
            slskmessages.UnknownPeerMessage: self.ignore
        }

    def start(self, ui_callback=None, network_callback=None):

        self.ui_callback = ui_callback
        self.network_callback = network_callback if network_callback else self.network_event
        script_dir = os.path.dirname(__file__)

        log.add(_("Loading %(program)s %(version)s"), {
            "program": "Python",
            "version": config.python_version
        })
        log.add_debug("Using %(program)s executable: %(exe)s", {
            "program": "Python",
            "exe": str(sys.executable)
        })
        log.add_debug("Using %(program)s executable: %(exe)s", {
            "program": config.application_name,
            "exe": script_dir
        })
        log.add(_("Loading %(program)s %(version)s"), {
            "program": config.application_name,
            "version": config.version
        })

        self.geoip = GeoIP(os.path.join(script_dir, "geoip/ipcountrydb.bin"))
        self.notifications = Notifications(config, ui_callback)
        self.network_filter = NetworkFilter(self, config, self.queue,
                                            self.geoip)
        self.now_playing = NowPlaying(config)
        self.statistics = Statistics(config, ui_callback)

        self.shares = Shares(self, config, self.queue, self.network_callback,
                             ui_callback)
        self.search = Search(self, config, self.queue, self.shares.share_dbs,
                             self.geoip, ui_callback)
        self.transfers = Transfers(self, config, self.queue,
                                   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.privatechats = PrivateChats(self, config, self.queue, ui_callback)
        self.chatrooms = ChatRooms(self, config, self.queue, ui_callback)

        self.transfers.init_transfers()
        self.privatechats.load_users()

        port_range = config.sections["server"]["portrange"]
        interface = config.sections["server"]["interface"]
        self.protothread = slskproto.SlskProtoThread(self.network_callback,
                                                     self.queue, self.bindip,
                                                     interface, self.port,
                                                     port_range, self)
        self.upnp = UPnP(self, config)
        self.pluginhandler = PluginHandler(self, config)

        self.set_connection_stats(slskmessages.SetConnectionStats())
        connect_ready = not config.need_config()

        if not connect_ready:
            log.add(
                _("You need to specify a username and password before connecting…"
                  ))

        return connect_ready

    def quit(self, signal_type=None, _frame=None):

        log.add(
            _("Quitting %(program)s %(version)s, %(status)s…"), {
                "program":
                config.application_name,
                "version":
                config.version,
                "status":
                _("terminating")
                if signal_type == signal.SIGTERM else _("application closing")
            })

        # Indicate that a shutdown has started, to prevent UI callbacks from networking thread
        self.shutdown = True

        if self.pluginhandler:
            self.pluginhandler.quit()

        # Shut down networking thread
        if self.protothread:
            if not self.protothread.server_disconnected:
                self.protothread.manual_server_disconnect = True
                self.server_disconnect()

            self.protothread.abort()

        # Save download/upload list to file
        if self.transfers:
            self.transfers.quit()

        # Closing up all shelves db
        if self.shares:
            self.shares.quit()

        if self.ui_callback:
            self.ui_callback.quit()

        config.write_configuration()

        log.add(
            _("Quit %(program)s %(version)s, %(status)s!"), {
                "program":
                config.application_name,
                "version":
                config.version,
                "status":
                _("terminated") if signal_type == signal.SIGTERM else _("done")
            })

    def connect(self):

        if not self.protothread.server_disconnected:
            return True

        valid_network_interface = self.protothread.validate_network_interface()

        if not valid_network_interface:
            message = _(
                "The network interface you specified, '%s', does not exist. Change or remove the specified "
                "network interface and restart Nicotine+.")
            log.add_important_error(message, self.protothread.interface)
            return False

        valid_listen_port = self.protothread.validate_listen_port()

        if not valid_listen_port:
            message = _(
                "The range you specified for client connection ports was "
                "{}-{}, but none of these were usable. Increase and/or ".
                format(self.protothread.portrange[0],
                       self.protothread.portrange[1]) +
                "move the range and restart Nicotine+.")
            if self.protothread.portrange[0] < 1024:
                message += "\n\n" + _(
                    "Note that part of your range lies below 1024, this is usually not allowed on"
                    " most operating systems with the exception of Windows.")
            log.add_important_error(message)
            return False

        self.protothread.server_connect()

        # Clear any potential messages queued up while offline
        self.queue.clear()

        addr = config.sections["server"]["server"]
        login = config.sections["server"]["login"]
        password = config.sections["server"]["passw"]

        log.add(_("Connecting to %(host)s:%(port)s"), {
            'host': addr[0],
            'port': addr[1]
        })
        self.queue.append(
            slskmessages.ServerConnect(addr, login=(login, password)))
        return True

    def disconnect(self):
        self.queue.append(slskmessages.ServerDisconnect())

    def server_disconnect(self, msg=None):

        self.logged_in = False

        # Clean up connections
        self.user_statuses.clear()
        self.watched_users.clear()

        self.pluginhandler.server_disconnect_notification(
            msg.manual_disconnect if msg else True)

        self.transfers.server_disconnect()
        self.search.server_disconnect()
        self.userlist.server_disconnect()
        self.chatrooms.server_disconnect()
        self.privatechats.server_disconnect()
        self.userinfo.server_disconnect()
        self.userbrowse.server_disconnect()
        self.interests.server_disconnect()

        if self.ui_callback:
            self.ui_callback.server_disconnect()

        self.login_username = None

    def send_message_to_peer(self, user, message, address=None):
        """ Sends message to a peer. Used when we know the username of a peer,
        but don't have/know an active connection. """

        self.queue.append(
            slskmessages.SendNetworkMessage(user, message, address))

    def set_away_mode(self, is_away, save_state=False):

        if save_state:
            config.sections["server"]["away"] = is_away

        self.away = is_away
        self.request_set_status(is_away and 1 or 2)

        # Reset away message users
        self.privatechats.set_away_mode(is_away)

        if self.ui_callback:
            self.ui_callback.set_away_mode(is_away)

    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 get_user_country(self, user):
        """ Retrieve a user's country code if previously cached, otherwise request
        user's IP address to determine country """

        user_address = self.protothread.user_addresses.get(user)

        if user_address and user != self.protothread.server_username:
            ip_address, _port = user_address
            country_code = self.geoip.get_country_code(ip_address)
            return country_code

        if user not in self.ip_requested:
            self.queue.append(slskmessages.GetPeerAddress(user))

        return None

    def watch_user(self, user, force_update=False):
        """ Tell the server we want to be notified of status/stat updates
        for a user """

        if not isinstance(user, str):
            return

        if not force_update and user in self.watched_users:
            # Already being watched, and we don't need to re-fetch the status/stats
            return

        self.queue.append(slskmessages.AddUser(user))

        # Get privilege status
        self.queue.append(slskmessages.GetUserStatus(user))

    @staticmethod
    def dummy_message(msg):
        log.add_msg_contents(msg)

    def ignore(self, msg):
        # Ignore received message
        pass

    def network_event(self, msgs):

        for i in msgs:
            if self.shutdown:
                return

            if i.__class__ in self.events:
                self.events[i.__class__](i)

            else:
                log.add("No handler for class %s %s", (i.__class__, dir(i)))

        msgs.clear()

    def show_connection_error_message(self, msg):
        """ Request UI to show error messages related to connectivity """

        for i in msg.msgs:
            if i.__class__ in (slskmessages.TransferRequest,
                               slskmessages.FileUploadInit):
                self.transfers.get_cant_connect_upload(i.token)

            elif i.__class__ is slskmessages.QueueUpload:
                self.transfers.get_cant_connect_queue_file(msg.user, i.file)

            elif i.__class__ is slskmessages.GetSharedFileList:
                self.userbrowse.show_connection_error(msg.user)

            elif i.__class__ is slskmessages.UserInfoRequest:
                self.userinfo.show_connection_error(msg.user)

    def message_progress(self, msg):

        if msg.msg_type is slskmessages.SharedFileList:
            self.userbrowse.message_progress(msg)

        elif msg.msg_type is slskmessages.UserInfoReply:
            self.userinfo.message_progress(msg)

    def server_timeout(self, *_args):
        if not config.need_config():
            self.connect()

    def check_download_queue(self, _msg):
        self.transfers.check_download_queue()

    def check_upload_queue(self, _msg):
        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 download_file_error(self, msg):
        log.add_msg_contents(msg)
        self.transfers.download_file_error(msg)

    def upload_file_error(self, msg):
        log.add_msg_contents(msg)
        self.transfers.upload_file_error(msg)

    def download_conn_close(self, msg):
        log.add_msg_contents(msg)
        self.transfers.download_conn_close(msg)

    def upload_conn_close(self, msg):
        log.add_msg_contents(msg)
        self.transfers.upload_conn_close(msg)

    def transfer_timeout(self, msg):
        self.transfers.transfer_timeout(msg)

    def set_connection_stats(self, msg):
        if self.ui_callback:
            self.ui_callback.set_connection_stats(msg)

    """
    Incoming Server Messages
    """

    def login(self, msg):
        """ Server code: 1 """

        log.add_msg_contents(msg)

        if msg.success:
            self.logged_in = True
            self.login_username = msg.username

            self.set_away_mode(config.sections["server"]["away"])
            self.watch_user(msg.username)

            if msg.ip_address is not None:
                self.user_ip_address = msg.ip_address

            self.transfers.server_login()
            self.search.server_login()
            self.userbrowse.server_login()
            self.userinfo.server_login()
            self.userlist.server_login()
            self.privatechats.server_login()
            self.chatrooms.server_login()

            if self.ui_callback:
                self.ui_callback.server_login()

            if msg.banner:
                log.add(msg.banner)

            self.interests.server_login()
            self.shares.send_num_shared_folders_files()

            self.queue.append(
                slskmessages.PrivateRoomToggle(
                    config.sections["server"]["private_chatrooms"]))
            self.pluginhandler.server_connect_notification()

        else:
            if msg.reason == "INVALIDPASS":
                self.ui_callback.invalid_password()
                return

            log.add_important_error(
                _("Unable to connect to the server. Reason: %s"), msg.reason)

    def get_peer_address(self, msg):
        """ Server code: 3 """

        log.add_msg_contents(msg)

        user = msg.user

        # User seems to be offline, don't update IP
        if msg.ip_address != "0.0.0.0":

            # If the IP address changed, make sure our IP block/ignore list reflects this
            self.network_filter.update_saved_user_ip_filters(user)

            if self.network_filter.block_unblock_user_ip_callback(user):
                return

            if self.network_filter.ignore_unignore_user_ip_callback(user):
                return

        country_code = self.geoip.get_country_code(msg.ip_address)

        self.chatrooms.set_user_country(user, country_code)
        self.userinfo.set_user_country(user, country_code)
        self.userlist.set_user_country(user, country_code)

        # From this point on all paths should call
        # self.pluginhandler.user_resolve_notification precisely once
        self.privatechats.private_message_queue_process(user)

        if user not in self.ip_requested:
            self.pluginhandler.user_resolve_notification(
                user, msg.ip_address, msg.port)
            return

        self.ip_requested.remove(user)
        self.pluginhandler.user_resolve_notification(user, msg.ip_address,
                                                     msg.port, country_code)

        if country_code:
            country = " (%(cc)s / %(country)s)" % {
                'cc': country_code,
                'country': self.geoip.country_code_to_name(country_code)
            }
        else:
            country = ""

        if msg.ip_address == "0.0.0.0":
            log.add(
                _("Cannot retrieve the IP of user %s, since this user is offline"
                  ), user)
            return

        log.add(
            _("IP address of user %(user)s is %(ip)s, port %(port)i%(country)s"
              ), {
                  'user': user,
                  'ip': msg.ip_address,
                  'port': msg.port,
                  'country': country
              })

    def add_user(self, msg):
        """ Server code: 5 """

        log.add_msg_contents(msg)

        self.watched_users.add(msg.user)

        if msg.userexists and msg.status is None:
            # Legacy support (Soulfind server)
            self.queue.append(slskmessages.GetUserStatus(msg.user))

        if msg.files is not None:
            self.get_user_stats(msg, log_contents=False)

    def get_user_status(self, msg, log_contents=True):
        """ Server code: 7 """

        if log_contents:
            log.add_msg_contents(msg)

        if msg.status is None:
            msg.status = -1

        self.user_statuses[msg.user] = msg.status

        if msg.privileged == 1:
            self.transfers.add_to_privileged(msg.user)

        elif msg.privileged == 0:
            self.transfers.remove_from_privileged(msg.user)

        self.interests.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)
        self.privatechats.get_user_status(msg)
        self.chatrooms.get_user_status(msg)

        self.pluginhandler.user_status_notification(msg.user, msg.status,
                                                    bool(msg.privileged))

    def say_chat_room(self, msg):
        """ Server code: 13 """

        log.add_msg_contents(msg)
        log.add_chat(
            _("Chat message from user '%(user)s' in room '%(room)s': %(message)s"
              ), {
                  "user": msg.user,
                  "room": msg.room,
                  "message": msg.msg
              })

        self.chatrooms.say_chat_room(msg)

    def join_room(self, msg):
        """ Server code: 14 """

        log.add_msg_contents(msg)
        self.chatrooms.join_room(msg)

    def leave_room(self, msg):
        """ Server code: 15 """

        log.add_msg_contents(msg)
        self.chatrooms.leave_room(msg)

    def user_joined_room(self, msg):
        """ Server code: 16 """

        log.add_msg_contents(msg)
        self.chatrooms.user_joined_room(msg)

    def user_left_room(self, msg):
        """ Server code: 17 """

        log.add_msg_contents(msg)
        self.chatrooms.user_left_room(msg)

    def connect_to_peer(self, msg):
        """ Server code: 18 """

        log.add_msg_contents(msg)

        if msg.privileged == 1:
            self.transfers.add_to_privileged(msg.user)

        elif msg.privileged == 0:
            self.transfers.remove_from_privileged(msg.user)

    def message_user(self, msg):
        """ Server code: 22 """

        log.add_msg_contents(msg)
        log.add_chat(_("Private message from user '%(user)s': %(message)s"), {
            "user": msg.user,
            "message": msg.msg
        })

        self.privatechats.message_user(msg)

    def search_request(self, msg):
        """ Server code: 26, 42 and 120 """

        log.add_msg_contents(msg)

        self.search.process_search_request(msg.searchterm,
                                           msg.user,
                                           msg.token,
                                           direct=True)
        self.pluginhandler.search_request_notification(msg.searchterm,
                                                       msg.user, msg.token)

    def get_user_stats(self, msg, log_contents=True):
        """ Server code: 36 """

        if log_contents:
            log.add_msg_contents(msg)

        if msg.user == self.login_username:
            self.transfers.upload_speed = msg.avgspeed

        self.interests.get_user_stats(msg)
        self.userinfo.get_user_stats(msg)
        self.userlist.get_user_stats(msg)
        self.chatrooms.get_user_stats(msg)

        stats = {
            'avgspeed': msg.avgspeed,
            'uploadnum': msg.uploadnum,
            'files': msg.files,
            'dirs': msg.dirs,
        }

        self.pluginhandler.user_stats_notification(msg.user, stats)

    @staticmethod
    def relogged(_msg):
        """ Server code: 41 """

        log.add_important_info(
            _("Someone logged in to your Soulseek account elsewhere"))

    def recommendations(self, msg):
        """ Server code: 54 """

        log.add_msg_contents(msg)
        self.interests.recommendations(msg)

    def global_recommendations(self, msg):
        """ Server code: 56 """

        log.add_msg_contents(msg)
        self.interests.global_recommendations(msg)

    def user_interests(self, msg):
        """ Server code: 57 """

        log.add_msg_contents(msg)
        self.userinfo.user_interests(msg)

    def room_list(self, msg):
        """ Server code: 64 """

        log.add_msg_contents(msg)
        self.chatrooms.room_list(msg)

    @staticmethod
    def admin_message(msg):
        """ Server code: 66 """

        log.add_important_info(msg.msg)

    def privileged_users(self, msg):
        """ Server code: 69 """

        log.add_msg_contents(msg)
        self.transfers.set_privileged_users(msg.users)
        log.add(_("%i privileged users"), (len(msg.users)))

    def add_to_privileged(self, msg):
        """ Server code: 91 """
        """ DEPRECATED """

        log.add_msg_contents(msg)
        self.transfers.add_to_privileged(msg.user)

    def check_privileges(self, msg):
        """ Server code: 92 """

        log.add_msg_contents(msg)

        mins = msg.seconds // 60
        hours = mins // 60
        days = hours // 24

        if msg.seconds == 0:
            log.add(
                _("You have no privileges. Privileges are not required, but allow your downloads "
                  "to be queued ahead of non-privileged users."))
        else:
            log.add(
                _("%(days)i days, %(hours)i hours, %(minutes)i minutes, %(seconds)i seconds of "
                  "download privileges left."), {
                      'days': days,
                      'hours': hours % 24,
                      'minutes': mins % 60,
                      'seconds': msg.seconds % 60
                  })

        self.privileges_left = msg.seconds

    def wishlist_interval(self, msg):
        """ Server code: 104 """

        log.add_msg_contents(msg)
        self.search.set_wishlist_interval(msg)

    def similar_users(self, msg):
        """ Server code: 110 """

        log.add_msg_contents(msg)
        self.interests.similar_users(msg)

    def item_recommendations(self, msg):
        """ Server code: 111 """

        log.add_msg_contents(msg)
        self.interests.item_recommendations(msg)

    def item_similar_users(self, msg):
        """ Server code: 112 """

        log.add_msg_contents(msg)
        self.interests.item_similar_users(msg)

    def room_ticker_state(self, msg):
        """ Server code: 113 """

        log.add_msg_contents(msg)
        self.chatrooms.ticker_set(msg)

    def room_ticker_add(self, msg):
        """ Server code: 114 """

        log.add_msg_contents(msg)
        self.chatrooms.ticker_add(msg)

    def room_ticker_remove(self, msg):
        """ Server code: 115 """

        log.add_msg_contents(msg)
        self.chatrooms.ticker_remove(msg)

    def private_room_users(self, msg):
        """ Server code: 133 """

        log.add_msg_contents(msg)
        self.chatrooms.private_room_users(msg)

    def private_room_add_user(self, msg):
        """ Server code: 134 """

        log.add_msg_contents(msg)
        self.chatrooms.private_room_add_user(msg)

    def private_room_remove_user(self, msg):
        """ Server code: 135 """

        log.add_msg_contents(msg)
        self.chatrooms.private_room_remove_user(msg)

    def private_room_disown(self, msg):
        """ Server code: 137 """

        log.add_msg_contents(msg)
        self.chatrooms.private_room_disown(msg)

    def private_room_added(self, msg):
        """ Server code: 139 """

        log.add_msg_contents(msg)
        self.chatrooms.private_room_added(msg)

    def private_room_removed(self, msg):
        """ Server code: 140 """

        log.add_msg_contents(msg)
        self.chatrooms.private_room_removed(msg)

    def private_room_toggle(self, msg):
        """ Server code: 141 """

        log.add_msg_contents(msg)
        self.chatrooms.private_room_toggle(msg)

    @staticmethod
    def change_password(msg):
        """ Server code: 142 """

        log.add_msg_contents(msg)

        password = msg.password
        config.sections["server"]["passw"] = password
        config.write_configuration()

        log.add_important_info(
            _("Your password has been changed. Password is %s"), password)

    def private_room_add_operator(self, msg):
        """ Server code: 143 """

        log.add_msg_contents(msg)
        self.chatrooms.private_room_add_operator(msg)

    def private_room_remove_operator(self, msg):
        """ Server code: 144 """

        log.add_msg_contents(msg)
        self.chatrooms.private_room_remove_operator(msg)

    def private_room_operator_added(self, msg):
        """ Server code: 145 """

        log.add_msg_contents(msg)
        self.chatrooms.private_room_operator_added(msg)

    def private_room_operator_removed(self, msg):
        """ Server code: 146 """

        log.add_msg_contents(msg)
        self.chatrooms.private_room_operator_removed(msg)

    def private_room_owned(self, msg):
        """ Server code: 148 """

        log.add_msg_contents(msg)
        self.chatrooms.private_room_owned(msg)

    def public_room_message(self, msg):
        """ Server code: 152 """

        log.add_msg_contents(msg)
        self.chatrooms.public_room_message(msg)

    """
    Incoming Peer Messages
    """

    def get_shared_file_list(self, msg):
        """ Peer code: 4 """

        log.add_msg_contents(msg)

        user = msg.init.target_user
        request_time = time.time()

        if user in self.requested_share_times and request_time < self.requested_share_times[
                user] + 0.4:
            # Ignoring request, because it's less than half a second since the
            # last one by this user
            return

        self.requested_share_times[user] = request_time

        log.add(_("User %(user)s is browsing your list of shared files"),
                {'user': user})

        ip_address, _port = msg.init.addr
        checkuser, reason = self.network_filter.check_user(user, ip_address)

        if not checkuser:
            message = self.ban_message % reason
            self.privatechats.send_automatic_message(user, message)

        shares_list = None

        if checkuser == 1:
            # Send Normal Shares
            shares_list = self.shares.get_compressed_shares_message("normal")

        elif checkuser == 2:
            # Send Buddy Shares
            shares_list = self.shares.get_compressed_shares_message("buddy")

        if not shares_list:
            # Nyah, Nyah
            shares_list = slskmessages.SharedFileList(msg.init, {})

        shares_list.init = msg.init
        self.queue.append(shares_list)

    def shared_file_list(self, msg):
        """ Peer code: 5 """

        username = msg.init.target_user
        self.userbrowse.shared_file_list(username, msg)

    def file_search_result(self, msg):
        """ Peer message: 9 """

        log.add_msg_contents(msg)
        self.search.file_search_result(msg)

    def user_info_request(self, msg):
        """ Peer code: 15 """

        log.add_msg_contents(msg)

        user = msg.init.target_user
        ip_address, _port = msg.init.addr
        request_time = time.time()

        if user in self.requested_info_times and request_time < self.requested_info_times[
                user] + 0.4:
            # Ignoring request, because it's less than half a second since the
            # last one by this user
            return

        self.requested_info_times[user] = request_time

        if self.login_username != user:
            log.add(_("User %(user)s is reading your user info"),
                    {'user': user})

        status, reason = self.network_filter.check_user(user, ip_address)

        if not status:
            pic = None
            descr = self.ban_message % reason
            descr += "\n\n----------------------------------------------\n\n"
            descr += unescape(config.sections["userinfo"]["descr"])

        else:
            try:
                userpic = config.sections["userinfo"]["pic"]

                with open(userpic, 'rb') as file_handle:
                    pic = file_handle.read()

            except Exception:
                pic = None

            descr = unescape(config.sections["userinfo"]["descr"])

        totalupl = self.transfers.get_total_uploads_allowed()
        queuesize = self.transfers.get_upload_queue_size()
        slotsavail = self.transfers.allow_new_uploads()

        if config.sections["transfers"]["remotedownloads"]:
            uploadallowed = config.sections["transfers"]["uploadallowed"]
        else:
            uploadallowed = 0

        self.queue.append(
            slskmessages.UserInfoReply(msg.init, descr, pic, totalupl,
                                       queuesize, slotsavail, uploadallowed))

    def user_info_reply(self, msg):
        """ Peer code: 16 """

        log.add_msg_contents(msg)

        username = msg.init.target_user
        self.userinfo.user_info_reply(username, msg)

    def p_message_user(self, msg):
        """ Peer code: 22 """

        log.add_msg_contents(msg)

        username = msg.init.target_user

        if username != msg.user:
            msg.msg = _(
                "(Warning: %(realuser)s is attempting to spoof %(fakeuser)s) "
            ) % {
                "realuser": username,
                "fakeuser": msg.user
            } + msg.msg
            msg.user = username

        self.privatechats.message_user(msg)

    def folder_contents_request(self, msg):
        """ Peer code: 36 """

        log.add_msg_contents(msg)

        init = msg.init
        ip_address, _port = msg.init.addr
        username = msg.init.target_user
        checkuser, reason = self.network_filter.check_user(
            username, ip_address)

        if not checkuser:
            message = self.ban_message % reason
            self.privatechats.send_automatic_message(username, message)

        normalshares = self.shares.share_dbs.get("streams")
        buddyshares = self.shares.share_dbs.get("buddystreams")

        if checkuser == 1 and normalshares is not None:
            shares = normalshares

        elif checkuser == 2 and buddyshares is not None:
            shares = buddyshares

        else:
            shares = {}

        if checkuser:
            try:
                if msg.dir in shares:
                    self.queue.append(
                        slskmessages.FolderContentsResponse(
                            init, msg.dir, shares[msg.dir]))
                    return

                if msg.dir.rstrip('\\') in shares:
                    self.queue.append(
                        slskmessages.FolderContentsResponse(
                            init, msg.dir, shares[msg.dir.rstrip('\\')]))
                    return

            except Exception as error:
                log.add(
                    _("Failed to fetch the shared folder %(folder)s: %(error)s"
                      ), {
                          "folder": msg.dir,
                          "error": error
                      })

            self.queue.append(
                slskmessages.FolderContentsResponse(init, msg.dir, None))

    def folder_contents_response(self, msg):
        """ Peer code: 37 """

        file_list = msg.list

        # Check for a large number of files
        many = False
        folder = ""

        for i in file_list:
            for j in file_list[i]:
                if os.path.commonprefix([i, j]) == j:
                    numfiles = len(file_list[i][j])
                    if numfiles > 100:
                        many = True
                        folder = j

        if many:
            username = msg.init.target_user
            self.transfers.downloadsview.download_large_folder(
                username, folder, numfiles, msg)
        else:
            self.transfers.folder_contents_response(msg)

    def transfer_request(self, msg):
        """ Peer code: 40 """

        log.add_msg_contents(msg)
        self.transfers.transfer_request(msg)

    def transfer_response(self, msg):
        """ Peer code: 41 """

        log.add_msg_contents(msg)
        self.transfers.transfer_response(msg)

    def queue_upload(self, msg):
        """ Peer code: 43 """

        log.add_msg_contents(msg)
        self.transfers.queue_upload(msg)

    def place_in_queue(self, msg):
        """ Peer code: 44 """

        log.add_msg_contents(msg)
        self.transfers.place_in_queue(msg)

    def upload_failed(self, msg):
        """ Peer code: 46 """

        log.add_msg_contents(msg)
        self.transfers.upload_failed(msg)

    def upload_denied(self, msg):
        """ Peer code: 50 """

        log.add_msg_contents(msg)
        self.transfers.upload_denied(msg)

    def place_in_queue_request(self, msg):
        """ Peer code: 51 """

        log.add_msg_contents(msg)
        self.transfers.place_in_queue_request(msg)

    """
    Incoming File Messages
    """

    def file_download_init(self, msg):
        log.add_msg_contents(msg)
        self.transfers.file_download_init(msg)

    def file_upload_init(self, msg):
        log.add_msg_contents(msg)
        self.transfers.file_upload_init(msg)

    """
    Incoming Distributed Messages
    """

    def distrib_search(self, msg):
        """ Distrib code: 3 """

        # Verbose: log.add_msg_contents(msg)

        self.search.process_search_request(msg.searchterm,
                                           msg.user,
                                           msg.token,
                                           direct=False)
        self.pluginhandler.distrib_search_notification(msg.searchterm,
                                                       msg.user, msg.token)
    def start(self, ui_callback=None, network_callback=None):

        self.ui_callback = ui_callback
        self.network_callback = network_callback if network_callback else self.network_event
        script_dir = os.path.dirname(__file__)

        log.add(_("Loading %(program)s %(version)s"), {
            "program": "Python",
            "version": config.python_version
        })
        log.add_debug("Using %(program)s executable: %(exe)s", {
            "program": "Python",
            "exe": str(sys.executable)
        })
        log.add_debug("Using %(program)s executable: %(exe)s", {
            "program": config.application_name,
            "exe": script_dir
        })
        log.add(_("Loading %(program)s %(version)s"), {
            "program": config.application_name,
            "version": config.version
        })

        self.geoip = GeoIP(os.path.join(script_dir, "geoip/ipcountrydb.bin"))
        self.notifications = Notifications(config, ui_callback)
        self.network_filter = NetworkFilter(self, config, self.queue,
                                            self.geoip)
        self.now_playing = NowPlaying(config)
        self.statistics = Statistics(config, ui_callback)

        self.shares = Shares(self, config, self.queue, self.network_callback,
                             ui_callback)
        self.search = Search(self, config, self.queue, self.shares.share_dbs,
                             self.geoip, ui_callback)
        self.transfers = Transfers(self, config, self.queue,
                                   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.privatechats = PrivateChats(self, config, self.queue, ui_callback)
        self.chatrooms = ChatRooms(self, config, self.queue, ui_callback)

        self.transfers.init_transfers()
        self.privatechats.load_users()

        port_range = config.sections["server"]["portrange"]
        interface = config.sections["server"]["interface"]
        self.protothread = slskproto.SlskProtoThread(self.network_callback,
                                                     self.queue, self.bindip,
                                                     interface, self.port,
                                                     port_range, self)
        self.upnp = UPnP(self, config)
        self.pluginhandler = PluginHandler(self, config)

        self.set_connection_stats(slskmessages.SetConnectionStats())
        connect_ready = not config.need_config()

        if not connect_ready:
            log.add(
                _("You need to specify a username and password before connecting…"
                  ))

        return connect_ready