├── .gitignore ├── Makefile ├── README.md ├── feedindicator.bash-completion ├── feedindicator ├── __init__.py ├── __main__.py ├── config │ ├── __init__.py │ ├── constants.py │ └── manager.py ├── dialogs │ ├── __init__.py │ ├── about.py │ ├── add_feed.py │ └── preferences.py ├── feedindicator.py ├── feeds.py ├── indicator.py ├── locale │ ├── de_DE │ │ └── LC_MESSAGES │ │ │ ├── feedindicator.mo │ │ │ └── feedindicator.po │ ├── ru │ │ └── LC_MESSAGES │ │ │ ├── feedindicator.mo │ │ │ └── feedindicator.po │ └── tr │ │ └── LC_MESSAGES │ │ ├── feedindicator.mo │ │ └── feedindicator.po ├── threads │ ├── __init__.py │ └── feeds.py └── utils │ ├── __init__.py │ ├── autostart.py │ ├── core.py │ ├── db.py │ ├── sqlite.py │ └── version.py └── icons ├── active.png ├── attention.png ├── logo-128x128.png └── logo-48x48.png /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | bin 3 | build 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BASH_COMPLETION_DIR=/usr/share/bash-completion/completions/ 2 | BIN_DIR=/usr/bin/ 3 | DOC_DIR=/usr/share/doc/feedindicator/ 4 | MAN_DIR=/usr/share/man/man1/ 5 | SHARE_DIR=/usr/share/feedindicator/ 6 | 7 | 8 | all: bin/feedindicator bin/feedindicator.desktop 9 | 10 | 11 | bin: 12 | @mkdir bin 13 | 14 | 15 | build: 16 | @mkdir build 17 | 18 | 19 | build/package/DEBIAN: build 20 | @mkdir -p build/package/DEBIAN 21 | 22 | 23 | bin/feedindicator: bin 24 | @echo "#!/usr/bin/env bash\n" > bin/feedindicator 25 | @echo "cd $(SHARE_DIR)" >> bin/feedindicator 26 | @echo "python3 -m feedindicator \$$@" >> bin/feedindicator 27 | @chmod a+x bin/feedindicator 28 | 29 | 30 | bin/feedindicator.desktop: bin 31 | @echo "[Desktop Entry]" > bin/feedindicator.desktop 32 | @echo "Version=2.0.0" >> bin/feedindicator.desktop 33 | @echo "Type=Application" >> bin/feedindicator.desktop 34 | @echo "Terminal=false" >> bin/feedindicator.desktop 35 | @echo "Exec=feedindicator" >> bin/feedindicator.desktop 36 | @echo "Icon=feedindicator" >> bin/feedindicator.desktop 37 | @echo "Name=Feedindicator" >> bin/feedindicator.desktop 38 | @echo "Comment=A RSS feed reader for the indicator area." >> bin/feedindicator.desktop 39 | @echo "Categories=Internet;Network;" >> bin/feedindicator.desktop 40 | @chmod a+x bin/feedindicator.desktop 41 | 42 | 43 | build/package/DEBIAN/md5sums: bin/feedindicator bin/feedindicator.desktop build/copyright build/changelog build/feedindicator.1 build/package/DEBIAN 44 | @mkdir -m 755 -p build/package$(BASH_COMPLETION_DIR) 45 | @mkdir -m 755 -p build/package$(BIN_DIR) 46 | @mkdir -m 755 -p build/package$(DOC_DIR) 47 | @mkdir -m 755 -p build/package$(MAN_DIR) 48 | @mkdir -m 755 -p build/package$(SHARE_DIR) 49 | @find build/package -type d -exec chmod 755 {} \; 50 | 51 | @cp -r bin/feedindicator build/package$(BIN_DIR) 52 | @chmod 755 build/package$(BIN_DIR)feedindicator 53 | @cp bin/feedindicator.desktop build/package$(SHARE_DIR) 54 | @chmod 755 build/package$(SHARE_DIR)feedindicator.desktop 55 | @cp -r feedindicator build/package$(SHARE_DIR) 56 | @cp -r icons build/package$(SHARE_DIR) 57 | @find build/package$(SHARE_DIR) -type f -exec chmod 644 {} \; 58 | @find build/package$(SHARE_DIR) -type d -exec chmod 755 {} \; 59 | @cp feedindicator.bash-completion build/package$(BASH_COMPLETION_DIR) 60 | @chmod 644 build/package$(BASH_COMPLETION_DIR)feedindicator.bash-completion 61 | 62 | @cat build/feedindicator.1 | gzip -n9 > build/package$(MAN_DIR)feedindicator.1.gz 63 | @chmod 644 build/package$(MAN_DIR)feedindicator.1.gz 64 | 65 | @cat build/changelog | gzip -n9 > build/package$(DOC_DIR)changelog.gz 66 | @chmod 644 build/package$(DOC_DIR)changelog.gz 67 | 68 | @cp build/copyright build/package$(DOC_DIR)copyright 69 | @chmod 644 build/package$(DOC_DIR)copyright 70 | 71 | @mkdir -p build/package/DEBIAN 72 | @md5sum `find build/package -type f -not -path "*DEBIAN*"` > build/md5sums 73 | @sed -e "s/build\/package\///" build/md5sums > build/package/DEBIAN/md5sums 74 | @chmod 644 build/package/DEBIAN/md5sums 75 | 76 | 77 | build/package/DEBIAN/control: build/package/DEBIAN/md5sums 78 | @echo "Package: feedindicator" > build/package/DEBIAN/control 79 | @echo "Version: `grep "__version_info__ =" feedindicator/__init__.py | grep -oE "[0-9]+, [0-9]+, [0-9]+" | sed -e "s/, /./g"`" >> build/package/DEBIAN/control 80 | @echo "Section: web" >> build/package/DEBIAN/control 81 | @echo "Priority: optional" >> build/package/DEBIAN/control 82 | @echo "Architecture: all" >> build/package/DEBIAN/control 83 | @echo "Depends: python3 (>= 3), python3-feedparser, python3-gi, python3-configobj, hicolor-icon-theme, indicator-application, xdg-utils" >> build/package/DEBIAN/control 84 | @echo "Installed-Size: `du -csk build/package/usr | grep -oE "[0-9]+\stotal" | cut -f 1`" >> build/package/DEBIAN/control 85 | @echo "Maintainer: Nathanael Philipp " >> build/package/DEBIAN/control 86 | @echo "Homepage: https://github.com/jnphilipp/Feedindicator" >> build/package/DEBIAN/control 87 | @echo "Description: RSS feed updates in the indicator area\n Editable, sortable list of feed URLs.\n Notification popups of new feed items.\n Adjustable update timer." >> build/package/DEBIAN/control 88 | 89 | 90 | build/package/DEBIAN/postinst: build/package/DEBIAN 91 | @echo "#!/bin/sh -e" > build/package/DEBIAN/postinst 92 | @echo "xdg-icon-resource install --theme hicolor --novendor --size 512 $(SHARE_DIR)icons/active.png feedindicator-active" >> build/package/DEBIAN/postinst 93 | @echo "xdg-icon-resource install --theme hicolor --novendor --size 512 $(SHARE_DIR)icons/attention.png feedindicator-attention" >> build/package/DEBIAN/postinst 94 | @echo "xdg-icon-resource install --theme hicolor --novendor --size 128 --context apps $(SHARE_DIR)icons/logo-128x128.png feedindicator" >> build/package/DEBIAN/postinst 95 | @echo "xdg-icon-resource install --theme hicolor --novendor --size 48 --context apps $(SHARE_DIR)icons/logo-48x48.png feedindicator" >> build/package/DEBIAN/postinst 96 | @echo "xdg-desktop-menu install --novendor $(SHARE_DIR)feedindicator.desktop" >> build/package/DEBIAN/postinst 97 | @chmod 755 build/package/DEBIAN/postinst 98 | 99 | 100 | build/package/DEBIAN/prerm: build/package/DEBIAN 101 | @echo "#!/bin/sh -e" > build/package/DEBIAN/prerm 102 | @echo "xdg-icon-resource uninstall --theme hicolor --novendor --size 512 feedindicator-active" >> build/package/DEBIAN/prerm 103 | @echo "xdg-icon-resource uninstall --theme hicolor --novendor --size 512 feedindicator-attention" >> build/package/DEBIAN/prerm 104 | @echo "xdg-icon-resource uninstall --theme hicolor --novendor --size 128 --context apps feedindicator" >> build/package/DEBIAN/prerm 105 | @echo "xdg-icon-resource uninstall --theme hicolor --novendor --size 48 --context apps feedindicator" >> build/package/DEBIAN/prerm 106 | @echo "xdg-desktop-menu uninstall --novendor feedindicator.desktop" >> build/package/DEBIAN/prerm 107 | @chmod 755 build/package/DEBIAN/prerm 108 | 109 | 110 | build/copyright: build 111 | @echo "Upstream-Name: feedindicator\nSource: https://github.com/jnphilipp/Feedindicator\n\nFiles: *\nCopyright: Copyright 2010-2017 Dave Gardner , Michael Judge , Nicolas Raoul , Nathanael Philipp (jnphilipp) \nLicense: GPL-3+\n This program is free software; you can redistribute it\n and/or modify it under the terms of the GNU General Public\n License as published by the Free Software Foundation; either\n version 3 of the License, or (at your option) any later\n version.\n .\n This program is distributed in the hope that it will be\n useful, but WITHOUT ANY WARRANTY; without even the implied\n warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR\n PURPOSE. See the GNU General Public License for more\n details.\n .\n You should have received a copy of the GNU General Public\n License along with this package; if not, write to the Free\n Software Foundation, Inc., 51 Franklin St, Fifth Floor,\n Boston, MA 02110-1301 USA\n .\n On Debian systems, the full text of the GNU General Public\n License version 3 can be found in the file\n '/usr/share/common-licenses/GPL-3'." > build/copyright 112 | 113 | 114 | build/changelog: build 115 | @git log --oneline --decorate > build/changelog 116 | 117 | 118 | build/feedindicator.1: build 119 | @help2man -n "feedindicator - A RSS feed reader for the indicator area." feedindicator > build/feedindicator.1 120 | 121 | 122 | install: bin/feedindicator bin/feedindicator.desktop build/copyright build/changelog build/feedindicator.1 123 | @apt install python3 python3-gi python3-feedparser python3-configobj hicolor-icon-theme indicator-application xdg-utils 124 | @xdg-icon-resource install --theme hicolor --novendor --size 512 icons/active.png feedindicator-active 125 | @xdg-icon-resource install --theme hicolor --novendor --size 512 icons/attention.png feedindicator-attention 126 | @xdg-icon-resource install --theme hicolor --novendor --size 128 --context apps icons/logo-128x128.png feedindicator 127 | @xdg-icon-resource install --theme hicolor --novendor --size 48 --context apps icons/logo-48x48.png feedindicator 128 | @xdg-desktop-menu install --novendor bin/feedindicator.desktop 129 | @mkdir -p $(SHARE_DIR) 130 | @cp -r feedindicator $(SHARE_DIR) 131 | @install bin/feedindicator $(BIN_DIR) 132 | @install feedindicator.bash-completion $(BASH_COMPLETION_DIR) 133 | @cat build/feedindicator.1 | gzip -n9 > $(MAN_DIR)feedindicator.1.gz 134 | @mkdir -p $(DOC_DIR) 135 | @cat build/changelog | gzip -n9 > $(DOC_DIR)changelog.gz 136 | @install build/copyright $(DOC_DIR)copyright 137 | @echo "feedindicator install completed." 138 | 139 | 140 | uninstall: 141 | @rm -r $(SHARE_DIR) 142 | @rm -r $(DOC_DIR) 143 | @rm $(BIN_DIR)/feedindicator 144 | @rm $(BASH_COMPLETION_DIR)feedindicator.bash-completion 145 | @rm $(MAN_DIR)feedindicator.1.gz 146 | @xdg-icon-resource uninstall --theme hicolor --novendor --size 512 feedindicator-active 147 | @xdg-icon-resource uninstall --theme hicolor --novendor --size 512 feedindicator-attention 148 | @xdg-icon-resource uninstall --theme hicolor --novendor --size 128 --context apps feedindicator 149 | @xdg-icon-resource uninstall --theme hicolor --novendor --size 48 --context apps feedindicator 150 | @xdg-desktop-menu uninstall --novendor feedindicator.desktop 151 | @if [ -f ~/.config/feedindicator/feedindicator.desktop ]; then\ 152 | unlink ~/.config/feedindicator/feedindicator.desktop;\ 153 | fi 154 | @if [ -d ~/.cache/feedindicator ]; then\ 155 | rm -r ~/.cache/feedindicator;\ 156 | fi 157 | @if [ -d ~/.config/feedindicator ]; then\ 158 | rm -r ~/.config/feedindicator;\ 159 | fi 160 | @if [ -d ~/.local/share/feedindicator ]; then\ 161 | rm -r ~/.local/share/feedindicator;\ 162 | fi 163 | @echo "feedindicator uninstall completed." 164 | 165 | 166 | deb: build/package/DEBIAN/control build/package/DEBIAN/postinst build/package/DEBIAN/prerm 167 | fakeroot dpkg-deb -b build/package build/feedindicator.deb 168 | lintian -Ivi build/feedindicator.deb 169 | 170 | 171 | clean: 172 | @rm -rf ./bin 173 | @rm -rf ./build 174 | @find . -name __pycache__ -exec rm -rf {} \; 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Feedindicator 2 | 3 | A RSS feed reader for the indicator area. 4 | 5 | ![Screenshot](http://4.bp.blogspot.com/-4Zk2DTUt4XQ/UZyDCGuQ7YI/AAAAAAAAALw/-BoBrH4BcDI/s400/feedindicator.png) 6 | 7 | ## Requirements 8 | * ```python3``` 9 | * ```python3-gi``` 10 | * ```python3-feedparser``` 11 | * ```indicator-application``` or ```Gtk3 StatusIcon``` 12 | 13 | ## Install/Uninstall 14 | * to install run ```make install``` 15 | * to uninstall run ```make uninstall``` 16 | 17 | ## Run 18 | To run after install either use the command ```feedindicator``` or the desktop file. 19 | 20 | ## Build Deb-Paket 21 | ### Requirements 22 | * ```help2man``` 23 | * ```fakeroot``` 24 | * ```lintian``` 25 | 26 | ### Build 27 | * run ```make deb``` 28 | -------------------------------------------------------------------------------- /feedindicator.bash-completion: -------------------------------------------------------------------------------- 1 | _feedindicator() 2 | { 3 | local cur prev opts base 4 | COMPREPLY=() 5 | cur="${COMP_WORDS[COMP_CWORD]}" 6 | prev="${COMP_WORDS[COMP_CWORD-1]}" 7 | 8 | opts="add update --autostarted -h --help -v --version" 9 | COMPREPLY=($(compgen -W "${opts}" -- ${cur})) 10 | return 0 11 | } 12 | complete -F _feedindicator feedindicator 13 | -------------------------------------------------------------------------------- /feedindicator/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | """A RSS feed reader for the indicator area.""" 24 | 25 | 26 | import os.path 27 | 28 | 29 | __author__ = 'Nathanael Philipp' 30 | __copyright__ = 'Copyright 2017 Nathanael Philipp (jnphilipp)' 31 | __license__ = 'GPLv3' 32 | __maintainer__ = __author__ 33 | __email__ = 'mail@jnphilipp.org' 34 | __app_name__ = 'feedindicator' 35 | __version_info__ = (2, 0, 0) 36 | __version__ = '.'.join(str(e) for e in __version_info__) 37 | __description__ = 'A RSS feed reader for the indicator area.' 38 | __github__ = 'https://github.com/jnphilipp/Feedindicator' 39 | 40 | basedir = os.path.dirname(os.path.realpath(__file__)) 41 | -------------------------------------------------------------------------------- /feedindicator/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2010-2017 Dave Gardner , 5 | # Michael Judge , 6 | # Nicolas Raoul , 7 | # Nathanael Philipp (jnphilipp) 8 | # 9 | # This file is part of feedindicator. 10 | # 11 | # Foobar is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # feedindicator is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with feedindicator. If not, see . 23 | 24 | 25 | import sys 26 | import feedindicator.feedindicator 27 | 28 | 29 | if __name__ == '__main__': 30 | sys.exit(feedindicator.feedindicator.main()) 31 | -------------------------------------------------------------------------------- /feedindicator/config/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | from .constants import * 25 | from .manager import ConfigManager 26 | -------------------------------------------------------------------------------- /feedindicator/config/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | import feedindicator 25 | import os 26 | 27 | from gi.repository import GLib 28 | 29 | 30 | # indicator icon names 31 | active_icon = 'feedindicator-active' 32 | attention_icon = 'feedindicator-attention' 33 | 34 | # XDG config 35 | xdg_config_dir = GLib.get_user_config_dir() 36 | app_config_dir = os.path.join(xdg_config_dir, feedindicator.__app_name__) 37 | app_autostart_dir = os.path.join(xdg_config_dir, 'autostart') 38 | app_autostart_file = os.path.join(app_autostart_dir, 39 | '%s.desktop' % feedindicator.__app_name__) 40 | 41 | # XDG cache 42 | xdg_cache_dir = GLib.get_user_cache_dir() 43 | app_cache_dir = os.path.join(xdg_cache_dir, feedindicator.__app_name__) 44 | 45 | # XDG data 46 | xdg_data_dir = GLib.get_user_data_dir() 47 | app_data_dir = os.path.join(xdg_data_dir, feedindicator.__app_name__) 48 | app_database = os.path.join(app_data_dir, 'db.sqlite3') 49 | 50 | # # /usr/share/ 51 | base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 52 | app_locale_dir = os.path.join(base_dir, 'locale') 53 | -------------------------------------------------------------------------------- /feedindicator/config/manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | import os 25 | 26 | from configparser import RawConfigParser 27 | from feedindicator.config.constants import app_config_dir 28 | 29 | 30 | class ConfigManager: 31 | """Configuration manager. All configurations can either be acces through 32 | key words or per '.'. 33 | 34 | Attributes: 35 | _configs: configuration dict 36 | """ 37 | def __init__(self): 38 | """"Init config manager with default configurations.""" 39 | self.__dict__['_configs'] = {# defaults 40 | 'autostart': False, 41 | 'refreshtime': 30 * 60, 42 | 'stoptimer': False, 43 | 'items_per_feed': 10, 44 | 'show_notifications': True, 45 | 'show_update_notifications': True, 46 | 'feeds_at_top': False, 47 | 'show_unread_feeds': True 48 | } 49 | 50 | def __getattr__(self, key): 51 | return self.__dict__['_configs'][key] 52 | 53 | def __setattr__(self, key, val): 54 | if key in self.__dict__['_configs']: 55 | self.__dict__['_configs'][key] = val 56 | else: 57 | raise KeyError(key) 58 | 59 | def __getitem__(self, key): 60 | return self._configs[key] 61 | 62 | def __setitem__(self, key, val): 63 | if key in self._configs: 64 | self._configs[key] = val 65 | else: 66 | raise KeyError(key) 67 | 68 | def items(self): 69 | """Get all configurations.""" 70 | return dict((k, v) for k, v in self._configs.items()) 71 | 72 | def update(self, updates): 73 | """Update configurations with given dict.""" 74 | for k, v in updates.items(): 75 | self._configs[k] = v 76 | 77 | def keys(self): 78 | """Get all configuration names.""" 79 | return self._configs.keys() 80 | 81 | def load(self): 82 | """Load configurations from file.""" 83 | parser = RawConfigParser() 84 | parser.optionxform = str 85 | parser.read(os.path.join(app_config_dir, 'config')) 86 | if parser.has_option('Options', 'autostart'): 87 | self.autostart = parser.getboolean('Options', 'autostart') 88 | if parser.has_option('Options', 'refreshtime'): 89 | self.refreshtime = parser.getint('Options', 'refreshtime') 90 | if parser.has_option('Options', 'stoptimer'): 91 | self.stoptimer = parser.getboolean('Options', 'stoptimer') 92 | if parser.has_option('Options', 'items_per_feed'): 93 | self.items_per_feed = parser.getint('Options', 'items_per_feed') 94 | if parser.has_option('Options', 'show_notifications'): 95 | self.show_notifications = parser.getboolean('Options', 96 | 'show_notifications') 97 | if parser.has_option('Options', 'show_update_notifications'): 98 | self.show_update_notifications = parser. \ 99 | getboolean('Options', 'show_update_notifications') 100 | if parser.has_option('Options', 'feeds_at_top'): 101 | self.feeds_at_top = parser.getboolean('Options', 'feeds_at_top') 102 | if parser.has_option('Options', 'show_unread_feeds'): 103 | self.show_unread_feeds = parser.getboolean('Options', 104 | 'show_unread_feeds') 105 | 106 | def save(self): 107 | """Save configuration to file.""" 108 | parser = RawConfigParser() 109 | parser.optionxform = str 110 | parser.read_dict({'Options': self._configs}) 111 | with open(os.path.join(app_config_dir, 'config'), 'w', 112 | encoding='utf-8') as f: 113 | parser.write(f) 114 | -------------------------------------------------------------------------------- /feedindicator/dialogs/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | from feedindicator.dialogs.about import AboutDialog 25 | from feedindicator.dialogs.add_feed import AddFeedDialog 26 | from feedindicator.dialogs.preferences import PreferencesDialog 27 | 28 | 29 | __all = ('AboutDialog', 'AddFeedDialog', 'PreferencesDialog') 30 | -------------------------------------------------------------------------------- /feedindicator/dialogs/about.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | import feedindicator 25 | import gi 26 | gi.require_version('Gtk', '3.0') 27 | 28 | from feedindicator import config 29 | from gi.repository import Gtk 30 | 31 | 32 | class AboutDialog(Gtk.AboutDialog): 33 | """About dialog.""" 34 | def __init__(self, widget): 35 | """Init dialog. 36 | 37 | Args: 38 | widget: Gtk widget 39 | """ 40 | Gtk.AboutDialog.__init__(self, _('About')) 41 | self.set_icon_name(feedindicator.__app_name__) 42 | self.set_logo(Gtk.IconTheme.get_default(). \ 43 | load_icon(feedindicator.__app_name__, 128, 0)) 44 | self.set_name(feedindicator.__app_name__) 45 | self.set_program_name(feedindicator.__app_name__) 46 | self.set_version(feedindicator.__version__) 47 | self.set_comments(feedindicator.__description__) 48 | self.set_copyright(feedindicator.__copyright__) 49 | self.set_license(feedindicator.__license__) 50 | self.set_website(feedindicator.__github__) 51 | self.set_website_label(feedindicator.__github__) 52 | self.set_authors(feedindicator.__author__.split(', ')) 53 | self.run() 54 | self.destroy() 55 | -------------------------------------------------------------------------------- /feedindicator/dialogs/add_feed.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | import feedindicator 25 | import gi 26 | gi.require_version('Gtk', '3.0') 27 | import sqlite3 28 | import sys 29 | 30 | from feedindicator import feeds 31 | from gi.repository import Gtk, Gdk 32 | 33 | 34 | class AddFeedDialog(Gtk.Window): 35 | """Dialog to add a new feed url. 36 | 37 | Attributes: 38 | _callback: callback function after url has been added 39 | _callback_args: arguments for callback function 40 | _textbox: textbox for feed url 41 | """ 42 | def __init__(self, widget, callback, *args): 43 | """Init dialog. 44 | 45 | Args: 46 | widget: Gtk widget 47 | callback: callback function after url has been added 48 | args: arguments for callback function 49 | """ 50 | Gtk.Window.__init__(self, title=_('Add feed')) 51 | self._callback = callback 52 | self._callback_args = args 53 | 54 | self.set_keep_above(True) 55 | self.set_position(Gtk.WindowPosition.CENTER) 56 | self.set_icon_name(feedindicator.__app_name__) 57 | self.connect('delete_event', self._close_window) 58 | self.connect('key-press-event', self._keypress) 59 | 60 | vbox = Gtk.VBox(False, 10) 61 | vbox.set_border_width(10) 62 | self.add(vbox) 63 | 64 | box = Gtk.HBox(False, 0) 65 | vbox.pack_start(box, False, True, 0) 66 | 67 | label = Gtk.Label(label=_('Feed url')) 68 | label.set_justify(Gtk.Justification.LEFT) 69 | label.set_line_wrap(True) 70 | box.pack_start(label, False, True, 0) 71 | 72 | box = Gtk.HBox(False, 0) 73 | vbox.pack_start(box, False, True, 0) 74 | 75 | self._textbox = Gtk.Entry() 76 | box.pack_start(self._textbox, True, True, 0) 77 | self._textbox.connect("activate", self._save) 78 | 79 | box = Gtk.HBox(True, 0) 80 | vbox.pack_end(box, False, True, 5) 81 | 82 | button = Gtk.Button(_('Cancel')) 83 | box.pack_start(button, True, True, 0) 84 | button.connect('clicked', self._close_window) 85 | button = Gtk.Button(_('Add feed')) 86 | box.pack_start(button, True, True, 0) 87 | button.connect('clicked', self._save) 88 | 89 | self.set_keep_above(True) 90 | self.show_all() 91 | 92 | def _keypress(self, widget, data): 93 | """Keypress handler. 94 | 95 | Args: 96 | widget: Gtk widget 97 | data: key event data 98 | """ 99 | if data.keyval == Gdk.KEY_Escape: 100 | self._close_window(widget) 101 | 102 | def _close_window(self, widget, data=None): 103 | """Close dialog. 104 | 105 | Args: 106 | widget: Gtk widget 107 | data: optional key event data 108 | """ 109 | self.close() 110 | 111 | def _save(self, widget): 112 | """Save feed and close dialog. 113 | 114 | Args: 115 | widget: Gtk widget 116 | """ 117 | self.set_modal(True) 118 | try: 119 | if self._textbox.get_text(): 120 | feeds.add(self._textbox.get_text()) 121 | self._close_window(widget) 122 | if self._callback: 123 | self._callback(self._callback_args) 124 | except sqlite3.Error as e: 125 | print('Could not add feed.', e, file=sys.stderr) 126 | dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.ERROR, 127 | Gtk.ButtonsType.CANCEL, 128 | _('Could not add feed.')) 129 | dialog.format_secondary_text(str(e)) 130 | dialog.run() 131 | dialog.destroy() 132 | -------------------------------------------------------------------------------- /feedindicator/dialogs/preferences.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | import feedindicator 25 | import gettext 26 | import gi 27 | gi.require_version('Gtk', '3.0') 28 | import os 29 | 30 | from feedindicator import config, feeds 31 | from feedindicator.utils import autostart, SQLite 32 | from gi.repository import Gtk, Gdk 33 | 34 | 35 | class PreferencesDialog(Gtk.Window): 36 | """Preferences dialog. 37 | 38 | Attributes: 39 | _configs: dict with configs 40 | _config_manager: config manager 41 | _callback: callback function after url has been added 42 | _callback_args: arguments for callback function 43 | _feeds: list store for the feeds with id, title, feed_url 44 | _treeview: treeview for the feeds 45 | _btn_remove_feed: remove button for feeds 46 | _refreshtime_label: label for refresh time config 47 | _scaletime: scaler for refresh time config 48 | _items_per_feed_label: label for items per feed config 49 | _btn_show_update_notifications: check button for show update 50 | notifications config 51 | """ 52 | def __init__(self, widget, config_manager, callback, *args): 53 | """Init dialog. 54 | 55 | Args: 56 | widget: Gtk widget 57 | configs: dict with configs 58 | callback: callback function after url has been added 59 | *args: callback function args 60 | """ 61 | Gtk.Window.__init__(self, title=_('Preferences')) 62 | self._config_manager = config_manager 63 | self._configs = self._config_manager.items() 64 | self._callback = callback 65 | self._callback_args = args 66 | 67 | self.set_position(Gtk.WindowPosition.CENTER) 68 | self.set_keep_above(True) 69 | self.set_icon_name(feedindicator.__app_name__) 70 | self.connect('delete_event', self._close_window) 71 | self.connect('key-press-event', self._keypress) 72 | 73 | vbox = Gtk.VBox(False, 1) 74 | vbox.set_border_width(1) 75 | self.add(vbox) 76 | 77 | notebook = Gtk.Notebook() 78 | notebook.set_tab_pos(Gtk.PositionType.LEFT) 79 | vbox.pack_start(notebook, False, True, 0) 80 | 81 | # Feed List 82 | frame = Gtk.Frame(label=_('Feed list')) 83 | frame.set_border_width(1) 84 | 85 | box = Gtk.VBox(False, 1) 86 | box.set_border_width(1) 87 | frame.add(box) 88 | 89 | label = Gtk.Label(label=_('Configure the feeds.')) 90 | label.set_justify(Gtk.Justification.LEFT) 91 | label.set_line_wrap(True) 92 | box.pack_start(label, False, True, 0) 93 | 94 | scrolled_window = Gtk.ScrolledWindow() 95 | scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, 96 | Gtk.PolicyType.ALWAYS) 97 | scrolled_window.set_shadow_type(Gtk.ShadowType.IN) 98 | scrolled_window.set_min_content_height(500) 99 | scrolled_window.set_min_content_width(500) 100 | box.pack_start(scrolled_window, False, True, 0) 101 | 102 | self._feeds = Gtk.ListStore(int, str, str) 103 | with SQLite() as db: 104 | for feed in db.s('SELECT id, title, feed_url FROM feeds ORDER ' + 105 | 'BY UPPER(title)'): 106 | self._feeds.append(feed) 107 | 108 | self._treeview = Gtk.TreeView.new_with_model(self._feeds) 109 | for i, column_title in enumerate([_('ID'), _('Title'), _('URL')]): 110 | renderer = Gtk.CellRendererText() 111 | if i > 0: 112 | renderer.set_property('editable', True) 113 | renderer.connect('edited', self._cell_edited, i) 114 | column = Gtk.TreeViewColumn(column_title, renderer, text=i) 115 | self._treeview.append_column(column) 116 | 117 | self._treeview.set_headers_clickable(False) 118 | self._treeview.set_reorderable(True) 119 | self._treeview.connect('cursor-changed', self._selection_made) 120 | scrolled_window.add(self._treeview) 121 | 122 | hbox = Gtk.HBox(True, 1) 123 | box.pack_start(hbox, False, True, 0) 124 | 125 | btn = Gtk.Button(label=_('Add')) 126 | btn.connect('clicked', self._add_feed) 127 | hbox.pack_start(btn, False, True, 0) 128 | 129 | self._btn_remove_feed = Gtk.Button(label=_('Remove')) 130 | self._btn_remove_feed.connect('clicked', self._remove_feed) 131 | self._btn_remove_feed.set_sensitive(False) 132 | hbox.pack_start(self._btn_remove_feed, False, True, 0) 133 | notebook.append_page(frame, Gtk.Label(label=_('Feed list'))) 134 | 135 | # Options 136 | frame = Gtk.Frame(label=_('Options')) 137 | frame.set_border_width(1) 138 | 139 | box = Gtk.VBox(False, 1) 140 | box.set_border_width(1) 141 | frame.add(box) 142 | 143 | btn = Gtk.CheckButton(label=_('Auto update feeds')) 144 | btn.set_active(not self._configs['stoptimer']) 145 | btn.connect('toggled', self._toggle_config, 'stoptimer') 146 | box.pack_start(btn, False, True, 0) 147 | 148 | hbox = Gtk.HBox(True, 0) 149 | box.pack_start(hbox, False, True, 0) 150 | 151 | self._refreshtime_label = Gtk.Label(label='') 152 | hbox.pack_start(self._refreshtime_label, False, True, 0) 153 | 154 | adjtimer = Gtk.Adjustment(value=self._configs['refreshtime'] / 60, 155 | lower=1.0, upper=90.0, step_increment=1.0, 156 | page_increment=10.0, page_size=0.0) 157 | adjtimer.connect('value_changed', self._change_refreshtime) 158 | self._scaletime = Gtk.HScale(adjustment=adjtimer) 159 | self._scaletime.set_draw_value(False) 160 | self._scaletime.set_digits(0) 161 | self._scaletime.set_sensitive(not self._configs['stoptimer']) 162 | hbox.pack_start(self._scaletime, False, True, 0) 163 | self._change_refreshtime(adjtimer) 164 | 165 | hbox = Gtk.HBox(True, 0) 166 | box.pack_start(hbox, False, True, 0) 167 | 168 | self._items_per_feed_label = Gtk.Label(label='') 169 | hbox.pack_start(self._items_per_feed_label, False, False, 0) 170 | 171 | adjitems = Gtk.Adjustment(value=self._configs['items_per_feed'], 172 | lower=1.0, upper=30.0, step_increment=1.0, 173 | page_increment=10.0, page_size=0.0) 174 | adjitems.connect('value_changed', self._change_items_per_feed) 175 | scaleitems = Gtk.HScale(adjustment=adjitems) 176 | scaleitems.set_draw_value(False) 177 | scaleitems.set_digits(0) 178 | hbox.pack_start(scaleitems, True, True, 0) 179 | self._change_items_per_feed(adjitems) 180 | 181 | btn = Gtk.CheckButton(label=_('Show feeds at top of menu')) 182 | btn.set_active(self._configs['feeds_at_top']) 183 | btn.connect('toggled', self._toggle_config, 'feeds_at_top') 184 | box.pack_start(btn, False, True, 0) 185 | 186 | btn = Gtk.CheckButton(label=_('Show feeds with no unread posts')) 187 | btn.set_active(self._configs['show_unread_feeds']) 188 | btn.connect('toggled', self._toggle_config, 'show_unread_feeds') 189 | box.pack_start(btn, False, True, 0) 190 | 191 | btn = Gtk.CheckButton(label=_('Launch at system startup')) 192 | btn.set_active(self._configs['autostart']) 193 | btn.connect('toggled', self._toggle_config, 'autostart') 194 | box.pack_start(btn, False, True, 0) 195 | 196 | btn = Gtk.CheckButton(label=_('Show notifications')) 197 | btn.set_active(self._configs['show_notifications']) 198 | btn.connect('toggled', self._toggle_config, 'show_notifications') 199 | box.pack_start(btn, False, True, 0) 200 | 201 | self._btn_show_update_notifications = Gtk. \ 202 | CheckButton(label=_('Show notifications at beginning ' + 203 | 'and end of update')) 204 | self._btn_show_update_notifications. \ 205 | set_sensitive(self._configs['show_notifications']) 206 | self._btn_show_update_notifications. \ 207 | set_active(self._configs['show_update_notifications']) 208 | self._btn_show_update_notifications. \ 209 | connect('toggled', self._toggle_config, 210 | 'show_update_notifications') 211 | box.pack_start(self._btn_show_update_notifications, False, True, 0) 212 | 213 | notebook.append_page(frame, Gtk.Label(label=_('Options'))) 214 | 215 | box = Gtk.HBox(True, 0) 216 | vbox.pack_end(box, False, True, 0) 217 | 218 | btn = Gtk.Button(label=_('Cancel')) 219 | btn.connect('clicked', self._close_window) 220 | box.pack_start(btn, False, True, 0) 221 | 222 | btn = Gtk.Button(label=_('Save')) 223 | btn.connect('clicked', self._save) 224 | box.pack_start(btn, False, True, 0) 225 | self.show_all() 226 | 227 | def _keypress(self, widget, data): 228 | """Keypress handler. 229 | 230 | Args: 231 | widget: Gtk widget 232 | data: key event data 233 | """ 234 | if data.keyval == Gdk.KEY_Escape: 235 | self._close_window(widget) 236 | 237 | def _close_window(self, widget, data=None): 238 | """Close dialog. 239 | 240 | Args: 241 | widget: Gtk widget 242 | data: optional key event data 243 | """ 244 | self.close() 245 | 246 | def _save(self, widget): 247 | """Save config and feeds and close dialog. 248 | 249 | Args: 250 | widget: Gtk widget 251 | """ 252 | self._config_manager.update(self._configs) 253 | self._config_manager.save() 254 | with SQLite() as db: 255 | for feed in self._feeds: 256 | if feed[0] == -1 and feed[2]: 257 | if not feed[1]: 258 | feed[1] = None 259 | db.s('INSERT INTO feeds (feed_url, title) VALUES (?,?)', 260 | (feed[2], feed[1])) 261 | else: 262 | db.s('UPDATE feeds set title=?, feed_url=? WHERE id=?', 263 | (feed[1], feed[2], feed[0])) 264 | self._close_window(widget) 265 | self._callback(self._callback_args) 266 | 267 | def _selection_made(self, widget): 268 | """Feed selected from list, so we enable the remove button. 269 | 270 | Args: 271 | widget: Gtk widget 272 | """ 273 | sel = self._treeview.get_selection().get_selected() 274 | if sel[1] is not None: 275 | self._btn_remove_feed.set_sensitive(True) 276 | 277 | def _cell_edited(self, widget, path, text, column): 278 | """Update list store after cell has been edited. 279 | 280 | Args: 281 | widget: Gtk widget 282 | path: row id 283 | text: new text 284 | column: column id 285 | """ 286 | self._feeds[path][column] = text 287 | 288 | def _add_feed(self, widget): 289 | """Add a row to list. 290 | 291 | Args: 292 | widget: Gtk widget 293 | """ 294 | sel = self._feeds.append([-1, '', '']) 295 | pos = self._feeds.get_path(sel) 296 | self._treeview.set_cursor(pos, None, False) 297 | 298 | def _remove_feed(self, widget): 299 | """Remove selected feed from list and delete from database. 300 | 301 | Args: 302 | widget: Gtk widget 303 | """ 304 | sel = self._treeview.get_selection().get_selected() 305 | feed_id = sel[0].get_value(sel[1], 0) 306 | if feed_id != -1: 307 | feeds.delete(feed_id) 308 | self._feeds.remove(sel[1]) 309 | self._treeview.get_selection().unselect_all() 310 | self._btn_remove_feed.set_sensitive(False) 311 | 312 | def _change_refreshtime(self, widget): 313 | """Change refresh time. 314 | 315 | Args: 316 | widget: Gtk widget 317 | """ 318 | value = widget.get_value() 319 | self._configs['refreshtime'] = int(value * 60) 320 | text = gettext.ngettext('Update feeds every minute', 321 | 'Update feeds every %(minutes)d minutes', 322 | value) % {'minutes': value} 323 | self._refreshtime_label.set_text(text) 324 | 325 | def _change_items_per_feed(self, widget): 326 | """Change items per feed. 327 | 328 | Args: 329 | widget: Gtk widget 330 | """ 331 | value = widget.get_value() 332 | self._configs['items_per_feed'] = int(value) 333 | text = gettext.ngettext('Show 1 item per feed', 334 | 'Show %(items)d items per feed', 335 | value) % {'items': value} 336 | self._items_per_feed_label.set_text(text) 337 | 338 | def _toggle_config(self, widget, key): 339 | """Toggles config values. 340 | 341 | Args: 342 | widget: Gtk widget 343 | key: config key 344 | """ 345 | self._configs[key] = widget.get_active() 346 | if key == 'autostart': 347 | if self._configs[key]: 348 | autostart.create() 349 | else: 350 | autostart.delete() 351 | elif key == 'stoptimer': 352 | if self._configs[key]: 353 | self._scaletime.set_sensitive(True) 354 | else: 355 | self._scaletime.set_sensitive(False) 356 | -------------------------------------------------------------------------------- /feedindicator/feedindicator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | import feedindicator 24 | import gettext 25 | import gi 26 | gi.require_version('Notify', '0.7') 27 | import os 28 | import sqlite3 29 | import sys 30 | 31 | from argparse import ArgumentParser, RawTextHelpFormatter 32 | from feedindicator import config, feeds, utils 33 | from feedindicator.indicator import AppIndicator 34 | from gi.repository import Gtk, Notify 35 | from time import sleep 36 | 37 | 38 | def _add_feed(args): 39 | try: 40 | feeds.add(args.url) 41 | except sqlite3.Error as e: 42 | print(_('Could not add feed.'), e, file=sys.stderr) 43 | 44 | 45 | def _update_feeds(args): 46 | try: 47 | nb_new_posts = feeds.update() 48 | print(_('Feeds updated. New posts: %(nr)d') % {'nr': nb_new_posts}) 49 | except Exception as e: 50 | print(_('Error while updating feeds.'), e, file=sys.stderr) 51 | 52 | 53 | def main(): 54 | gettext.install(feedindicator.__app_name__, config.app_locale_dir, 55 | codeset="utf-8") 56 | Notify.init(feedindicator.__app_name__) 57 | 58 | if not os.path.exists(config.app_cache_dir): 59 | os.makedirs(config.app_cache_dir) 60 | if not os.path.exists(config.app_config_dir): 61 | os.makedirs(config.app_config_dir) 62 | if not os.path.exists(config.app_data_dir): 63 | os.makedirs(config.app_data_dir) 64 | 65 | parser = ArgumentParser(prog=feedindicator.__app_name__, 66 | description=feedindicator.__description__, 67 | formatter_class=RawTextHelpFormatter) 68 | parser.add_argument('-v', '--version', action='version', 69 | version=utils.app_version()) 70 | parser.add_argument('--autostarted', action='store_true', help='Option ' + 71 | 'to indicate feedindicator was autostarted.') 72 | subparsers = parser.add_subparsers(dest='subparser') 73 | 74 | # create the parser for the "add" subcommand 75 | add_parser = subparsers.add_parser('add', help='Add a new feed.') 76 | add_parser.set_defaults(func=_add_feed) 77 | add_parser.add_argument('url', help='Feed URL') 78 | 79 | # create the parser for the "update" subcommand 80 | update_parser = subparsers.add_parser('update', help='Update feeds.') 81 | update_parser.set_defaults(func=_update_feeds) 82 | 83 | argv = sys.argv[1:] 84 | args = parser.parse_args(argv) 85 | if args.autostarted: 86 | sleep(5) 87 | 88 | config_manager = config.ConfigManager() 89 | config_manager.load() 90 | 91 | try: 92 | utils.db_init() 93 | except sqlite3.Error as e: 94 | print(_('Could not init database.'), e, file=sys.stderr) 95 | 96 | if args.subparser: 97 | args.func(args) 98 | else: 99 | indicator = AppIndicator(config_manager) 100 | Gtk.main() 101 | -------------------------------------------------------------------------------- /feedindicator/feeds.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | import feedparser 25 | import json 26 | import os 27 | import urllib.request 28 | import sys 29 | 30 | from feedindicator import config, utils 31 | from feedindicator.utils import SQLite 32 | 33 | 34 | # clear out the html element list so all are removed 35 | feedparser._HTMLSanitizer.acceptable_elements = [] 36 | 37 | 38 | def add(url): 39 | """Add a new feed url to the database. 40 | 41 | Args: 42 | url: feed url 43 | """ 44 | with SQLite() as db: 45 | db.s('INSERT INTO feeds (feed_url) VALUES (?)', (url,)) 46 | print(_('Feed added.')) 47 | 48 | 49 | def delete(feed_id): 50 | """Delete a feed and all its posts from the database. 51 | 52 | Args: 53 | feed_id: feed id 54 | """ 55 | with SQLite() as db: 56 | img = db.s('SELECT img FROM feeds WHERE id=?', (feed_id,))[0][0] 57 | if img and os.path.exists(os.path.join(config.app_cache_dir, img)): 58 | os.remove(os.path.join(config.app_cache_dir, img)) 59 | db.s('DELETE FROM posts WHERE feed_id=?', (feed_id,)) 60 | db.s('DELETE FROM feeds WHERE id=?', (feed_id,)) 61 | 62 | 63 | def update(): 64 | """Update all feeds.""" 65 | new_posts = 0 66 | with SQLite() as db: 67 | feeds = db.s('SELECT id, feed_url, title, url, img FROM feeds') 68 | for feed in feeds: 69 | result = _parse_feed(feed[1]) 70 | if not result['success']: 71 | continue 72 | 73 | if feed[2] == None: 74 | db.s('UPDATE feeds SET title=? WHERE id=?', 75 | (result['feed']['title'], feed[0])) 76 | if feed[3] != result['feed']['link']: 77 | db.s('UPDATE feeds SET url=? WHERE id=?', 78 | (result['feed']['link'], feed[0])) 79 | if not result['feed']['img']: 80 | db.s('UPDATE feeds SET img=? WHERE id=?', 81 | (result['feed']['img'], feed[0])) 82 | 83 | items = () 84 | hashes = () 85 | for p in result['posts']: 86 | hash = utils.get_hash(p['date'], p['title']) 87 | hashes += (hash,) 88 | if db.s('SELECT COUNT(*) FROM posts WHERE hash=?', 89 | (hash,))[0][0] == 0: 90 | items += ((hash, p['link'], p['title'], 91 | json.dumps(p['raw']), feed[0]),) 92 | 93 | new_posts += len(items) 94 | if len(items) > 0: 95 | db.many('INSERT INTO posts (hash, url, title, raw, feed_id) ' + 96 | 'VALUES (?,?,?,?,?)', items) 97 | db.s('DELETE FROM posts WHERE read="true" AND hash NOT IN ' 98 | '(%s) AND feed_id=?' % ','.join('?' for p in hashes), 99 | hashes + (feed[0],)) 100 | return new_posts 101 | 102 | 103 | def _parse_feed(url): 104 | """Parses a feed and returns the data. 105 | 106 | Args: 107 | url: feed url 108 | """ 109 | result = { 110 | 'feed': {}, 111 | 'posts': [], 112 | 'success': False 113 | } 114 | 115 | try: 116 | rssfeed = feedparser.parse(url) 117 | posts = [] 118 | for e in rssfeed.entries: 119 | e.title = e.title.replace('\n', '') 120 | if len(e.title) > 72: 121 | substr = e.title[:72].rpartition(' ') 122 | if substr[0] != '': 123 | e.title = substr[0] + '...' 124 | 125 | date = None 126 | if 'published' in e: 127 | date = e.published 128 | elif 'updated' in e: 129 | date = e.updated 130 | elif 'created' in e: 131 | date = e.created 132 | posts.append({ 133 | 'title': e.title, 134 | 'link': e.link, 135 | 'date': date, 136 | 'raw': e 137 | }) 138 | result['posts'] = posts 139 | feedimg = None 140 | webimg = None 141 | if 'image' in rssfeed.feed: 142 | if 'href' in rssfeed.feed.image: 143 | webimg = rssfeed.feed.image.href 144 | elif 'url' in rssfeed.feed.image: 145 | webimg = rssfeed.feed.image.url 146 | elif 'logo' in rssfeed.feed: 147 | webimg = rssfeed.feed.logo 148 | elif 'icon' in rssfeed.feed: 149 | webimg = rssfeed.feed.icon 150 | if webimg != None: 151 | ext = webimg.rsplit('.', 1) 152 | if len(ext[1]) > 5 or len(ext[1]) == 0: 153 | ext[0] = webimg 154 | ext[1] = 'jpg' 155 | shash = hashlib.sha512(url.encode('utf-8')).hexdigest() 156 | localimg = os.path.join(config.app_cache_dir, 157 | '%s.%s' % (shash, ext[1])) 158 | feedimg = '%s.%s' % (shash, ext[1]) 159 | 160 | request = urllib.request.Request(webimg) 161 | with urllib.request.urlopen(request) as response: 162 | with open(localimg, 'bw') as f: 163 | f.write(response.read()) 164 | 165 | result['feed'] = { 166 | 'title': rssfeed.feed.title, 167 | 'link': rssfeed.feed.link, 168 | 'img': feedimg 169 | } 170 | result['success'] = True 171 | except Exception as e: 172 | print('Error while parsing feed (feed url: %s).' % url, e, 173 | file=sys.stderr) 174 | return result 175 | -------------------------------------------------------------------------------- /feedindicator/indicator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | import feedindicator 24 | import gettext 25 | import gi 26 | gi.require_version('Gtk', '3.0') 27 | import os 28 | import sys 29 | import webbrowser 30 | 31 | from feedindicator import config, feeds 32 | from feedindicator.dialogs import AboutDialog, AddFeedDialog, PreferencesDialog 33 | from feedindicator.threads import FeedThread 34 | from feedindicator.utils import SQLite 35 | from gi.repository import GLib, Gtk, Notify 36 | from time import sleep, time 37 | 38 | try: 39 | gi.require_version('AppIndicator3', '0.1') 40 | from gi.repository import AppIndicator3 as appindicator 41 | except (ImportError, ValueError): 42 | appindicator = None 43 | 44 | 45 | class AppIndicator: 46 | """App indicator. 47 | 48 | Attributes: 49 | _config_manager: config manager 50 | _feeds_thread: feeds thread 51 | _indicator: app indicator 52 | _status_icon: Gtk status icon, if app indicator isn't available 53 | _menu: menu 54 | """ 55 | def __init__(self, config_manager): 56 | """Init indicator. 57 | 58 | Args: 59 | config_manager: config manager 60 | """ 61 | self._config_manager = config_manager 62 | self._feeds_thread = None 63 | 64 | if appindicator: 65 | # Create indicator 66 | self._indicator = appindicator.Indicator. \ 67 | new(feedindicator.__app_name__, feedindicator.__app_name__, 68 | appindicator.IndicatorCategory.APPLICATION_STATUS) 69 | self._indicator.set_status(appindicator.IndicatorStatus.ACTIVE) 70 | self._indicator.set_attention_icon_full(config.attention_icon, 71 | _('New Posts.')) 72 | self._indicator.set_icon_full(config.active_icon, 73 | _('Nothing new.')) 74 | else: 75 | # Create status icon 76 | self._status_icon = Gtk.StatusIcon() 77 | self._status_icon.set_name(feedindicator.__app_name__) 78 | self._status_icon.set_from_icon_name(config.active_icon) 79 | self._status_icon.set_tooltip_text(feedindicator.__app_name__) 80 | 81 | # Create popup menu 82 | self._menu = Gtk.Menu() 83 | if appindicator: 84 | self._menu.show_all() 85 | self._indicator.set_menu(self._menu) 86 | else: 87 | self._status_icon.connect('activate', self._toggle_status_icon) 88 | self._status_icon.connect('popup-menu', self._popup_menu) 89 | self._status_icon.set_visible(True) 90 | 91 | self._update(None, True, True) 92 | 93 | def _popup_menu(self, widget, button, time): 94 | """Callback when the popup menu on the status icon has to open. 95 | 96 | Args: 97 | widget: Gtk widget 98 | button: mouse button 99 | time: activation time 100 | """ 101 | self._menu.show_all() 102 | self._menu.popup(None, None, Gtk.StatusIcon.position_menu, 103 | self._status_icon, button, time) 104 | 105 | def _toggle_status_icon(self, widget): 106 | """Callback when a request to toggle feedindicator was made. 107 | 108 | Args: 109 | widget: Gtk widget 110 | """ 111 | self._popup_menu(widget, 0, Gtk.get_current_event_time()) 112 | 113 | def _set_status(self, status): 114 | """Turns the appindicator to attention and back to normal. 115 | 116 | Args: 117 | status: new indicator status: attention/active 118 | """ 119 | if status and appindicator: 120 | self._indicator.set_status(appindicator.IndicatorStatus.ATTENTION) 121 | elif status and not appindicator: 122 | self._status_icon.set_from_icon_name(config.attention_icon) 123 | elif not status and appindicator: 124 | self._indicator.set_status(appindicator.IndicatorStatus.ACTIVE) 125 | else: 126 | self._status_icon.set_from_icon_name(config.active_icon) 127 | 128 | def _exit(self, widget): 129 | """Close Feedindicator. 130 | 131 | Args: 132 | widget: Gtk widget 133 | """ 134 | Notify.uninit() 135 | Gtk.main_quit() 136 | 137 | def _notify(self, title, msg, img=feedindicator.__app_name__): 138 | """Send feed updates to notify-osd. 139 | 140 | Args: 141 | title: notification title 142 | msg: message 143 | img: image (default: app icon) 144 | """ 145 | n = Notify.Notification.new(title, msg, img) 146 | n.show() 147 | 148 | def _open_website(self, widget, url, post_id=None): 149 | """Open website. 150 | 151 | Args: 152 | widget: Gtk widget 153 | url: url to open 154 | post_id: optional post id to change status to read 155 | """ 156 | webbrowser.open(url) 157 | 158 | if post_id: 159 | with SQLite() as db: 160 | db.s('UPDATE posts SET read="true" WHERE id=?', (post_id,)) 161 | sleep(0.5) 162 | self._render_menu() 163 | 164 | def _update(self, widget=None, timeroverride=False, starttimer=False): 165 | """Start updating feeds. 166 | 167 | Args: 168 | widget: Gtk widget 169 | timeroverride: if true timer will be overridden 170 | starttimer: if true timer will be started 171 | """ 172 | if self._config_manager.stoptimer and not timeroverride: 173 | return False 174 | 175 | with SQLite() as db: 176 | if db.s('SELECT COUNT(*) FROM feeds')[0][0] > 0: 177 | if starttimer: 178 | GLib.timeout_add_seconds(self._config_manager.refreshtime, 179 | self._update, True) 180 | 181 | self._loading_menu() 182 | if db.s('SELECT COUNT(*) FROM feeds')[0][0] != 0: 183 | self._feeds_thread = FeedThread(self._finished_update) 184 | self._feeds_thread.start() 185 | if self._config_manager.show_notifications: 186 | if self._config_manager.show_update_notifications: 187 | self._notify(feedindicator.__app_name__, 188 | _('Begin updating Feeds.')) 189 | else: 190 | self._render_menu() 191 | return True 192 | 193 | def _finished_update(self, *args): 194 | """Renders menu and shows notification after updating is finished.""" 195 | if self._feeds_thread is not None and self._feeds_thread.is_alive(): 196 | return 197 | 198 | if self._feeds_thread: 199 | self._feeds_thread.join() 200 | sleep(1) 201 | self._feeds_thread = None 202 | self._render_menu() 203 | 204 | if self._config_manager.show_notifications: 205 | with SQLite() as db: 206 | feeds = db.s('SELECT id, title, img FROM feeds WHERE ' + 207 | 'feed_url IN (SELECT feed_id FROM posts WHERE ' + 208 | 'read="false" GROUP BY feed_id LIMIT 50) ORDER' + 209 | ' BY (SELECT count(feed_id) AS c FROM posts ' + 210 | 'WHERE read="false" GROUP BY feed_id ORDER BY ' + 211 | 'c desc), UPPER(title)') 212 | for feed in feeds: 213 | img = os.path.join(config.app_cache_dir, 214 | feed[2]) if feed[2] else None 215 | posts = db.s('SELECT title FROM posts WHERE read=' + 216 | '"false" AND feed_id=? LIMIT 3', (feed[0],)) 217 | if len(posts) > 0: 218 | msg = '\n'.join('* %s' % p[0] for p in posts) 219 | self._notify(feed[1], msg, img) 220 | if len(feeds) == 0: 221 | if self._config_manager.show_update_notifications: 222 | self._notify(feedindicator.__app_name__, 223 | _('Finished updating feeds.')) 224 | 225 | def _clear_menu(self): 226 | """Removes all entries from menu.""" 227 | for child in self._menu.get_children(): 228 | child.destroy() 229 | 230 | def _loading_menu(self): 231 | """Populate a loading menu.""" 232 | self._clear_menu() 233 | 234 | item = Gtk.MenuItem(label=_('Loading')) 235 | item.set_sensitive(False) 236 | self._menu.append(item) 237 | self._menu.show_all() 238 | if appindicator: 239 | self._indicator.set_menu(self._menu) 240 | self._set_status(False) 241 | 242 | def _render_menu(self): 243 | """Populate the menu.""" 244 | self._clear_menu() 245 | 246 | with SQLite() as db: 247 | feeds = db.s('SELECT id, title, url, feed_url, (SELECT COUNT(*) ' + 248 | 'FROM posts WHERE posts.feed_id=feeds.id AND ' + 249 | 'read="false") AS c FROM feeds ORDER BY c DESC, ' + 250 | 'UPPER(title)') 251 | 252 | if not self._config_manager.feeds_at_top: 253 | self._conf_menu(len(feeds) > 0) 254 | self._menu.append(Gtk.SeparatorMenuItem()) 255 | 256 | if len(feeds) > 0: 257 | self._feeds_menu_header() 258 | for feed in feeds: 259 | posts = db.s('SELECT id, title, url FROM posts WHERE ' + 260 | 'feed_id=? AND read="false" ORDER BY id ' + 261 | 'LIMIT %d' % 262 | self._config_manager.items_per_feed, 263 | (feed[0],)) 264 | if self._config_manager.show_unread_feeds: 265 | self._feed_submenu(feed, posts) 266 | else: 267 | if feed[4] > 0: 268 | self._feed_submenu(feed, posts) 269 | if db.s('SELECT COUNT(*) FROM posts WHERE ' + 270 | 'read="false"')[0][0] == 0: 271 | menu_notice = Gtk.MenuItem(label=_('No unread posts.')) 272 | menu_notice.set_sensitive(False) 273 | self._menu.append(menu_notice) 274 | else: 275 | item = Gtk.MenuItem(label=_('No feeds defined!')) 276 | item.set_sensitive(False) 277 | self._menu.append(item) 278 | 279 | if self._config_manager.feeds_at_top: 280 | self._menu.append(Gtk.SeparatorMenuItem()) 281 | self._conf_menu(len(feeds) > 0) 282 | 283 | self._menu.show_all() 284 | if appindicator: 285 | self._indicator.set_menu(self._menu) 286 | self._set_status(db.s('SELECT COUNT(*) FROM posts WHERE ' + 287 | 'read="false"')[0][0] > 0) 288 | 289 | def _feeds_menu_header(self): 290 | """Add items to menu with for all feeds.""" 291 | item = Gtk.MenuItem(label=_('Open all unread')) 292 | item.connect('activate', self._open_unread, '') 293 | self._menu.append(item) 294 | 295 | item = Gtk.MenuItem(label=_('Mark all as read')) 296 | item.connect('activate', self._mark_feed_as_read, '') 297 | self._menu.append(item) 298 | 299 | item = Gtk.MenuItem(label=_('Mark all as unread')) 300 | item.connect('activate', self._mark_feed_as_unread) 301 | self._menu.append(item) 302 | 303 | item = Gtk.MenuItem(label=_('Reload all feeds')) 304 | item.connect('activate', self._update, True, False) 305 | self._menu.append(item) 306 | self._menu.append(Gtk.SeparatorMenuItem()) 307 | 308 | def _feed_submenu(self, feed, posts): 309 | """Add a feed submenu to the menu. 310 | 311 | Args: 312 | feed: tuple feed info (id, title, url, feed_url, number of posts) 313 | posts: list of posts to append with (id, title, url) for each post 314 | """ 315 | menu_header = Gtk.MenuItem('%s (%d)'.replace(' (0)', '') % (feed[1], 316 | feed[4])) 317 | 318 | submenu = Gtk.Menu() 319 | item = Gtk.MenuItem(_('Open Website')) 320 | item.connect('activate', self._open_website, feed[2]) 321 | submenu.append(item) 322 | 323 | item = Gtk.MenuItem(label=_('Open unread')) 324 | item.connect('activate', self._open_unread, feed[0]) 325 | submenu.append(item) 326 | 327 | if feed[4] > self._config_manager.items_per_feed: 328 | item = Gtk.MenuItem(label=_('Open displayed posts')) 329 | item.connect('activate', self._open_displayed, feed[0]) 330 | submenu.append(item) 331 | 332 | item = Gtk.MenuItem(label=_('Mark as read')) 333 | item.connect('activate', self._mark_feed_as_read, feed[0]) 334 | submenu.append(item) 335 | 336 | if feed[4] > self._config_manager.items_per_feed: 337 | item = Gtk.MenuItem(label=_('Mark displayed posts as read')) 338 | item.connect('activate', self._mark_displayed_as_read, feed[0]) 339 | submenu.append(item) 340 | 341 | item = Gtk.MenuItem(label=_('Mark as unread')) 342 | item.connect('activate', self._mark_feed_as_unread, feed[0]) 343 | submenu.append(item) 344 | 345 | for i, post in enumerate(posts): 346 | if i == 0: 347 | submenu.append(Gtk.SeparatorMenuItem()) 348 | item = Gtk.MenuItem(post[1]) 349 | item.connect('activate', self._open_website, post[2], post[0]) 350 | submenu.append(item) 351 | 352 | menu_header.set_submenu(submenu) 353 | self._menu.append(menu_header) 354 | 355 | def _conf_menu(self, has_feeds): 356 | """Adds config items to menu. 357 | 358 | Args: 359 | has_feeds: if feeds are displayed in the menu 360 | """ 361 | if self._config_manager.feeds_at_top: 362 | self._menu.append(Gtk.SeparatorMenuItem()) 363 | 364 | item = Gtk.MenuItem(label=_('Add feed')) 365 | item.connect('activate', AddFeedDialog, self._update, None, True, 366 | False) 367 | self._menu.append(item) 368 | 369 | if has_feeds: 370 | item = Gtk.CheckMenuItem(label=self._timer_text()) 371 | if self._config_manager.stoptimer: 372 | item.set_active(False) 373 | else: 374 | item.set_active(True) 375 | item.connect('toggled', self._toggle_update) 376 | self._menu.append(item) 377 | 378 | menu_configure = Gtk.MenuItem(label=_('Preferences')) 379 | menu_configure.connect('activate', PreferencesDialog, 380 | self._config_manager, self._update, None, True, 381 | True) 382 | self._menu.append(menu_configure) 383 | menu_about = Gtk.MenuItem(label=_('About')) 384 | menu_about.connect('activate', AboutDialog) 385 | self._menu.append(menu_about) 386 | item = Gtk.MenuItem(label=_('Exit')) 387 | item.connect('activate', self._exit) 388 | self._menu.append(item) 389 | 390 | def _open_unread(self, widget, feed_id=None): 391 | """Opens all unread post of the given feed or all unread posts. 392 | 393 | Args: 394 | widget: Gtk widget 395 | feed_id: optional feed id, if not given opens all unread 396 | """ 397 | with SQLite() as db: 398 | if feed_id: 399 | posts = db.s('SELECT url FROM posts WHERE feed_id=? AND ' + 400 | 'read="false"', (feed_id,)) 401 | for post in posts: 402 | webbrowser.open(post[0]) 403 | sleep(0.6) 404 | 405 | db.s('UPDATE posts SET read="true" WHERE feed_id=?', 406 | (feed_id,)) 407 | else: 408 | for feed in db.s('SELECT id FROM feeds'): 409 | posts = db.s('SELECT url FROM posts WHERE feed_id=? AND ' + 410 | 'read="false"', feed) 411 | for post in posts: 412 | webbrowser.open(post[0]) 413 | sleep(0.6) 414 | db.s('UPDATE posts SET read="true" WHERE feed_id=?', feed) 415 | sleep(0.5) 416 | self._render_menu() 417 | 418 | def _open_displayed(self, widget, feed_id): 419 | """Opens all unread post of the given feed or all unread posts. 420 | 421 | Args: 422 | widget: Gtk widget 423 | feed_id: optional feed id, if not given opens all unread 424 | """ 425 | with SQLite() as db: 426 | posts = db.s('SELECT id, url FROM posts WHERE feed_id=? AND ' + 427 | 'read="false" ORDER BY id DESC LIMIT %d' % 428 | self._config_manager.items_per_feed, (feed_id,)) 429 | for post in posts: 430 | webbrowser.open(post[1]) 431 | db.s('UPDATE posts SET read="true" WHERE id=?', (post[0],)) 432 | sleep(0.6) 433 | self._render_menu() 434 | 435 | def _mark_feed_as_read(self, widget, feed_id=None): 436 | """Mark all posts of all feeds or a specific feed as read. 437 | 438 | Args: 439 | widget: Gtk widget 440 | feed_id: optinal feed id, if not given all will be updated 441 | """ 442 | with SQLite() as db: 443 | f = 'WHERE feed_id=?' if feed_id else '' 444 | db.s('UPDATE posts SET read="true" %s' % f, 445 | (feed_id,) if feed_id else ()) 446 | sleep(0.5) 447 | self._render_menu() 448 | 449 | def _mark_displayed_as_read(self, widget, feed_id): 450 | """Marks displayed posts of given feed as read. 451 | 452 | Args: 453 | widget: Gtk widget 454 | feed_id: feed id 455 | """ 456 | with SQLite() as db: 457 | db.s('UPDATE posts SET read="true" WHERE feed_id=? AND id IN ' + 458 | '(SELECT id FROM posts WHERE feed_id=? AND read="false" ' + 459 | 'ORDER BY id DESC LIMIT %d)' % 460 | self._config_manager.items_per_feed, (feed_id, feed_id)) 461 | sleep(0.5) 462 | self._render_menu() 463 | 464 | def _mark_feed_as_unread(self, widget, feed_id=None): 465 | """Mark all posts of all feeds or a specific feed as read. 466 | 467 | Args: 468 | widget: Gtk widget 469 | feed_id: optinal feed id, if not given all will be updated 470 | """ 471 | with SQLite() as db: 472 | f = 'WHERE feed_id=?' if feed_id else '' 473 | db.d('UPDATE posts SET read="false" %s' % f, 474 | (feed_id,) if feed_id else ()) 475 | sleep(0.5) 476 | self._render_menu() 477 | 478 | def _toggle_update(self, widget): 479 | """Toggle timer updating. 480 | 481 | Args: 482 | widget: Gtk widget 483 | """ 484 | if self._config_manager.stoptimer: 485 | self._config_manager.stoptimer = False 486 | if self._config_manager.show_notifications: 487 | self._notify(feedindicator.__app_name__, self._timer_text()) 488 | self._update(None, True, True) 489 | else: 490 | self._config_manager.stoptimer = True 491 | if self._config_manager.show_notifications: 492 | self._notify(config.app_name, 493 | _('Feeds will not update automatically.')) 494 | 495 | def _timer_text(self): 496 | """"Text for timer element.""" 497 | minutes = self._config_manager.refreshtime / 60 498 | return gettext.ngettext('Update feeds every minute', 499 | 'Update feeds every %(minutes)d minutes', 500 | minutes) % {'minutes': minutes} 501 | -------------------------------------------------------------------------------- /feedindicator/locale/de_DE/LC_MESSAGES/feedindicator.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feedindicator/Feedindicator/990d56a417be822b7d59da23deada927ab8d2ba6/feedindicator/locale/de_DE/LC_MESSAGES/feedindicator.mo -------------------------------------------------------------------------------- /feedindicator/locale/de_DE/LC_MESSAGES/feedindicator.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: feedindicator\n" 4 | "POT-Creation-Date: 2017-10-10 17:00+0200\n" 5 | "PO-Revision-Date: 2017-10-10 17:00+0200\n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: de_DE\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.8.7.1\n" 13 | "X-Poedit-Basepath: ../../..\n" 14 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 15 | "X-Poedit-SourceCharset: UTF-8\n" 16 | "X-Poedit-SearchPath-0: .\n" 17 | 18 | #: dialogs/about.py:40 indicator.py:383 19 | msgid "About" 20 | msgstr "Über" 21 | 22 | #: dialogs/add_feed.py:50 dialogs/add_feed.py:85 indicator.py:364 23 | msgid "Add feed" 24 | msgstr "Feed hinzufügen" 25 | 26 | #: dialogs/add_feed.py:67 27 | msgid "Feed url" 28 | msgstr "Feed url" 29 | 30 | #: dialogs/add_feed.py:82 dialogs/preferences.py:218 31 | msgid "Cancel" 32 | msgstr "Abbrechen" 33 | 34 | #: dialogs/add_feed.py:128 feedindicator.py:42 35 | msgid "Could not add feed." 36 | msgstr "Feed konnte nicht hinzugefügt werden." 37 | 38 | #: dialogs/preferences.py:61 indicator.py:378 39 | msgid "Preferences" 40 | msgstr "Einstellungen" 41 | 42 | #: dialogs/preferences.py:82 dialogs/preferences.py:133 43 | msgid "Feed list" 44 | msgstr "Feedliste" 45 | 46 | #: dialogs/preferences.py:89 47 | msgid "Configure the feeds." 48 | msgstr "Feeds konfigurieren." 49 | 50 | #: dialogs/preferences.py:109 51 | msgid "ID" 52 | msgstr "ID" 53 | 54 | #: dialogs/preferences.py:109 55 | msgid "Title" 56 | msgstr "Titel" 57 | 58 | #: dialogs/preferences.py:109 59 | msgid "URL" 60 | msgstr "URL" 61 | 62 | #: dialogs/preferences.py:125 63 | msgid "Add" 64 | msgstr "Hinzufügen" 65 | 66 | #: dialogs/preferences.py:129 67 | msgid "Remove" 68 | msgstr "Entfernen" 69 | 70 | #: dialogs/preferences.py:136 dialogs/preferences.py:213 71 | msgid "Options" 72 | msgstr "Optionen" 73 | 74 | #: dialogs/preferences.py:143 75 | msgid "Auto update feeds" 76 | msgstr "Auto aktualisiere feeds" 77 | 78 | #: dialogs/preferences.py:181 79 | msgid "Show feeds at top of menu" 80 | msgstr "Zeige Feeds an oberster Stelle im Menü" 81 | 82 | #: dialogs/preferences.py:186 83 | msgid "Show feeds with no unread posts" 84 | msgstr "Zeige Feeds mit keinen ungelesenen Posts" 85 | 86 | #: dialogs/preferences.py:191 87 | msgid "Launch at system startup" 88 | msgstr "Bei Systemstart starten" 89 | 90 | #: dialogs/preferences.py:196 91 | msgid "Show notifications" 92 | msgstr "Zeige Benachrichtigungen" 93 | 94 | #: dialogs/preferences.py:202 95 | msgid "Show notifications at beginning and end of update" 96 | msgstr "Zeige Benachrichtigungen am Anfang und Ende der Aktualisierung" 97 | 98 | #: dialogs/preferences.py:222 99 | msgid "Save" 100 | msgstr "Speichern" 101 | 102 | #: dialogs/preferences.py:320 indicator.py:498 103 | #, python-format 104 | msgid "Update feeds every minute" 105 | msgid_plural "Update feeds every %(minutes)d minutes" 106 | msgstr[0] "Feeds jede Minute aktualiesierenq" 107 | msgstr[1] "Feeds alle %(minutes)d Minuten aktualiesiren" 108 | 109 | #: dialogs/preferences.py:333 110 | #, python-format 111 | msgid "Show 1 item per feed" 112 | msgid_plural "Show %(items)d items per feed" 113 | msgstr[0] "Zeige ein Eintrag pro Feed" 114 | msgstr[1] "Zeige %(items)d Einträge pro Feed" 115 | 116 | #: feedindicator.py:48 117 | #, python-format 118 | msgid "Feeds updated. New posts: %(nr)d" 119 | msgstr "Feeds aktualisiert. Neue Posts: %(nr)d" 120 | 121 | #: feedindicator.py:50 122 | msgid "Error while updating feeds." 123 | msgstr "Es ist ein Fehler während der Aktualisierung der Feeds aufgetreten." 124 | 125 | #: feedindicator.py:94 126 | msgid "Could not init database." 127 | msgstr "Die Datenbank konnte nicht erstellt werden." 128 | 129 | #: feeds.py:46 130 | msgid "Feed added." 131 | msgstr "Feed hinzugefügt." 132 | 133 | #: indicator.py:71 134 | msgid "New Posts." 135 | msgstr "Neue Posts." 136 | 137 | #: indicator.py:73 138 | msgid "Nothing new." 139 | msgstr "Nichts neues." 140 | 141 | #: indicator.py:188 142 | msgid "Begin updating Feeds." 143 | msgstr "Starte Aktualisierung der Feeds." 144 | 145 | #: indicator.py:223 146 | msgid "Finished updating feeds." 147 | msgstr "Aktualisierung der Feeds beendet." 148 | 149 | #: indicator.py:234 150 | msgid "Loading" 151 | msgstr "Laden" 152 | 153 | #: indicator.py:271 154 | msgid "No unread posts." 155 | msgstr "Keine ungelesenen Posts." 156 | 157 | #: indicator.py:275 158 | msgid "No feeds defined!" 159 | msgstr "Keine Feeds vorhanden!" 160 | 161 | #: indicator.py:291 162 | msgid "Open all unread" 163 | msgstr "Alle ungelesenen öffnen" 164 | 165 | #: indicator.py:295 166 | msgid "Mark all as read" 167 | msgstr "Alle als gelesen makieren" 168 | 169 | #: indicator.py:299 170 | msgid "Mark all as unread" 171 | msgstr "Alle als ungelesen makieren" 172 | 173 | #: indicator.py:303 174 | msgid "Reload all feeds" 175 | msgstr "Alle Feeds neuladen" 176 | 177 | #: indicator.py:319 178 | msgid "Open Website" 179 | msgstr "Webseite öffnen" 180 | 181 | #: indicator.py:323 182 | msgid "Open unread" 183 | msgstr "Ungelesene öffnen" 184 | 185 | #: indicator.py:328 186 | msgid "Open displayed posts" 187 | msgstr "Öffne angezeigte Posts." 188 | 189 | #: indicator.py:332 190 | msgid "Mark as read" 191 | msgstr "Als gelesen makieren" 192 | 193 | #: indicator.py:337 194 | msgid "Mark displayed posts as read" 195 | msgstr "Alle angezeigten Posts als gelesen makieren" 196 | 197 | #: indicator.py:341 198 | msgid "Mark as unread" 199 | msgstr "Als ungelesen makieren" 200 | 201 | #: indicator.py:386 202 | msgid "Exit" 203 | msgstr "Beenden" 204 | 205 | #: indicator.py:493 206 | msgid "Feeds will not update automatically." 207 | msgstr "Feeds werden nicht automatisch aktualisiert." 208 | 209 | #: utils/db.py:33 210 | msgid "No database found, creating it." 211 | msgstr "Keine Datenbank gefunden, sie wird erstellt." 212 | 213 | #: utils/db.py:55 214 | msgid "Database created." 215 | msgstr "Datenbank erstellt." 216 | 217 | #~ msgid "Add Feed" 218 | #~ msgstr "Feed hinzufügen" 219 | 220 | #~ msgid "Feed url:" 221 | #~ msgstr "Feed url" 222 | -------------------------------------------------------------------------------- /feedindicator/locale/ru/LC_MESSAGES/feedindicator.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feedindicator/Feedindicator/990d56a417be822b7d59da23deada927ab8d2ba6/feedindicator/locale/ru/LC_MESSAGES/feedindicator.mo -------------------------------------------------------------------------------- /feedindicator/locale/ru/LC_MESSAGES/feedindicator.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: feedindicator\n" 4 | "POT-Creation-Date: 2017-10-10 15:10+0200\n" 5 | "PO-Revision-Date: 2017-10-10 16:59+0200\n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: ru\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.8.7.1\n" 13 | "X-Poedit-Basepath: ../../..\n" 14 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 15 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" 16 | "X-Poedit-SearchPath-0: .\n" 17 | 18 | #: dialogs/about.py:40 indicator.py:383 19 | msgid "About" 20 | msgstr "О программе" 21 | 22 | #: dialogs/add_feed.py:50 dialogs/add_feed.py:85 indicator.py:364 23 | msgid "Add feed" 24 | msgstr "Добавить новостную ленту" 25 | 26 | #: dialogs/add_feed.py:67 27 | msgid "Feed url" 28 | msgstr "" 29 | 30 | #: dialogs/add_feed.py:82 dialogs/preferences.py:218 31 | msgid "Cancel" 32 | msgstr "Отмена" 33 | 34 | #: dialogs/add_feed.py:128 feedindicator.py:42 35 | msgid "Could not add feed." 36 | msgstr "" 37 | 38 | #: dialogs/preferences.py:61 indicator.py:378 39 | msgid "Preferences" 40 | msgstr "" 41 | 42 | #: dialogs/preferences.py:82 dialogs/preferences.py:133 43 | msgid "Feed list" 44 | msgstr "" 45 | 46 | #: dialogs/preferences.py:89 47 | msgid "Configure the feeds." 48 | msgstr "Настройка новостных лент." 49 | 50 | #: dialogs/preferences.py:109 51 | msgid "ID" 52 | msgstr "" 53 | 54 | #: dialogs/preferences.py:109 55 | msgid "Title" 56 | msgstr "Заголовок" 57 | 58 | #: dialogs/preferences.py:109 59 | msgid "URL" 60 | msgstr "Ссылка" 61 | 62 | #: dialogs/preferences.py:125 63 | msgid "Add" 64 | msgstr "Добавить" 65 | 66 | #: dialogs/preferences.py:129 67 | msgid "Remove" 68 | msgstr "Удалить" 69 | 70 | #: dialogs/preferences.py:136 dialogs/preferences.py:213 71 | msgid "Options" 72 | msgstr "" 73 | 74 | #: dialogs/preferences.py:143 75 | msgid "Auto update feeds" 76 | msgstr "" 77 | 78 | #: dialogs/preferences.py:181 79 | msgid "Show feeds at top of menu" 80 | msgstr "Отображать ленты в верху меню" 81 | 82 | #: dialogs/preferences.py:186 83 | msgid "Show feeds with no unread posts" 84 | msgstr "" 85 | 86 | #: dialogs/preferences.py:191 87 | msgid "Launch at system startup" 88 | msgstr "Запускать при старте" 89 | 90 | #: dialogs/preferences.py:196 91 | msgid "Show notifications" 92 | msgstr "Показывать уведомления" 93 | 94 | #: dialogs/preferences.py:202 95 | msgid "Show notifications at beginning and end of update" 96 | msgstr "Уведомлять о начале и завершении обновления" 97 | 98 | #: dialogs/preferences.py:222 99 | msgid "Save" 100 | msgstr "Сохранить" 101 | 102 | #: dialogs/preferences.py:320 indicator.py:498 103 | #, python-format 104 | msgid "Update feeds every minute" 105 | msgid_plural "Update feeds every %(minutes)d minutes" 106 | msgstr[0] "" 107 | msgstr[1] "" 108 | msgstr[2] "" 109 | 110 | #: dialogs/preferences.py:333 111 | #, python-format 112 | msgid "Show 1 item per feed" 113 | msgid_plural "Show %(items)d items per feed" 114 | msgstr[0] "" 115 | msgstr[1] "" 116 | msgstr[2] "" 117 | 118 | #: feedindicator.py:48 119 | #, python-format 120 | msgid "Feeds updated. New posts: %(nr)d" 121 | msgstr "" 122 | 123 | #: feedindicator.py:50 124 | msgid "Error while updating feeds." 125 | msgstr "" 126 | 127 | #: feedindicator.py:94 128 | msgid "Could not init database." 129 | msgstr "" 130 | 131 | #: feeds.py:46 132 | msgid "Feed added." 133 | msgstr "" 134 | 135 | #: indicator.py:71 136 | msgid "New Posts." 137 | msgstr "" 138 | 139 | #: indicator.py:73 140 | msgid "Nothing new." 141 | msgstr "" 142 | 143 | #: indicator.py:188 144 | msgid "Begin updating Feeds." 145 | msgstr "Новостные ленты обновляются." 146 | 147 | #: indicator.py:223 148 | msgid "Finished updating feeds." 149 | msgstr "Обновление новостных лент завершено." 150 | 151 | #: indicator.py:234 152 | msgid "Loading" 153 | msgstr "Загрузка" 154 | 155 | #: indicator.py:271 156 | msgid "No unread posts." 157 | msgstr "" 158 | 159 | #: indicator.py:275 160 | msgid "No feeds defined!" 161 | msgstr "Новостные ленты отсутствуют!" 162 | 163 | #: indicator.py:291 164 | msgid "Open all unread" 165 | msgstr "" 166 | 167 | #: indicator.py:295 168 | msgid "Mark all as read" 169 | msgstr "Пометить все как прочитанные" 170 | 171 | #: indicator.py:299 172 | msgid "Mark all as unread" 173 | msgstr "Пометить все как непрочитанные" 174 | 175 | #: indicator.py:303 176 | msgid "Reload all feeds" 177 | msgstr "Перезагрузить все ленты" 178 | 179 | #: indicator.py:319 180 | msgid "Open Website" 181 | msgstr "Открыть сайт" 182 | 183 | #: indicator.py:323 184 | msgid "Open unread" 185 | msgstr "Открыть непрочитанные" 186 | 187 | #: indicator.py:328 188 | msgid "Open displayed posts" 189 | msgstr "" 190 | 191 | #: indicator.py:332 192 | msgid "Mark as read" 193 | msgstr "Пометить ленту как прочитанную" 194 | 195 | #: indicator.py:337 196 | msgid "Mark displayed posts as read" 197 | msgstr "" 198 | 199 | #: indicator.py:341 200 | msgid "Mark as unread" 201 | msgstr "Пометить ленту как непрочитанную" 202 | 203 | #: indicator.py:386 204 | msgid "Exit" 205 | msgstr "Выход" 206 | 207 | #: indicator.py:493 208 | msgid "Feeds will not update automatically." 209 | msgstr "Не удалось обновить новостные ленты." 210 | 211 | #: utils/db.py:33 212 | msgid "No database found, creating it." 213 | msgstr "" 214 | 215 | #: utils/db.py:55 216 | msgid "Database created." 217 | msgstr "" 218 | -------------------------------------------------------------------------------- /feedindicator/locale/tr/LC_MESSAGES/feedindicator.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feedindicator/Feedindicator/990d56a417be822b7d59da23deada927ab8d2ba6/feedindicator/locale/tr/LC_MESSAGES/feedindicator.mo -------------------------------------------------------------------------------- /feedindicator/locale/tr/LC_MESSAGES/feedindicator.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: feedindicator\n" 4 | "POT-Creation-Date: 2017-10-10 15:20+0200\n" 5 | "PO-Revision-Date: 2017-10-10 16:57+0200\n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: tr\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.8.7.1\n" 13 | "X-Poedit-Basepath: ../../..\n" 14 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 15 | "X-Poedit-SearchPath-0: .\n" 16 | 17 | #: dialogs/about.py:40 indicator.py:383 18 | msgid "About" 19 | msgstr "Hakkında" 20 | 21 | #: dialogs/add_feed.py:50 dialogs/add_feed.py:85 indicator.py:364 22 | msgid "Add feed" 23 | msgstr "Besleme Ekle" 24 | 25 | #: dialogs/add_feed.py:67 26 | msgid "Feed url" 27 | msgstr "" 28 | 29 | #: dialogs/add_feed.py:82 dialogs/preferences.py:218 30 | msgid "Cancel" 31 | msgstr "İptal" 32 | 33 | #: dialogs/add_feed.py:128 feedindicator.py:42 34 | msgid "Could not add feed." 35 | msgstr "" 36 | 37 | #: dialogs/preferences.py:61 indicator.py:378 38 | msgid "Preferences" 39 | msgstr "" 40 | 41 | #: dialogs/preferences.py:82 dialogs/preferences.py:133 42 | msgid "Feed list" 43 | msgstr "" 44 | 45 | #: dialogs/preferences.py:89 46 | msgid "Configure the feeds." 47 | msgstr "Menüde gösterilecek beslemeleri yapılandır." 48 | 49 | #: dialogs/preferences.py:109 50 | msgid "ID" 51 | msgstr "" 52 | 53 | #: dialogs/preferences.py:109 54 | msgid "Title" 55 | msgstr "Başlık" 56 | 57 | #: dialogs/preferences.py:109 58 | msgid "URL" 59 | msgstr "URL" 60 | 61 | #: dialogs/preferences.py:125 62 | msgid "Add" 63 | msgstr "Ekle" 64 | 65 | #: dialogs/preferences.py:129 66 | msgid "Remove" 67 | msgstr "Kaldır" 68 | 69 | #: dialogs/preferences.py:136 dialogs/preferences.py:213 70 | msgid "Options" 71 | msgstr "" 72 | 73 | #: dialogs/preferences.py:143 74 | msgid "Auto update feeds" 75 | msgstr "" 76 | 77 | #: dialogs/preferences.py:181 78 | msgid "Show feeds at top of menu" 79 | msgstr "Beslemeleri menünü başında göster" 80 | 81 | #: dialogs/preferences.py:186 82 | msgid "Show feeds with no unread posts" 83 | msgstr "" 84 | 85 | #: dialogs/preferences.py:191 86 | msgid "Launch at system startup" 87 | msgstr "Başlangıçta çalıştır" 88 | 89 | #: dialogs/preferences.py:196 90 | msgid "Show notifications" 91 | msgstr "Bildirimleri göster" 92 | 93 | #: dialogs/preferences.py:202 94 | msgid "Show notifications at beginning and end of update" 95 | msgstr "Bildirimleri güncellemenin sonunda göster" 96 | 97 | #: dialogs/preferences.py:222 98 | msgid "Save" 99 | msgstr "Kaydet" 100 | 101 | #: dialogs/preferences.py:320 indicator.py:498 102 | #, python-format 103 | msgid "Update feeds every minute" 104 | msgid_plural "Update feeds every %(minutes)d minutes" 105 | msgstr[0] "" 106 | msgstr[1] "" 107 | 108 | #: dialogs/preferences.py:333 109 | #, python-format 110 | msgid "Show 1 item per feed" 111 | msgid_plural "Show %(items)d items per feed" 112 | msgstr[0] "" 113 | msgstr[1] "" 114 | 115 | #: feedindicator.py:48 116 | #, python-format 117 | msgid "Feeds updated. New posts: %(nr)d" 118 | msgstr "" 119 | 120 | #: feedindicator.py:50 121 | msgid "Error while updating feeds." 122 | msgstr "" 123 | 124 | #: feedindicator.py:94 125 | msgid "Could not init database." 126 | msgstr "" 127 | 128 | #: feeds.py:46 129 | msgid "Feed added." 130 | msgstr "" 131 | 132 | #: indicator.py:71 133 | msgid "New Posts." 134 | msgstr "" 135 | 136 | #: indicator.py:73 137 | msgid "Nothing new." 138 | msgstr "" 139 | 140 | #: indicator.py:188 141 | msgid "Begin updating Feeds." 142 | msgstr "Beslemeler kısa süre içinde güncellenecek." 143 | 144 | #: indicator.py:223 145 | msgid "Finished updating feeds." 146 | msgstr "Beslemeleri güncelleme bitti." 147 | 148 | #: indicator.py:234 149 | msgid "Loading" 150 | msgstr "Yükleniyor" 151 | 152 | #: indicator.py:271 153 | msgid "No unread posts." 154 | msgstr "" 155 | 156 | #: indicator.py:275 157 | msgid "No feeds defined!" 158 | msgstr "Hiç besleme tanımlanmamış!" 159 | 160 | #: indicator.py:291 161 | msgid "Open all unread" 162 | msgstr "Okunmamışların hepsini aç" 163 | 164 | #: indicator.py:295 165 | msgid "Mark all as read" 166 | msgstr "Hepsini okundu işaretle" 167 | 168 | #: indicator.py:299 169 | msgid "Mark all as unread" 170 | msgstr "Hepsini okunmadı işaretle" 171 | 172 | #: indicator.py:303 173 | msgid "Reload all feeds" 174 | msgstr "Beslemeleri yeniden yükle" 175 | 176 | #: indicator.py:319 177 | msgid "Open Website" 178 | msgstr "Website'yi aç" 179 | 180 | #: indicator.py:323 181 | msgid "Open unread" 182 | msgstr "" 183 | 184 | #: indicator.py:328 185 | msgid "Open displayed posts" 186 | msgstr "" 187 | 188 | #: indicator.py:332 189 | msgid "Mark as read" 190 | msgstr "Okundu işaretle" 191 | 192 | #: indicator.py:337 193 | msgid "Mark displayed posts as read" 194 | msgstr "" 195 | 196 | #: indicator.py:341 197 | msgid "Mark as unread" 198 | msgstr "Okunmadı işaretle" 199 | 200 | #: indicator.py:386 201 | msgid "Exit" 202 | msgstr "Çıkış" 203 | 204 | #: indicator.py:493 205 | msgid "Feeds will not update automatically." 206 | msgstr "Beslemeler otomatik olarak güncellenmeyecek." 207 | 208 | #: utils/db.py:33 209 | msgid "No database found, creating it." 210 | msgstr "" 211 | 212 | #: utils/db.py:55 213 | msgid "Database created." 214 | msgstr "" 215 | -------------------------------------------------------------------------------- /feedindicator/threads/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | from feedindicator.threads.feeds import FeedThread 25 | 26 | 27 | __all = ('FeedThread',) 28 | -------------------------------------------------------------------------------- /feedindicator/threads/feeds.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | from feedindicator import feeds 25 | from gi.repository import GLib 26 | from threading import Thread 27 | 28 | 29 | class FeedThread(Thread): 30 | """Feed thread, to run feed updates in a separate thread. 31 | 32 | Attributes: 33 | _callback: callback function after url has been added 34 | _callback_args: arguments for callback function 35 | """ 36 | def __init__(self, callback, *args): 37 | """Init feed thread. 38 | 39 | Args: 40 | callback: callback function after url has been added 41 | args: arguments for callback function 42 | """ 43 | Thread.__init__(self, name='FeedThread') 44 | self._callback = callback 45 | self._callback_args = args 46 | 47 | def run(self): 48 | """Updates all feeds in the database und updates the infos""" 49 | feeds.update() 50 | GLib.idle_add(self._callback, self._callback_args) 51 | -------------------------------------------------------------------------------- /feedindicator/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | from feedindicator.utils.core import get_hash 24 | from feedindicator.utils.db import init as db_init 25 | from feedindicator.utils.sqlite import SQLite 26 | from feedindicator.utils.version import app_version 27 | 28 | 29 | __all__ = ('app_version', 'db_init', 'get_hash', 'SQLite') 30 | -------------------------------------------------------------------------------- /feedindicator/utils/autostart.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | import feedindicator 25 | import os 26 | 27 | from feedindicator import config 28 | 29 | 30 | def create(): 31 | """Create autostart file.""" 32 | if not os.path.exists(config.app_autostart_dir): 33 | os.makedirs(config.app_autostart_dir, 0o700) 34 | with open(config.app_autostart_file, 'w', encoding='utf-8') as f: 35 | f.write('[Desktop Entry]\n') 36 | f.write('Type=Application\n') 37 | f.write('Exec=%s --autostarted\n' % feedindicator.__app_name__) 38 | f.write('X-GNOME-Autostart-enabled=true\n') 39 | f.write('Icon=%s\n' % feedindicator.__app_name__) 40 | f.write('Name=%s\n' % feedindicator.__app_name__) 41 | f.write('Comment=%s' % feedindicator.__description__) 42 | 43 | 44 | def delete(): 45 | """Delete autostart file.""" 46 | if os.path.exists(config.app_autostart_file): 47 | os.remove(config.app_autostart_file) 48 | -------------------------------------------------------------------------------- /feedindicator/utils/core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | import hashlib 25 | 26 | 27 | def get_hash(*args): 28 | """Calculate hash for combined string of arguments. 29 | 30 | Args: 31 | args: values to hash 32 | """ 33 | return hashlib.sha512('@'.join(a for a in args if a). \ 34 | encode('utf-8')).hexdigest() 35 | -------------------------------------------------------------------------------- /feedindicator/utils/db.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | import os 25 | 26 | from feedindicator import config 27 | from feedindicator.utils.sqlite import SQLite 28 | 29 | 30 | def init(): 31 | """Init SQLite database, creates tables.""" 32 | if not os.path.exists(config.app_database): 33 | print(_('No database found, creating it.')) 34 | 35 | with SQLite() as db: 36 | db.s('CREATE TABLE feeds (id integer NOT NULL PRIMARY KEY ' + 37 | 'AUTOINCREMENT, created_at datetime NOT NULL DEFAULT ' + 38 | 'CURRENT_TIMESTAMP, updated_at datetime NOT NULL DEFAULT ' + 39 | 'CURRENT_TIMESTAMP, feed_url TEXT NOT NULL UNIQUE, url ' + 40 | 'TEXT, title TEXT, img TEXT)') 41 | db.s('CREATE TRIGGER feeds_updated_at_trigger AFTER UPDATE ON ' + 42 | 'feeds FOR EACH ROW BEGIN UPDATE feeds SET ' + 43 | 'updated_at=CURRENT_TIMESTAMP WHERE id=old.id; END') 44 | db.s('CREATE TABLE posts (id integer NOT NULL PRIMARY KEY ' + 45 | 'AUTOINCREMENT, created_at datetime NOT NULL DEFAULT ' + 46 | 'CURRENT_TIMESTAMP, updated_at datetime NOT NULL DEFAULT ' + 47 | 'CURRENT_TIMESTAMP, hash TEXT NOT NULL UNIQUE, url TEXT, ' + 48 | 'title TEXT, raw TEXT, read BOOLEAN DEFAULT false, feed_id ' + 49 | 'integer NOT NULL, FOREIGN KEY(feed_id) REFERENCES ' + 50 | 'feeds(id))') 51 | db.s('CREATE INDEX posts_feed_id_idx ON posts(feed_id)') 52 | db.s('CREATE TRIGGER posts_updated_at_trigger AFTER UPDATE ON ' + 53 | 'posts FOR EACH ROW BEGIN UPDATE posts SET ' + 54 | 'updated_at=CURRENT_TIMESTAMP WHERE id=old.id; END') 55 | print(_('Database created.')) 56 | -------------------------------------------------------------------------------- /feedindicator/utils/sqlite.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | import sqlite3 25 | 26 | from feedindicator import config 27 | 28 | 29 | class SQLite: 30 | """SQlite database wrapper. 31 | 32 | Attributes: 33 | _con: database connection 34 | """ 35 | def __init__(self): 36 | self._con = None 37 | 38 | def get_con(self): 39 | """Get database connection.""" 40 | return self._con 41 | 42 | con = property(get_con) 43 | 44 | def __enter__(self): 45 | self.open(config.app_database) 46 | return self 47 | 48 | def __exit__(self, exc_type, exc_value, traceback): 49 | self.close() 50 | 51 | def open(self, path): 52 | """Opens a new connection. 53 | 54 | Args: 55 | path: path to sqlite database file 56 | """ 57 | self._con = sqlite3.connect(path) 58 | 59 | def close(self): 60 | """Closes the current connection.""" 61 | if self._con: 62 | self._con.close() 63 | 64 | def s(self, query, data=(), auto_commit=True): 65 | """Executes the given query and returns the results. 66 | 67 | Args: 68 | query: SQL query 69 | data: optinal additional data 70 | auto_commit: if true commits after query execution 71 | """ 72 | if not self._con: 73 | self.open(config.app_database) 74 | cur = self._con.cursor() 75 | if data == (): 76 | cur.execute(query) 77 | else: 78 | cur.execute(query, data) 79 | rows = cur.fetchall() 80 | if auto_commit: 81 | self.commit() 82 | return rows 83 | 84 | def many(self, query, data, auto_commit=True): 85 | """Executes the given query with executemany() function. 86 | 87 | Args: 88 | query: SQL query 89 | data: additional data 90 | auto_commit: if true commits after query execution 91 | """ 92 | if not self._con: 93 | self.open(config.app_database) 94 | cur = self._con.cursor() 95 | cur.executemany(query, data) 96 | if auto_commit: 97 | self.commit() 98 | 99 | def commit(self): 100 | """Commit changes to the database. When an error occurs a roll back 101 | is done. 102 | """ 103 | if not self._con: 104 | self.open(config.app_database) 105 | try: 106 | self._con.commit() 107 | except sqlite3.Error as e: 108 | self._con.rollback() 109 | raise e 110 | -------------------------------------------------------------------------------- /feedindicator/utils/version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2010-2017 Dave Gardner , 4 | # Michael Judge , 5 | # Nicolas Raoul , 6 | # Nathanael Philipp (jnphilipp) 7 | # 8 | # This file is part of feedindicator. 9 | # 10 | # Foobar is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # feedindicator is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with feedindicator. If not, see . 22 | 23 | 24 | import feedindicator 25 | 26 | from math import ceil, floor 27 | 28 | 29 | def app_version(): 30 | """Generate command line version text.""" 31 | def line(text): 32 | """Make single line with padding left and right.""" 33 | return '#%s%s%s#\n' % (' ' * floor((47 - len(text)) / 2), 34 | text, 35 | ' ' * ceil((47 - len(text)) / 2)) 36 | 37 | return (('#################################################\n' + 38 | line(feedindicator.__app_name__) + 39 | line(feedindicator.__description__) + 40 | line('') + 41 | line(feedindicator.__version__) + 42 | line(feedindicator.__license__) + 43 | line(feedindicator.__author__) + 44 | line(feedindicator.__email__) + 45 | line('') + 46 | line(feedindicator.__github__) + 47 | '#################################################')) 48 | -------------------------------------------------------------------------------- /icons/active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feedindicator/Feedindicator/990d56a417be822b7d59da23deada927ab8d2ba6/icons/active.png -------------------------------------------------------------------------------- /icons/attention.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feedindicator/Feedindicator/990d56a417be822b7d59da23deada927ab8d2ba6/icons/attention.png -------------------------------------------------------------------------------- /icons/logo-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feedindicator/Feedindicator/990d56a417be822b7d59da23deada927ab8d2ba6/icons/logo-128x128.png -------------------------------------------------------------------------------- /icons/logo-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feedindicator/Feedindicator/990d56a417be822b7d59da23deada927ab8d2ba6/icons/logo-48x48.png --------------------------------------------------------------------------------