summaryrefslogtreecommitdiffstats
path: root/appinfo/info.xml
blob: 4b5070f669ada4038de0dfc528c0d2d1c529cae5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<?xml version="1.0"?>
<info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
    <id>news</id>
    <name>News</name>
    <summary>An RSS/Atom feed reader</summary>
    <description><![CDATA[📰 A RSS/Atom Feed reader App for Nextcloud

- 📲 Synchronize your feeds with multiple mobile or desktop [clients](https://nextcloud.github.io/news/clients/)
- 🔄 Automatic updates of your news feeds
- 🆓 Free and open source under AGPLv3, no ads or premium functions

**System Cron is currently required for this app to work**

Requirements can be found [here](https://nextcloud.github.io/news/install/#dependencies)

The Changelog is available [here](https://github.com/nextcloud/news/blob/master/CHANGELOG.md)

Create a [bug report](https://github.com/nextcloud/news/issues/new/choose)

Create a [feature request](https://github.com/nextcloud/news/discussions/new)

Report a [feed issue](https://github.com/nextcloud/news/discussions/new)
    ]]></description>
    <version>21.2.0</version>
    <licence>agpl</licence>
    <author>Benjamin Brahmer</author>
    <author>Sean Molenaar</author>
    <author>Bernhard Posselt (former)</author>
    <author>Alessandro Cosentino (former)</author>
    <author>Jan-Christoph Borchardt (former)</author>
    <namespace>News</namespace>
    <documentation>
        <user>https://nextcloud.github.io/news/</user>
        <admin>https://nextcloud.github.io/news/admin/</admin>
        <developer>https://nextcloud.github.io/news/developer</developer>
    </documentation>
    <category>multimedia</category>
    <website>https://github.com/nextcloud/news</website>
    <discussion>https://github.com/nextcloud/news/discussions</discussion>
    <bugs>https://github.com/nextcloud/news/issues</bugs>
    <repository type="git">https://github.com/nextcloud/news.git</repository>
    <screenshot small-thumbnail="https://raw.githubusercontent.com/nextcloud/news/master/screenshots/1-small.png">https://raw.githubusercontent.com/nextcloud/news/master/screenshots/1.png</screenshot>
    <screenshot small-thumbnail="https://raw.githubusercontent.com/nextcloud/news/master/screenshots/2-small.png">https://raw.githubusercontent.com/nextcloud/news/master/screenshots/2.png</screenshot>
    <screenshot small-thumbnail="https://raw.githubusercontent.com/nextcloud/news/master/screenshots/3-small.png">https://raw.githubusercontent.com/nextcloud/news/master/screenshots/3.png</screenshot>
    <dependencies>
        <php min-version="7.4" min-int-size="64"/>
        <database min-version="10">pgsql</database>
        <database>sqlite</database>
        <database min-version="8.0">mysql</database>
        <lib min-version="2.7.8">libxml</lib>
        <lib>curl</lib>
        <lib>dom</lib>
        <lib>SimpleXML</lib>
        <lib>iconv</lib>
        <lib>json</lib>

        <owncloud max-version="0" min-version="0"/>
        <nextcloud min-version="24" max-version="26"/>
    </dependencies>

    <background-jobs>
        <job>OCA\News\Cron\UpdaterJob</job>
    </background-jobs>

    <repair-steps>
        <post-migration>
                <step>OCA\News\Migration\RemoveUnusedJob</step>
        </post-migration>
    </repair-steps>

    <commands>
        <command>OCA\News\Command\ExploreGenerator</command>
        <command>OCA\News\Command\ShowFeed</command>
        <command>OCA\News\Command\Updater\UpdateFeed</command>
        <command>OCA\News\Command\Updater\UpdateUser</command>
        <command>OCA\News\Command\Updater\BeforeUpdate</command>
        <command>OCA\News\Command\Updater\AfterUpdate</command>
        <command>OCA\News\Command\Config\FolderList</command>
        <command>OCA\News\Command\Config\FolderAdd</command>
        <command>OCA\News\Command\Config\FolderDelete</command>
        <command>OCA\News\Command\Config\FeedList</command>
        <command>OCA\News\Command\Config\FeedAdd</command>
        <command>OCA\News\Command\Config\FeedDelete</command>
        <command>OCA\News\Command\Config\FeedDelete</command>
        <command>OCA\News\Command\Config\OpmlExport</command>
        <command>OCA\News\Command\Debug\ItemList</command>
        <command>OCA\News\Command\Debug\FolderItemList</command>
        <command>OCA\News\Command\Debug\FeedItemList</command>
        <command>OCA\News\Command\Debug\ItemRead</command>
        <command>OCA\News\Command\Debug\FolderRead</command>
        <command>OCA\News\Command\Debug\FeedRead</command>
    </commands>

    <settings>
        <admin>OCA\News\Settings\AdminSettings</admin>
        <admin-section>OCA\News\Settings\AdminSection</admin-section>
    </settings>

    <navigations>
        <navigation>
            <name>News</name>
            <route>news.page.index</route>
        </navigation>
    </navigations>
</info>
hlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#!/usr/bin/env bash
'''':; exec "$(command -v python || command -v python3 || command -v python2 || echo "ERROR python IS NOT AVAILABLE IN THIS SYSTEM")" "$0" "$@" # '''
# -*- coding: utf-8 -*-

# Description: netdata python modules supervisor
# Author: Pawel Krupa (paulfantom)

import os
import sys
import time
import threading

# -----------------------------------------------------------------------------
# globals & environment setup
# https://github.com/firehol/netdata/wiki/External-Plugins#environment-variables
MODULE_EXTENSION = ".chart.py"
BASE_CONFIG = {'update_every': os.getenv('NETDATA_UPDATE_EVERY', 1),
               'priority': 90000,
               'retries': 10}

MODULES_DIR = os.path.abspath(os.getenv('NETDATA_PLUGINS_DIR',
                                        os.path.dirname(__file__)) + "/../python.d") + "/"
CONFIG_DIR = os.getenv('NETDATA_CONFIG_DIR', "/etc/netdata/")
# directories should end with '/'
if CONFIG_DIR[-1] != "/":
    CONFIG_DIR += "/"
sys.path.append(MODULES_DIR + "python_modules")

PROGRAM = os.path.basename(__file__).replace(".plugin", "")
DEBUG_FLAG = False
OVERRIDE_UPDATE_EVERY = False

# -----------------------------------------------------------------------------
# custom, third party and version specific python modules management
import msg

try:
    assert sys.version_info >= (3, 1)
    import importlib.machinery
    PY_VERSION = 3
    # change this hack below if we want PY_VERSION to be used in modules
    # import builtins
    # builtins.PY_VERSION = 3
    msg.info('Using python v3')
except (AssertionError, ImportError):
    try:
        import imp

        # change this hack below if we want PY_VERSION to be used in modules
        # import __builtin__
        # __builtin__.PY_VERSION = 2
        PY_VERSION = 2
        msg.info('Using python v2')
    except ImportError:
        msg.fatal('Cannot start. No importlib.machinery on python3 or lack of imp on python2')
# try:
#     import yaml
# except ImportError:
#     msg.fatal('Cannot find yaml library')
try:
    if PY_VERSION == 3:
        import pyyaml3 as yaml
    else:
        import pyyaml2 as yaml
except ImportError:
    msg.fatal('Cannot find yaml library')


class PythonCharts(object):
    """
    Main class used to control every python module.
    """

    def __init__(self,
                 modules=None,
                 modules_path='../python.d/',
                 modules_configs='../conf.d/',
                 modules_disabled=None):
        """
        :param modules: list
        :param modules_path: str
        :param modules_configs: str
        :param modules_disabled: list
        """

        if modules is None:
            modules = []
        if modules_disabled is None:
            modules_disabled = []

        self.first_run = True
        # set configuration directory
        self.configs = modules_configs

        # load modules
        loaded_modules = self._load_modules(modules_path, modules, modules_disabled)

        # load configuration files
        configured_modules = self._load_configs(loaded_modules)

        # good economy and prosperity:
        self.jobs = self._create_jobs(configured_modules)  # type: list

        # enable timetable override like `python.d.plugin mysql debug 1`
        if DEBUG_FLAG and OVERRIDE_UPDATE_EVERY:
            for job in self.jobs:
                job.create_timetable(BASE_CONFIG['update_every'])

    @staticmethod
    def _import_module(path, name=None):
        """
        Try to import module using only its path.
        :param path: str
        :param name: str
        :return: object
        """

        if name is None:
            name = path.split('/')[-1]
            if name[-len(MODULE_EXTENSION):] != MODULE_EXTENSION:
                return None
            name = name[:-len(MODULE_EXTENSION)]
        try:
            if PY_VERSION == 3:
                return importlib.machinery.SourceFileLoader(name, path).load_module()
            else:
                return imp.load_source(name, path)
        except Exception as e:
            msg.error("Problem loading", name, str(e))
            return None

    def _load_modules(self, path, modules, disabled):
        """
        Load modules from 'modules' list or dynamically every file from 'path' (only .chart.py files)
        :param path: str
        :param modules: list
        :param disabled: list
        :return: list
        """

        # check if plugin directory exists
        if not os.path.isdir(path):
            msg.fatal("cannot find charts directory ", path)

        # load modules
        loaded = []
        if len(modules) > 0:
            for m in modules:
                if m in disabled:
                    continue
                mod = self._import_module(path + m + MODULE_EXTENSION)
                if mod is not None:
                    loaded.append(mod)
                else:  # exit if plugin is not found
                    msg.fatal('no modules found.')
        else:
            # scan directory specified in path and load all modules from there
            names = os.listdir(path)
            for mod in names:
                if mod.replace(MODULE_EXTENSION, "") in disabled:
                    msg.error(mod + ": disabled module ", mod.replace(MODULE_EXTENSION, ""))
                    continue
                m = self._import_module(path + mod)
                if m is not None:
                    msg.debug(mod + ": loading module '" + path + mod + "'")
                    loaded.append(m)
        return loaded

    def _load_configs(self, modules):
        """
        Append configuration in list named `config` to every module.
        For multi-job modules `config` list is created in _parse_config,
        otherwise it is created here based on BASE_CONFIG prototype with None as identifier.
        :param modules: list
        :return: list
        """
        for mod in modules:
            configfile = self.configs + mod.__name__ + ".conf"
            if os.path.isfile(configfile):
                msg.debug(mod.__name__ + ": loading module configuration: '" + configfile + "'")
                try:
                    if not hasattr(mod, 'config'):
                        mod.config = {}
                    setattr(mod,
                            'config',
                            self._parse_config(mod, read_config(configfile)))
                except Exception as e:
                    msg.error(mod.__name__ + ": cannot parse configuration file '" + configfile + "':", str(e))
            else:
                msg.error(mod.__name__ + ": configuration file '" + configfile + "' not found. Using defaults.")
                # set config if not found
                if not hasattr(mod, 'config'):
                    msg.debug(mod.__name__ + ": setting configuration for only one job")
                    mod.config = {None: {}}
                    for var in BASE_CONFIG:
                        try:
                            mod.config[None][var] = getattr(mod, var)
                        except AttributeError:
                            mod.config[None][var] = BASE_CONFIG[var]
        return modules

    @staticmethod
    def _parse_config(module, config):
        """
        Parse configuration file or extract configuration from module file.
        Example of returned dictionary:
            config = {'name': {
                            'update_every': 2,
                            'retries': 3,
                            'priority': 30000
                            'other_val': 123}}
        :param module: object
        :param config: dict
        :return: dict
        """
        if config is None:
            config = {}
        # get default values
        defaults = {}
        msg.debug(module.__name__ + ": reading configuration")
        for key in BASE_CONFIG:
            try:
                # get defaults from module config
                defaults[key] = int(config.pop(key))
            except (KeyError, ValueError):
                try:
                    # get defaults from module source code
                    defaults[key] = getattr(module, key)
                except (KeyError, ValueError, AttributeError):
                    # if above failed, get defaults from global dict
                    defaults[key] = BASE_CONFIG[key]

        # check if there are dict in config dict
        many_jobs = False
        for name in config:
            if type(config[name]) is dict:
                many_jobs = True
                break

        # assign variables needed by supervisor to every job configuration
        if many_jobs:
            for name in config:
                for key in defaults:
                    if key not in config[name]:
                        config[name][key] = defaults[key]
        # if only one job is needed, values doesn't have to be in dict (in YAML)
        else:
            config = {None: config.copy()}
            config[None].update(defaults)

        # return dictionary of jobs where every job has BASE_CONFIG variables
        return config

    @staticmethod
    def _create_jobs(modules):
        """
        Create jobs based on module.config dictionary and module.Service class definition.
        :param modules: list
        :return: list
        """
        jobs = []
        for module in modules:
            for name in module.config:
                # register a new job
                conf = module.config[name]
                try:
                    job = module.Service(configuration=conf, name=name)
                except Exception as e:
                    msg.error(module.__name__ +
                              ("/" + str(name) if name is not None else "") +
                              ": cannot start job: '" +
                              str(e))
                    return None
                else:
                    # set chart_name (needed to plot run time graphs)
                    job.chart_name = module.__name__
                    if name is not None:
                        job.chart_name += "_" + name
                jobs.append(job)
                msg.debug(module.__name__ + ("/" + str(name) if name is not None else "") + ": job added")

        return [j for j in jobs if j is not None]

    def _stop(self,