diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..87b0f15 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# 4 space indentation +[*.py] +indent_style = space +indent_size = 4 + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/README.md b/README.md index f9e0133..fc45605 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,9 @@ HackerTray accepts its various options via the command line. Run `hackertray -h` 1. `-c`: Enables comments support. Clicking on links will also open the comments page on HN. Can be switched off via the UI, but the setting is not remembered. 2. `--chrome PROFILE_PATH`: Specifying a profile path to a chrome directory will make HackerTray read the Chrome History file to mark links as read. Links are checked once every 5 minutes, which is when the History file is copied (to override the lock in case Chrome is open), searched using sqlite and deleted. This feature is still experimental. +3. `--firefox PROFILE_PATH`: Specify path to a firefox profile directory. HackerTray will read your firefox history from this profile, and use it to mark links as read. + +Note that the `--chrome` and `--firefox` options are independent, and can be used together. However, they cannot be specified multiple times (so reading from 2 chrome profiles is not possible). ###Google Chrome Profile Path @@ -68,6 +71,9 @@ Where your Profile is stored depends on which version of chrome you are using: Replace `Default` with `Profile 1`, `Profile 2` or so on if you use multiple profiles on Chrome. Note that the `--chrome` option accepts a `PROFILE_PATH`, not the History file itself. Also note that sometimes `~` might not be set, so you might need to use the complete path (such as `/home/nemo/.config/google-chrome/Default/`). +###Firefox Profile Path +The default firefox profile path is `~/.mozilla/firefox/*.default`, where `*` denotes a random 8 digit string. You can also read `~/.mozilla/firefox/profiles.ini` to get a list of profiles. + ##Features 1. Minimalist Approach to HN 2. Opens links in your default browser diff --git a/hackertray/__init__.py b/hackertray/__init__.py index 5be0862..6c80999 100644 --- a/hackertray/__init__.py +++ b/hackertray/__init__.py @@ -25,6 +25,7 @@ import signal from hackernews import HackerNews from chrome import Chrome +from firefox import Firefox from version import Version class HackerNewsApp: @@ -89,7 +90,7 @@ class HackerNewsApp: self.menu.show() self.ind.set_menu(self.menu) - self.refresh(chrome_data_directory=args.chrome) + self.refresh(chrome_data_directory=args.chrome, firefox_data_directory=args.firefox) def toggleComments(self, widget): """Whether comments page is opened or not""" @@ -159,14 +160,19 @@ class HackerNewsApp: self.menu.prepend(i) i.show() - def refresh(self, widget=None, no_timer=False, chrome_data_directory=None): + def refresh(self, widget=None, no_timer=False, chrome_data_directory=None, firefox_data_directory=None): """Refreshes the menu """ try: + # Create an array of 20 false to denote matches in History + searchResults = [False]*20 data = list(reversed(HackerNews.getHomePage()[0:20])) + urls = [item['url'] for item in data] if(chrome_data_directory): - urls = [item['url'] for item in data] - searchResults = Chrome.search(urls, chrome_data_directory) + searchResults = self.mergeBoolArray(searchResults, Chrome.search(urls, chrome_data_directory)) + + if(firefox_data_directory): + searchResults = self.mergeBoolArray(searchResults, Firefox.search(urls, firefox_data_directory)) #Remove all the current stories for i in self.menu.get_children(): @@ -175,10 +181,7 @@ class HackerNewsApp: #Add back all the refreshed news for index, item in enumerate(data): - if(chrome_data_directory): - item['history'] = searchResults[index] - else: - item['history'] = False + item['history'] = searchResults[index] self.addItem(item) # Catch network errors except requests.exceptions.RequestException as e: @@ -188,13 +191,18 @@ class HackerNewsApp: if not no_timer: gtk.timeout_add(10 * 30 * 1000, self.refresh, widget, no_timer, chrome_data_directory) - + # Merges two boolean arrays, using OR operation against each pair + def mergeBoolArray(self, original, patch): + for index, var in enumerate(original): + original[index] = original[index] or patch[index] + return original def main(): parser = argparse.ArgumentParser(description='Hacker News in your System Tray') parser.add_argument('-v','--version', action='version', version=Version.current()) parser.add_argument('-c','--comments', dest='comments',action='store_true', help="Load the HN comments link for the article as well") parser.add_argument('--chrome', dest='chrome', help="Specify a Google Chrome Profile directory to use for matching chrome history") + parser.add_argument('--firefox', dest='firefox', help="Specify a Firefox Profile directory to use for matching firefox history") parser.set_defaults(comments=False) args = parser.parse_args() indicator = HackerNewsApp(args) diff --git a/hackertray/firefox.py b/hackertray/firefox.py new file mode 100644 index 0000000..56a2041 --- /dev/null +++ b/hackertray/firefox.py @@ -0,0 +1,30 @@ +from __future__ import print_function +import sqlite3 +import shutil +import os +import sys + +class Firefox: + HISTORY_TMP_LOCATION = '/tmp/hackertray.firefox' + HISTORY_FILE_NAME = '/places.sqlite' + @staticmethod + def search(urls, config_folder_path): + Firefox.setup(config_folder_path) + conn = sqlite3.connect(Firefox.HISTORY_TMP_LOCATION) + db = conn.cursor() + result = [] + for url in urls: + db_result = db.execute('SELECT url from moz_places WHERE url=:url',{"url":url}) + if(db.fetchone() == None): + result.append(False) + else: + result.append(True) + os.remove(Firefox.HISTORY_TMP_LOCATION) + return result + @staticmethod + def setup(config_folder_path): + file_name = os.path.abspath(config_folder_path+Firefox.HISTORY_FILE_NAME) + if not os.path.isfile(file_name): + print("ERROR: ", "Could not find Firefox history file", file=sys.stderr) + sys.exit(1) + shutil.copyfile(file_name, Firefox.HISTORY_TMP_LOCATION) \ No newline at end of file diff --git a/test/chrome_test.py b/test/chrome_test.py index ce37e37..1b30683 100644 --- a/test/chrome_test.py +++ b/test/chrome_test.py @@ -5,11 +5,11 @@ from hackertray import Chrome class ChromeTest(unittest.TestCase): def runTest(self): - config_folder_path = os.getcwd()+'/test/' + config_folder_path = os.getcwd()+'/test/' data = Chrome.search([ - "https://github.com/", - "https://news.ycombinator.com/", - "https://github.com/captn3m0/hackertray", - "http://invalid_url/"], - config_folder_path) - self.assertTrue(data == [True,True,True,False]) \ No newline at end of file + "https://github.com/", + "https://news.ycombinator.com/", + "https://github.com/captn3m0/hackertray", + "http://invalid_url/"], + config_folder_path) + self.assertTrue(data == [True,True,True,False]) \ No newline at end of file diff --git a/test/firefox_test.py b/test/firefox_test.py new file mode 100644 index 0000000..5c5d28a --- /dev/null +++ b/test/firefox_test.py @@ -0,0 +1,15 @@ +import unittest +import os + +from hackertray import Firefox + +class ChromeTest(unittest.TestCase): + def runTest(self): + config_folder_path = os.getcwd()+'/test/' + data = Firefox.search([ + "http://www.hckrnews.com/", + "http://www.google.com/", + "http://wiki.ubuntu.com/", + "http://invalid_url/"], + config_folder_path) + self.assertTrue(data == [True,True,True,False]) \ No newline at end of file diff --git a/test/places.sqlite b/test/places.sqlite new file mode 100644 index 0000000..4d6c000 Binary files /dev/null and b/test/places.sqlite differ