示例#1
0
def _check_complex_addon_entrypoint(report: Report, addon_path,
                                    max_entrypoint_line_count):

    addon_xml_path = os.path.join(addon_path, "addon.xml")
    tree = ET.parse(addon_xml_path).getroot()

    for i in tree.findall("extension"):
        library = i.get("library")

        if library:
            filepath = os.path.join(addon_path, library)

            if not os.path.isdir(filepath):

                if os.path.exists(filepath):
                    lineno = number_of_lines(filepath)
                    if lineno >= max_entrypoint_line_count:
                        report.add(
                            Record(
                                WARNING,
                                "Complex entry point. Check: %s | Counted lines: %d | Lines allowed: %d"
                                %
                                (library, lineno, max_entrypoint_line_count)))

                else:
                    report.add(
                        Record(PROBLEM,
                               "%s Entry point does not exists" % library))
示例#2
0
def _addon_xml_matches_folder(report: Report, addon_path, addon_xml):
    addon = addon_xml.getroot()
    if os.path.basename(
            os.path.normpath(addon_path)) == addon.attrib.get("id"):
        report.add(Record(INFORMATION, "Addon id matches folder name"))
    else:
        report.add(Record(PROBLEM, "Addon id and folder name does not match."))
示例#3
0
def _check_for_legacy_strings_xml(report: Report, addon_path):
    if _find_file_recursive("strings.xml", addon_path) is not None:
        report.add(
            Record(
                PROBLEM,
                "Found strings.xml in folder %s please migrate to strings.po."
                % relative_path(addon_path)))
示例#4
0
def _check_dependencies(report: Report, addon_path, repo_addons):
    deps = _get_users_dependencies(addon_path)
    ignore = [
        'xbmc.json', 'xbmc.gui', 'xbmc.json', 'xbmc.metadata', 'xbmc.python'
    ]

    for required_addon, required_version in deps.items():
        if required_addon not in repo_addons:
            if required_addon not in ignore:
                report.add(
                    Record(
                        PROBLEM,
                        "Required addon %s not available in current repository."
                        % required_addon))
        else:
            available_version = repo_addons[required_addon]

            if LooseVersion(available_version) < LooseVersion(
                    required_version) and (required_addon not in ignore):
                report.add(
                    Record(
                        PROBLEM,
                        "Version mismatch for addon %s. Required: %s, Available: %s "
                        %
                        (required_addon, required_version, available_version)))
示例#5
0
def _find_blacklisted_strings(report: Report, addon_path, problem_list, warning_list, whitelisted_file_types):
    for result in _find_in_file(addon_path, problem_list, whitelisted_file_types):
        report.add(Record(PROBLEM, "Found blacklisted term %s in file %s:%s (%s)"
                          % (result["term"], result["searchfile"], result["linenumber"], result["line"])))

    for result in _find_in_file(addon_path, warning_list, whitelisted_file_types):
        report.add(Record(WARNING, "Found blacklisted term %s in file %s:%s (%s)"
                          % (result["term"], result["searchfile"], result["linenumber"], result["line"])))
示例#6
0
def _check_for_legacy_language_path(report: Report, addon_path):
    language_path = os.path.join(addon_path, "resources", "language")
    if os.path.exists(language_path):
        dirs = next(os.walk(language_path))[1]
        found_warning = False
        for dir in dirs:
            if not found_warning and "resource.language." not in dir:
                report.add(Record(WARNING, "Using the old language directory structure, please move to the new one."))
                found_warning = True
示例#7
0
def _check_for_invalid_xml_files(report: Report, file_index):
    for file in file_index:
        if ".xml" in file["name"]:
            xml_path = os.path.join(file["path"], file["name"])
            try:
                # Just try if we can successfully parse it
                ET.parse(xml_path)
            except ET.ParseError:
                report.add(Record(PROBLEM, "Invalid xml found. %s" % relative_path(xml_path)))
示例#8
0
def _check_for_invalid_json_files(report: Report, file_index):
    for file in file_index:
        if ".json" in file["name"]:
            path = os.path.join(file["path"], file["name"])
            try:
                # Just try if we can successfully parse it
                with open(path) as json_data:
                    json.load(json_data)
            except ValueError:
                report.add(Record(PROBLEM, "Invalid json found. %s" % relative_path(path)))
示例#9
0
def start(addon_path, config=None):
    addon_id = os.path.basename(os.path.normpath(addon_path))
    addon_report = Report(addon_id)

    global REL_PATH
    # Extract common path from addon paths
    # All paths will be printed relative to this path
    REL_PATH = os.path.split(addon_path[:-1])[0]
    addon_xml = _check_addon_xml(addon_report, addon_path)

    if addon_xml is not None:
        if len(addon_xml.findall("*//broken")) == 0:
            file_index = _create_file_index(addon_path)

            _check_for_invalid_xml_files(addon_report, file_index)

            _check_for_invalid_json_files(addon_report, file_index)

            _check_artwork(addon_report, addon_path, addon_xml, file_index)

            if config.is_enabled("check_license_file_exists"):
                # check if license file is existing
                _addon_file_exists(addon_report, addon_path,
                                   "LICENSE\.txt|LICENSE\.md|LICENSE")

            if config.is_enabled("check_legacy_strings_xml"):
                _check_for_legacy_strings_xml(addon_report, addon_path)

            if config.is_enabled("check_legacy_language_path"):
                _check_for_legacy_language_path(addon_report, addon_path)

            # Kodi 18 Leia + deprecations
            if config.is_enabled("check_kodi_leia_deprecations"):
                _find_blacklisted_strings(addon_report, addon_path, [
                    "System.HasModalDialog", "StringCompare", "SubString",
                    "IntegerGreaterThan", "ListItem.ChannelNumber",
                    "ListItem.SubChannelNumber", "MusicPlayer.ChannelNumber",
                    "MusicPlayer.SubChannelNumber",
                    "VideoPlayer.ChannelNumber", "VideoPlayer.SubChannelNumber"
                ], [], [".py", ".xml"])

            # General blacklist
            _find_blacklisted_strings(addon_report, addon_path, [], [], [])

            _check_file_whitelist(addon_report, file_index, addon_path)
        else:
            addon_report.add(
                Record(INFORMATION, "Addon marked as broken - skipping"))

    return addon_report
示例#10
0
def _check_addon_xml(report: Report, addon_path):
    addon_xml_path = os.path.join(addon_path, "addon.xml")
    addon_xml = None
    try:
        _addon_file_exists(report, addon_path, r"addon\.xml")

        addon_xml = ET.parse(addon_xml_path)
        addon = addon_xml.getroot()
        report.add(Record(INFORMATION, "Created by %s" % addon.attrib.get("provider-name")))
        _addon_xml_matches_folder(report, addon_path, addon_xml)
    except ET.ParseError:
        report.add(Record(PROBLEM, "Addon xml not valid, check xml. %s" % relative_path(addon_xml_path)))

    return addon_xml
示例#11
0
def _check_artwork(report: Report, addon_path, addon_xml, file_index):
    # icon, fanart, screenshot - these will also check if the addon.xml links correctly
    _check_image_type(report, "icon", addon_xml, addon_path)
    _check_image_type(report, "fanart", addon_xml, addon_path)
    _check_image_type(report, "screenshot", addon_xml, addon_path)

    # go through all but the above and try to open the image
    for file in file_index:
        if re.match(r"(?!fanart\.jpg|icon\.png).*\.(png|jpg|jpeg|gif)$", file["name"]) is not None:
            image_path = os.path.join(file["path"], file["name"])
            try:
                # Just try if we can successfully open it
                Image.open(image_path)
            except IOError:
                report.add(
                    Record(PROBLEM, "Could not open image, is the file corrupted? %s" % relative_path(image_path)))
示例#12
0
def check_for_new_language_directory_structure(report: Report, addon_path, supported=True):
    language_path = os.path.join(addon_path, "resources", "language")
    if os.path.exists(language_path):
        dirs = next(os.walk(language_path))[1]
        found_warning = False
        for directory in dirs:
            if not found_warning and "resource.language." not in directory and supported:
                report.add(Record(
                    WARNING, "Using the old language directory structure in %s, please move to the new one." %
                    os.path.join(language_path, directory)))
                found_warning = True
            elif not found_warning "resource.language." in directory and not supported:
                report.add(Record(
                    WARNING, "Using the new language directory structure in %s for a Kodi version that does not" \
                             "support it. Please use the old language file struture or move the addon to" \
                             "an upper branch/kodi version." % os.path.join(language_path, directory)))
                found_warning = True
示例#13
0
def main():
    """The entry point to kodi-addon-checker
    """
    load_plugins()
    parser = argparse.ArgumentParser(prog="kodi-addon-checker",
                                     description="Checks Kodi repo for best practices and creates \
                                     problem and warning reports.\r\nIf optional add-on \
                                     directories are provided, check only those add-ons. \
                                     Otherwise, scan current repository and check all add-ons in \
                                     the current directory.")
    parser.add_argument("--version", action="version",
                        version="%(prog)s {version}".format(version=__version__))
    parser.add_argument("dir", type=dir_type, nargs="*", help="optional add-on or repo directories")
    parser.add_argument("--branch", choices=ValidKodiVersions, required=True,
                        help="Target branch name where the checker will resolve dependencies")
    parser.add_argument("--PR", help="Tell if tool is to run on a pull requests or not", action='store_true')
    parser.add_argument("--allow-folder-id-mismatch", help="Allow the addon's folder name and id to mismatch",
                        action="store_true")
    ConfigManager.fill_cmd_args(parser)
    args = parser.parse_args()

    log_file_name = os.path.join(os.getcwd(), "kodi-addon-checker.log")
    Logger.create_logger(log_file_name, __package__)

    all_repo_addons = check_addon.get_all_repo_addons()

    if args.dir:
        # Following report is a wrapper for all sub reports
        report = Report("")
        for directory in args.dir:
            report.add(check_artifact(directory, args, all_repo_addons))
    else:
        report = check_artifact(os.getcwd(), args, all_repo_addons)

    if report.problem_count > 0:
        report.add(Record(PROBLEM, "We found %s problems and %s warnings, please check the logfile." %
                          (report.problem_count, report.warning_count)))
        sys.exit(1)
    elif report.warning_count > 0:
        report.add(Record(WARNING, "We found no problems and %s warnings, please check the logfile." %
                          report.warning_count))
    else:
        report.add(Record(INFORMATION, "We found no problems and no warnings, please enjoy your day."))
示例#14
0
def _check_file_whitelist(report: Report, file_index, addon_path):
    if ".module." in addon_path:
        report.add(Record(INFORMATION, "Module skipping whitelist"))
        return

    whitelist = (
        r"\.?(py|xml|gif|png|jpg|jpeg|md|txt|po|json|gitignore|markdown|yml|"
        r"rst|ini|flv|wav|mp4|html|css|lst|pkla|g|template|in|cfg|xsd|directory|"
        r"help|list|mpeg|pls|info|ttf|xsp|theme|yaml|dict|crt)?$"
    )

    for file in file_index:
        file_parts = file["name"].rsplit(".")
        # Only check file endings if there are file endings...
        # This will not check "README" or ".gitignore"
        if len(file_parts) > 1:
            file_ending = "." + file_parts[len(file_parts) - 1]
            if re.match(whitelist, file_ending, re.IGNORECASE) is None:
                report.add(Record(WARNING,
                                  "Found non whitelisted file ending in filename %s" %
                                  relative_path(os.path.join(file["path"], file["name"]))))
示例#15
0
def start(addon_path, repo_addons, config=None):
    addon_id = os.path.basename(os.path.normpath(addon_path))
    addon_report = Report(addon_id)
    addon_report.add(Record(INFORMATION, "Checking add-on %s" % addon_id))

    global REL_PATH
    # Extract common path from addon paths
    # All paths will be printed relative to this path
    REL_PATH = os.path.split(addon_path[:-1])[0]
    addon_xml = _check_addon_xml(addon_report, addon_path)

    if addon_xml is not None:
        if len(addon_xml.findall("*//broken")) == 0:
            file_index = _create_file_index(addon_path)

            _check_dependencies(addon_report, addon_path, repo_addons)

            _check_for_invalid_xml_files(addon_report, file_index)

            _check_for_invalid_json_files(addon_report, file_index)

            _check_artwork(addon_report, addon_path, addon_xml, file_index)

            max_entrypoint_line_count = config.configs.get("max_entrypoint_line_count", 15)
            _check_complex_addon_entrypoint(addon_report, addon_path, max_entrypoint_line_count)

            if config.is_enabled("check_license_file_exists"):
                # check if license file is existing
                _addon_file_exists(addon_report, addon_path, r"^LICENSE\.txt|LICENSE\.md|LICENSE$")

            _check_for_legacy_strings_xml(addon_report, addon_path)

            if branch_name not in ['gotham', 'helix']:
                check_for_new_language_directory_structure(addon_report, addon_path)
            else:
                check_for_new_language_directory_structure(addon_report, addon_path, supported=False)

            # Kodi 18 Leia + deprecations
            if config.is_enabled("check_kodi_leia_deprecations"):
                _find_blacklisted_strings(addon_report, addon_path,
                                          ["System.HasModalDialog", "StringCompare", "SubString", "IntegerGreaterThan",
                                           "ListItem.ChannelNumber", "ListItem.SubChannelNumber",
                                           "MusicPlayer.ChannelNumber",
                                           "MusicPlayer.SubChannelNumber", "VideoPlayer.ChannelNumber",
                                           "VideoPlayer.SubChannelNumber"],
                                          [], [".py", ".xml"])

            # General blacklist
            _find_blacklisted_strings(addon_report, addon_path, [], [], [])

            _check_file_whitelist(addon_report, file_index, addon_path)
        else:
            addon_report.add(Record(INFORMATION, "Addon marked as broken - skipping"))

    return addon_report
示例#16
0
 def setUp(self):
     load_plugins()
     ReportManager.enable(["array"])
     self.report = Report("")
示例#17
0
def _addon_file_exists(report: Report, addon_path, file_name):
    if _find_file(file_name, addon_path) is None:
        report.add(Record(PROBLEM, "Not found %s in folder %s" % (file_name, relative_path(addon_path))))
示例#18
0
def _check_image_type(report: Report, image_type, addon_xml, addon_path):
    images = addon_xml.findall("*//" + image_type)

    icon_fallback = False
    fanart_fallback = False
    if not images and image_type == "icon":
        icon_fallback = True
        image = type('image', (object,),
                     {'text': 'icon.png'})()
        images.append(image)
    elif not images and image_type == "fanart":
        skip_addon_types = [".module.", "metadata.", "context.", ".language."]
        for addon_type in skip_addon_types:
            if addon_type in addon_path:
                break
        else:
            fanart_fallback = True
            image = type('image', (object,),
                         {'text': 'fanart.jpg'})()
            images.append(image)

    for image in images:
        if image.text:
            filepath = os.path.join(addon_path, image.text)
            if os.path.isfile(filepath):
                report.add(Record(INFORMATION, "Image %s exists" % image_type))
                try:
                    im = Image.open(filepath)
                    width, height = im.size

                    if image_type == "icon":
                        if has_transparency(im):
                            report.add(Record(PROBLEM, "Icon.png should be solid. It has transparency."))
                        if (width != 256 and height != 256) and (width != 512 and height != 512):
                            report.add(Record(PROBLEM, "Icon should have either 256x256 or 512x512 but it has %sx%s" % (
                                width, height)))
                        else:
                            report.add(
                                Record(INFORMATION, "%s dimensions are fine %sx%s" % (image_type, width, height)))
                    elif image_type == "fanart":
                        fanart_sizes = [(1280, 720), (1920, 1080), (3840, 2160)]
                        fanart_sizes_str = " or ".join(["%dx%d" % (w, h) for w, h in fanart_sizes])
                        if (width, height) not in fanart_sizes:
                            report.add(Record(PROBLEM, "Fanart should have either %s but it has %sx%s" % (
                                fanart_sizes_str, width, height)))
                        else:
                            report.add(Record(INFORMATION, "%s dimensions are fine %sx%s" %
                                              (image_type, width, height)))
                    else:
                        # screenshots have no size definitions
                        pass
                except IOError:
                    report.add(
                        Record(PROBLEM, "Could not open image, is the file corrupted? %s" % relative_path(filepath)))

            else:
                # if it's a fallback path addons.xml should still be able to
                # get build
                if fanart_fallback or icon_fallback:
                    if icon_fallback:
                        report.add(Record(INFORMATION, "You might want to add a icon"))
                    elif fanart_fallback:
                        report.add(Record(INFORMATION, "You might want to add a fanart"))
                # it's no fallback path, so building addons.xml will crash -
                # this is a problem ;)
                else:
                    report.add(Record(PROBLEM, "%s does not exist at specified path." % image_type))
        else:
            report.add(Record(WARNING, "Empty image tag found for %s" % image_type))
示例#19
0
def check_repo(config, repo_path, parameters):
    repo_report = Report(repo_path)
    repo_report.add(Record(INFORMATION, "Checking repository %s" % repo_path))
    if len(parameters) == 0:
        toplevel_folders = sorted(next(os.walk(repo_path))[1])
    else:
        toplevel_folders = sorted(parameters)

    for addon_folder in toplevel_folders:
        if addon_folder[0] != '.':
            repo_report.add(
                Record(INFORMATION, "Checking add-on %s" % addon_folder))
            addon_path = os.path.join(repo_path, addon_folder)
            addon_report = check_addon.start(addon_path, config)
            repo_report.add(addon_report)

    if repo_report.problem_count > 0:
        repo_report.add(
            Record(
                PROBLEM,
                "We found %s problems and %s warnings, please check the logfile."
                % (repo_report.problem_count, repo_report.warning_count)))
    elif repo_report.warning_count > 0:
        repo_report.add(
            Record(
                WARNING,
                "We found %s problems and %s warnings, please check the logfile."
                % (repo_report.problem_count, repo_report.warning_count)))
    else:
        repo_report.add(
            Record(
                INFORMATION,
                "We found no problems and no warnings, please enjoy your day.")
        )

    ReportManager.report(repo_report)
示例#20
0
 def setUp(self):
     self.path = join(HERE, "test_data", "PO_files")
     load_plugins()
     ReportManager.enable(["array"])
     self.report = Report("")
     self.report_matches = ("ERROR: Invalid PO file")