Example #1
0
def load_custom_icons(names):
    """ Load custom icon theme if one is selected """

    if not config.sections["ui"].get("icontheme"):
        return False

    icon_theme_path = config.sections["ui"]["icontheme"]
    log.add_debug("Loading custom icon theme from %s", icon_theme_path)
    extensions = ["jpg", "jpeg", "bmp", "png", "svg"]

    for name in names:
        path = None
        exts = extensions[:]
        loaded = False

        while not path or (exts and not loaded):
            path = os.path.expanduser(
                os.path.join(icon_theme_path, "%s.%s" % (name, exts.pop())))

            try:
                if os.path.isfile(path):
                    ICONS[name] = Gio.Icon.new_for_string(path)
                    loaded = True

            except Exception as error:
                log.add(_("Error loading custom icon %(path)s: %(error)s"), {
                    "path": path,
                    "error": error
                })

        if name not in ICONS:
            ICONS[name] = load_ui_icon(name)

    return True
Example #2
0
    def write_configuration(self):

        if not self.config_loaded:
            return

        # Write new config options to file
        for section, options in self.sections.items():
            if not self.parser.has_section(section):
                self.parser.add_section(section)

            for option, value in options.items():
                self.parser.set(section, option, str(value))

        # Remove legacy config options
        for section, options in self.removed_options.items():
            if not self.parser.has_section(section):
                continue

            for option in options:
                self.parser.remove_option(section, option)

        if not self.create_config_folder():
            return

        from pynicotine.logfacility import log
        from pynicotine.utils import write_file_and_backup

        write_file_and_backup(self.filename,
                              self.write_config_callback,
                              protect=True)
        log.add_debug("Saved configuration: %(file)s", {"file": self.filename})
Example #3
0
    def check_icon_path(icon_name, icon_path, icon_type="local"):
        """
        Check if tray icons exist in the specified icon path.
        There are two naming schemes for tray icons:
        - System-wide/local icons: "org.nicotine_plus.Nicotine-<icon_name>"
        - Custom icons: "trayicon_<icon_name>"
        """

        if icon_type == "local":
            icon_scheme = config.application_id + "-" + icon_name + "."
        else:
            icon_scheme = "trayicon_" + icon_name + "."

        try:
            for entry in os.scandir(icon_path):
                if entry.is_file() and entry.name.startswith(icon_scheme):
                    return True

        except OSError as error:
            log.add_debug(
                "Error accessing %(type)s tray icon path %(path)s: %(error)s" %
                {
                    "type": icon_type,
                    "path": icon_path,
                    "error": error
                })

        return False
Example #4
0
    def add_port_mapping(self, np):
        """
        This function supports creating a Port Mapping via the UPnP
        IGDv1 and IGDv2 protocol.

        Need a reference to the np object to extract the internal LAN
        local from the protothread socket.

        Any UPnP port mapping done with IGDv2 will expire after a
        maximum of 7 days (lease period), according to the protocol.
        We set the lease period to a shorter 24 hours, and regularly
        renew the port mapping (see pynicotine.py).
        """

        try:
            self._add_port_mapping(np)

        except Exception as error:
            log.add(_('UPnP exception: %(error)s'), {'error': error})
            log.add(
                _('Failed to automate the creation of UPnP Port Mapping rule.')
            )
            return

        log.add_debug(
            _('Managed to map external WAN port %(externalwanport)s ' +
              'to your local host %(internalipaddress)s ' +
              'port %(internallanport)s.'), {
                  'externalwanport': self.externalwanport,
                  'internalipaddress': self.internalipaddress,
                  'internallanport': self.internallanport
              })
Example #5
0
    def __init__(self, core, config):

        self.core = core
        self.config = config

        log.add_debug("Loading plugin handler")

        self.my_username = self.config.sections["server"]["login"]
        self.plugindirs = []
        self.enabled_plugins = {}

        try:
            os.makedirs(config.plugin_dir)
        except Exception:
            pass

        # Load system-wide plugins
        prefix = os.path.dirname(os.path.realpath(__file__))
        self.plugindirs.append(os.path.join(prefix, "plugins"))

        # Load home directory plugins
        self.plugindirs.append(config.plugin_dir)

        if os.path.isdir(config.plugin_dir):
            self.load_enabled()
        else:
            log.add(
                _("It appears '%s' is not a directory, not loading plugins."),
                config.plugin_dir)
Example #6
0
    def delete_port_mapping(cls, router, protocol, public_port):
        """ Deletes a port mapping from a router """

        log.add_debug("Deleting port mapping (%s, %s, %s)",
                      (router, protocol, public_port))

        url = '{}{}'.format(router.base_url, router.control_url)
        log.add_debug('Deleting port mapping (%s/%s) at url "%s"',
                      (public_port, protocol, url))

        headers = {
            'Host': '{}:{}'.format(router.ip, router.port),
            'Content-Type': 'text/xml; charset=utf-8',
            'SOAPACTION': '"{}#DeletePortMapping"'.format(router.svc_type)
        }

        data = UPnp._delete_port_mapping_template.format(public_port, protocol)
        log.add_debug('UPnP: Delete port mapping request headers: %s', headers)
        log.add_debug('UPnP: Delete port mapping request contents: %s', data)

        response = http_request(router.url_scheme,
                                router.base_url,
                                router.control_url,
                                request_type="POST",
                                body=data,
                                headers=headers)

        log.add_debug('UPnP: Delete port mapping response: %s',
                      response.encode('utf-8'))
Example #7
0
    def plugin_settings(self, plugin_name, plugin):
        plugin_name = plugin_name.lower()
        try:
            if not plugin.settings:
                return

            if plugin_name not in self.config.sections["plugins"]:
                self.config.sections["plugins"][plugin_name] = plugin.settings

            for i in plugin.settings:
                if i not in self.config.sections["plugins"][plugin_name]:
                    self.config.sections["plugins"][plugin_name][
                        i] = plugin.settings[i]

            customsettings = self.config.sections["plugins"][plugin_name]

            for key in customsettings:
                if key in plugin.settings:
                    plugin.settings[key] = customsettings[key]

                else:
                    log.add_debug(
                        "Stored setting '%(key)s' is no longer present in the '%(name)s' plugin",
                        {
                            'key': key,
                            'name': plugin_name
                        })

        except KeyError:
            log.add_debug("No stored settings found for %s", plugin.human_name)
Example #8
0
    def ok_to_respond(self, room, nick, request, seconds_limit_min=30):
        self.room = room
        self.nick = nick
        self.request = request

        willing_to_respond = True
        current_time = time()

        if room not in self.plugin_usage:
            self.plugin_usage[room] = {'last_time': 0, 'last_request': "", 'last_nick': ""}

        last_time = self.plugin_usage[room]['last_time']
        last_nick = self.plugin_usage[room]['last_nick']
        last_request = self.plugin_usage[room]['last_request']

        port = False
        try:
            ip, port = self.frame.np.users[nick].addr
        except Exception:
            port = True

        if nick in self.frame.np.config.sections["server"]["ignorelist"]:
            willing_to_respond, reason = False, "The nick is ignored"

        elif self.frame.user_ip_is_ignored(nick):
            willing_to_respond, reason = False, "The nick's Ip is ignored"

        elif not port:
            willing_to_respond, reason = False, "Request likely from simple PHP based griefer bot"

        elif [nick, request] == [last_nick, last_request]:
            if (current_time - last_time) < 12 * seconds_limit_min:
                willing_to_respond, reason = False, "Too soon for same nick to request same resource in room"

        elif (request == last_request):
            if (current_time - last_time) < 3 * seconds_limit_min:
                willing_to_respond, reason = False, "Too soon for different nick to request same resource in room"

        else:
            recent_responses = 0

            for responded_room in self.plugin_usage:
                if (current_time - self.plugin_usage[responded_room]['last_time']) < seconds_limit_min:
                    recent_responses += 1

                    if responded_room == room:
                        willing_to_respond, reason = False, "Responded in specified room too recently"
                        break

            if recent_responses > 3:
                willing_to_respond, reason = False, "Responded in multiple rooms enough"

        if self.logging:
            if not willing_to_respond:
                base_log_msg = "{} plugin request rejected - room '{}', nick '{}'".format(self.plugin_name, room, nick)
                log.add_debug("{} - {}".format(base_log_msg, reason))

        return willing_to_respond
Example #9
0
    def trigger_event(self, function_name, args):
        """ Triggers an event for the plugins. Since events and notifications
        are precisely the same except for how n+ responds to them, both can be
        triggered by this function. """

        function_name_camelcase = function_name.title().replace('_', '')

        for module, plugin in self.enabled_plugins.items():
            try:
                if hasattr(plugin, function_name_camelcase):
                    plugin.log(
                        "%(old_function)s is deprecated, please use %(new_function)s"
                        % {
                            "old_function": function_name_camelcase,
                            "new_function": function_name
                        })
                    return_value = getattr(plugin,
                                           function_name_camelcase)(*args)
                else:
                    return_value = getattr(plugin, function_name)(*args)

            except Exception:
                self.show_plugin_error(module,
                                       sys.exc_info()[0],
                                       sys.exc_info()[1],
                                       sys.exc_info()[2])
                continue

            if return_value is None:
                # Nothing changed, continue to the next plugin
                continue

            if isinstance(return_value, tuple):
                # The original args were modified, update them
                args = return_value
                continue

            if return_value == returncode['zap']:
                return None

            if return_value == returncode['break']:
                return args

            if return_value == returncode['pass']:
                continue

            log.add_debug(
                "Plugin %(module)s returned something weird, '%(value)s', ignoring",
                {
                    'module': module,
                    'value': return_value
                })

        return args
Example #10
0
    def _add_port_mapping(self, np):
        """
        Function that actually creates the port mapping.
        If a port mapping already exists, it is updated with a lease
        period of 24 hours.
        """

        log.add_debug('Creating Port Mapping rule via UPnP...')

        # Find router
        router = UPnp.find_router()

        if not router:
            raise RuntimeError('UPnP does not work on this network')

        # Create a UDP socket
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

        # Send a broadcast packet on a local address (doesn't need to be reachable, but MacOS requires port to be non-zero)
        s.connect(('10.255.255.255', 1))

        # This returns the "primary" IP on the local box, even if that IP is a NAT/private/internal IP.
        self.internalipaddress = s.getsockname()[0]

        # Close the socket
        s.close()

        # Store the Local LAN port
        self.internallanport = np.protothread._p.getsockname()[1]
        self.externalwanport = self.internallanport

        # Do the port mapping
        log.add_debug('Trying to redirect external WAN port %s TCP => %s port %s TCP', (
            self.externalwanport,
            self.internalipaddress,
            self.internallanport
        ))

        try:
            UPnp.add_port_mapping(
                router=router,
                protocol='TCP',
                public_port=self.externalwanport,
                private_ip=self.internalipaddress,
                private_port=self.internallanport,
                mapping_description='Nicotine+',
                lease_duration=86400  # Expires in 24 hours
            )

        except Exception as e:
            raise RuntimeError(
                _('Failed to map the external WAN port: %(error)s') %
                {'error': str(e)}
            )
Example #11
0
    def create_search_result_list(self, searchterm, wordindex, excluded_words,
                                  partial_words):
        """ Returns a list of common file indices for each word in a search term """

        try:
            words = searchterm.split()
            original_length = len(words)
            results = None
            i = 0

            while i < len(words):
                word = words[i]
                exclude_word = False
                i += 1

                if word in excluded_words:
                    # Excluded search words (e.g. -hello)

                    if results is None and i < original_length:
                        # Re-append the word so we can re-process it once we've found a match
                        words.append(word)
                        continue

                    exclude_word = True

                elif word in partial_words:
                    # Partial search words (e.g. *ello)

                    partial_results = set()

                    for complete_word, indices in wordindex.items():
                        if complete_word.endswith(word):
                            partial_results.update(indices)

                    if partial_results:
                        results = self.update_search_results(
                            results, partial_results)
                        continue

                results = self.update_search_results(results,
                                                     wordindex.get(word),
                                                     exclude_word)

                if results is None:
                    # No matches found
                    break

            return results

        except ValueError:
            log.add_debug(
                "Error: DB closed during search, perhaps due to rescanning shares or closing the application"
            )
            return None
Example #12
0
 def sendto(self, transport, addr):
     """
     Send request to a given address via given transport.
     Args:
         transport (asyncio.DatagramTransport):
             Write transport to send the message on.
         addr (Tuple[str, int]):
             IP address and port pair to send the message to.
     """
     msg = bytes(self) + b'\r\n\r\n'
     log.add_debug('UPnP: SSDP request: %s', msg)
     transport.sendto(msg, addr)
Example #13
0
    def load_enabled(self):
        enable = self.config.sections["plugins"]["enable"]

        if not enable:
            return

        log.add(_("Loading plugin system"))

        to_enable = self.config.sections["plugins"]["enabled"]
        log.add_debug("Enabled plugin(s): %s" % ', '.join(to_enable))

        for plugin in to_enable:
            self.enable_plugin(plugin)
Example #14
0
    def add_port_mapping(cls, router, protocol, public_port, private_ip,
                         private_port, mapping_description, lease_duration):
        """ Adds a port mapping to a router """

        from xml.etree import ElementTree

        log.add_debug(
            "Adding port mapping (%s, %s, %s, %s, %s)",
            (router.uuid, protocol, public_port, private_ip, private_port))

        url = '{}{}'.format(router.base_url, router.control_url)
        log.add_debug('Adding port mapping (%s %s/%s) at url "%s"',
                      (private_ip, private_port, protocol, url))

        headers = {
            'Host': '{}:{}'.format(router.ip, router.port),
            'Content-Type': 'text/xml; charset=utf-8',
            'SOAPACTION': '"{}#AddPortMapping"'.format(router.svc_type)
        }

        data = UPnp._add_port_mapping_template.format(public_port, protocol,
                                                      private_port, private_ip,
                                                      mapping_description,
                                                      lease_duration)

        log.add_debug('UPnP: Add port mapping request headers: %s', headers)
        log.add_debug('UPnP: Add port mapping request contents: %s', data)

        response = http_request(router.url_scheme,
                                router.base_url,
                                router.control_url,
                                request_type="POST",
                                body=data,
                                headers=headers)

        log.add_debug('UPnP: Add port mapping response: %s',
                      response.encode('utf-8'))

        xml = ElementTree.fromstring(response)

        error_code = next((x.text for x in xml.findall(
            ".//{urn:schemas-upnp-org:control-1-0}errorCode")), None)

        error_description = next((x.text for x in xml.findall(
            ".//{urn:schemas-upnp-org:control-1-0}errorDescription")), None)

        if error_code or error_description:
            raise Exception('Error code %(code)s: %(description)s' % {
                'code': error_code,
                'description': error_description
            })
Example #15
0
    def _request_port_mapping(self, router, protocol, public_port, private_ip,
                              private_port, mapping_description,
                              lease_duration):
        """
        Function that adds a port mapping to the router.
        If a port mapping already exists, it is updated with a lease period of 24 hours.
        """

        from xml.etree import ElementTree

        url = '%s%s' % (router.base_url, router.control_url)
        log.add_debug(
            "UPnP: Adding port mapping (%s %s/%s, %s) at url '%s'",
            (private_ip, private_port, protocol, router.search_target, url))

        headers = {
            "Host": router.base_url,
            "Content-Type": "text/xml; charset=utf-8",
            "SOAPACTION": '"%s#AddPortMapping"' % router.service_type
        }

        body = (
            self.request_body %
            (router.service_type, public_port, protocol, private_port,
             private_ip, mapping_description, lease_duration)).encode('utf-8')

        log.add_debug("UPnP: Add port mapping request headers: %s", headers)
        log.add_debug("UPnP: Add port mapping request contents: %s", body)

        response = http_request(router.url_scheme,
                                router.base_url,
                                router.control_url,
                                request_type="POST",
                                body=body,
                                headers=headers)

        xml = ElementTree.fromstring(response)

        if xml.find(
                ".//{http://schemas.xmlsoap.org/soap/envelope/}Body") is None:
            raise Exception(
                _("Invalid response: %s") % response.encode('utf-8'))

        log.add_debug("UPnP: Add port mapping response: %s",
                      response.encode('utf-8'))

        error_code = xml.findtext(
            ".//{urn:schemas-upnp-org:control-1-0}errorCode")
        error_description = xml.findtext(
            ".//{urn:schemas-upnp-org:control-1-0}errorDescription")

        if error_code or error_description:
            raise Exception(
                _("Error code %(code)s: %(description)s") % {
                    "code": error_code,
                    "description": error_description
                })
Example #16
0
    def find_suitable_external_wan_port(self):
        """Function to find a suitable external WAN port to map to the client.

        It will detect if a port mapping to the client already exist.
        """

        # Output format: (e_port, protocol, (int_client, iport), desc, enabled,
        # rHost, duration)
        log.add_debug(
            'Existing Port Mappings: %s',
            (sorted(self.existingportsmappings, key=lambda tup: tup[0])))

        # Analyze ports mappings
        for m in sorted(self.existingportsmappings, key=lambda tup: tup[0]):

            (e_port, protocol, (int_client, iport), desc, enabled, rhost,
             duration) = m

            # A Port Mapping is already in place with the client: we will
            # rewrite it to avoid a timeout on the duration of the mapping
            if protocol == "TCP" and \
               str(int_client) == str(self.internalipaddress) and \
               iport == self.internallanport:
                log.add_debug('Port Mapping already in place: %s', str(m))
                self.externalwanport = e_port
                self.foundexistingmapping = True
                break

        # If no mapping already in place we try to found a suitable external
        # WAN port
        if not self.foundexistingmapping:

            # Find the first external WAN port > requestedwanport that's not
            # already reserved
            tcpportsreserved = [
                x[0] for x in sorted(self.existingportsmappings)
                if x[1] == "TCP"
            ]

            while self.externalwanport in tcpportsreserved:
                if self.externalwanport + 1 <= 65535:
                    self.externalwanport += 1
                else:
                    raise AssertionError(
                        _('Failed to find a suitable external WAN port, ' +
                          'bailing out.'))
Example #17
0
    def list_port_mappings(cls, router):
        """ Lists the port mappings for a router """

        log.add_debug('Listing existing port mappings...')

        headers = {
            'Host': '{}:{}'.format(router.ip, router.port),
            'Content-Type': 'text/xml; charset=utf-8',
            'SOAPACTION':
            '"{}#GetGenericPortMappingEntry"'.format(router.svc_type)
        }

        index = -1
        portmap_found = True
        portmaps = []

        while portmap_found:
            index += 1
            data = UPnp._list_port_mappings_template.format(index)
            log.add_debug('UPnP: List port mappings request headers: %s',
                          headers)
            log.add_debug('UPnP: List port mappings request contents: %s',
                          data)

            response = http_request(router.url_scheme,
                                    router.base_url,
                                    router.control_url,
                                    request_type="POST",
                                    body=data,
                                    headers=headers)

            log.add_debug('UPnP: List port mappings response: %s',
                          response.encode('utf-8'))

            portmap = PortMapping.parse_port_map_xml(response, router.svc_type)

            if not portmap:
                portmap_found = False
            else:
                portmaps.append(portmap)

        log.add_debug('Existing port mappings: %s', portmaps)

        return portmaps
Example #18
0
    def _trigger_command(self, command, source, args, public_command):

        self.command_source = (public_command, source)

        for module, plugin in self.enabled_plugins.items():
            if plugin is None:
                continue

            return_value = None
            commands = plugin.__publiccommands__ if public_command else plugin.__privatecommands__

            try:
                for trigger, func in commands:
                    if trigger == command:
                        return_value = getattr(plugin, func.__name__)(source,
                                                                      args)

            except Exception:
                self.show_plugin_error(module,
                                       sys.exc_info()[0],
                                       sys.exc_info()[1],
                                       sys.exc_info()[2])
                continue

            if return_value is None:
                # Nothing changed, continue to the next plugin
                continue

            if return_value == returncode['zap']:
                self.command_source = None
                return True

            if return_value == returncode['pass']:
                continue

            log.add_debug(
                "Plugin %(module)s returned something weird, '%(value)s', ignoring",
                {
                    'module': module,
                    'value': str(return_value)
                })

        self.command_source = None
        return False
Example #19
0
    def parse_ssdp_response(cls, ssdp_response, sender):
        response_headers = dict(ssdp_response.headers)

        if 'LOCATION' not in response_headers:
            log.add_debug(
                'The M-SEARCH response from %s:%d did not contain a Location header.',
                (sender[0], sender[1]))
            log.add_debug(ssdp_response)
            return None

        from urllib.parse import urlsplit
        urlparts = urlsplit(response_headers['LOCATION'])

        return Router(ip=sender[0],
                      port=sender[1],
                      wan_ip_type=response_headers['ST'],
                      url_scheme=urlparts.scheme,
                      base_url=urlparts.netloc,
                      root_url=urlparts.path)
Example #20
0
    def get_router_control_url(url_scheme, base_url, root_url):

        service_type = None
        control_url = None

        try:
            from xml.etree import ElementTree

            response = http_request(url_scheme, base_url, root_url, timeout=2)
            log.add_debug(
                "UPnP: Device description response from %s://%s%s: %s",
                (url_scheme, base_url, root_url, response.encode('utf-8')))

            xml = ElementTree.fromstring(response)

            for service in xml.findall(
                    ".//{urn:schemas-upnp-org:device-1-0}service"):
                found_service_type = service.find(
                    ".//{urn:schemas-upnp-org:device-1-0}serviceType").text

                if found_service_type in (
                        "urn:schemas-upnp-org:service:WANIPConnection:1",
                        "urn:schemas-upnp-org:service:WANPPPConnection:1",
                        "urn:schemas-upnp-org:service:WANIPConnection:2"):
                    # We found a router with UPnP enabled
                    service_type = found_service_type
                    control_url = service.find(
                        ".//{urn:schemas-upnp-org:device-1-0}controlURL").text
                    break

        except Exception as error:
            # Invalid response
            log.add_debug(
                "UPnP: Invalid device description response from %s://%s%s: %s",
                (url_scheme, base_url, root_url, error))

        return service_type, control_url
Example #21
0
    def __init__(self, frame):

        super().__init__(frame)

        try:
            # Check if AyatanaAppIndicator3 is available
            gi.require_version('AyatanaAppIndicator3', '0.1')
            from gi.repository import AyatanaAppIndicator3
            self.implementation_class = AyatanaAppIndicator3

        except (ImportError, ValueError):
            try:
                # Check if AppIndicator3 is available
                gi.require_version('AppIndicator3', '0.1')
                from gi.repository import AppIndicator3
                self.implementation_class = AppIndicator3

            except (ImportError, ValueError) as error:
                raise AttributeError(
                    "AppIndicator implementation not available") from error

        self.tray_icon = self.implementation_class.Indicator.new(
            id=config.application_name,
            icon_name="",
            category=self.implementation_class.IndicatorCategory.
            APPLICATION_STATUS)

        self.tray_icon.set_menu(self.menu)

        # Action to hide/unhide main window when middle clicking the tray icon
        self.tray_icon.set_secondary_activate_target(
            self.menu.get_children()[0])

        if self.final_icon_path:
            log.add_debug("Using tray icon path %s", self.final_icon_path)
            self.tray_icon.set_icon_theme_path(self.final_icon_path)
Example #22
0
    def add_router(routers, ssdp_response):

        from urllib.parse import urlsplit
        response_headers = {k.upper(): v for k, v in ssdp_response.headers}

        log.add_debug("UPnP: Device search response: %s", bytes(ssdp_response))

        if "LOCATION" not in response_headers:
            log.add_debug(
                "UPnP: M-SEARCH response did not contain a LOCATION header: %s",
                ssdp_response.headers)
            return

        url_parts = urlsplit(response_headers["LOCATION"])
        service_type, control_url = SSDP.get_router_control_url(
            url_parts.scheme, url_parts.netloc, url_parts.path)

        if service_type is None or control_url is None:
            log.add_debug(
                "UPnP: No router with UPnP enabled in device search response, ignoring"
            )
            return

        log.add_debug(
            "UPnP: Device details: service_type '%s'; control_url '%s'",
            (service_type, control_url))

        routers.append(
            Router(wan_ip_type=response_headers['ST'],
                   url_scheme=url_parts.scheme,
                   base_url=url_parts.netloc,
                   root_url=url_parts.path,
                   service_type=service_type,
                   control_url=control_url))

        log.add_debug("UPnP: Added device to list")
Example #23
0
    def add_result_list(self,
                        result_list,
                        user,
                        country,
                        inqueue,
                        ulspeed,
                        h_speed,
                        h_queue,
                        color,
                        private=False):
        """ Adds a list of search results to the treeview. Lists can either contain publicly or
        privately shared files. """

        update_ui = False

        for result in result_list:
            if self.num_results_found >= self.max_limit:
                self.max_limited = True
                break

            fullpath = result[1]
            fullpath_lower = fullpath.lower()

            if any(word in fullpath_lower
                   for word in self.searchterm_words_ignore):
                # Filter out results with filtered words (e.g. nicotine -music)
                log.add_debug((
                    "Filtered out excluded search result %(filepath)s from user %(user)s for "
                    "search term \"%(query)s\""), {
                        "filepath": fullpath,
                        "user": user,
                        "query": self.text
                    })
                continue

            if not any(word in fullpath_lower
                       for word in self.searchterm_words_include):
                # Certain users may send us wrong results, filter out such ones
                log.add_search(
                    _("Filtered out incorrect search result %(filepath)s from user %(user)s for "
                      "search query \"%(query)s\""), {
                          "filepath": fullpath,
                          "user": user,
                          "query": self.text
                      })
                continue

            self.num_results_found += 1
            fullpath_split = fullpath.split('\\')

            if config.sections["ui"]["reverse_file_paths"]:
                # Reverse file path, file name is the first item. next() retrieves the name and removes
                # it from the iterator.
                fullpath_split = reversed(fullpath_split)
                name = next(fullpath_split)

            else:
                # Regular file path, file name is the last item. Retrieve it and remove it from the list.
                name = fullpath_split.pop()

            # Join the resulting items into a folder path
            directory = '\\'.join(fullpath_split)

            size = result[2]
            h_size = human_size(size)
            h_bitrate, bitrate, h_length, length = get_result_bitrate_length(
                size, result[4])

            if private:
                name = _("[PRIVATE]  %s") % name

            is_result_visible = self.append([
                self.num_results_found, user,
                get_flag_icon_name(country), h_speed, h_queue, directory, name,
                h_size, h_bitrate, h_length,
                GObject.Value(GObject.TYPE_UINT, bitrate), fullpath, country,
                GObject.Value(GObject.TYPE_UINT64, size),
                GObject.Value(GObject.TYPE_UINT, ulspeed),
                GObject.Value(GObject.TYPE_UINT64, inqueue),
                GObject.Value(GObject.TYPE_UINT, length),
                GObject.Value(GObject.TYPE_STRING, color)
            ])

            if is_result_visible:
                update_ui = True

        return update_ui
Example #24
0
    def process_search_request(self, searchterm, user, token, direct=False):
        """ Note: since this section is accessed every time a search request arrives several
            times per second, please keep it as optimized and memory sparse as possible! """

        if not searchterm:
            return

        if not self.config.sections["searches"]["search_results"]:
            # Don't return _any_ results when this option is disabled
            return

        if not direct and user == self.core.login_username:
            # We shouldn't send a search response if we initiated the search request,
            # unless we're specifically searching our own username
            return

        maxresults = self.config.sections["searches"]["maxresults"]

        if maxresults == 0:
            return

        # Remember excluded/partial words for later
        excluded_words = []
        partial_words = []

        if '-' in searchterm or '*' in searchterm:
            for word in searchterm.split():
                if len(word) < 1:
                    continue

                if word.startswith('-'):
                    for subword in word.translate(
                            self.translatepunctuation).split():
                        excluded_words.append(subword)

                elif word.startswith('*'):
                    for subword in word.translate(
                            self.translatepunctuation).split():
                        partial_words.append(subword)

        # Strip punctuation
        searchterm_old = searchterm
        searchterm = searchterm.lower().translate(
            self.translatepunctuation).strip()

        if len(searchterm
               ) < self.config.sections["searches"]["min_search_chars"]:
            # Don't send search response if search term contains too few characters
            return

        checkuser, _reason = self.core.network_filter.check_user(user, None)

        if not checkuser:
            return

        if checkuser == 2:
            wordindex = self.share_dbs.get("buddywordindex")
        else:
            wordindex = self.share_dbs.get("wordindex")

        if wordindex is None:
            return

        # Find common file matches for each word in search term
        resultlist = self.create_search_result_list(searchterm, wordindex,
                                                    excluded_words,
                                                    partial_words)

        if not resultlist:
            return

        if checkuser == 2:
            fileindex = self.share_dbs.get("buddyfileindex")
        else:
            fileindex = self.share_dbs.get("fileindex")

        if fileindex is None:
            return

        fileinfos = []
        numresults = min(len(resultlist), maxresults)

        for index in islice(resultlist, numresults):
            fileinfo = fileindex.get(repr(index))

            if fileinfo is not None:
                fileinfos.append(fileinfo)

        if numresults != len(fileinfos):
            log.add_debug(
                ("Error: File index inconsistency while responding to search request \"%(query)s\". "
                 "Expected %(expected_num)i results, but found %(total_num)i results in database."
                 ), {
                     "query": searchterm_old,
                     "expected_num": numresults,
                     "total_num": len(fileinfos)
                 })
            numresults = len(fileinfos)

        if not numresults:
            return

        uploadspeed = self.core.transfers.upload_speed
        queuesize = self.core.transfers.get_upload_queue_size()
        slotsavail = self.core.transfers.allow_new_uploads()
        fifoqueue = self.config.sections["transfers"]["fifoqueue"]

        message = slskmessages.FileSearchResult(None, self.core.login_username,
                                                token, fileinfos, slotsavail,
                                                uploadspeed, queuesize,
                                                fifoqueue)

        self.core.send_message_to_peer(user, message)

        log.add_search(
            _("User %(user)s is searching for \"%(query)s\", found %(num)i results"
              ), {
                  'user': user,
                  'query': searchterm_old,
                  'num': numresults
              })
Example #25
0
    def sendto(self, sock, addr):

        msg = bytes(self)
        sock.sendto(msg, addr)

        log.add_debug("UPnP: SSDP request: %s", msg)
Example #26
0
    def _update_port_mapping(self, listening_port):
        """
        This function supports creating a Port Mapping via the UPnP
        IGDv1 and IGDv2 protocol.

        Any UPnP port mapping done with IGDv2 will expire after a
        maximum of 7 days (lease period), according to the protocol.
        We set the lease period to a shorter 24 hours, and regularly
        renew the port mapping.
        """

        try:
            log.add_debug("UPnP: Creating Port Mapping rule...")

            # Find local IP address
            local_ip_address = self.find_local_ip_address()

            # Find router
            router = self.find_router(local_ip_address)

            if not router:
                raise RuntimeError(_("UPnP is not available on this network"))

            # Perform the port mapping
            log.add_debug(
                "UPnP: Trying to redirect external WAN port %s TCP => %s port %s TCP",
                (listening_port, local_ip_address, listening_port))

            try:
                self._request_port_mapping(
                    router=router,
                    protocol="TCP",
                    public_port=listening_port,
                    private_ip=local_ip_address,
                    private_port=listening_port,
                    mapping_description="NicotinePlus",
                    lease_duration=86400  # Expires in 24 hours
                )

            except Exception as error:
                raise RuntimeError(
                    _("Failed to map the external WAN port: %(error)s") %
                    {"error": error}) from error

        except Exception as error:
            from traceback import format_exc
            log.add(
                _("UPnP: Failed to forward external port %(external_port)s: %(error)s"
                  ), {
                      "external_port": listening_port,
                      "error": error
                  })
            log.add_debug(format_exc())
            return

        log.add(
            _("UPnP: External port %(external_port)s successfully forwarded to local "
              "IP address %(ip_address)s port %(local_port)s"), {
                  "external_port": listening_port,
                  "ip_address": local_ip_address,
                  "local_port": listening_port
              })
Example #27
0
    def get_routers(private_ip=None):

        log.add_debug("UPnP: Discovering... delay=%s seconds",
                      SSDP.response_time_secs)

        # Create a UDP socket and set its timeout
        sock = socket.socket(family=socket.AF_INET,
                             type=socket.SOCK_DGRAM,
                             proto=socket.IPPROTO_UDP)
        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL,
                        SSDP.response_time_secs)
        sock.setblocking(False)

        if private_ip:
            sock.bind((private_ip, 0))

        # Protocol 1
        wan_ip1_sent = False
        wan_ip1 = SSDPRequest("urn:schemas-upnp-org:service:WANIPConnection:1")

        wan_ppp1_sent = False
        wan_ppp1 = SSDPRequest(
            "urn:schemas-upnp-org:service:WANPPPConnection:1")

        wan_igd1_sent = False
        wan_igd1 = SSDPRequest(
            "urn:schemas-upnp-org:device:InternetGatewayDevice:1")

        # Protocol 2
        wan_ip2_sent = False
        wan_ip2 = SSDPRequest("urn:schemas-upnp-org:service:WANIPConnection:2")

        wan_igd2_sent = False
        wan_igd2 = SSDPRequest(
            "urn:schemas-upnp-org:device:InternetGatewayDevice:2")

        routers = []
        time_end = time.time() + SSDP.response_time_secs

        while time.time() < time_end:
            readable, writable, _ = select.select([sock], [sock], [sock], 0)

            for sock in readable:
                msg, _sender = sock.recvfrom(4096)
                SSDP.add_router(routers, SSDPResponse(msg.decode('utf-8')))

            for sock in writable:
                if not wan_ip1_sent:
                    wan_ip1.sendto(sock,
                                   (SSDP.multicast_host, SSDP.multicast_port))
                    log.add_debug("UPnP: Sent M-SEARCH IP request 1")
                    time_end = time.time() + SSDP.response_time_secs
                    wan_ip1_sent = True

                if not wan_ppp1_sent:
                    wan_ppp1.sendto(sock,
                                    (SSDP.multicast_host, SSDP.multicast_port))
                    log.add_debug("UPnP: Sent M-SEARCH PPP request 1")
                    time_end = time.time() + SSDP.response_time_secs
                    wan_ppp1_sent = True

                if not wan_igd1_sent:
                    wan_igd1.sendto(sock,
                                    (SSDP.multicast_host, SSDP.multicast_port))
                    log.add_debug("UPnP: Sent M-SEARCH IGD request 1")
                    time_end = time.time() + SSDP.response_time_secs
                    wan_igd1_sent = True

                if not wan_ip2_sent:
                    wan_ip2.sendto(sock,
                                   (SSDP.multicast_host, SSDP.multicast_port))
                    log.add_debug("UPnP: Sent M-SEARCH IP request 2")
                    time_end = time.time() + SSDP.response_time_secs
                    wan_ip2_sent = True

                if not wan_igd2_sent:
                    wan_igd2.sendto(sock,
                                    (SSDP.multicast_host, SSDP.multicast_port))
                    log.add_debug("UPnP: Sent M-SEARCH IGD request 2")
                    time_end = time.time() + SSDP.response_time_secs
                    wan_igd2_sent = True

            # Cooldown
            time.sleep(0.01)

        log.add_debug("UPnP: %s device(s) detected", str(len(routers)))

        sock.close()
        return routers
Example #28
0
    def load_config(self):

        from pynicotine.utils import load_file

        log_dir = os.path.join(self.data_dir, "logs")
        self.defaults = {
            "server": {
                "server": ("server.slsknet.org", 2242),
                "login": "",
                "passw": "",
                "interface": "",
                "ctcpmsgs": False,
                "autosearch": [],
                "autoreply": "",
                "portrange": (2234, 2239),
                "upnp": True,
                "upnp_interval": 4,
                "auto_connect_startup": True,
                "userlist": [],
                "banlist": [],
                "ignorelist": [],
                "ipignorelist": {},
                "ipblocklist": {},
                "autojoin": ["nicotine"],
                "autoaway": 15,
                "away": False,
                "private_chatrooms": False,
                "command_aliases": {}
            },
            "transfers": {
                "incompletedir":
                os.path.join(self.data_dir, 'incomplete'),
                "downloaddir":
                os.path.join(self.data_dir, 'downloads'),
                "uploaddir":
                os.path.join(self.data_dir, 'received'),
                "usernamesubfolders":
                False,
                "shared": [],
                "buddyshared": [],
                "uploadbandwidth":
                10,
                "uselimit":
                False,
                "usealtlimits":
                False,
                "uploadlimit":
                1000,
                "uploadlimitalt":
                100,
                "downloadlimit":
                0,
                "downloadlimitalt":
                100,
                "preferfriends":
                False,
                "useupslots":
                False,
                "uploadslots":
                2,
                "afterfinish":
                "",
                "afterfolder":
                "",
                "lock":
                True,
                "reverseorder":
                False,
                "fifoqueue":
                False,
                "usecustomban":
                False,
                "limitby":
                True,
                "customban":
                "Banned, don't bother retrying",
                "usecustomgeoblock":
                False,
                "customgeoblock":
                "Sorry, your country is blocked",
                "queuelimit":
                10000,
                "filelimit":
                100,
                "buddysharestrustedonly":
                False,
                "friendsnolimits":
                False,
                "groupdownloads":
                "folder_grouping",
                "groupuploads":
                "folder_grouping",
                "geoblock":
                False,
                "geoblockcc": [""],
                "remotedownloads":
                True,
                "uploadallowed":
                2,
                "autoclear_downloads":
                False,
                "autoclear_uploads":
                False,
                "uploadsinsubdirs":
                True,
                "rescanonstartup":
                True,
                "enablefilters":
                False,
                "downloadregexp":
                "",
                "downloadfilters":
                [["desktop.ini", 1], ["folder.jpg", 1], ["*.url", 1],
                 ["thumbs.db", 1],
                 [
                     "albumart(_{........-....-....-....-............}_)?(_?(large|small))?\\.jpg",
                     0
                 ]],
                "download_doubleclick":
                1,
                "upload_doubleclick":
                1,
                "downloadsexpanded":
                True,
                "uploadsexpanded":
                True
            },
            "userinfo": {
                "descr": "''",
                "pic": ""
            },
            "words": {
                "censored": [],
                "autoreplaced": {
                    "teh ": "the ",
                    "taht ": "that ",
                    "tihng": "thing",
                    "youre": "you're",
                    "jsut": "just",
                    "thier": "their",
                    "tihs": "this"
                },
                "censorfill": "*",
                "censorwords": False,
                "replacewords": False,
                "tab": True,
                "cycle": False,
                "dropdown": False,
                "characters": 3,
                "roomnames": True,
                "buddies": True,
                "roomusers": True,
                "commands": True,
                "aliases": True,
                "onematch": False
            },
            "logging": {
                "debug": False,
                "debugmodes": [],
                "debuglogsdir": os.path.join(log_dir, "debug"),
                "logcollapsed": True,
                "transferslogsdir": os.path.join(log_dir, "transfers"),
                "rooms_timestamp": "%H:%M:%S",
                "private_timestamp": "%Y-%m-%d %H:%M:%S",
                "log_timestamp": "%Y-%m-%d %H:%M:%S",
                "timestamps": True,
                "privatechat": True,
                "chatrooms": True,
                "transfers": False,
                "debug_file_output": False,
                "roomlogsdir": os.path.join(log_dir, "rooms"),
                "privatelogsdir": os.path.join(log_dir, "private"),
                "readroomlogs": True,
                "readroomlines": 15,
                "readprivatelines": 15,
                "rooms": []
            },
            "privatechat": {
                "store": True,
                "users": []
            },
            "columns": {
                "file_search": {},
                "download": {},
                "upload": {},
                "user_browse": {},
                "buddy_list": {},
                "chat_room": {}
            },
            "searches": {
                "expand_searches": True,
                "group_searches": "folder_grouping",
                "maxresults": 50,
                "enable_history": True,
                "history": [],
                "enablefilters": False,
                "filters_visible": False,
                "defilter": ["", "", "", "", 0, ""],
                "filtercc": [],
                "filterin": [],
                "filterout": [],
                "filtersize": [],
                "filterbr": [],
                "filtertype": [],
                "search_results": True,
                "max_displayed_results": 1500,
                "min_search_chars": 3,
                "remove_special_chars": True,
                "private_search_results": True
            },
            "ui": {
                "dark_mode":
                False,
                "header_bar":
                True,
                "icontheme":
                "",
                "chatme":
                "#908e8b",
                "chatremote":
                "",
                "chatlocal":
                "",
                "chathilite":
                "#5288ce",
                "urlcolor":
                "#5288ce",
                "useronline":
                "#16bb5c",
                "useraway":
                "#c9ae13",
                "useroffline":
                "#e04f5e",
                "usernamehotspots":
                True,
                "usernamestyle":
                "bold",
                "textbg":
                "",
                "search":
                "",
                "searchq":
                "GREY",
                "inputcolor":
                "",
                "spellcheck":
                True,
                "exitdialog":
                1,
                "tab_default":
                "",
                "tab_hilite":
                "#497ec2",
                "tab_changed":
                "#497ec2",
                "tab_select_previous":
                True,
                "tabmain":
                "Top",
                "tabrooms":
                "Top",
                "tabprivate":
                "Top",
                "tabinfo":
                "Top",
                "tabbrowse":
                "Top",
                "tabsearch":
                "Top",
                "tab_status_icons":
                True,
                "globalfont":
                "",
                "chatfont":
                "",
                "tabclosers":
                True,
                "searchfont":
                "",
                "listfont":
                "",
                "browserfont":
                "",
                "transfersfont":
                "",
                "last_tab_id":
                "",
                "modes_visible": {
                    "search": True,
                    "downloads": True,
                    "uploads": True,
                    "userbrowse": True,
                    "userinfo": True,
                    "private": True,
                    "chatrooms": True,
                    "interests": True
                },
                "modes_order": [
                    "search", "downloads", "uploads", "userbrowse", "userinfo",
                    "private", "userlist", "chatrooms", "interests"
                ],
                "buddylistinchatrooms":
                "tab",
                "trayicon":
                True,
                "startup_hidden":
                False,
                "filemanager":
                "",
                "speechenabled":
                False,
                "speechprivate":
                "User %(user)s told you: %(message)s",
                "speechrooms":
                "In room %(room)s, user %(user)s said: %(message)s",
                "speechcommand":
                "flite -t $",
                "width":
                1000,
                "height":
                600,
                "xposition":
                -1,
                "yposition":
                -1,
                "maximized":
                True,
                "urgencyhint":
                True,
                "file_path_tooltips":
                True,
                "reverse_file_paths":
                True
            },
            "private_rooms": {
                "rooms": {}
            },
            "urls": {
                "protocols": {}
            },
            "interests": {
                "likes": [],
                "dislikes": []
            },
            "players": {
                "default": "",
                "npothercommand": "",
                "npplayer": "mpris",
                "npformatlist": [],
                "npformat": ""
            },
            "notifications": {
                "notification_window_title": True,
                "notification_tab_colors": False,
                "notification_popup_sound": False,
                "notification_popup_file": True,
                "notification_popup_folder": True,
                "notification_popup_private_message": True,
                "notification_popup_chatroom": False,
                "notification_popup_chatroom_mention": True
            },
            "plugins": {
                "enable": True,
                "enabled": []
            },
            "statistics": {
                "started_downloads": 0,
                "completed_downloads": 0,
                "downloaded_size": 0,
                "started_uploads": 0,
                "completed_uploads": 0,
                "uploaded_size": 0
            }
        }

        self.removed_options = {
            "transfers":
            ("pmqueueddir", "autoretry_downloads", "shownotification",
             "shownotificationperfolder", "prioritize", "sharedownloaddir",
             "geopanic", "enablebuddyshares", "friendsonly",
             "enabletransferbuttons"),
            "server":
            ("lastportstatuscheck", "serverlist", "enc", "fallbackencodings",
             "roomencoding", "userencoding", "firewalled"),
            "ui": ("enabletrans", "mozembed", "open_in_mozembed", "tooltips",
                   "transalpha", "transfilter", "transtint", "soundenabled",
                   "soundtheme", "soundcommand", "tab_colors", "tab_icons",
                   "searchoffline", "chat_hidebuttons", "tab_reorderable",
                   "private_search_results", "private_shares", "labelmain",
                   "labelrooms", "labelprivate", "labelinfo", "labelbrowse",
                   "labelsearch", "notexists", "roomlistcollapsed", "showaway",
                   "decimalsep"),
            "columns":
            ("downloads", "uploads", "search", "search_widths",
             "downloads_columns", "downloads_widths", "uploads_columns",
             "uploads_widths", "userbrowse", "userbrowse_widths", "userlist",
             "userlist_widths", "chatrooms", "chatrooms_widths",
             "download_columns", "download_widths", "upload_columns",
             "upload_widths", "filesearch_columns", "filesearch_widths",
             "hideflags"),
            "searches": ("distrib_timer", "distrib_ignore", "reopen_tabs",
                         "max_stored_results", "re_filter"),
            "userinfo": ("descrutf8"),
            "private_rooms": ("enabled"),
            "logging": ("logsdir"),
            "ticker": ("default", "rooms", "hide"),
            "language": ("language", "setlanguage"),
            "urls": ("urlcatching", "humanizeurls"),
            "notifications": ("notification_tab_icons")
        }

        # Windows specific stuff
        if sys.platform == "win32":
            self.defaults['players']['npplayer'] = 'other'

        # Initialize config with default values
        for key, value in self.defaults.items():
            self.sections[key] = value.copy()

        self.create_config_folder()
        self.create_data_folder()

        load_file(self.filename, self.parse_config)

        # Update config values from file
        self.set_config()

        if sys.platform == "darwin":
            # Disable header bar in macOS for now due to GTK 3 performance issues
            self.sections["ui"]["header_bar"] = False

        # Convert special download folder share to regular share
        if self.sections["transfers"].get("sharedownloaddir", False):
            shares = self.sections["transfers"]["shared"]
            virtual_name = "Downloaded"
            shared_folder = (virtual_name,
                             self.sections["transfers"]["downloaddir"])

            if shared_folder not in shares and virtual_name not in (
                    x[0] for x in shares):
                shares.append(shared_folder)

        # Load command aliases from legacy file
        try:
            if not self.sections["server"][
                    "command_aliases"] and os.path.exists(self.filename +
                                                          ".alias"):
                with open(self.filename + ".alias", 'rb') as file_handle:
                    from pynicotine.utils import RestrictedUnpickler
                    self.sections["server"][
                        "command_aliases"] = RestrictedUnpickler(
                            file_handle, encoding='utf-8').load()

        except Exception:
            pass

        self.config_loaded = True

        from pynicotine.logfacility import log
        log.add_debug("Using configuration: %(file)s", {"file": self.filename})
Example #29
0
    def list(cls, refresh=False):
        """ list finds all devices responding to an SSDP search """

        log.add_debug('UPnP: Discovering... delay=%s seconds',
                      SSDP.response_time_secs)

        # Create a UDP socket and set its timeout
        sock = socket.socket(family=socket.AF_INET,
                             type=socket.SOCK_DGRAM,
                             proto=socket.IPPROTO_UDP)
        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
        sock.setblocking(False)

        # Create the WANIPConnection:1 and WANIPConnection:2 request objects
        headers = {
            'HOST': "{}:{}".format(SSDP.multicast_host, SSDP.multicast_port),
            'ST': None,
            'MAN': '"ssdp:discover"',
            'MX': str(SSDP.response_time_secs)
        }

        # Protocol 1
        wan_ip1_sent = False
        wan_ip1 = SSDP._create_msearch_request(
            'urn:schemas-upnp-org:service:WANIPConnection:1', headers=headers)

        wan_ppp1_sent = False
        wan_ppp1 = SSDP._create_msearch_request(
            'urn:schemas-upnp-org:service:WANPPPConnection:1', headers=headers)

        wan_igd1_sent = False
        wan_igd1 = SSDP._create_msearch_request(
            'urn:schemas-upnp-org:device:InternetGatewayDevice:1',
            headers=headers)

        # Protocol 2
        wan_ip2_sent = False
        wan_ip2 = SSDP._create_msearch_request(
            'urn:schemas-upnp-org:service:WANIPConnection:2', headers=headers)

        wan_igd2_sent = False
        wan_igd2 = SSDP._create_msearch_request(
            'urn:schemas-upnp-org:device:InternetGatewayDevice:2',
            headers=headers)

        inputs = [sock]
        outputs = [sock]

        routers = []
        time_end = time.time() + SSDP.response_time_secs

        while time.time() < time_end:
            _timeout = 1
            readable, writable, _ = select.select(inputs, outputs, inputs,
                                                  _timeout)

            for _sock in readable:
                msg, sender = _sock.recvfrom(SSDP.buffer_size)
                response = SSDPResponse.parse(msg.decode('utf-8'))
                log.add_debug('UPnP: Device search response: %s',
                              bytes(response))

                router = Router.parse_ssdp_response(response, sender)

                if router:
                    routers.append(router)

            for _sock in writable:
                if not wan_ip1_sent:
                    wan_ip1.sendto(_sock,
                                   (SSDP.multicast_host, SSDP.multicast_port))
                    log.add_debug('UPnP: Sent M-SEARCH IP request 1')
                    time_end = time.time() + SSDP.response_time_secs
                    wan_ip1_sent = True

                if not wan_ppp1_sent:
                    wan_ppp1.sendto(_sock,
                                    (SSDP.multicast_host, SSDP.multicast_port))
                    log.add_debug('UPnP: Sent M-SEARCH PPP request 1')
                    time_end = time.time() + SSDP.response_time_secs
                    wan_ppp1_sent = True

                if not wan_igd1_sent:
                    wan_igd1.sendto(_sock,
                                    (SSDP.multicast_host, SSDP.multicast_port))
                    log.add_debug('UPnP: Sent M-SEARCH IGD request 1')
                    time_end = time.time() + SSDP.response_time_secs
                    wan_igd1_sent = True

                if not wan_ip2_sent:
                    wan_ip2.sendto(_sock,
                                   (SSDP.multicast_host, SSDP.multicast_port))
                    log.add_debug('UPnP: Sent M-SEARCH IP request 2')
                    time_end = time.time() + SSDP.response_time_secs
                    wan_ip2_sent = True

                if not wan_igd2_sent:
                    wan_igd2.sendto(_sock,
                                    (SSDP.multicast_host, SSDP.multicast_port))
                    log.add_debug('UPnP: Sent M-SEARCH IGD request 2')
                    time_end = time.time() + SSDP.response_time_secs
                    wan_igd2_sent = True

            # Cooldown
            time.sleep(0.4)

        for r in routers:
            serial_number, control_url, uuid, svc_type = SSDP._get_router_service_description(
                r.url_scheme, r.base_url, r.root_url)

            r.serial_number = serial_number
            r.control_url = control_url
            r.uuid = uuid
            r.svc_type = svc_type

        sock.close()
        log.add_debug('UPnP: %s device(s) detected', str(len(routers)))

        return routers
Example #30
0
    def get_files_list(self, sharestype, mtimes, oldmtimes, oldfiles, oldstreams, rebuild=False):
        """ Get a list of files with their filelength, bitrate and track length in seconds """

        files = {}
        streams = {}
        count = 0
        lastpercent = 0.0

        for folder in mtimes:

            try:
                count += 1

                if self.ui_callback:
                    # Truncate the percentage to two decimal places to avoid sending data to the GUI thread too often
                    percent = float("%.2f" % (float(count) / len(mtimes) * 0.75))

                    if percent > lastpercent and percent <= 1.0:
                        self.ui_callback.set_scan_progress(sharestype, percent)
                        lastpercent = percent

                virtualdir = self.real2virtual(folder)

                if not rebuild and folder in oldmtimes:
                    if mtimes[folder] == oldmtimes[folder]:
                        if os.path.exists(folder):
                            try:
                                files[virtualdir] = oldfiles[virtualdir]
                                streams[virtualdir] = oldstreams[virtualdir]
                                continue
                            except KeyError:
                                log.add_debug(_("Inconsistent cache for '%(vdir)s', rebuilding '%(dir)s'"), {
                                    'vdir': virtualdir,
                                    'dir': folder
                                })
                        else:
                            log.add_debug(_("Dropping missing folder %(dir)s"), {'dir': folder})
                            continue

                files[virtualdir] = []

                for entry in os.scandir(folder):

                    if entry.is_file():
                        filename = entry.name

                        if self.is_hidden(folder, filename):
                            continue

                        # Get the metadata of the file
                        data = self.get_file_info(filename, entry.path, entry)
                        if data is not None:
                            files[virtualdir].append(data)

                streams[virtualdir] = self.get_dir_stream(files[virtualdir])

            except OSError as errtuple:
                log.add(_("Error while scanning folder %(path)s: %(error)s"), {'path': folder, 'error': errtuple})
                continue

        return files, streams