diff --git a/.gitignore b/.gitignore index 05cc6ff..7e287d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -debian_packaging/feedindicator_*.deb -debian_packaging/package +__pycache__ +bin +build diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..733dbe6 --- /dev/null +++ b/Makefile @@ -0,0 +1,174 @@ +BASH_COMPLETION_DIR=/usr/share/bash-completion/completions/ +BIN_DIR=/usr/bin/ +DOC_DIR=/usr/share/doc/feedindicator/ +MAN_DIR=/usr/share/man/man1/ +SHARE_DIR=/usr/share/feedindicator/ + + +all: bin/feedindicator bin/feedindicator.desktop + + +bin: + @mkdir bin + + +build: + @mkdir build + + +build/package/DEBIAN: build + @mkdir -p build/package/DEBIAN + + +bin/feedindicator: bin + @echo "#!/usr/bin/env bash\n" > bin/feedindicator + @echo "cd $(SHARE_DIR)" >> bin/feedindicator + @echo "python3 -m feedindicator \$$@" >> bin/feedindicator + @chmod a+x bin/feedindicator + + +bin/feedindicator.desktop: bin + @echo "[Desktop Entry]" > bin/feedindicator.desktop + @echo "Version=2.0.0" >> bin/feedindicator.desktop + @echo "Type=Application" >> bin/feedindicator.desktop + @echo "Terminal=false" >> bin/feedindicator.desktop + @echo "Exec=feedindicator" >> bin/feedindicator.desktop + @echo "Icon=feedindicator" >> bin/feedindicator.desktop + @echo "Name=Feedindicator" >> bin/feedindicator.desktop + @echo "Comment=A RSS feed reader for the indicator area." >> bin/feedindicator.desktop + @echo "Categories=Internet;Network;" >> bin/feedindicator.desktop + @chmod a+x bin/feedindicator.desktop + + +build/package/DEBIAN/md5sums: bin/feedindicator bin/feedindicator.desktop build/copyright build/changelog build/feedindicator.1 build/package/DEBIAN + @mkdir -m 755 -p build/package$(BASH_COMPLETION_DIR) + @mkdir -m 755 -p build/package$(BIN_DIR) + @mkdir -m 755 -p build/package$(DOC_DIR) + @mkdir -m 755 -p build/package$(MAN_DIR) + @mkdir -m 755 -p build/package$(SHARE_DIR) + @find build/package -type d -exec chmod 755 {} \; + + @cp -r bin/feedindicator build/package$(BIN_DIR) + @chmod 755 build/package$(BIN_DIR)feedindicator + @cp bin/feedindicator.desktop build/package$(SHARE_DIR) + @chmod 755 build/package$(SHARE_DIR)feedindicator.desktop + @cp -r feedindicator build/package$(SHARE_DIR) + @cp -r icons build/package$(SHARE_DIR) + @find build/package$(SHARE_DIR) -type f -exec chmod 644 {} \; + @find build/package$(SHARE_DIR) -type d -exec chmod 755 {} \; + @cp feedindicator.bash-completion build/package$(BASH_COMPLETION_DIR) + @chmod 644 build/package$(BASH_COMPLETION_DIR)feedindicator.bash-completion + + @cat build/feedindicator.1 | gzip -n9 > build/package$(MAN_DIR)feedindicator.1.gz + @chmod 644 build/package$(MAN_DIR)feedindicator.1.gz + + @cat build/changelog | gzip -n9 > build/package$(DOC_DIR)changelog.gz + @chmod 644 build/package$(DOC_DIR)changelog.gz + + @cp build/copyright build/package$(DOC_DIR)copyright + @chmod 644 build/package$(DOC_DIR)copyright + + @mkdir -p build/package/DEBIAN + @md5sum `find build/package -type f -not -path "*DEBIAN*"` > build/md5sums + @sed -e "s/build\/package\///" build/md5sums > build/package/DEBIAN/md5sums + @chmod 644 build/package/DEBIAN/md5sums + + +build/package/DEBIAN/control: build/package/DEBIAN/md5sums + @echo "Package: feedindicator" > build/package/DEBIAN/control + @echo "Version: `grep "__version_info__ =" feedindicator/__init__.py | grep -oE "[0-9]+, [0-9]+, [0-9]+" | sed -e "s/, /./g"`" >> build/package/DEBIAN/control + @echo "Section: web" >> build/package/DEBIAN/control + @echo "Priority: optional" >> build/package/DEBIAN/control + @echo "Architecture: all" >> build/package/DEBIAN/control + @echo "Depends: python3 (>= 3), python3-feedparser, python3-gi, hicolor-icon-theme, indicator-application, xdg-utils" >> build/package/DEBIAN/control + @echo "Installed-Size: `du -csk build/package/usr | grep -oE "[0-9]+\stotal" | cut -f 1`" >> build/package/DEBIAN/control + @echo "Maintainer: Nathanael Philipp " >> build/package/DEBIAN/control + @echo "Homepage: https://github.com/jnphilipp/Feedindicator" >> build/package/DEBIAN/control + @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 + + +build/package/DEBIAN/postinst: build/package/DEBIAN + @echo "#!/bin/sh -e" > build/package/DEBIAN/postinst + @echo "xdg-icon-resource install --theme hicolor --novendor --size 512 $(SHARE_DIR)icons/active.png feedindicator-active" >> build/package/DEBIAN/postinst + @echo "xdg-icon-resource install --theme hicolor --novendor --size 512 $(SHARE_DIR)icons/attention.png feedindicator-attention" >> build/package/DEBIAN/postinst + @echo "xdg-icon-resource install --theme hicolor --novendor --size 128 --context apps $(SHARE_DIR)icons/logo-128x128.png feedindicator" >> build/package/DEBIAN/postinst + @echo "xdg-icon-resource install --theme hicolor --novendor --size 48 --context apps $(SHARE_DIR)icons/logo-48x48.png feedindicator" >> build/package/DEBIAN/postinst + @echo "xdg-desktop-menu install --novendor $(SHARE_DIR)feedindicator.desktop" >> build/package/DEBIAN/postinst + @chmod 755 build/package/DEBIAN/postinst + + +build/package/DEBIAN/prerm: build/package/DEBIAN + @echo "#!/bin/sh -e" > build/package/DEBIAN/prerm + @echo "xdg-icon-resource uninstall --theme hicolor --novendor --size 512 feedindicator-active" >> build/package/DEBIAN/prerm + @echo "xdg-icon-resource uninstall --theme hicolor --novendor --size 512 feedindicator-attention" >> build/package/DEBIAN/prerm + @echo "xdg-icon-resource uninstall --theme hicolor --novendor --size 128 --context apps feedindicator" >> build/package/DEBIAN/prerm + @echo "xdg-icon-resource uninstall --theme hicolor --novendor --size 48 --context apps feedindicator" >> build/package/DEBIAN/prerm + @echo "xdg-desktop-menu uninstall --novendor feedindicator.desktop" >> build/package/DEBIAN/prerm + @chmod 755 build/package/DEBIAN/prerm + + +build/copyright: build + @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 + + +build/changelog: build + @git log --oneline --decorate > build/changelog + + +build/feedindicator.1: build + @help2man -n "feedindicator - A RSS feed reader for the indicator area." feedindicator > build/feedindicator.1 + + +install: bin/feedindicator bin/feedindicator.desktop build/copyright build/changelog build/feedindicator.1 + @apt install python3 python3-gi python3-feedparser hicolor-icon-theme indicator-application xdg-utils + @xdg-icon-resource install --theme hicolor --novendor --size 512 icons/active.png feedindicator-active + @xdg-icon-resource install --theme hicolor --novendor --size 512 icons/attention.png feedindicator-attention + @xdg-icon-resource install --theme hicolor --novendor --size 128 --context apps icons/logo-128x128.png feedindicator + @xdg-icon-resource install --theme hicolor --novendor --size 48 --context apps icons/logo-48x48.png feedindicator + @xdg-desktop-menu install --novendor bin/feedindicator.desktop + @mkdir -p $(SHARE_DIR) + @cp -r feedindicator $(SHARE_DIR) + @install bin/feedindicator $(BIN_DIR) + @install feedindicator.bash-completion $(BASH_COMPLETION_DIR) + @cat build/feedindicator.1 | gzip -n9 > $(MAN_DIR)feedindicator.1.gz + @mkdir -p $(DOC_DIR) + @cat build/changelog | gzip -n9 > $(DOC_DIR)changelog.gz + @install build/copyright $(DOC_DIR)copyright + @echo "feedindicator install completed." + + +uninstall: + @rm -r $(SHARE_DIR) + @rm -r $(DOC_DIR) + @rm $(BIN_DIR)/feedindicator + @rm $(BASH_COMPLETION_DIR)feedindicator.bash-completion + @rm $(MAN_DIR)feedindicator.1.gz + @xdg-icon-resource uninstall --theme hicolor --novendor --size 512 feedindicator-active + @xdg-icon-resource uninstall --theme hicolor --novendor --size 512 feedindicator-attention + @xdg-icon-resource uninstall --theme hicolor --novendor --size 128 --context apps feedindicator + @xdg-icon-resource uninstall --theme hicolor --novendor --size 48 --context apps feedindicator + @xdg-desktop-menu uninstall --novendor feedindicator.desktop + @if [ -f ~/.config/feedindicator/feedindicator.desktop ]; then\ + unlink ~/.config/feedindicator/feedindicator.desktop;\ + fi + @if [ -d ~/.cache/feedindicator ]; then\ + rm -r ~/.cache/feedindicator;\ + fi + @if [ -d ~/.config/feedindicator ]; then\ + rm -r ~/.config/feedindicator;\ + fi + @if [ -d ~/.local/share/feedindicator ]; then\ + rm -r ~/.local/share/feedindicator;\ + fi + @echo "feedindicator uninstall completed." + + +deb: build/package/DEBIAN/control build/package/DEBIAN/postinst build/package/DEBIAN/prerm + fakeroot dpkg-deb -b build/package build/feedindicator.deb + lintian -Ivi build/feedindicator.deb + + +clean: + @rm -rf ./bin + @rm -rf ./build + @find . -name __pycache__ -exec rm -rf {} \; diff --git a/README.md b/README.md index 29175df..d75179f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,25 @@ -Feedindicator -============= +# Feedindicator -RSS feed updates in Ubuntu Unity indicator area +A RSS feed reader for the indicator area. -http://code.google.com/p/feedindicator/ +## Requirements + * ```python3``` + * ```python3-gi``` + * ```python3-feedparser``` + * ```indicator-application``` or ```Gtk3 StatusIcon``` + +## Install/Uninstall + * to install run ```make install``` + * to uninstall run ```make uninstall``` + +## Run + To run after install either use the command ```feedindicator``` or the desktop file. + +## Build Deb-Paket +### Requirements + * ```help2man``` + * ```fakeroot``` + * ```lintian``` + +### Build + * run ```make deb``` diff --git a/dark/indicator-feedindicator-attention.png b/dark/indicator-feedindicator-attention.png deleted file mode 100644 index 4dbced3..0000000 Binary files a/dark/indicator-feedindicator-attention.png and /dev/null differ diff --git a/dark/indicator-feedindicator.png b/dark/indicator-feedindicator.png deleted file mode 100644 index 31aa4c9..0000000 Binary files a/dark/indicator-feedindicator.png and /dev/null differ diff --git a/debian_packaging/generate_debian_package.sh b/debian_packaging/generate_debian_package.sh deleted file mode 100755 index 9c2c939..0000000 --- a/debian_packaging/generate_debian_package.sh +++ /dev/null @@ -1,32 +0,0 @@ -# Generate the Debian package for feedindicator -# Remember to change the version number in skeleton/DEBIAN/control - -rm -rf package *.deb -cp -r skeleton package -cd package - -# Copy program and icons -mkdir usr/bin -mkdir usr/share/feedindicator -cp ../../feedindicator usr/bin/ -cp ../../*.desktop usr/share/feedindicator/ -cp ../../*.png usr/share/feedindicator/ -cp -r ../../dark/ usr/share/feedindicator/ -cp -r ../../light/ usr/share/feedindicator/ -cp -r ../../hicolor/ usr/share/feedindicator/ - -# Calculate MD5 sums -md5sum $(find usr -type f) > DEBIAN/md5sums -chmod 644 DEBIAN/md5sums - -# Calculate installed size -INSTALLED_SIZE=`du -ks usr|cut -f 1` -sed -e "s/INSTALLED_SIZE/$INSTALLED_SIZE/" ../skeleton/DEBIAN/control > DEBIAN/control - -# Generate Debian package -cd .. -VERSION=`grep "Version" skeleton/DEBIAN/control | sed -e "s/.* //"` -fakeroot dpkg-deb -b package feedindicator_$VERSION.deb - -# Check the package -lintian -Ivi feedindicator_$VERSION.deb diff --git a/debian_packaging/skeleton/DEBIAN/control b/debian_packaging/skeleton/DEBIAN/control deleted file mode 100644 index 2bf9d93..0000000 --- a/debian_packaging/skeleton/DEBIAN/control +++ /dev/null @@ -1,14 +0,0 @@ -Package: feedindicator -Version: 1.05-1 -Section: web -Priority: optional -Architecture: all -Depends: python (>= 2.3), python-feedparser, python-appindicator, hicolor-icon-theme, indicator-application, xdg-utils -Installed-Size: INSTALLED_SIZE -Maintainer: Nicolas Raoul -Homepage: http://code.google.com/p/feedindicator/ -Description: RSS feed updates in the indicator area - Editable, sortable list of feed URLs. - Notification popups of new feed items. - Adjustable update timer. - diff --git a/debian_packaging/skeleton/DEBIAN/postinst b/debian_packaging/skeleton/DEBIAN/postinst deleted file mode 100755 index d3a48c7..0000000 --- a/debian_packaging/skeleton/DEBIAN/postinst +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -xdg-icon-resource install --theme ubuntu-mono-dark --novendor --size 22 /usr/share/feedindicator/dark/indicator-feedindicator.png indicator-feedindicator -xdg-icon-resource install --theme ubuntu-mono-dark --novendor --size 22 /usr/share/feedindicator/dark/indicator-feedindicator-attention.png indicator-feedindicator-attention -xdg-icon-resource install --theme ubuntu-mono-light --novendor --size 22 /usr/share/feedindicator/light/indicator-feedindicator.png indicator-feedindicator -xdg-icon-resource install --theme ubuntu-mono-light --novendor --size 22 /usr/share/feedindicator/light/indicator-feedindicator-attention.png indicator-feedindicator-attention -xdg-icon-resource install --theme hicolor --novendor --size 22 /usr/share/feedindicator/hicolor/indicator-feedindicator.png indicator-feedindicator -xdg-icon-resource install --theme hicolor --novendor --size 22 /usr/share/feedindicator/hicolor/indicator-feedindicator-attention.png indicator-feedindicator-attention -xdg-icon-resource install --theme hicolor --novendor --size 128 --context apps /usr/share/feedindicator/feedindicator-logo.png feedindicator -xdg-icon-resource install --theme hicolor --novendor --size 48 --context apps /usr/share/feedindicator/feedindicator-48x48.png feedindicator - -xdg-desktop-menu install --novendor /usr/share/feedindicator/feedindicator.desktop - diff --git a/debian_packaging/skeleton/DEBIAN/prerm b/debian_packaging/skeleton/DEBIAN/prerm deleted file mode 100755 index 4d00540..0000000 --- a/debian_packaging/skeleton/DEBIAN/prerm +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -xdg-icon-resource uninstall --theme ubuntu-mono-dark --size 22 indicator-feedindicator -xdg-icon-resource uninstall --theme ubuntu-mono-dark --size 22 indicator-feedindicator-attention -xdg-icon-resource uninstall --theme ubuntu-mono-light --size 22 indicator-feedindicator -xdg-icon-resource uninstall --theme ubuntu-mono-light --size 22 indicator-feedindicator-attention -xdg-icon-resource uninstall --theme hicolor --size 22 indicator-feedindicator -xdg-icon-resource uninstall --theme hicolor --size 22 indicator-feedindicator-attention -xdg-icon-resource uninstall --theme hicolor --size 128 --context apps feedindicator -xdg-icon-resource uninstall --theme hicolor --size 48 --context apps feedindicator - -xdg-desktop-menu uninstall feedindicator.desktop - diff --git a/debian_packaging/skeleton/usr/share/doc/feedindicator/copyright b/debian_packaging/skeleton/usr/share/doc/feedindicator/copyright deleted file mode 100644 index 180b7c7..0000000 --- a/debian_packaging/skeleton/usr/share/doc/feedindicator/copyright +++ /dev/null @@ -1,27 +0,0 @@ -Upstream-Name: feedindicator -Source: http://code.google.com/p/feedindicator/ - -Files: * -Copyright: Copyright 2010-2011 Dave Gardner & Michael Judge -License: GPL-3+ - This program is free software; you can redistribute it - and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later - version. - . - This program is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even the implied - warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - PURPOSE. See the GNU General Public License for more - details. - . - You should have received a copy of the GNU General Public - License along with this package; if not, write to the Free - Software Foundation, Inc., 51 Franklin St, Fifth Floor, - Boston, MA 02110-1301 USA - . - On Debian systems, the full text of the GNU General Public - License version 3 can be found in the file - `/usr/share/common-licenses/GPL-3'. - diff --git a/feedindicator b/feedindicator deleted file mode 100755 index 0828720..0000000 --- a/feedindicator +++ /dev/null @@ -1,1529 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# feedindicator -# Copyright (C) 2010-2011 Dave Gardner & Michael Judge -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# [SNIPPET_NAME: Parse an RSS feed] -# [SNIPPET_CATEGORIES: feedparser] -# [SNIPPET_DESCRIPTION: Parse and iterate over the items in an RSS feed] -# [SNIPPET_AUTHOR: Tim Voet ] -# [SNIPPET_DOCS: http://www.feedparser.org/docs/introduction.html] -# [SNIPPET_LICENSE: GPL] - -# need python-feedparser and app-indicator(should come with ubuntu 10.04 and above) - -import pygtk -pygtk.require('2.0') -import glib -import gtk -import appindicator -import feedparser -import gc -import hashlib -import os -import pynotify -import string -import sys -import urllib -import webbrowser -import gconf -import time -import gobject -import shutil -import sqlite3 as lite -from threading import Thread -from time import sleep -from gconf import VALUE_BOOL, VALUE_INT, VALUE_STRING, VALUE_FLOAT -from types import BooleanType, StringType, IntType, FloatType -from configobj import ConfigObj - -# clear out the html element list so all are removed -feedparser._HTMLSanitizer.acceptable_elements = [] - -# common name used for file paths, gconf and indicator identifier -app_identifier = 'feedindicator' - -# indicator icon names -app_indicator_icon = 'indicator-feedindicator' -app_indicator_icon_attention = 'indicator-feedindicator-attention' - -# XDG config -xdg_config_dir = glib.get_user_config_dir() -app_config_dir = os.path.join(xdg_config_dir, app_identifier) - -# XDG cache -xdg_cache_dir = glib.get_user_cache_dir() -app_cache_dir = os.path.join(xdg_cache_dir, app_identifier) - -# /usr/share/ -app_share_dir = '/usr/share/'+app_identifier+'/' -app_icon = os.path.join(app_share_dir, 'feedindicator-icon.png') -app_logo = os.path.join(app_share_dir, 'feedindicator-logo.png') -app_language_dir = os.path.join(app_share_dir, 'languages/') - -# database -app_database = os.path.join(app_config_dir, 'feedindicator.db') - -# language properties file -app_language_file = os.path.join(app_config_dir, 'language.properties') - -# autostart file -app_autostart_folder = os.path.join(xdg_config_dir, 'autostart') -app_autostart_file = os.path.join(app_autostart_folder, app_identifier+'.desktop') - -# general app info mostly used in about dialog -app_name = 'Feedindicator' -app_version = '1.03' -app_comments = 'RSS feed updates in the indicator area' -app_copyright = 'Copyright (C) 2010-2011 Dave Gardner & Michael Judge' -app_website = 'http://code.google.com/p/feedindicator/' -app_license = (('This program is free software: you can redistribute it and/or modify\n'+ -'it under the terms of the GNU General Public License as published by\n'+ -'the Free Software Foundation, either version 3 of the License, or\n'+ -'(at your option) any later version.\n'+ -'\n'+ -'This program is distributed in the hope that it will be useful,\n'+ -'but WITHOUT ANY WARRANTY; without even the implied warranty of\n'+ -'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n'+ -'GNU General Public License for more details.\n'+ -'\n'+ -'You should have received a copy of the GNU General Public License\n'+ -'along with this program. If not, see .')) -app_authors = ['Dave Gardner ','Michael Judge '] -app_documenters = ['Dave Gardner ','Michael Judge '] - -# default autostart status -autostart = False - -# default feed refresh time (minutes*seconds) -refreshtime = 30*60 - -# default stop the timer from running? -stoptimer = False - -# default menu items to show per feed -itemsperfeed = 5 - -# default put feed items in submenus -usesubmenus = False - -# default show notification popups -shownotifications = True - -# default show notification popups at begining and end of update -shownotificationsupdate = True - -# default sound for new posts -sound = "/usr/bin/mplayer -ao alsa /usr/share/sounds/ubuntu/stereo/message.ogg" - -# default position of the feeds top or button of the menu -feedstop = True - -# default shows feeds with no unread posts -unreadfeeds = True - -# language properties -language_properties = None - - - -################################################## -############### SQLite ############### -################################################## -class SQLite: - def get_con(): - return self._con - - con = property(get_con) - - def __init__(self): - self._con = None - - def open(self, path): - """opens a new connection""" - try: - self._con = lite.connect(path) - except lite.Error, e: - print e - - def close(self): - """closes the current connection""" - if self._con: - try: - self._con.close() - except lite.Error, e: - print e - - def execute(self, query, data=()): - """executes the given query and returns the results""" - if self._con: - try: - cur = self._con.cursor() - if data == (): - cur.execute(query) - else: - cur.execute(query, data) - rows = cur.fetchall() - return rows - except lite.Error, e: - print e - - return None - - def executemany(self, query, data): - """executes the given query with executemany()""" - if self._con: - try: - cur = self._con.cursor() - cur.executemany(query, data) - except lite.Error, e: - print e - - def commit(self): - """commits changes to the database""" - if self._con: - try: - self._con.commit() - except lite.Error, e: - self._con.rollback() - print e - - def contains(self, query, data=()): - """checks wheter the given query has results or not""" - if self._con: - try: - cur = self._con.cursor() - if data == (): - cur.execute(query) - else: - cur.execute(query, data) - rows = cur.fetchone() - - if rows: - return True; - else: - return False; - except lite.Error, e: - print e - - - -################################################## -############### Exit ############### -################################################## -class Exit: - def __init__(self, widget): - sys.exit() - - - -################################################## -############### GConf ############### -################################################## -class GConf: - def __init__ (self, appname, allowed={}): - self._domain = '/apps/%s/' % appname - self._allowed = allowed - self._gconf_client = gconf.client_get_default () - - def __getitem__ (self, attr): - return self.get_value (attr) - - def __setitem__ (self, key, val): - allowed = self._allowed - if allowed.has_key(key): - if not key in allowed[key]: - good = ', '.join (allowed[key]) - return False - self.set_value (key, val) - - def _get_type (self, key): - KeyType = type (key) - if KeyType == BooleanType: - return 'bool' - elif KeyType == StringType: - return 'string' - elif KeyType == IntType: - return 'int' - elif KeyType == FloatType: - return 'float' - else: - return None - - # Public functions - def set_allowed (self, allowed): - self._allowed = allowed - - def set_domain (self, domain): - self._domain = domain - - def get_domain (self): - return self._domain - - def get_gconf_client (self): - return self._gconf_client - - def get_value (self, key): - """returns the value of key 'key' """ - if '/' in key: - raise 'GConfError', 'key must not contain /' - value = self._gconf_client.get(self._domain + key) - if value is not None: - ValueType = value.type - if ValueType == VALUE_BOOL: - return value.get_bool() - elif ValueType == VALUE_INT: - return value.get_int() - elif ValueType == VALUE_STRING: - return value.get_string() - elif ValueType == VALUE_FLOAT: - return value.get_float() - else: - return None - else: - return None - - def set_value (self, key, value): - """sets the value of key 'key' to 'value' """ - value_type = self._get_type(value) - if value_type is not None: - if '/' in key: - raise 'GConfError', 'key must not contain /' - func = getattr (self._gconf_client, 'set_' + value_type) - apply(func, (self._domain + key, value)) - - def get_string (self, key): - if '/' in key: - raise 'GConfError', 'key must not contain /' - return self._gconf_client.get_string(self._domain + key) - - def set_string (self, key, value): - if type (value) != StringType: - raise 'GConfError', 'value must be a string' - if '/' in key: - raise 'GConfError', 'key must not contain /' - self._gconf_client.set_string(self._domain + key, value) - - def get_bool (self, key): - if '/' in key: - raise 'GConfError', 'key must not contain /' - return self._gconf_client.get_bool(self._domain + key) - - def set_bool (self, key, value): - if type (value) != IntType and (key != 0 or key != 1): - raise 'GConfError', 'value must be a boolean' - if '/' in key: - raise 'GConfError', 'key must not contain /' - self._gconf_client.set_bool(self._domain + key, value) - - def get_int (self, key): - if '/' in key: - raise 'GConfError', 'key must not contain /' - return self._gconf_client.get_int(self._domain + key) - - def set_int (self, key, value): - if type (value) != IntType: - raise 'GConfError', 'value must be an int' - if '/' in key: - raise 'GConfError', 'key must not contain /' - self._gconf_client.set_int(self._domain + key, value) - - def get_float (self, key): - if '/' in key: - raise 'GConfError', 'key must not contain /' - return self._gconf_client.get_float(self._domain + key) - - def set_float (self, key, value): - if type (value) != FloatType: - raise 'GConfError', 'value must be a float' - if '/' in key: - raise 'GConfError', 'key must not contain /' - self._gconf_client.set_float(self._domain + key, value) - - - -################################################## -############### FeedThread ############### -################################################## -class FeedThread(Thread): - def run(self): - """updates all feeds in the database und updates the infos""" - sq = SQLite() - sq.open(app_database) - - feeds = sq.execute('select feed_url, title, url, img from feeds') - if len(feeds) > 0: - for feed in feeds: - feedinfo = self.parse_feed(feed[0]) - - if feedinfo['success']: - if feed[1] == None: - sq.execute('update feeds set title=? where feed_url=?', (unicode(feedinfo['feeddata']['title']), feed[0])) - if not feed[2] == unicode(feedinfo['feeddata']['link']): - sq.execute('update feeds set url=? where feed_url=?', (unicode(feedinfo['feeddata']['link']), feed[0])) - if not unicode(feedinfo['feeddata']['img']) == 'None': - sq.execute('update feeds set img=? where feed_url=?', (unicode(feedinfo['feeddata']['img']), feed[0])) - - items = () - posts = () - for postdata in feedinfo['postdata']: - if not postdata['date'] == '': - md5 = hashlib.md5(str(postdata['date']) + '@' + str(postdata['title'])).hexdigest() - posts = posts + (md5,) - if not sq.contains('select * from posts where id=?', (md5,)): - items = items + ((md5, unicode(postdata['link']), unicode(postdata['title']), unicode(feed[0])), ) - else: - md5 = hashlib.md5(str(postdata['link'])).hexdigest() - posts = posts + (md5,) - if not sq.contains('select * from posts where id=?', (md5,)): - items = items + ((md5, unicode(postdata['link']), unicode(postdata['title']), unicode(feed[0])), ) - - if not items == (): - sq.executemany('insert into posts (id, post_url, title, feed_url) values (?,?,?,?)', items) - - placeholder= '?' - placeholders= ', '.join(placeholder for unused in posts) - posts = posts + (feed[0],) - sq.execute('delete from posts where read=\'true\' and id not in (%s) and feed_url=?' % placeholders, posts) - - sq.commit() - sq.close() - - gobject.idle_add(indicator.finnished_update) - - def parse_feed(self, url): - """parses a feed and returns the infos""" - feedinfo = {} - feedinfo['feeddata'] = {} - feedinfo['postdata'] = [] - feedinfo['success'] = False - try: - rssfeed = feedparser.parse(url) - postdata = [] - for val in rssfeed.entries: - val.title = string.replace(val.title, '\n', '') - if len(val.title) > 72: - substr = val.title[:72] - substr = substr.rpartition(' ') - if substr[0] != '': - val.title = substr[0] + '...' - - date = '' - if 'published' in val: - date = val.published - elif 'updated' in val: - date = val.updated - elif 'created' in val: - date = val.created - - v = {'title': val.title, 'link': val.link, 'date': date} - postdata.append(v) - feedinfo['postdata'] = postdata - feedimg = None - webimg = None - if 'image' in rssfeed.feed: - if 'href' in rssfeed.feed.image: - webimg = rssfeed.feed.image.href - elif 'url' in rssfeed.feed.image: - webimg = rssfeed.feed.image.url - elif 'logo' in rssfeed.feed: - webimg = rssfeed.feed.logo - elif 'icon' in rssfeed.feed: - webimg = rssfeed.feed.icon - if webimg != None: - ext = webimg.rsplit('.', 1) - if len(ext[1]) > 5 or len(ext[1]) == 0: - ext[0] = webimg - ext[1] = 'jpg' - md5hash = hashlib.md5(str(url)).hexdigest() - localimg = os.path.join(app_cache_dir, md5hash + '.' + ext[1]) - urllib.urlretrieve(webimg, localimg) - feedimg = md5hash + '.' + ext[1] - feedinfo['feeddata'] = {'title': rssfeed.feed.title, 'link': rssfeed.feed.link, 'img': feedimg} - feedinfo['success'] = True - except: - pass - - return feedinfo - - - -################################################## -############### AddFeedDialog ############### -################################################## -class AddFeedDialog: - def __init__(self, widget): - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.window.set_size_request(450, 120) - self.window.set_position(gtk.WIN_POS_CENTER) - self.window.set_title(language_properties['add_feed'] + ' | ' + app_name) - self.window.connect('delete_event', self.cancel_dialog) - self.window.connect('key-press-event', self.keypress) - - vbox = gtk.VBox(False, 10) - vbox.set_border_width(10) - self.window.add(vbox) - vbox.show() - - hboxl = gtk.HBox(False, 0) - vbox.pack_start(hboxl, False, True, 0) - hboxl.show() - - label = gtk.Label(language_properties['add_dialog_column']) - label.set_justify(gtk.JUSTIFY_LEFT) - label.set_line_wrap(True) - hboxl.pack_start(label, False, True, 0) - label.show() - - hbox2 = gtk.HBox(False, 0) - vbox.pack_start(hbox2, False, True, 0) - hbox2.show() - - self._textbox = gtk.Entry() - hbox2.pack_start(self._textbox, True, True, 0) - self._textbox.connect("activate", self.save_dialog) - self._textbox.show() - - hbox3 = gtk.HBox(True, 0) - vbox.pack_end(hbox3, False, True, 5) - hbox3.show() - - button_cancel = gtk.Button(language_properties['cancel']) - hbox3.pack_start(button_cancel, True, True, 0) - button_cancel.connect_object('clicked', self.cancel_dialog, button_cancel) - button_cancel.show() - button_save = gtk.Button(language_properties['add']) - hbox3.pack_start(button_save, True, True, 0) - button_save.connect_object('clicked', self.save_dialog, None) - button_save.show() - - self.window.set_keep_above(True) - self.window.show() - - def cancel_dialog(self, widget, data=None): - """cancel dialog""" - self.window.destroy() - - def save_dialog(self, data): - """save feed to config file and close add feed dialog""" - self.window.set_modal(True) - sq = SQLite() - sq.open(app_database) - sq.execute('insert into feeds (feed_url) values (?)', (unicode(self._textbox.get_text()),)) - sq.commit() - sq.close() - self.window.destroy() - - indicator.update_feeds(None, True, False) - - def keypress(self, widget, data): - """keypress handler""" - if data.keyval == gtk.keysyms.Escape: - self.window.destroy() - elif data.keyval == gtk.keysyms.Return: - self.save_dialog(None) - - - -################################################## -############### AboutDialog ############### -################################################## -class AboutDialog: - def __init__(self, widget): - about = gtk.AboutDialog() - about.set_logo(gtk.gdk.pixbuf_new_from_file(app_logo)) - about.set_name(app_name) - about.set_program_name(app_name) - about.set_version(app_version) - about.set_comments(app_comments) - about.set_copyright(app_copyright) - about.set_license(app_license) - about.set_website(app_website) - about.set_website_label(app_website) - about.set_authors(app_authors) - about.set_documenters(app_documenters) - about.run() - about.destroy() - - - -################################################## -############### ConfigureDialog ############### -################################################## -class ConfigureDialog: - def __init__(self, widget): - self.refreshtimeset = refreshtime - self.itemsperfeedset = itemsperfeed - self.autostartset = autostart - self.shownotificationsset = shownotifications - self.shownotificationsupdateset = shownotificationsupdate - self.usesubmenusset = usesubmenus - self.feedstopset = feedstop - self.unreadfeedsset = unreadfeeds - self.langaugeset = '' - - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.window.set_size_request(900, 550) - self.window.set_border_width(10) - self.window.set_position(gtk.WIN_POS_CENTER) - self.window.set_title(str(language_properties['configure'] % app_name)) - self.window.connect('delete_event', self.cancel_dialog) - self.window.connect('key-press-event', self.keypress) - - table = gtk.Table(3,6,False) - self.window.add(table) - - notebook = gtk.Notebook() - notebook.set_tab_pos(gtk.POS_LEFT) - table.attach(notebook, 0,6,0,1) - notebook.show() - - #Feed List - frame_feed_list = gtk.Frame(language_properties['feed_list']) - frame_feed_list.set_border_width(10) - frame_feed_list.set_size_request(800, 500) - frame_feed_list.show() - - vbox = gtk.VBox(False, 10) - vbox.set_border_width(10) - frame_feed_list.add(vbox) - vbox.show() - - label = gtk.Label(language_properties['configure_dialog_label']) - label.set_justify(gtk.JUSTIFY_LEFT) - label.set_line_wrap(True) - vbox.pack_start(label, False, True, 0) - label.show() - - scrolled_window = gtk.ScrolledWindow() - scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) - scrolled_window.set_shadow_type(gtk.SHADOW_IN) - vbox.pack_start(scrolled_window, True, True, 0) - scrolled_window.show() - - liststore = gtk.ListStore(str, str, str) - - sq = SQLite() - sq.open(app_database) - - feeds = sq.execute('select feed_url, title from feeds order by upper(title)') - for feed in feeds: - liststore.append([unicode(feed[1]), unicode(feed[0]), unicode(feed[0])]) - - sq.close() - - self.treeview = gtk.TreeView(liststore) - textrenderer = gtk.CellRendererText() - self.titlecolumn = gtk.TreeViewColumn(language_properties['column_title']) - self.treeview.append_column(self.titlecolumn) - self.titlecolumn.pack_start(textrenderer, True) - self.titlecolumn.add_attribute(textrenderer, 'text', 0) - textrenderer.set_property('editable', True) - textrenderer.connect('edited', self.cell_edited, liststore, 0) - - textrenderer = gtk.CellRendererText() - self.urlcolumn = gtk.TreeViewColumn(language_properties['column_url']) - self.treeview.append_column(self.urlcolumn) - self.urlcolumn.pack_start(textrenderer, True) - self.urlcolumn.add_attribute(textrenderer, 'text', 1) - textrenderer.set_property('editable', True) - textrenderer.connect('edited', self.cell_edited, liststore, 1) - - self.treeview.set_headers_clickable(False) - self.treeview.set_reorderable(True) - self.treeview.connect('cursor-changed', self.selection_made) - scrolled_window.add(self.treeview) - self.treeview.show() - - hbox1 = gtk.HBox(True, 0) - vbox.pack_start(hbox1, False, True, 0) - hbox1.show() - - self.button_change = gtk.Button(language_properties['add_new']) - hbox1.pack_start(self.button_change, True, True, 0) - self.button_change.connect_object('clicked', self.button_add_clicked, liststore) - self.button_change.show() - self.button_remove = gtk.Button(language_properties['remove']) - hbox1.pack_start(self.button_remove, True, True, 0) - self.button_remove.connect_object('clicked', self.button_remove_clicked, liststore) - self.button_remove.set_sensitive(False) - self.button_remove.show() - - notebook.append_page(frame_feed_list, gtk.Label(language_properties['feed_list'])) - - - #Feed Options - frame_feed_options = gtk.Frame(language_properties['feed_options']) - frame_feed_options.set_border_width(10) - frame_feed_options.set_size_request(800, 500) - frame_feed_options.show() - - vbox = gtk.VBox(False, 10) - vbox.set_border_width(10) - frame_feed_options.add(vbox) - vbox.show() - - hbox2sl = gtk.HBox(False, 0) - vbox.pack_start(hbox2sl, False, True, 0) - hbox2sl.show() - - self.scaletimerlabel = gtk.Label('') - hbox2sl.pack_start(self.scaletimerlabel, False, False, 0) - self.scaletimerlabel.show() - - hbox2s = gtk.HBox(False, 0) - vbox.pack_start(hbox2s, False, True, 0) - hbox2s.show() - - adjtimer = gtk.Adjustment(int(self.refreshtimeset/60), 1.0, 90.0, 1.0, 10.0, 0.0) - adjtimer.connect('value_changed', self.timer_scale) - scaletimer = gtk.HScale(adjtimer) - scaletimer.set_draw_value(False) - scaletimer.set_digits(0) - hbox2s.pack_start(scaletimer, True, True, 0) - self.timer_scale(adjtimer) - scaletimer.show() - - hbox2il = gtk.HBox(False, 0) - vbox.pack_start(hbox2il, False, True, 0) - hbox2il.show() - - self.scaleitemslabel = gtk.Label('') - hbox2il.pack_start(self.scaleitemslabel, False, False, 0) - self.scaleitemslabel.show() - - hbox2i = gtk.HBox(False, 0) - vbox.pack_start(hbox2i, False, True, 0) - hbox2i.show() - - adjitems = gtk.Adjustment(int(self.itemsperfeedset), 1.0, 30.0, 1.0, 10.0, 0.0) - adjitems.connect('value_changed', self.items_scale) - scaleitems = gtk.HScale(adjitems) - scaleitems.set_draw_value(False) - scaleitems.set_digits(0) - hbox2i.pack_start(scaleitems, True, True, 0) - self.items_scale(adjitems) - scaleitems.show() - - check_usesubmenus = gtk.CheckButton(language_properties['submenu_feed']) - vbox.pack_start(check_usesubmenus, False, True, 0) - if usesubmenus: - check_usesubmenus.set_active(True) - check_usesubmenus.connect('toggled', self.usesubmenus_toggle) - check_usesubmenus.show() - - check_feedstop = gtk.CheckButton(language_properties['feeds_top_menu']) - vbox.pack_start(check_feedstop, False, True, 0) - if feedstop: - check_feedstop.set_active(True) - check_feedstop.connect('toggled', self.feedstop_toggle) - check_feedstop.show() - - check_unreadfeeds = gtk.CheckButton(language_properties['unread_feeds_menu']) - vbox.pack_start(check_unreadfeeds, False, True, 0) - if unreadfeeds: - check_unreadfeeds.set_active(True) - check_unreadfeeds.connect('toggled', self.unreadfeeds_toggle) - check_unreadfeeds.show() - - notebook.append_page(frame_feed_options, gtk.Label(language_properties['feed_options'])) - - - #Global Options - frame_feed_options = gtk.Frame(language_properties['global_options']) - frame_feed_options.set_border_width(10) - frame_feed_options.set_size_request(800, 500) - frame_feed_options.show() - - vbox = gtk.VBox(False, 10) - vbox.set_border_width(10) - frame_feed_options.add(vbox) - vbox.show() - - check_autostart = gtk.CheckButton(language_properties['run_startup']) - vbox.pack_start(check_autostart, False, True, 0) - if autostart: - check_autostart.set_active(True) - check_autostart.connect('toggled', self.autostart_toggle) - check_autostart.show() - - check_shownotifications = gtk.CheckButton(language_properties['show_notifications']) - vbox.pack_start(check_shownotifications, False, True, 0) - if shownotifications: - check_shownotifications.set_active(True) - check_shownotifications.connect('toggled', self.shownotifications_toggle) - check_shownotifications.show() - - self.check_shownotificationsupdate = gtk.CheckButton(language_properties['show_notifications_update']) - vbox.pack_start(self.check_shownotificationsupdate, False, True, 0) - if not shownotifications: - self.check_shownotificationsupdate.set_sensitive(False) - if shownotificationsupdate: - self.check_shownotificationsupdate.set_active(True) - self.check_shownotificationsupdate.connect('toggled', self.shownotificationsupdate_toggle) - self.check_shownotificationsupdate.show() - - hbox3l = gtk.HBox(False, 0) - vbox.pack_start(hbox3l, False, True, 0) - hbox3l.show() - - entrylabel = gtk.Label(language_properties['signal_command']) - hbox3l.pack_start(entrylabel, False, False, 0) - entrylabel.set_line_wrap(True) - entrylabel.show() - - hbox3 = gtk.HBox(True, 0) - vbox.pack_start(hbox3, False, True, 0) - hbox3.show() - - self._textbox = gtk.Entry() - hbox3.pack_start(self._textbox, True, True, 0) - self._textbox.set_text(sound) - self._textbox.show() - - hbox4 = gtk.HBox(True, 0) - vbox.pack_start(hbox4, False, True, 0) - hbox4.show() - - langaugelabel = gtk.Label(language_properties['choose_language']) - hbox4.pack_start(langaugelabel, True, True, 0) - langaugelabel.show() - - language_store = gtk.ListStore(str, str) - languages = os.listdir(app_language_dir) - i = 0 - index = 0 - for lang in languages: - prop = ConfigObj(os.path.join(app_language_dir, lang)) - language_store.append([lang, prop['language']]) - if prop['language'] == language_properties['language']: - index = i - i = i + 1 - - combobox_language = gtk.ComboBox(language_store) - cell = gtk.CellRendererText() - combobox_language.pack_start(cell, True) - combobox_language.add_attribute(cell, 'text', 1) - combobox_language.set_active(index) - combobox_language.connect('changed', self.language_toggle) - hbox4.pack_start(combobox_language, True, True, 0) - combobox_language.show() - - notebook.append_page(frame_feed_options, gtk.Label(language_properties['global_options'])) - - table.attach(gtk.Label(''), 0,1,1,2) - button_about = gtk.Button(language_properties['about']) - table.attach(button_about, 3,4,1,2) - button_about.connect_object('clicked', AboutDialog, button_about) - button_about.show() - button_cancel = gtk.Button(language_properties['cancel']) - table.attach(button_cancel, 4,5,1,2) - button_cancel.connect_object('clicked', self.cancel_dialog, button_cancel) - button_cancel.show() - button_save = gtk.Button(language_properties['save']) - button_save.connect('clicked', self.save_dialog, liststore) - table.attach(button_save, 5,6,1,2) - button_save.show() - - table.show() - self.window.set_keep_above(True) - self.window.show() - - def selection_made(self, data=None): - """entry clicked so we enable the remove button""" - treesel = self.treeview.get_selection() - sel = treesel.get_selected() - if sel[1] != None: - self.button_remove.set_sensitive(True) - - def cell_edited(self, cell, row, new_text, data, column): - """update entry with edited value""" - data[row][column] = new_text - return - - def button_add_clicked(self, data): - """add entry to list and focus cursor""" - sel = data.append(['', '', '']) - pos = data.get_path(sel) - self.treeview.set_cursor(pos, self.urlcolumn, True) - - def button_remove_clicked(self, data): - """remove selected entry from list""" - treesel = self.treeview.get_selection() - sel = treesel.get_selected() - - sq = SQLite() - sq.open(app_database) - - feed_url = sel[0].get_value(sel[1], 2) - sq.execute('delete from posts where feed_url=?', (feed_url,)) - - img = sq.execute('select img from feeds where feed_url=?', (feed_url,)) - if os.path.exists(os.path.join(app_cache_dir, unicode(img[0]))): - os.remove(os.path.join(app_cache_dir, unicode(img[0]))) - - sq.execute('delete from feeds where feed_url=?', (feed_url,)) - sq.commit() - sq.close() - - data.remove(sel[1]) - self.button_remove.set_sensitive(False) - - def timer_scale(self, widget): - """refreshtimer slider interaction""" - value = widget.get_value(); - if int(value) == 1: - self.refreshtimeset = int(value)*60 - self.scaletimerlabel.set_text(language_properties['update_feed_every_m']) - else: - self.refreshtimeset = int(value)*60 - self.scaletimerlabel.set_text(str(language_properties['update_feed_every'] % str(self.refreshtimeset/60))) - - def items_scale(self, widget): - """scaleitems slider interaction""" - value = widget.get_value(); - if int(value) == 1: - self.itemsperfeedset = int(value) - self.scaleitemslabel.set_text(language_properties['menu_shows_one_item_per_feed']) - else: - self.itemsperfeedset = int(value) - self.scaleitemslabel.set_text(str(language_properties['menu_shows'] % str(self.itemsperfeedset))) - - def autostart_toggle(self, widget): - """check toggle status""" - if widget.get_active(): - self.autostartset = True - else: - self.autostartset = False - - def shownotifications_toggle(self, widget): - """check toggle status""" - if widget.get_active(): - self.shownotificationsset = True - self.check_shownotificationsupdate.set_sensitive(True) - else: - self.shownotificationsset = False - self.check_shownotificationsupdate.set_sensitive(False) - - def shownotificationsupdate_toggle(self, widget): - """check toggle status""" - if widget.get_active(): - self.shownotificationsupdateset = True - else: - self.shownotificationsupdateset = False - - def usesubmenus_toggle(self, widget): - """check toggle status""" - if widget.get_active(): - self.usesubmenusset = True - else: - self.usesubmenusset = False - - def language_toggle(self, widget): - """check toggle status""" - tree_iter = widget.get_active_iter() - if tree_iter != None: - model = widget.get_model() - self.langaugeset = model[tree_iter][0] - - def feedstop_toggle(self, widget): - """check toggle status""" - if widget.get_active(): - self.feedstopset = True - else: - self.feedstopset = False - - def unreadfeeds_toggle(self, widget): - """check toggle status""" - if widget.get_active(): - self.unreadfeedsset = True - else: - self.unreadfeedsset = False - - def autostart_create(self): - """create autostart file""" - global autostart - content = "\n"+"[Desktop Entry]\n"+"Type=Application\n"+"Exec="+app_identifier+" --autostarted\n"+"X-GNOME-Autostart-enabled=true\n"+"Icon="+app_identifier+"\n"+"Name="+app_name+"\n"+"Comment="+app_comments - if not os.path.exists(app_autostart_folder): - os.makedirs(app_autostart_folder, 0700) - f = open(app_autostart_file, 'w') - f.write(content) - f.close() - autostart = True - - def autostart_delete(self): - """remove autostart file""" - global autostart - if os.path.exists(app_autostart_file): - os.remove(app_autostart_file) - autostart = False - - def keypress(self, widget, data): - """keypress handler""" - if data.keyval == gtk.keysyms.Escape: - self.window.destroy() - - def cancel_dialog(self, widget, data=None): - """cancel dialog""" - self.window.destroy() - - def save_dialog(self, widget, data): - """save list to config file and close config dialog""" - global refreshtime - global itemsperfeed - global autostart - global shownotifications - global shownotificationsupdate - global usesubmenus - global sound - global feedstop - global unreadfeeds - global language_properties - - refreshtime = self.refreshtimeset - gconf['refreshtime'] = self.refreshtimeset - itemsperfeed = self.itemsperfeedset - gconf['itemsperfeed'] = self.itemsperfeedset - shownotifications = self.shownotificationsset - gconf['shownotifications'] = self.shownotificationsset - shownotificationsupdate = self.shownotificationsupdateset - gconf['shownotificationsupdate'] = self.shownotificationsupdateset - usesubmenus = self.usesubmenusset - gconf['usesubmenus'] = self.usesubmenusset - feedstop = self.feedstopset - gconf['feedstop'] = self.feedstopset - unreadfeeds = self.unreadfeedsset - gconf['unreadfeeds'] = self.unreadfeedsset - sound = self._textbox.get_text() - gconf['sound'] = self._textbox.get_text() - autostart = self.autostartset - if autostart == True: - self.autostart_create() - else: - self.autostart_delete() - - if not self.langaugeset == '': - shutil.copyfile(os.path.join(app_language_dir, self.langaugeset), app_language_file) - language_properties = ConfigObj(app_language_file) - - sq = SQLite() - sq.open(app_database) - - for row in data: - if row[2] == '' and row[0] == '': - sq.execute('insert into feeds (feed_url) values (?)', (unicode(row[1]),)) - elif row[2] == '' and row[0] != '': - sq.execute('insert into feeds (feed_url, title) values (?, ?)', (unicode(row[1]), unicode(row[0]))) - elif row[2] == row[1]: - sq.execute('update feeds set title=? where feed_url=?', (unicode(row[0]), unicode(row[2]))) - else: - sq.execute('update posts set feed_url=? where feed_url=?', (unicode(row[1]), unicode(row[2]))) - sq.execute('update feeds set title=?, feed_url=? where feed_url=?', (unicode(row[0]), unicode(row[1]), unicode(row[2]))) - - sq.commit() - sq.close() - - self.window.destroy() - indicator.update_feeds(None, True, True) - - - -################################################## -############### AppIndicator ############### -################################################## -class AppIndicator: - def __init__(self): - global refreshtime - global stoptimer - global itemsperfeed - global autostart - global shownotifications - global shownotificationsupdate - global usesubmenus - global feedstop - global unreadfeeds - global sound - - if gconf['refreshtime'] is not None: - refreshtime = gconf['refreshtime']; - else: - gconf['refreshtime'] = refreshtime; - - if gconf['itemsperfeed'] is not None: - itemsperfeed = gconf['itemsperfeed']; - else: - gconf['itemsperfeed'] = itemsperfeed; - - if gconf['stoptimer'] is not None: - stoptimer = gconf['stoptimer']; - else: - gconf['stoptimer'] = stoptimer; - - if gconf['shownotifications'] is not None: - shownotifications = gconf['shownotifications']; - else: - gconf['shownotifications'] = shownotifications; - - if gconf['shownotificationsupdate'] is not None: - shownotificationsupdate = gconf['shownotificationsupdate']; - else: - gconf['shownotificationsupdate'] = shownotificationsupdate; - - if gconf['usesubmenus'] is not None: - usesubmenus = gconf['usesubmenus']; - else: - gconf['usesubmenus'] = usesubmenus; - - if gconf['feedstop'] is not None: - feedstop = gconf['feedstop']; - else: - gconf['feedstop'] = feedstop; - - if gconf['unreadfeeds'] is not None: - unreadfeeds = gconf['unreadfeeds']; - else: - gconf['unreadfeeds'] = unreadfeeds; - - if gconf['sound'] is not None: - sound = gconf['sound']; - else: - gconf['sound'] = sound; - - if os.path.exists(app_autostart_file): - autostart = True - - if not os.path.exists(app_database): - sq = SQLite() - sq.open(app_database) - sq.execute('CREATE TABLE feeds (feed_url TEXT PRIMARY KEY, url TEXT, title TEXT, img TEXT)') - sq.execute('CREATE TABLE posts (id TEXT PRIMARY KEY, post_url TEXT, title TEXT, read BOOLEAN DEFAULT false, feed_url TEXT NOT NULL, FOREIGN KEY(feed_url) REFERENCES feeds(feed_url))') - - app_config_file = os.path.join(app_config_dir, 'feeds.cfg') - app_cache_file = os.path.join(app_cache_dir, 'feeds.dat') - - if os.path.exists(app_config_file): - rssf = open(app_config_file) - for line in rssf: - line = string.replace(line, '\n', '') - sq.execute('insert into feeds (feed_url) values (?)', (unicode(line),)) - - rssf.close() - os.remove(app_config_file) - - if os.path.exists(app_cache_file): - os.remove(app_cache_file) - - sq.commit() - sq.close() - else: - sq = SQLite() - sq.open(app_database) - - posts = sq.execute('select id from posts') - if posts == None: - sq.execute('DROP TABLE posts') - sq.execute('CREATE TABLE posts (id TEXT PRIMARY KEY, post_url TEXT, title TEXT, read BOOLEAN DEFAULT false, feed_url TEXT NOT NULL, FOREIGN KEY(feed_url) REFERENCES feeds(feed_url))') - - sq.commit() - sq.close() - - self.ind = appindicator.Indicator(app_name, app_identifier, appindicator.CATEGORY_APPLICATION_STATUS) - self.ind.set_status(appindicator.STATUS_ACTIVE) #STATUS_PASSIVE = hidden, STATUS_ACTIVE = visible, STATUS_ATTENTION = want the users attention - self.ind.set_attention_icon(app_indicator_icon_attention) - self.ind.set_icon(app_indicator_icon) - - self.menu = gtk.Menu() - self.update_feeds(None, True, True) - - def set_status(self, status): - """turns the appindicator to attention and back to normal""" - if status: - self.ind.set_status(appindicator.STATUS_ATTENTION) - else: - self.ind.set_status(appindicator.STATUS_ACTIVE) - - def send_notification(self, title, message, image): - """send feed updates to notify-osd""" - if not title == None: - pynotify.init(title) - if image == None: - image = 'file://' + app_icon - n = pynotify.Notification((title if not title == '' else ''), message, image) - n.set_hint_string('x-canonical-append','') - n.show() - - def menuitem_response_website(self, data, url): - """open the feed's website url""" - webbrowser.open(url) - - def menuitem_response(self, data, post_id, url): - """open the website page url""" - webbrowser.open(url) - - sq = SQLite() - sq.open(app_database) - sq.execute('update posts set read =\'true\' where id =? ', (post_id,)) - sq.commit() - sq.close() - - sleep(0.5) - self.render_menu() - - def updates_toggle(self, data): - """toggle updating""" - global stoptimer - if stoptimer == True: - stoptimer = False - gconf['stoptimer'] = False - if shownotifications: - self.send_notification(app_name + ' - ' + language_properties['started'], str(language_properties['feed_update_time_notification'] % str(refreshtime/60)), app_logo) - self.update_feeds(None, True, True) - else: - stoptimer = True - gconf['stoptimer'] = True - if shownotifications: - self.send_notification(app_name + ' - ' + language_properties['stopped'], language_properties['feed_update_stop_notification'], app_logo) - - def render_menu_empty(self, data): - """populate an empty menu""" - for child in self.menu.get_children(): - child.destroy() - menu_loading = gtk.MenuItem(language_properties['loading']) - self.menu.append(menu_loading) - menu_loading.set_sensitive(False) - self.menu.show_all() - self.ind.set_menu(self.menu) - self.set_status(False) - - def render_menu(self): - """populate the menu""" - global stoptimer - global feedstop - global unreadfeeds - - for child in self.menu.get_children(): - child.destroy() - - sq = SQLite() - sq.open(app_database) - - if unreadfeeds: - feeds = sq.execute('select title, url, feed_url, (select count(*) from posts where posts.feed_url=feeds.feed_url and read=\'false\') as c from feeds order by c desc, upper(title)') - else: - feeds = sq.execute('select title, url, feed_url, (select count(*) from posts where posts.feed_url=feeds.feed_url and read=\'false\') as c from feeds where c > 0 order by c desc, upper(title)') - empty = len(feeds) > 0 - hasfeeds = len(sq.execute('select count(*) from feeds')) > 0 - - if feedstop == False: - self.render_menu_conf(hasfeeds) - if hasfeeds: - self.menu.append(gtk.SeparatorMenuItem()) - - if not empty: - no_unread = gtk.MenuItem('No unread feeds.') - self.menu.append(no_unread) - no_unread.set_sensitive(False) - - - - for feed in feeds: - posts = sq.execute(str('select id, title, post_url from posts where feed_url=? and read == \'false\' order by id limit %i' % itemsperfeed), (feed[2],)) - if usesubmenus == True: - menu_header = self.create_menu_header(('(' + unicode(feed[3]) + ') ' if not feed[3] == 0 else '') + unicode(feed[0]), '') - menu_row = self.create_menu_header(language_properties['open_website'], feed[1]) - submenu = gtk.Menu() - submenu.append(menu_row) - submenu_open_unread = gtk.MenuItem(language_properties['open_all_unread']) - submenu_open_unread.connect('activate', self.open_unread, feed[2]) - submenu.append(submenu_open_unread) - - if int(feed[3]) > itemsperfeed: - submenu_open2 = gtk.MenuItem(language_properties['open_displayed']) - submenu_open2.connect('activate', self.feed_open_displayed, feed[2]) - submenu.append(submenu_open2) - - submenu_read = gtk.MenuItem(language_properties['mark_as_read']) - submenu_read.connect('activate', self.feed_read, feed[2]) - submenu.append(submenu_read) - - if int(feed[3]) > itemsperfeed: - submenu_read2 = gtk.MenuItem(language_properties['mark_displayed_as_read']) - submenu_read2.connect('activate', self.feed_read_displayed, feed[2]) - submenu.append(submenu_read2) - - submenu_read = gtk.MenuItem(language_properties['mark_as_unread']) - submenu_read.connect('activate', self.feed_unread, feed[2]) - submenu.append(submenu_read) - - if len(posts) > 0: - posts.reverse() - submenu.append(gtk.SeparatorMenuItem()) - - for post in posts: - menu_row = self.create_menu_item(post[1], post[0], post[2]) - submenu.append(menu_row) - - menu_header.set_submenu(submenu) - self.menu.append(menu_header) - else: - menu_header = self.create_menu_header(('(' + unicode(feed[3]) + ') ' if not feed[3] == 0 else '') + unicode(feed[0]), feed[1]) - self.menu.append(menu_header) - - if len(posts) > 0: - posts.reverse() - - for post in posts: - menu_row = self.create_menu_item(post[1], post[0], post[2]) - self.menu.append(menu_row) - - hasunread = sq.contains('select * from posts where read=\'false\'') - - sq.close() - - if feedstop == True: - self.render_menu_conf(hasfeeds) - - self.menu.show_all() - self.ind.set_menu(self.menu) - - if hasunread: - self.set_status(True) - else: - self.set_status(False) - - def render_menu_conf(self, hasfeeds): - global stoptimer - global feedstop - - if hasfeeds: - if feedstop == True: - self.menu.append(gtk.SeparatorMenuItem()) - - menu_open_unread = gtk.MenuItem(language_properties['open_all_unread']) - menu_open_unread.connect('activate', self.open_unread, '') - self.menu.append(menu_open_unread) - menu_read = gtk.MenuItem(language_properties['mark_all_as_read']) - menu_read.connect('activate', self.feed_read, '') - self.menu.append(menu_read) - menu_unread = gtk.MenuItem(language_properties['mark_all_as_unread']) - menu_unread.connect('activate', self.feed_unread, '') - self.menu.append(menu_unread) - menu_update = gtk.MenuItem(language_properties['reload_all_feeds']) - menu_update.connect('activate', self.update_feeds, True, False) - self.menu.append(menu_update) - self.menu.append(gtk.SeparatorMenuItem()) - if refreshtime/60 == 1: - menu_updatecheck = gtk.CheckMenuItem(language_properties['update_feed_every_m']) - else: - menu_updatecheck = gtk.CheckMenuItem(str(language_properties['update_feed_every'] % str(refreshtime/60))) - if stoptimer == True: - menu_updatecheck.set_active(False) - else: - menu_updatecheck.set_active(True) - menu_updatecheck.connect('toggled', self.updates_toggle) - self.menu.append(menu_updatecheck) - else: - menu_notice = gtk.MenuItem(language_properties['no_feeds_defined']) - self.menu.append(menu_notice) - menu_notice.set_sensitive(False) - self.menu.append(gtk.SeparatorMenuItem()) - - menu_add = gtk.MenuItem(language_properties['add_feed']) - self.menu.append(menu_add) - menu_add.connect('activate', AddFeedDialog) - menu_configure = gtk.MenuItem(str(language_properties['configure'] % app_name)) - self.menu.append(menu_configure) - menu_configure.connect('activate', ConfigureDialog) - menu_quit = gtk.MenuItem(language_properties['quit']) - self.menu.append(menu_quit) - menu_quit.connect('activate', Exit) - - def create_menu_header(self, title, url): - """create the menu heading for each feed""" - menu_row = gtk.MenuItem(title) - if url != '': - menu_row.connect('activate', self.menuitem_response_website, url) - return menu_row - - def create_menu_item(self, title, post_id, url): - """create the menu entries for each feed""" - if usesubmenus == True: - menu_row = gtk.MenuItem(title) - else: - menu_row = gtk.MenuItem(" " + title) - menu_row.connect('activate', self.menuitem_response, post_id, url) - return menu_row - - def open_unread(self, data, url): - """opens all unread post of the given feed""" - sq = SQLite() - sq.open(app_database) - - if not url == '': - posts = sq.execute('select post_url from posts where feed_url=? and read=\'false\'', (url,)) - for post in posts: - webbrowser.open(post[0]) - sleep(0.6) - - sq.execute('update posts set read=\'true\' where feed_url=?', (url,)) - else: - feeds = sq.execute('select feed_url from feeds') - - for feed in feeds: - posts = sq.execute('select post_url from posts where feed_url=? and read=\'false\'', feed) - for post in posts: - webbrowser.open(post[0]) - sleep(0.6) - - sq.execute('update posts set read=\'true\' where feed_url=?', feed) - - sq.commit() - sq.close() - - sleep(0.5) - self.render_menu() - - def feed_open_displayed(self, data, url): - """opens all unread post of the given feed""" - sq = SQLite() - sq.open(app_database) - - posts = sq.execute(str('select post_url from posts where feed_url=? and read=\'false\' order by id limit %i' % itemsperfeed), (url,)) - for post in posts: - webbrowser.open(post[0]) - sleep(0.6) - - sq.execute(str('update posts set read=\'true\' where id IN (select id from posts where feed_url=? and read=\'false\' order by id limit %i)' % itemsperfeed), (url,)) - sq.commit() - sq.close() - - sleep(0.5) - self.render_menu() - - def feed_read(self, data, url): - """marks all posts of all feeds or a specific feed as read""" - sq = SQLite() - sq.open(app_database) - if url == '': - sq.execute('update posts set read=\'true\'') - else: - sq.execute('update posts set read=\'true\' where feed_url=?', (url,)) - sq.commit() - sq.close() - - sleep(0.5) - self.render_menu() - - def feed_read_displayed(self, data, url): - """marks displayed posts of given feed as read""" - sq = SQLite() - sq.open(app_database) - sq.execute(str('update posts set read=\'true\' where feed_url=? and id in (select id from posts where feed_url=? and read == \'false\' order by id limit %i)' % itemsperfeed), (url,url)) - sq.commit() - sq.close() - - sleep(0.5) - self.render_menu() - - def feed_unread(self, data, url): - """marks all posts of all feeds or a specific feed as read""" - sq = SQLite() - sq.open(app_database) - if url == '': - sq.execute('update posts set read=\'false\'') - else: - sq.execute('update posts set read=\'false\' where feed_url=?', (url,)) - sq.commit() - sq.close() - - sleep(0.5) - self.render_menu() - - def update_feeds(self, data=None, timeroverride=False, starttimer=False): - """start updating feeds""" - global stoptimer - - if stoptimer == True and timeroverride == False: - return False - - if starttimer: - glib.timeout_add_seconds(refreshtime, self.update_feeds, True) - - if shownotifications and shownotificationsupdate: - self.send_notification(app_name, language_properties['feed_update_start_notification'], app_logo) - - self.render_menu_empty(None) - FeedThread().start() - return True - - def finnished_update(self): - """renders menu and shows notification after FeadThread has ended""" - self.render_menu() - - if shownotifications: - sq = SQLite() - sq.open(app_database) - - feeds = sq.execute('select title, img, feed_url from feeds where feed_url in (select feed_url from posts where read = \'false\' group by feed_url limit 50) order by (select count(feed_url) as c from posts where read = \'false\' group by feed_url order by c desc limit 49), upper(title)') #notification stack is limit to 50 - - if len(feeds) > 0: - if not sound == '': - os.system(sound) - - for feed in feeds: - count = 0 - notifymsg = '' - posts = sq.execute('select title from posts where read = \'false\' and feed_url=? limit 3', (feed[2],)) - - if len(posts) > 0: - for post in posts: - notifymsg += '\n' + unicode(post[0]) - - if feed[1] == None: - self.send_notification(feed[0], notifymsg, None) - else: - self.send_notification(feed[0], notifymsg, os.path.join(app_cache_dir, feed[1])) - else: - if shownotificationsupdate: - self.send_notification(app_name, language_properties['feed_update_finish_notification'], app_logo) - - sq.close() - - - -################################################## -def main(): - gtk.gdk.threads_init() - gtk.mainloop() - return 0 - -if __name__ == "__main__": - """delay to make sure config files are accessible on autostart""" - cmdline = sys.argv - if len(cmdline) > 1: - if cmdline[1] == '--autostarted': - time.sleep(5) - if gc.isenabled() == False: - gc.enable() - - if not os.path.exists(app_language_file): - shutil.copyfile(os.path.join(app_language_dir, 'english.properties'), app_language_file) - language_properties = ConfigObj(app_language_file) - - gconf = GConf(app_identifier) - indicator = AppIndicator() - main() \ No newline at end of file diff --git a/feedindicator-icon.png b/feedindicator-icon.png deleted file mode 100644 index ea50b84..0000000 Binary files a/feedindicator-icon.png and /dev/null differ diff --git a/feedindicator-install.sh b/feedindicator-install.sh deleted file mode 100755 index 77039e0..0000000 --- a/feedindicator-install.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh - -sudo apt-get install python-feedparser python-configobj -sudo xdg-icon-resource install --theme ubuntu-mono-dark --novendor --size 22 dark/indicator-feedindicator.png indicator-feedindicator -sudo xdg-icon-resource install --theme ubuntu-mono-dark --novendor --size 22 dark/indicator-feedindicator-attention.png indicator-feedindicator-attention -sudo xdg-icon-resource install --theme ubuntu-mono-light --novendor --size 22 light/indicator-feedindicator.png indicator-feedindicator -sudo xdg-icon-resource install --theme ubuntu-mono-light --novendor --size 22 light/indicator-feedindicator-attention.png indicator-feedindicator-attention -sudo xdg-icon-resource install --theme hicolor --novendor --size 22 hicolor/indicator-feedindicator.png indicator-feedindicator -sudo xdg-icon-resource install --theme hicolor --novendor --size 22 hicolor/indicator-feedindicator-attention.png indicator-feedindicator-attention -sudo xdg-icon-resource install --theme hicolor --novendor --size 128 --context apps feedindicator-logo.png feedindicator -sudo xdg-icon-resource install --theme hicolor --novendor --size 48 --context apps feedindicator-48x48.png feedindicator - -sudo xdg-desktop-menu install --novendor feedindicator.desktop - -if [ ! -d /usr/share/feedindicator/ ] - then - sudo mkdir /usr/share/feedindicator -fi - -sudo cp feedindicator-icon.png /usr/share/feedindicator/feedindicator-icon.png -sudo cp feedindicator-logo.png /usr/share/feedindicator/feedindicator-logo.png -sudo cp -r languages/ /usr/share/feedindicator/ - -sudo cp feedindicator /usr/bin/feedindicator -sudo chmod +x /usr/bin/feedindicator - -MESSAGE="Feedindicator install completed." -echo $MESSAGE - diff --git a/feedindicator-uninstall.sh b/feedindicator-uninstall.sh deleted file mode 100755 index f032bcc..0000000 --- a/feedindicator-uninstall.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/sh - -sudo xdg-icon-resource uninstall --theme ubuntu-mono-dark --size 22 indicator-feedindicator -sudo xdg-icon-resource uninstall --theme ubuntu-mono-dark --size 22 indicator-feedindicator-attention -sudo xdg-icon-resource uninstall --theme ubuntu-mono-light --size 22 indicator-feedindicator -sudo xdg-icon-resource uninstall --theme ubuntu-mono-light --size 22 indicator-feedindicator-attention -sudo xdg-icon-resource uninstall --theme hicolor --size 22 indicator-feedindicator -sudo xdg-icon-resource uninstall --theme hicolor --size 22 indicator-feedindicator-attention -sudo xdg-icon-resource uninstall --theme hicolor --size 128 --context apps feedindicator -sudo xdg-icon-resource uninstall --theme hicolor --size 48 --context apps feedindicator - -sudo xdg-desktop-menu uninstall feedindicator.desktop - -sudo unlink /usr/bin/feedindicator - -sudo unlink /usr/share/feedindicator/feedindicator-icon.png -sudo unlink /usr/share/feedindicator/feedindicator-logo.png - -sudo rmdir /usr/share/feedindicator - -[ ! "$XDG_CACHE_HOME" ] && XDG_CACHE_HOME=~/.cache -cachedir="$XDG_CACHE_HOME"/feedindicator -[ ! "$XDG_CONFIG_HOME" ] && XDG_CONFIG_HOME=~/.config -configdir="$XDG_CONFIG_HOME"/feedindicator -autostartfile="$XDG_CONFIG_HOME"/autostart/feedindicator.desktop - -if [ -f "$autostartfile" ] -then -unlink "$autostartfile" -fi - -if [ -d "$configdir" ] -then -rm -r "$configdir" -fi - -if [ -d "$cachedir" ] -then -rm -r "$cachedir" -fi - -MESSAGE="Feedindicator uninstall completed." -echo $MESSAGE - diff --git a/feedindicator.bash-completion b/feedindicator.bash-completion new file mode 100644 index 0000000..b858f7f --- /dev/null +++ b/feedindicator.bash-completion @@ -0,0 +1,12 @@ +_feedindicator() +{ + local cur prev opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + opts="add update --autostarted -h --help -v --version" + COMPREPLY=($(compgen -W "${opts}" -- ${cur})) + return 0 +} +complete -F _feedindicator feedindicator diff --git a/feedindicator.desktop b/feedindicator.desktop deleted file mode 100644 index 99c2196..0000000 --- a/feedindicator.desktop +++ /dev/null @@ -1,10 +0,0 @@ -[Desktop Entry] -Version=1.0 -Type=Application -Terminal=false -Exec=feedindicator -Icon=feedindicator -Name=Feedindicator -Comment=RSS feed updates in the indicator area -Categories=Internet;Network; - diff --git a/feedindicator/__init__.py b/feedindicator/__init__.py new file mode 100644 index 0000000..fd1268a --- /dev/null +++ b/feedindicator/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + +"""A RSS feed reader for the indicator area.""" + + +import os.path + + +__author__ = 'Nathanael Philipp' +__copyright__ = 'Copyright 2017 Nathanael Philipp (jnphilipp)' +__license__ = 'GPLv3' +__maintainer__ = __author__ +__email__ = 'mail@jnphilipp.org' +__app_name__ = 'feedindicator' +__version_info__ = (2, 0, 0) +__version__ = '.'.join(str(e) for e in __version_info__) +__description__ = 'A RSS feed reader for the indicator area.' +__github__ = 'https://github.com/jnphilipp/Feedindicator' + +basedir = os.path.dirname(os.path.realpath(__file__)) diff --git a/feedindicator/__main__.py b/feedindicator/__main__.py new file mode 100644 index 0000000..ce3dfa2 --- /dev/null +++ b/feedindicator/__main__.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +import sys +import feedindicator.feedindicator + + +if __name__ == '__main__': + sys.exit(feedindicator.feedindicator.main()) diff --git a/feedindicator/config/__init__.py b/feedindicator/config/__init__.py new file mode 100644 index 0000000..a73e023 --- /dev/null +++ b/feedindicator/config/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +from .constants import * +from .manager import ConfigManager diff --git a/feedindicator/config/constants.py b/feedindicator/config/constants.py new file mode 100644 index 0000000..f346773 --- /dev/null +++ b/feedindicator/config/constants.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +import feedindicator +import os + +from gi.repository import GLib + + +# indicator icon names +active_icon = 'feedindicator-active' +attention_icon = 'feedindicator-attention' + +# XDG config +xdg_config_dir = GLib.get_user_config_dir() +app_config_dir = os.path.join(xdg_config_dir, feedindicator.__app_name__) +app_autostart_dir = os.path.join(xdg_config_dir, 'autostart') +app_autostart_file = os.path.join(app_autostart_dir, + '%s.desktop' % feedindicator.__app_name__) + +# XDG cache +xdg_cache_dir = GLib.get_user_cache_dir() +app_cache_dir = os.path.join(xdg_cache_dir, feedindicator.__app_name__) + +# XDG data +xdg_data_dir = GLib.get_user_data_dir() +app_data_dir = os.path.join(xdg_data_dir, feedindicator.__app_name__) +app_database = os.path.join(app_data_dir, 'db.sqlite3') + +# # /usr/share/ +base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +app_locale_dir = os.path.join(base_dir, 'locale') diff --git a/feedindicator/config/manager.py b/feedindicator/config/manager.py new file mode 100644 index 0000000..b287343 --- /dev/null +++ b/feedindicator/config/manager.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +import os + +from configparser import RawConfigParser +from feedindicator.config.constants import app_config_dir + + +class ConfigManager: + """Configuration manager. All configurations can either be acces through + key words or per '.'. + + Attributes: + _configs: configuration dict + """ + def __init__(self): + """"Init config manager with default configurations.""" + self.__dict__['_configs'] = {# defaults + 'autostart': False, + 'refreshtime': 30 * 60, + 'stoptimer': False, + 'items_per_feed': 10, + 'show_notifications': True, + 'show_update_notifications': True, + 'feeds_at_top': False, + 'show_unread_feeds': True + } + + def __getattr__(self, key): + return self.__dict__['_configs'][key] + + def __setattr__(self, key, val): + if key in self.__dict__['_configs']: + self.__dict__['_configs'][key] = val + else: + raise KeyError(key) + + def __getitem__(self, key): + return self._configs[key] + + def __setitem__(self, key, val): + if key in self._configs: + self._configs[key] = val + else: + raise KeyError(key) + + def items(self): + """Get all configurations.""" + return dict((k, v) for k, v in self._configs.items()) + + def update(self, updates): + """Update configurations with given dict.""" + for k, v in updates.items(): + self._configs[k] = v + + def keys(self): + """Get all configuration names.""" + return self._configs.keys() + + def load(self): + """Load configurations from file.""" + parser = RawConfigParser() + parser.optionxform = str + parser.read(os.path.join(app_config_dir, 'config')) + if parser.has_option('Options', 'autostart'): + self.autostart = parser.getboolean('Options', 'autostart') + if parser.has_option('Options', 'refreshtime'): + self.refreshtime = parser.getint('Options', 'refreshtime') + if parser.has_option('Options', 'stoptimer'): + self.stoptimer = parser.getboolean('Options', 'stoptimer') + if parser.has_option('Options', 'items_per_feed'): + self.items_per_feed = parser.getint('Options', 'items_per_feed') + if parser.has_option('Options', 'show_notifications'): + self.show_notifications = parser.getboolean('Options', + 'show_notifications') + if parser.has_option('Options', 'show_update_notifications'): + self.show_update_notifications = parser. \ + getboolean('Options', 'show_update_notifications') + if parser.has_option('Options', 'feeds_at_top'): + self.feeds_at_top = parser.getboolean('Options', 'feeds_at_top') + if parser.has_option('Options', 'show_unread_feeds'): + self.show_unread_feeds = parser.getboolean('Options', + 'show_unread_feeds') + + def save(self): + """Save configuration to file.""" + parser = RawConfigParser() + parser.optionxform = str + parser.read_dict({'Options': self._configs}) + with open(os.path.join(app_config_dir, 'config'), 'w', + encoding='utf-8') as f: + parser.write(f) diff --git a/feedindicator/dialogs/__init__.py b/feedindicator/dialogs/__init__.py new file mode 100644 index 0000000..4c734ae --- /dev/null +++ b/feedindicator/dialogs/__init__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +from feedindicator.dialogs.about import AboutDialog +from feedindicator.dialogs.add_feed import AddFeedDialog +from feedindicator.dialogs.preferences import PreferencesDialog + + +__all = ('AboutDialog', 'AddFeedDialog', 'PreferencesDialog') diff --git a/feedindicator/dialogs/about.py b/feedindicator/dialogs/about.py new file mode 100644 index 0000000..b4d4a64 --- /dev/null +++ b/feedindicator/dialogs/about.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +import feedindicator +import gi +gi.require_version('Gtk', '3.0') + +from feedindicator import config +from gi.repository import Gtk + + +class AboutDialog(Gtk.AboutDialog): + """About dialog.""" + def __init__(self, widget): + """Init dialog. + + Args: + widget: Gtk widget + """ + Gtk.AboutDialog.__init__(self, _('About')) + self.set_icon_name(feedindicator.__app_name__) + self.set_logo(Gtk.IconTheme.get_default(). \ + load_icon(feedindicator.__app_name__, 128, 0)) + self.set_name(feedindicator.__app_name__) + self.set_program_name(feedindicator.__app_name__) + self.set_version(feedindicator.__version__) + self.set_comments(feedindicator.__description__) + self.set_copyright(feedindicator.__copyright__) + self.set_license(feedindicator.__license__) + self.set_website(feedindicator.__github__) + self.set_website_label(feedindicator.__github__) + self.set_authors(feedindicator.__author__.split(', ')) + self.run() + self.destroy() diff --git a/feedindicator/dialogs/add_feed.py b/feedindicator/dialogs/add_feed.py new file mode 100644 index 0000000..e6a805a --- /dev/null +++ b/feedindicator/dialogs/add_feed.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +import feedindicator +import gi +gi.require_version('Gtk', '3.0') +import sqlite3 +import sys + +from feedindicator import feeds +from gi.repository import Gtk, Gdk + + +class AddFeedDialog(Gtk.Window): + """Dialog to add a new feed url. + + Attributes: + _callback: callback function after url has been added + _callback_args: arguments for callback function + _textbox: textbox for feed url + """ + def __init__(self, widget, callback, *args): + """Init dialog. + + Args: + widget: Gtk widget + callback: callback function after url has been added + args: arguments for callback function + """ + Gtk.Window.__init__(self, title=_('Add feed')) + self._callback = callback + self._callback_args = args + + self.set_keep_above(True) + self.set_position(Gtk.WindowPosition.CENTER) + self.set_icon_name(feedindicator.__app_name__) + self.connect('delete_event', self._close_window) + self.connect('key-press-event', self._keypress) + + vbox = Gtk.VBox(False, 10) + vbox.set_border_width(10) + self.add(vbox) + + box = Gtk.HBox(False, 0) + vbox.pack_start(box, False, True, 0) + + label = Gtk.Label(label=_('Feed url')) + label.set_justify(Gtk.Justification.LEFT) + label.set_line_wrap(True) + box.pack_start(label, False, True, 0) + + box = Gtk.HBox(False, 0) + vbox.pack_start(box, False, True, 0) + + self._textbox = Gtk.Entry() + box.pack_start(self._textbox, True, True, 0) + self._textbox.connect("activate", self._save) + + box = Gtk.HBox(True, 0) + vbox.pack_end(box, False, True, 5) + + button = Gtk.Button(_('Cancel')) + box.pack_start(button, True, True, 0) + button.connect('clicked', self._close_window) + button = Gtk.Button(_('Add feed')) + box.pack_start(button, True, True, 0) + button.connect('clicked', self._save) + + self.set_keep_above(True) + self.show_all() + + def _keypress(self, widget, data): + """Keypress handler. + + Args: + widget: Gtk widget + data: key event data + """ + if data.keyval == Gdk.KEY_Escape: + self._close_window(widget) + + def _close_window(self, widget, data=None): + """Close dialog. + + Args: + widget: Gtk widget + data: optional key event data + """ + self.close() + + def _save(self, widget): + """Save feed and close dialog. + + Args: + widget: Gtk widget + """ + self.set_modal(True) + try: + if self._textbox.get_text(): + feeds.add(self._textbox.get_text()) + self._close_window(widget) + if self._callback: + self._callback(self._callback_args) + except sqlite3.Error as e: + print('Could not add feed.', e, file=sys.stderr) + dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.ERROR, + Gtk.ButtonsType.CANCEL, + _('Could not add feed.')) + dialog.format_secondary_text(str(e)) + dialog.run() + dialog.destroy() diff --git a/feedindicator/dialogs/preferences.py b/feedindicator/dialogs/preferences.py new file mode 100644 index 0000000..edb0081 --- /dev/null +++ b/feedindicator/dialogs/preferences.py @@ -0,0 +1,355 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +import feedindicator +import gettext +import gi +gi.require_version('Gtk', '3.0') +import os + +from feedindicator import config, feeds +from feedindicator.utils import autostart, SQLite +from gi.repository import Gtk, Gdk + + +class PreferencesDialog(Gtk.Window): + """Preferences dialog. + + Attributes: + _configs: dict with configs + _config_manager: config manager + _callback: callback function after url has been added + _callback_args: arguments for callback function + _feeds: list store for the feeds with id, title, feed_url + _treeview: treeview for the feeds + _btn_remove_feed: remove button for feeds + _refreshtime_label: label for refresh time config + _scaletime: scaler for refresh time config + _items_per_feed_label: label for items per feed config + _btn_show_update_notifications: check button for show update + notifications config + """ + def __init__(self, widget, config_manager, callback, *args): + """Init dialog. + + Args: + widget: Gtk widget + configs: dict with configs + callback: callback function after url has been added + *args: callback function args + """ + Gtk.Window.__init__(self, title=_('Preferences')) + self._config_manager = config_manager + self._configs = self._config_manager.items() + self._callback = callback + self._callback_args = args + + self.set_position(Gtk.WindowPosition.CENTER) + self.set_keep_above(True) + self.set_icon_name(feedindicator.__app_name__) + self.connect('delete_event', self._close_window) + self.connect('key-press-event', self._keypress) + + vbox = Gtk.VBox(False, 1) + vbox.set_border_width(1) + self.add(vbox) + + notebook = Gtk.Notebook() + notebook.set_tab_pos(Gtk.PositionType.LEFT) + vbox.pack_start(notebook, False, True, 0) + + # Feed List + frame = Gtk.Frame(label=_('Feed list')) + frame.set_border_width(1) + + box = Gtk.VBox(False, 1) + box.set_border_width(1) + frame.add(box) + + label = Gtk.Label(label=_('Configure the feeds.')) + label.set_justify(Gtk.Justification.LEFT) + label.set_line_wrap(True) + box.pack_start(label, False, True, 0) + + scrolled_window = Gtk.ScrolledWindow() + scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, + Gtk.PolicyType.ALWAYS) + scrolled_window.set_shadow_type(Gtk.ShadowType.IN) + scrolled_window.set_min_content_height(500) + scrolled_window.set_min_content_width(500) + box.pack_start(scrolled_window, False, True, 0) + + self._feeds = Gtk.ListStore(int, str, str) + with SQLite() as db: + for feed in db.s('SELECT id, title, feed_url FROM feeds ORDER ' + + 'BY UPPER(title)'): + self._feeds.append(feed) + + self._treeview = Gtk.TreeView.new_with_model(self._feeds) + for i, column_title in enumerate([_('ID'), _('Title'), _('URL')]): + renderer = Gtk.CellRendererText() + if i > 0: + renderer.set_property('editable', True) + renderer.connect('edited', self._cell_edited, i) + column = Gtk.TreeViewColumn(column_title, renderer, text=i) + self._treeview.append_column(column) + + self._treeview.set_headers_clickable(False) + self._treeview.set_reorderable(True) + self._treeview.connect('cursor-changed', self._selection_made) + scrolled_window.add(self._treeview) + + hbox = Gtk.HBox(True, 1) + box.pack_start(hbox, False, True, 0) + + btn = Gtk.Button(label=_('Add')) + btn.connect('clicked', self._add_feed) + hbox.pack_start(btn, False, True, 0) + + self._btn_remove_feed = Gtk.Button(label=_('Remove')) + self._btn_remove_feed.connect('clicked', self._remove_feed) + self._btn_remove_feed.set_sensitive(False) + hbox.pack_start(self._btn_remove_feed, False, True, 0) + notebook.append_page(frame, Gtk.Label(label=_('Feed list'))) + + # Options + frame = Gtk.Frame(label=_('Options')) + frame.set_border_width(1) + + box = Gtk.VBox(False, 1) + box.set_border_width(1) + frame.add(box) + + btn = Gtk.CheckButton(label=_('Auto update feeds')) + btn.set_active(not self._configs['stoptimer']) + btn.connect('toggled', self._toggle_config, 'stoptimer') + box.pack_start(btn, False, True, 0) + + hbox = Gtk.HBox(True, 0) + box.pack_start(hbox, False, True, 0) + + self._refreshtime_label = Gtk.Label(label='') + hbox.pack_start(self._refreshtime_label, False, True, 0) + + adjtimer = Gtk.Adjustment(value=self._configs['refreshtime'] / 60, + lower=1.0, upper=90.0, step_increment=1.0, + page_increment=10.0, page_size=0.0) + adjtimer.connect('value_changed', self._change_refreshtime) + self._scaletime = Gtk.HScale(adjustment=adjtimer) + self._scaletime.set_draw_value(False) + self._scaletime.set_digits(0) + self._scaletime.set_sensitive(not self._configs['stoptimer']) + hbox.pack_start(self._scaletime, False, True, 0) + self._change_refreshtime(adjtimer) + + hbox = Gtk.HBox(True, 0) + box.pack_start(hbox, False, True, 0) + + self._items_per_feed_label = Gtk.Label(label='') + hbox.pack_start(self._items_per_feed_label, False, False, 0) + + adjitems = Gtk.Adjustment(value=self._configs['items_per_feed'], + lower=1.0, upper=30.0, step_increment=1.0, + page_increment=10.0, page_size=0.0) + adjitems.connect('value_changed', self._change_items_per_feed) + scaleitems = Gtk.HScale(adjustment=adjitems) + scaleitems.set_draw_value(False) + scaleitems.set_digits(0) + hbox.pack_start(scaleitems, True, True, 0) + self._change_items_per_feed(adjitems) + + btn = Gtk.CheckButton(label=_('Show feeds at top of menu')) + btn.set_active(self._configs['feeds_at_top']) + btn.connect('toggled', self._toggle_config, 'feeds_at_top') + box.pack_start(btn, False, True, 0) + + btn = Gtk.CheckButton(label=_('Show feeds with no unread posts')) + btn.set_active(self._configs['show_unread_feeds']) + btn.connect('toggled', self._toggle_config, 'show_unread_feeds') + box.pack_start(btn, False, True, 0) + + btn = Gtk.CheckButton(label=_('Launch at system startup')) + btn.set_active(self._configs['autostart']) + btn.connect('toggled', self._toggle_config, 'autostart') + box.pack_start(btn, False, True, 0) + + btn = Gtk.CheckButton(label=_('Show notifications')) + btn.set_active(self._configs['show_notifications']) + btn.connect('toggled', self._toggle_config, 'show_notifications') + box.pack_start(btn, False, True, 0) + + self._btn_show_update_notifications = Gtk. \ + CheckButton(label=_('Show notifications at beginning ' + + 'and end of update')) + self._btn_show_update_notifications. \ + set_sensitive(self._configs['show_notifications']) + self._btn_show_update_notifications. \ + set_active(self._configs['show_update_notifications']) + self._btn_show_update_notifications. \ + connect('toggled', self._toggle_config, + 'show_update_notifications') + box.pack_start(self._btn_show_update_notifications, False, True, 0) + + notebook.append_page(frame, Gtk.Label(label=_('Options'))) + + box = Gtk.HBox(True, 0) + vbox.pack_end(box, False, True, 0) + + btn = Gtk.Button(label=_('Cancel')) + btn.connect('clicked', self._close_window) + box.pack_start(btn, False, True, 0) + + btn = Gtk.Button(label=_('Save')) + btn.connect('clicked', self._save) + box.pack_start(btn, False, True, 0) + self.show_all() + + def _keypress(self, widget, data): + """Keypress handler. + + Args: + widget: Gtk widget + data: key event data + """ + if data.keyval == Gdk.KEY_Escape: + self._close_window(widget) + + def _close_window(self, widget, data=None): + """Close dialog. + + Args: + widget: Gtk widget + data: optional key event data + """ + self.close() + + def _save(self, widget): + """Save config and feeds and close dialog. + + Args: + widget: Gtk widget + """ + self._config_manager.update(self._configs) + self._config_manager.save() + with SQLite() as db: + for feed in self._feeds: + if feed[0] == -1 and feed[2]: + if not feed[1]: + feed[1] = None + db.s('INSERT INTO feeds (feed_url, title) VALUES (?,?)', + (feed[2], feed[1])) + else: + db.s('UPDATE feeds set title=?, feed_url=? WHERE id=?', + (feed[1], feed[2], feed[0])) + self._close_window(widget) + self._callback(self._callback_args) + + def _selection_made(self, widget): + """Feed selected from list, so we enable the remove button. + + Args: + widget: Gtk widget + """ + sel = self._treeview.get_selection().get_selected() + if sel[1] is not None: + self._btn_remove_feed.set_sensitive(True) + + def _cell_edited(self, widget, path, text, column): + """Update list store after cell has been edited. + + Args: + widget: Gtk widget + path: row id + text: new text + column: column id + """ + self._feeds[path][column] = text + + def _add_feed(self, widget): + """Add a row to list. + + Args: + widget: Gtk widget + """ + sel = self._feeds.append([-1, '', '']) + pos = self._feeds.get_path(sel) + self._treeview.set_cursor(pos, None, False) + + def _remove_feed(self, widget): + """Remove selected feed from list and delete from database. + + Args: + widget: Gtk widget + """ + sel = self._treeview.get_selection().get_selected() + feed_id = sel[0].get_value(sel[1], 0) + if feed_id != -1: + feeds.delete(feed_id) + self._feeds.remove(sel[1]) + self._treeview.get_selection().unselect_all() + self._btn_remove_feed.set_sensitive(False) + + def _change_refreshtime(self, widget): + """Change refresh time. + + Args: + widget: Gtk widget + """ + value = widget.get_value() + self._configs['refreshtime'] = int(value * 60) + text = gettext.ngettext('Update feeds every minute', + 'Update feeds every %(minutes)d minutes', + value) % {'minutes': value} + self._refreshtime_label.set_text(text) + + def _change_items_per_feed(self, widget): + """Change items per feed. + + Args: + widget: Gtk widget + """ + value = widget.get_value() + self._configs['items_per_feed'] = int(value) + text = gettext.ngettext('Show 1 item per feed', + 'Show %(items)d items per feed', + value) % {'items': value} + self._items_per_feed_label.set_text(text) + + def _toggle_config(self, widget, key): + """Toggles config values. + + Args: + widget: Gtk widget + key: config key + """ + self._configs[key] = widget.get_active() + if key == 'autostart': + if self._configs[key]: + autostart.create() + else: + autostart.delete() + elif key == 'stoptimer': + if self._configs[key]: + self._scaletime.set_sensitive(True) + else: + self._scaletime.set_sensitive(False) diff --git a/feedindicator/feedindicator.py b/feedindicator/feedindicator.py new file mode 100644 index 0000000..6888628 --- /dev/null +++ b/feedindicator/feedindicator.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + +import feedindicator +import gettext +import gi +gi.require_version('Notify', '0.7') +import os +import sqlite3 +import sys + +from argparse import ArgumentParser, RawTextHelpFormatter +from feedindicator import config, feeds, utils +from feedindicator.indicator import AppIndicator +from gi.repository import Gtk, Notify +from time import sleep + + +def _add_feed(args): + try: + feeds.add(args.url) + except sqlite3.Error as e: + print(_('Could not add feed.'), e, file=sys.stderr) + + +def _update_feeds(args): + try: + nb_new_posts = feeds.update() + print(_('Feeds updated. New posts: %(nr)d') % {'nr': nb_new_posts}) + except Exception as e: + print(_('Error while updating feeds.'), e, file=sys.stderr) + + +def main(): + gettext.install(feedindicator.__app_name__, config.app_locale_dir, + codeset="utf-8") + Notify.init(feedindicator.__app_name__) + + if not os.path.exists(config.app_cache_dir): + os.makedirs(config.app_cache_dir) + if not os.path.exists(config.app_config_dir): + os.makedirs(config.app_config_dir) + if not os.path.exists(config.app_data_dir): + os.makedirs(config.app_data_dir) + + parser = ArgumentParser(prog=feedindicator.__app_name__, + description=feedindicator.__description__, + formatter_class=RawTextHelpFormatter) + parser.add_argument('-v', '--version', action='version', + version=utils.app_version()) + parser.add_argument('--autostarted', action='store_true', help='Option ' + + 'to indicate feedindicator was autostarted.') + subparsers = parser.add_subparsers(dest='subparser') + + # create the parser for the "add" subcommand + add_parser = subparsers.add_parser('add', help='Add a new feed.') + add_parser.set_defaults(func=_add_feed) + add_parser.add_argument('url', help='Feed URL') + + # create the parser for the "update" subcommand + update_parser = subparsers.add_parser('update', help='Update feeds.') + update_parser.set_defaults(func=_update_feeds) + + argv = sys.argv[1:] + args = parser.parse_args(argv) + if args.autostarted: + sleep(5) + + config_manager = config.ConfigManager() + config_manager.load() + + try: + utils.db_init() + except sqlite3.Error as e: + print(_('Could not init database.'), e, file=sys.stderr) + + if args.subparser: + args.func(args) + else: + indicator = AppIndicator(config_manager) + Gtk.main() diff --git a/feedindicator/feeds.py b/feedindicator/feeds.py new file mode 100644 index 0000000..3d228e7 --- /dev/null +++ b/feedindicator/feeds.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +import feedparser +import json +import os +import urllib.request +import sys + +from feedindicator import config, utils +from feedindicator.utils import SQLite + + +# clear out the html element list so all are removed +feedparser._HTMLSanitizer.acceptable_elements = [] + + +def add(url): + """Add a new feed url to the database. + + Args: + url: feed url + """ + with SQLite() as db: + db.s('INSERT INTO feeds (feed_url) VALUES (?)', (url,)) + print(_('Feed added.')) + + +def delete(feed_id): + """Delete a feed and all its posts from the database. + + Args: + feed_id: feed id + """ + with SQLite() as db: + img = db.s('SELECT img FROM feeds WHERE id=?', (feed_id,))[0][0] + if img and os.path.exists(os.path.join(config.app_cache_dir, img)): + os.remove(os.path.join(config.app_cache_dir, img)) + db.s('DELETE FROM posts WHERE feed_id=?', (feed_id,)) + db.s('DELETE FROM feeds WHERE id=?', (feed_id,)) + + +def update(): + """Update all feeds.""" + new_posts = 0 + with SQLite() as db: + feeds = db.s('SELECT id, feed_url, title, url, img FROM feeds') + for feed in feeds: + result = _parse_feed(feed[1]) + if not result['success']: + continue + + if feed[2] == None: + db.s('UPDATE feeds SET title=? WHERE id=?', + (result['feed']['title'], feed[0])) + if feed[3] != result['feed']['link']: + db.s('UPDATE feeds SET url=? WHERE id=?', + (result['feed']['link'], feed[0])) + if not result['feed']['img']: + db.s('UPDATE feeds SET img=? WHERE id=?', + (result['feed']['img'], feed[0])) + + items = () + hashes = () + for p in result['posts']: + hash = utils.get_hash(p['date'], p['title']) + hashes += (hash,) + if db.s('SELECT COUNT(*) FROM posts WHERE hash=?', + (hash,))[0][0] == 0: + items += ((hash, p['link'], p['title'], + json.dumps(p['raw']), feed[0]),) + + new_posts += len(items) + if len(items) > 0: + db.many('INSERT INTO posts (hash, url, title, raw, feed_id) ' + + 'VALUES (?,?,?,?,?)', items) + db.s('DELETE FROM posts WHERE read="true" AND hash NOT IN ' + '(%s) AND feed_id=?' % ','.join('?' for p in hashes), + hashes + (feed[0],)) + return new_posts + + +def _parse_feed(url): + """Parses a feed and returns the data. + + Args: + url: feed url + """ + result = { + 'feed': {}, + 'posts': [], + 'success': False + } + + try: + rssfeed = feedparser.parse(url) + posts = [] + for e in rssfeed.entries: + e.title = e.title.replace('\n', '') + if len(e.title) > 72: + substr = e.title[:72].rpartition(' ') + if substr[0] != '': + e.title = substr[0] + '...' + + date = None + if 'published' in e: + date = e.published + elif 'updated' in e: + date = e.updated + elif 'created' in e: + date = e.created + posts.append({ + 'title': e.title, + 'link': e.link, + 'date': date, + 'raw': e + }) + result['posts'] = posts + feedimg = None + webimg = None + if 'image' in rssfeed.feed: + if 'href' in rssfeed.feed.image: + webimg = rssfeed.feed.image.href + elif 'url' in rssfeed.feed.image: + webimg = rssfeed.feed.image.url + elif 'logo' in rssfeed.feed: + webimg = rssfeed.feed.logo + elif 'icon' in rssfeed.feed: + webimg = rssfeed.feed.icon + if webimg != None: + ext = webimg.rsplit('.', 1) + if len(ext[1]) > 5 or len(ext[1]) == 0: + ext[0] = webimg + ext[1] = 'jpg' + shash = hashlib.sha512(url.encode('utf-8')).hexdigest() + localimg = os.path.join(config.app_cache_dir, + '%s.%s' % (shash, ext[1])) + feedimg = '%s.%s' % (shash, ext[1]) + + request = urllib.request.Request(webimg) + with urllib.request.urlopen(request) as response: + with open(localimg, 'bw') as f: + f.write(response.read()) + + result['feed'] = { + 'title': rssfeed.feed.title, + 'link': rssfeed.feed.link, + 'img': feedimg + } + result['success'] = True + except Exception as e: + print('Error while parsing feed (feed url: %s).' % url, e, + file=sys.stderr) + return result diff --git a/feedindicator/indicator.py b/feedindicator/indicator.py new file mode 100644 index 0000000..2b5fdcb --- /dev/null +++ b/feedindicator/indicator.py @@ -0,0 +1,500 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + +import feedindicator +import gettext +import gi +gi.require_version('Gtk', '3.0') +import os +import sys +import webbrowser + +from feedindicator import config, feeds +from feedindicator.dialogs import AboutDialog, AddFeedDialog, PreferencesDialog +from feedindicator.threads import FeedThread +from feedindicator.utils import SQLite +from gi.repository import GLib, Gtk, Notify +from time import sleep, time + +try: + gi.require_version('AppIndicator3', '0.1') + from gi.repository import AppIndicator3 as appindicator +except (ImportError, ValueError): + appindicator = None + + +class AppIndicator: + """App indicator. + + Attributes: + _config_manager: config manager + _feeds_thread: feeds thread + _indicator: app indicator + _status_icon: Gtk status icon, if app indicator isn't available + _menu: menu + """ + def __init__(self, config_manager): + """Init indicator. + + Args: + config_manager: config manager + """ + self._config_manager = config_manager + self._feeds_thread = None + + if appindicator: + # Create indicator + self._indicator = appindicator.Indicator. \ + new(feedindicator.__app_name__, feedindicator.__app_name__, + appindicator.IndicatorCategory.APPLICATION_STATUS) + self._indicator.set_status(appindicator.IndicatorStatus.ACTIVE) + self._indicator.set_attention_icon_full(config.attention_icon, + _('New Posts.')) + self._indicator.set_icon_full(config.active_icon, + _('Nothing new.')) + else: + # Create status icon + self._status_icon = Gtk.StatusIcon() + self._status_icon.set_name(feedindicator.__app_name__) + self._status_icon.set_from_icon_name(config.active_icon) + self._status_icon.set_tooltip_text(feedindicator.__app_name__) + + # Create popup menu + self._menu = Gtk.Menu() + if appindicator: + self._menu.show_all() + self._indicator.set_menu(self._menu) + else: + self._status_icon.connect('activate', self._toggle_status_icon) + self._status_icon.connect('popup-menu', self._popup_menu) + self._status_icon.set_visible(True) + + self._update(None, True, True) + + def _popup_menu(self, widget, button, time): + """Callback when the popup menu on the status icon has to open. + + Args: + widget: Gtk widget + button: mouse button + time: activation time + """ + self._menu.show_all() + self._menu.popup(None, None, Gtk.StatusIcon.position_menu, + self._status_icon, button, time) + + def _toggle_status_icon(self, widget): + """Callback when a request to toggle feedindicator was made. + + Args: + widget: Gtk widget + """ + self._popup_menu(widget, 0, Gtk.get_current_event_time()) + + def _set_status(self, status): + """Turns the appindicator to attention and back to normal. + + Args: + status: new indicator status: attention/active + """ + if status and appindicator: + self._indicator.set_status(appindicator.IndicatorStatus.ATTENTION) + elif status and not appindicator: + self._status_icon.set_from_icon_name(config.attention_icon) + elif not status and appindicator: + self._indicator.set_status(appindicator.IndicatorStatus.ACTIVE) + else: + self._status_icon.set_from_icon_name(config.active_icon) + + def _exit(self, widget): + """Close Feedindicator. + + Args: + widget: Gtk widget + """ + Notify.uninit() + Gtk.main_quit() + + def _notify(self, title, msg, img=feedindicator.__app_name__): + """Send feed updates to notify-osd. + + Args: + title: notification title + msg: message + img: image (default: app icon) + """ + n = Notify.Notification.new(title, msg, img) + n.show() + + def _open_website(self, widget, url, post_id=None): + """Open website. + + Args: + widget: Gtk widget + url: url to open + post_id: optional post id to change status to read + """ + webbrowser.open(url) + + if post_id: + with SQLite() as db: + db.s('UPDATE posts SET read="true" WHERE id=?', (post_id,)) + sleep(0.5) + self._render_menu() + + def _update(self, widget=None, timeroverride=False, starttimer=False): + """Start updating feeds. + + Args: + widget: Gtk widget + timeroverride: if true timer will be overridden + starttimer: if true timer will be started + """ + if self._config_manager.stoptimer and not timeroverride: + return False + + with SQLite() as db: + if db.s('SELECT COUNT(*) FROM feeds')[0][0] > 0: + if starttimer: + GLib.timeout_add_seconds(self._config_manager.refreshtime, + self._update, True) + + self._loading_menu() + if db.s('SELECT COUNT(*) FROM feeds')[0][0] != 0: + self._feeds_thread = FeedThread(self._finished_update) + self._feeds_thread.start() + if self._config_manager.show_notifications: + if self._config_manager.show_update_notifications: + self._notify(feedindicator.__app_name__, + _('Begin updating Feeds.')) + else: + self._render_menu() + return True + + def _finished_update(self, *args): + """Renders menu and shows notification after updating is finished.""" + if self._feeds_thread is not None and self._feeds_thread.is_alive(): + return + + if self._feeds_thread: + self._feeds_thread.join() + sleep(1) + self._feeds_thread = None + self._render_menu() + + if self._config_manager.show_notifications: + with SQLite() as db: + feeds = db.s('SELECT id, title, img FROM feeds WHERE ' + + 'feed_url IN (SELECT feed_id FROM posts WHERE ' + + 'read="false" GROUP BY feed_id LIMIT 50) ORDER' + + ' BY (SELECT count(feed_id) AS c FROM posts ' + + 'WHERE read="false" GROUP BY feed_id ORDER BY ' + + 'c desc), UPPER(title)') + for feed in feeds: + img = os.path.join(config.app_cache_dir, + feed[2]) if feed[2] else None + posts = db.s('SELECT title FROM posts WHERE read=' + + '"false" AND feed_id=? LIMIT 3', (feed[0],)) + if len(posts) > 0: + msg = '\n'.join('* %s' % p[0] for p in posts) + self._notify(feed[1], msg, img) + if len(feeds) == 0: + if self._config_manager.show_update_notifications: + self._notify(feedindicator.__app_name__, + _('Finished updating feeds.')) + + def _clear_menu(self): + """Removes all entries from menu.""" + for child in self._menu.get_children(): + child.destroy() + + def _loading_menu(self): + """Populate a loading menu.""" + self._clear_menu() + + item = Gtk.MenuItem(label=_('Loading')) + item.set_sensitive(False) + self._menu.append(item) + self._menu.show_all() + if appindicator: + self._indicator.set_menu(self._menu) + self._set_status(False) + + def _render_menu(self): + """Populate the menu.""" + self._clear_menu() + + with SQLite() as db: + feeds = db.s('SELECT id, title, url, feed_url, (SELECT COUNT(*) ' + + 'FROM posts WHERE posts.feed_id=feeds.id AND ' + + 'read="false") AS c FROM feeds ORDER BY c DESC, ' + + 'UPPER(title)') + + if not self._config_manager.feeds_at_top: + self._conf_menu(len(feeds) > 0) + self._menu.append(Gtk.SeparatorMenuItem()) + + if len(feeds) > 0: + self._feeds_menu_header() + for feed in feeds: + posts = db.s('SELECT id, title, url FROM posts WHERE ' + + 'feed_id=? AND read="false" ORDER BY id ' + + 'LIMIT %d' % + self._config_manager.items_per_feed, + (feed[0],)) + if self._config_manager.show_unread_feeds: + self._feed_submenu(feed, posts) + else: + if feed[4] > 0: + self._feed_submenu(feed, posts) + if db.s('SELECT COUNT(*) FROM posts WHERE ' + + 'read="false"')[0][0] == 0: + menu_notice = Gtk.MenuItem(label=_('No unread posts.')) + menu_notice.set_sensitive(False) + self._menu.append(menu_notice) + else: + item = Gtk.MenuItem(label=_('No feeds defined!')) + item.set_sensitive(False) + self._menu.append(item) + + if self._config_manager.feeds_at_top: + self._menu.append(Gtk.SeparatorMenuItem()) + self._conf_menu(len(feeds) > 0) + + self._menu.show_all() + if appindicator: + self._indicator.set_menu(self._menu) + self._set_status(db.s('SELECT COUNT(*) FROM posts WHERE ' + + 'read="false"')[0][0] > 0) + + def _feeds_menu_header(self): + """Add items to menu with for all feeds.""" + item = Gtk.MenuItem(label=_('Open all unread')) + item.connect('activate', self._open_unread, '') + self._menu.append(item) + + item = Gtk.MenuItem(label=_('Mark all as read')) + item.connect('activate', self._mark_feed_as_read, '') + self._menu.append(item) + + item = Gtk.MenuItem(label=_('Mark all as unread')) + item.connect('activate', self._mark_feed_as_unread) + self._menu.append(item) + + item = Gtk.MenuItem(label=_('Reload all feeds')) + item.connect('activate', self._update, True, False) + self._menu.append(item) + self._menu.append(Gtk.SeparatorMenuItem()) + + def _feed_submenu(self, feed, posts): + """Add a feed submenu to the menu. + + Args: + feed: tuple feed info (id, title, url, feed_url, number of posts) + posts: list of posts to append with (id, title, url) for each post + """ + menu_header = Gtk.MenuItem('%s (%d)'.replace(' (0)', '') % (feed[1], + feed[4])) + + submenu = Gtk.Menu() + item = Gtk.MenuItem(_('Open Website')) + item.connect('activate', self._open_website, feed[2]) + submenu.append(item) + + item = Gtk.MenuItem(label=_('Open unread')) + item.connect('activate', self._open_unread, feed[0]) + submenu.append(item) + + if feed[4] > self._config_manager.items_per_feed: + item = Gtk.MenuItem(label=_('Open displayed posts')) + item.connect('activate', self._open_displayed, feed[0]) + submenu.append(item) + + item = Gtk.MenuItem(label=_('Mark as read')) + item.connect('activate', self._mark_feed_as_read, feed[0]) + submenu.append(item) + + if feed[4] > self._config_manager.items_per_feed: + item = Gtk.MenuItem(label=_('Mark displayed posts as read')) + item.connect('activate', self._mark_displayed_as_read, feed[0]) + submenu.append(item) + + item = Gtk.MenuItem(label=_('Mark as unread')) + item.connect('activate', self._mark_feed_as_unread, feed[0]) + submenu.append(item) + + for i, post in enumerate(posts): + if i == 0: + submenu.append(Gtk.SeparatorMenuItem()) + item = Gtk.MenuItem(post[1]) + item.connect('activate', self._open_website, post[2], post[0]) + submenu.append(item) + + menu_header.set_submenu(submenu) + self._menu.append(menu_header) + + def _conf_menu(self, has_feeds): + """Adds config items to menu. + + Args: + has_feeds: if feeds are displayed in the menu + """ + if self._config_manager.feeds_at_top: + self._menu.append(Gtk.SeparatorMenuItem()) + + item = Gtk.MenuItem(label=_('Add feed')) + item.connect('activate', AddFeedDialog, self._update, None, True, + False) + self._menu.append(item) + + if has_feeds: + item = Gtk.CheckMenuItem(label=self._timer_text()) + if self._config_manager.stoptimer: + item.set_active(False) + else: + item.set_active(True) + item.connect('toggled', self._toggle_update) + self._menu.append(item) + + menu_configure = Gtk.MenuItem(label=_('Preferences')) + menu_configure.connect('activate', PreferencesDialog, + self._config_manager, self._update, None, True, + True) + self._menu.append(menu_configure) + menu_about = Gtk.MenuItem(label=_('About')) + menu_about.connect('activate', AboutDialog) + self._menu.append(menu_about) + item = Gtk.MenuItem(label=_('Exit')) + item.connect('activate', self._exit) + self._menu.append(item) + + def _open_unread(self, widget, feed_id=None): + """Opens all unread post of the given feed or all unread posts. + + Args: + widget: Gtk widget + feed_id: optional feed id, if not given opens all unread + """ + with SQLite() as db: + if feed_id: + posts = db.s('SELECT url FROM posts WHERE feed_id=? AND ' + + 'read="false"', (feed_id,)) + for post in posts: + webbrowser.open(post[0]) + sleep(0.6) + + db.s('UPDATE posts SET read="true" WHERE feed_id=?', + (feed_id,)) + else: + for feed in db.s('SELECT id FROM feeds'): + posts = db.s('SELECT url FROM posts WHERE feed_id=? AND ' + + 'read="false"', feed) + for post in posts: + webbrowser.open(post[0]) + sleep(0.6) + db.s('UPDATE posts SET read="true" WHERE feed_id=?', feed) + sleep(0.5) + self._render_menu() + + def _open_displayed(self, widget, feed_id): + """Opens all unread post of the given feed or all unread posts. + + Args: + widget: Gtk widget + feed_id: optional feed id, if not given opens all unread + """ + with SQLite() as db: + posts = db.s('SELECT id, url FROM posts WHERE feed_id=? AND ' + + 'read="false" ORDER BY id DESC LIMIT %d' % + self._config_manager.items_per_feed, (feed_id,)) + for post in posts: + webbrowser.open(post[1]) + db.s('UPDATE posts SET read="true" WHERE id=?', (post[0],)) + sleep(0.6) + self._render_menu() + + def _mark_feed_as_read(self, widget, feed_id=None): + """Mark all posts of all feeds or a specific feed as read. + + Args: + widget: Gtk widget + feed_id: optinal feed id, if not given all will be updated + """ + with SQLite() as db: + f = 'WHERE feed_id=?' if feed_id else '' + db.s('UPDATE posts SET read="true" %s' % f, + (feed_id,) if feed_id else ()) + sleep(0.5) + self._render_menu() + + def _mark_displayed_as_read(self, widget, feed_id): + """Marks displayed posts of given feed as read. + + Args: + widget: Gtk widget + feed_id: feed id + """ + with SQLite() as db: + db.s('UPDATE posts SET read="true" WHERE feed_id=? AND id IN ' + + '(SELECT id FROM posts WHERE feed_id=? AND read="false" ' + + 'ORDER BY id DESC LIMIT %d)' % + self._config_manager.items_per_feed, (feed_id, feed_id)) + sleep(0.5) + self._render_menu() + + def _mark_feed_as_unread(self, widget, feed_id=None): + """Mark all posts of all feeds or a specific feed as read. + + Args: + widget: Gtk widget + feed_id: optinal feed id, if not given all will be updated + """ + with SQLite() as db: + f = 'WHERE feed_id=?' if feed_id else '' + db.d('UPDATE posts SET read="false" %s' % f, + (feed_id,) if feed_id else ()) + sleep(0.5) + self._render_menu() + + def _toggle_update(self, widget): + """Toggle timer updating. + + Args: + widget: Gtk widget + """ + if self._config_manager.stoptimer: + self._config_manager.stoptimer = False + if self._config_manager.show_notifications: + self._notify(feedindicator.__app_name__, self._timer_text()) + self._update(None, True, True) + else: + self._config_manager.stoptimer = True + if self._config_manager.show_notifications: + self._notify(config.app_name, + _('Feeds will not update automatically.')) + + def _timer_text(self): + """"Text for timer element.""" + minutes = self._config_manager.refreshtime / 60 + return gettext.ngettext('Update feeds every minute', + 'Update feeds every %(minutes)d minutes', + minutes) % {'minutes': minutes} diff --git a/feedindicator/locale/de_DE/LC_MESSAGES/feedindicator.mo b/feedindicator/locale/de_DE/LC_MESSAGES/feedindicator.mo new file mode 100644 index 0000000..a4ad91a Binary files /dev/null and b/feedindicator/locale/de_DE/LC_MESSAGES/feedindicator.mo differ diff --git a/feedindicator/locale/de_DE/LC_MESSAGES/feedindicator.po b/feedindicator/locale/de_DE/LC_MESSAGES/feedindicator.po new file mode 100644 index 0000000..6f2cae4 --- /dev/null +++ b/feedindicator/locale/de_DE/LC_MESSAGES/feedindicator.po @@ -0,0 +1,221 @@ +msgid "" +msgstr "" +"Project-Id-Version: feedindicator\n" +"POT-Creation-Date: 2017-10-04 22:55+0200\n" +"PO-Revision-Date: 2017-10-04 22:55+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.8.7.1\n" +"X-Poedit-Basepath: ../../..\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-SourceCharset: UTF-8\n" +"X-Poedit-SearchPath-0: .\n" + +#: dialogs/about.py:40 indicator.py:383 +msgid "About" +msgstr "Über" + +#: dialogs/add_feed.py:50 dialogs/add_feed.py:85 indicator.py:364 +msgid "Add feed" +msgstr "Feed hinzufügen" + +#: dialogs/add_feed.py:67 +msgid "Feed url" +msgstr "Feed url" + +#: dialogs/add_feed.py:82 dialogs/preferences.py:218 +msgid "Cancel" +msgstr "Abbrechen" + +#: dialogs/add_feed.py:128 feedindicator.py:42 +msgid "Could not add feed." +msgstr "Feed konnte nicht hinzugefügt werden." + +#: dialogs/preferences.py:61 indicator.py:378 +msgid "Preferences" +msgstr "Einstellungen" + +#: dialogs/preferences.py:82 dialogs/preferences.py:133 +msgid "Feed list" +msgstr "Feedliste" + +#: dialogs/preferences.py:89 +msgid "Configure the feeds." +msgstr "Feeds konfigurieren." + +#: dialogs/preferences.py:109 +msgid "ID" +msgstr "ID" + +#: dialogs/preferences.py:109 +msgid "Title" +msgstr "Titel" + +#: dialogs/preferences.py:109 +msgid "URL" +msgstr "URL" + +#: dialogs/preferences.py:125 +msgid "Add" +msgstr "Hinzufügen" + +#: dialogs/preferences.py:129 +msgid "Remove" +msgstr "Entfernen" + +#: dialogs/preferences.py:136 dialogs/preferences.py:213 +msgid "Options" +msgstr "Optionen" + +#: dialogs/preferences.py:143 +msgid "Auto update feeds" +msgstr "Auto aktualisiere feeds" + +#: dialogs/preferences.py:181 +msgid "Show feeds at top of menu" +msgstr "Zeige Feeds an oberster Stelle im Menü" + +#: dialogs/preferences.py:186 +msgid "Show feeds with no unread posts" +msgstr "Zeige Feeds mit keinen ungelesenen Posts" + +#: dialogs/preferences.py:191 +msgid "Launch at system startup" +msgstr "Bei Systemstart starten" + +#: dialogs/preferences.py:196 +msgid "Show notifications" +msgstr "Zeige Benachrichtigungen" + +#: dialogs/preferences.py:202 +msgid "Show notifications at beginning and end of update" +msgstr "Zeige Benachrichtigungen am Anfang und Ende der Aktualisierung" + +#: dialogs/preferences.py:222 +msgid "Save" +msgstr "Speichern" + +#: dialogs/preferences.py:320 indicator.py:498 +#, python-format +msgid "Update feeds every minute" +msgid_plural "Update feeds every %(minutes)d minutes" +msgstr[0] "Feeds jede Minute aktualiesierenq" +msgstr[1] "Feeds alle %(minutes)d Minuten aktualiesiren" + +#: dialogs/preferences.py:333 +#, python-format +msgid "Show 1 item per feed" +msgid_plural "Show %(items)d items per feed" +msgstr[0] "Zeige ein Eintrag pro Feed" +msgstr[1] "Zeige %(items)d Einträge pro Feed" + +#: feedindicator.py:48 +#, python-format +msgid "Feeds updated. New posts: %(nr)d" +msgstr "Feeds aktualisiert. Neue Posts: %(nr)d" + +#: feedindicator.py:50 +msgid "Error while updating feeds." +msgstr "Es ist ein Fehler während der Aktualisierung der Feeds aufgetreten." + +#: feedindicator.py:94 +msgid "Could not init database." +msgstr "Die Datenbank konnte nicht erstellt werden." + +#: feeds.py:46 +msgid "Feed added." +msgstr "Feed hinzugefügt." + +#: indicator.py:71 +msgid "New Posts." +msgstr "Neue Posts." + +#: indicator.py:73 +msgid "Nothing new." +msgstr "Nichts neues." + +#: indicator.py:188 +msgid "Begin updating Feeds." +msgstr "Starte Aktualisierung der Feeds." + +#: indicator.py:223 +msgid "Finished updating feeds." +msgstr "Aktualisierung der Feeds beendet." + +#: indicator.py:234 +msgid "Loading" +msgstr "Laden" + +#: indicator.py:271 +msgid "No unread posts." +msgstr "Keine ungelesenen Posts." + +#: indicator.py:275 +msgid "No feeds defined!" +msgstr "Keine Feeds vorhanden!" + +#: indicator.py:291 +msgid "Open all unread" +msgstr "Alle ungelesenen öffnen" + +#: indicator.py:295 +msgid "Mark all as read" +msgstr "Alle als gelesen makieren" + +#: indicator.py:299 +msgid "Mark all as unread" +msgstr "Alle als ungelesen makieren" + +#: indicator.py:303 +msgid "Reload all feeds" +msgstr "Alle Feeds neuladen" + +#: indicator.py:319 +msgid "Open Website" +msgstr "Webseite öffnen" + +#: indicator.py:323 +msgid "Open unread" +msgstr "Ungelesene öffnen" + +#: indicator.py:328 +msgid "Open displayed posts" +msgstr "Öffne angezeigte Posts." + +#: indicator.py:332 +msgid "Mark as read" +msgstr "Als gelesen makieren" + +#: indicator.py:337 +msgid "Mark displayed posts as read" +msgstr "Alle angezeigten Posts als gelesen makieren" + +#: indicator.py:341 +msgid "Mark as unread" +msgstr "Als ungelesen makieren" + +#: indicator.py:386 +msgid "Exit" +msgstr "Beenden" + +#: indicator.py:493 +msgid "Feeds will not update automatically." +msgstr "Feeds werden nicht automatisch aktualisiert." + +#: utils/db.py:33 +msgid "No database found, creating it." +msgstr "Keine Datenbank gefunden, sie wird erstellt." + +#: utils/db.py:55 +msgid "Database created." +msgstr "Datenbank erstellt." + +#~ msgid "Add Feed" +#~ msgstr "Feed hinzufügen" + +#~ msgid "Feed url:" +#~ msgstr "Feed url" diff --git a/feedindicator/threads/__init__.py b/feedindicator/threads/__init__.py new file mode 100644 index 0000000..62d14f9 --- /dev/null +++ b/feedindicator/threads/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +from feedindicator.threads.feeds import FeedThread + + +__all = ('FeedThread',) diff --git a/feedindicator/threads/feeds.py b/feedindicator/threads/feeds.py new file mode 100644 index 0000000..06df2a0 --- /dev/null +++ b/feedindicator/threads/feeds.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +from feedindicator import feeds +from gi.repository import GLib +from threading import Thread + + +class FeedThread(Thread): + """Feed thread, to run feed updates in a separate thread. + + Attributes: + _callback: callback function after url has been added + _callback_args: arguments for callback function + """ + def __init__(self, callback, *args): + """Init feed thread. + + Args: + callback: callback function after url has been added + args: arguments for callback function + """ + Thread.__init__(self, name='FeedThread') + self._callback = callback + self._callback_args = args + + def run(self): + """Updates all feeds in the database und updates the infos""" + feeds.update() + GLib.idle_add(self._callback, self._callback_args) diff --git a/feedindicator/utils/__init__.py b/feedindicator/utils/__init__.py new file mode 100644 index 0000000..66d30e0 --- /dev/null +++ b/feedindicator/utils/__init__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + +from feedindicator.utils.core import get_hash +from feedindicator.utils.db import init as db_init +from feedindicator.utils.sqlite import SQLite +from feedindicator.utils.version import app_version + + +__all__ = ('app_version', 'db_init', 'get_hash', 'SQLite') diff --git a/feedindicator/utils/autostart.py b/feedindicator/utils/autostart.py new file mode 100644 index 0000000..ccdab72 --- /dev/null +++ b/feedindicator/utils/autostart.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +import feedindicator +import os + +from feedindicator import config + + +def create(): + """Create autostart file.""" + if not os.path.exists(config.app_autostart_dir): + os.makedirs(config.app_autostart_dir, 0o700) + with open(config.app_autostart_file, 'w', encoding='utf-8') as f: + f.write('[Desktop Entry]\n') + f.write('Type=Application\n') + f.write('Exec=%s --autostarted\n' % feedindicator.__app_name__) + f.write('X-GNOME-Autostart-enabled=true\n') + f.write('Icon=%s\n' % feedindicator.__app_name__) + f.write('Name=%s\n' % feedindicator.__app_name__) + f.write('Comment=%s' % feedindicator.__description__) + + +def delete(): + """Delete autostart file.""" + if os.path.exists(config.app_autostart_file): + os.remove(config.app_autostart_file) diff --git a/feedindicator/utils/core.py b/feedindicator/utils/core.py new file mode 100644 index 0000000..9a5b6b0 --- /dev/null +++ b/feedindicator/utils/core.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +import hashlib + + +def get_hash(*args): + """Calculate hash for combined string of arguments. + + Args: + args: values to hash + """ + return hashlib.sha512('@'.join(a for a in args if a). \ + encode('utf-8')).hexdigest() diff --git a/feedindicator/utils/db.py b/feedindicator/utils/db.py new file mode 100644 index 0000000..1bd22c2 --- /dev/null +++ b/feedindicator/utils/db.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +import os + +from feedindicator import config +from feedindicator.utils.sqlite import SQLite + + +def init(): + """Init SQLite database, creates tables.""" + if not os.path.exists(config.app_database): + print(_('No database found, creating it.')) + + with SQLite() as db: + db.s('CREATE TABLE feeds (id integer NOT NULL PRIMARY KEY ' + + 'AUTOINCREMENT, created_at datetime NOT NULL DEFAULT ' + + 'CURRENT_TIMESTAMP, updated_at datetime NOT NULL DEFAULT ' + + 'CURRENT_TIMESTAMP, feed_url TEXT NOT NULL UNIQUE, url ' + + 'TEXT, title TEXT, img TEXT)') + db.s('CREATE TRIGGER feeds_updated_at_trigger AFTER UPDATE ON ' + + 'feeds FOR EACH ROW BEGIN UPDATE feeds SET ' + + 'updated_at=CURRENT_TIMESTAMP WHERE id=old.id; END') + db.s('CREATE TABLE posts (id integer NOT NULL PRIMARY KEY ' + + 'AUTOINCREMENT, created_at datetime NOT NULL DEFAULT ' + + 'CURRENT_TIMESTAMP, updated_at datetime NOT NULL DEFAULT ' + + 'CURRENT_TIMESTAMP, hash TEXT NOT NULL UNIQUE, url TEXT, ' + + 'title TEXT, raw TEXT, read BOOLEAN DEFAULT false, feed_id ' + + 'integer NOT NULL, FOREIGN KEY(feed_id) REFERENCES ' + + 'feeds(id))') + db.s('CREATE INDEX posts_feed_id_idx ON posts(feed_id)') + db.s('CREATE TRIGGER posts_updated_at_trigger AFTER UPDATE ON ' + + 'posts FOR EACH ROW BEGIN UPDATE posts SET ' + + 'updated_at=CURRENT_TIMESTAMP WHERE id=old.id; END') + print(_('Database created.')) diff --git a/feedindicator/utils/sqlite.py b/feedindicator/utils/sqlite.py new file mode 100644 index 0000000..b4a43a8 --- /dev/null +++ b/feedindicator/utils/sqlite.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +import sqlite3 + +from feedindicator import config + + +class SQLite: + """SQlite database wrapper. + + Attributes: + _con: database connection + """ + def __init__(self): + self._con = None + + def get_con(self): + """Get database connection.""" + return self._con + + con = property(get_con) + + def __enter__(self): + self.open(config.app_database) + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + def open(self, path): + """Opens a new connection. + + Args: + path: path to sqlite database file + """ + self._con = sqlite3.connect(path) + + def close(self): + """Closes the current connection.""" + if self._con: + self._con.close() + + def s(self, query, data=(), auto_commit=True): + """Executes the given query and returns the results. + + Args: + query: SQL query + data: optinal additional data + auto_commit: if true commits after query execution + """ + if not self._con: + self.open(config.app_database) + cur = self._con.cursor() + if data == (): + cur.execute(query) + else: + cur.execute(query, data) + rows = cur.fetchall() + if auto_commit: + self.commit() + return rows + + def many(self, query, data, auto_commit=True): + """Executes the given query with executemany() function. + + Args: + query: SQL query + data: additional data + auto_commit: if true commits after query execution + """ + if not self._con: + self.open(config.app_database) + cur = self._con.cursor() + cur.executemany(query, data) + if auto_commit: + self.commit() + + def commit(self): + """Commit changes to the database. When an error occurs a roll back + is done. + """ + if not self._con: + self.open(config.app_database) + try: + self._con.commit() + except sqlite3.Error as e: + self._con.rollback() + raise e diff --git a/feedindicator/utils/version.py b/feedindicator/utils/version.py new file mode 100644 index 0000000..a998320 --- /dev/null +++ b/feedindicator/utils/version.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 Dave Gardner , +# Michael Judge , +# Nicolas Raoul , +# Nathanael Philipp (jnphilipp) +# +# This file is part of feedindicator. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# feedindicator is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with feedindicator. If not, see . + + +import feedindicator + +from math import ceil, floor + + +def app_version(): + """Generate command line version text.""" + def line(text): + """Make single line with padding left and right.""" + return '#%s%s%s#\n' % (' ' * floor((47 - len(text)) / 2), + text, + ' ' * ceil((47 - len(text)) / 2)) + + return (('#################################################\n' + + line(feedindicator.__app_name__) + + line(feedindicator.__description__) + + line('') + + line(feedindicator.__version__) + + line(feedindicator.__license__) + + line(feedindicator.__author__) + + line(feedindicator.__email__) + + line('') + + line(feedindicator.__github__) + + '#################################################')) diff --git a/hicolor/indicator-feedindicator-attention.png b/hicolor/indicator-feedindicator-attention.png deleted file mode 100644 index 2cee60d..0000000 Binary files a/hicolor/indicator-feedindicator-attention.png and /dev/null differ diff --git a/hicolor/indicator-feedindicator.png b/hicolor/indicator-feedindicator.png deleted file mode 100644 index 4d5185d..0000000 Binary files a/hicolor/indicator-feedindicator.png and /dev/null differ diff --git a/icons/active.png b/icons/active.png new file mode 100644 index 0000000..6745761 Binary files /dev/null and b/icons/active.png differ diff --git a/icons/attention.png b/icons/attention.png new file mode 100644 index 0000000..e1849cb Binary files /dev/null and b/icons/attention.png differ diff --git a/feedindicator-logo.png b/icons/logo-128x128.png similarity index 100% rename from feedindicator-logo.png rename to icons/logo-128x128.png diff --git a/feedindicator-48x48.png b/icons/logo-48x48.png similarity index 100% rename from feedindicator-48x48.png rename to icons/logo-48x48.png diff --git a/languages/english.properties b/languages/english.properties deleted file mode 100644 index 7d678dc..0000000 --- a/languages/english.properties +++ /dev/null @@ -1,59 +0,0 @@ -#English -language=English - -# add dialog -add_dialog_column=Add a new feed url to show in the menu. - -# config dialog -feed_list=Feed List -feed_options=Feed Options -global_options=Global Options -configure_dialog_label=Configure feeds to show in the menu. -menu_shows=Menu shows %s items per feed -menu_shows_one_item_per_feed=Menu shows 1 item per feed -signal_command=Command for signal tone for new posts (or any other command you want to run after finishing updating the feeds): -feeds_top_menu=Show feeds at top of menu -unread_feeds_menu=Show feeds with no unread posts -submenu_feed=Submenu for each feed -show_notifications=Show notifications -show_notifications_update=Show notifications at begining and end of update -run_startup=Run at startup -column_title=Title -column_url=URL -choose_language=Choose language - -# buttons -about=About -cancel=Cancel -save=Save -remove=Remove -add=Add -add_new=Add New - -# notifications -feed_update_time_notification=Feeds will update every %s minutes -feed_update_stop_notification=Feeds will not update automatically -feed_update_start_notification=Feeds will be updated shortly -feed_update_finish_notification=Finished updating feeds - -# menu -quit=Quit -add_feed=Add feed -no_feeds_defined=No feeds defined! -update_feed_every=Update feeds every %s minutes -update_feed_every_m=Update feeds every minute -reload_all_feeds=Reload all feeds -open_website=Open Website -open_all_unread=Open all unread -open_displayed=Open all displayed -mark_as_read=Mark as read -mark_all_as_read=Mark all as read -mark_as_unread=Mark as unread -mark_all_as_unread=Mark all as unread -mark_displayed_as_read=Mark displayed posts as read -loading=Loading ... - -# other -configure=Configure %s -stopped=Stopped -started=Started \ No newline at end of file diff --git a/languages/german.properties b/languages/german.properties deleted file mode 100644 index b65c888..0000000 --- a/languages/german.properties +++ /dev/null @@ -1,59 +0,0 @@ -#German -language=Deutsch - -# add dialog -add_dialog_column=Füge ein neuen Feed hinzu. - -# config dialog -feed_list=Feed Liste -feed_options=Feed Optionen -global_options=Globale Optionen -configure_dialog_label=Bearbeiten der Feeds. -menu_shows=Menü zeigt %s Einträge pro Feed an -menu_shows_one_item_per_feed=Menü zeigt 1 Eintrag pro Feed an -signal_command=Befehl für Signalton bei neuen Posts (oder andere Befehle zum starten am ende der Aktualisierung): -feeds_top_menu=Zeige Feeds am Anfang des Menüs -unread_feeds_menu=Zeige Feeds ohne ungelesene Posts -submenu_feed=Menüs für jeden Feed -show_notifications=Zeige Benachrichtigungen -show_notifications_update=Zeige Benachrichtigungen am Anfang und Ende der Aktualisierung -run_startup=Bei Systemstart starten -column_title=Titel -column_url=URL -choose_language=Sprache wählen - -# buttons -about=Über -cancel=Abbrechen -save=Speichern -remove=Löschen -add=Hinzufügen -add_new=Hinzufügen - -# notifications -feed_update_time_notification=Feeds werden alle %s Minuten aktualisiert -feed_update_stop_notification=Feeds werden nicht automatisch aktualisiert -feed_update_start_notification=Feeds werden in kürze aktualisiert sein -feed_update_finish_notification=Feeds sind aktualisiert - -# menu -quit=Beenden -add_feed=Feed hinzufüegen -no_feeds_defined=Keine Feeds vorhanden! -update_feed_every=Aktualisiere Feeds alle %s Minuten -update_feed_every_m=Aktualisiere Feeds jede Minute -reload_all_feeds=Feeds neuladen -open_website=Öffne Webseite -open_all_unread=Öffne alle ungelesenen -open_displayed=Öffne alle angezeigten -mark_as_read=Makiere als gelesen -mark_all_as_read=Makiere alle als gelesen -mark_as_unread=Makiere als ungelesen -mark_all_as_unread=Makiere alle als ungelesen -mark_displayed_as_read=Markiere angezeigte Posts als gelesen -loading=Lädt ... - -# other -configure=Konfiguriere %s -stopped=Gestoppt -started=Gestartet \ No newline at end of file diff --git a/light/indicator-feedindicator-attention.png b/light/indicator-feedindicator-attention.png deleted file mode 100644 index 2cee60d..0000000 Binary files a/light/indicator-feedindicator-attention.png and /dev/null differ diff --git a/light/indicator-feedindicator.png b/light/indicator-feedindicator.png deleted file mode 100644 index 4d5185d..0000000 Binary files a/light/indicator-feedindicator.png and /dev/null differ