Skip to content

Commit

Permalink
add icons
Browse files Browse the repository at this point in the history
  • Loading branch information
fuzzy69 committed Apr 26, 2017
1 parent 3c78687 commit 1529743
Show file tree
Hide file tree
Showing 16 changed files with 924 additions and 3,330 deletions.
4 changes: 4 additions & 0 deletions application/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import os
from pathlib import Path

__author__ = "fuzzy69"
__title__ = "Alexa Rank Checker"
__description__ = "PyQt5 application for checking Alexa rank for multiple sites"

ROOT = str(Path(os.path.realpath(os.path.dirname(__file__))).parent)
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; rv:52.0) Gecko/20100101 Firefox/52.0",
Expand Down
3 changes: 2 additions & 1 deletion application/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
#!/usr/bin/env python

THREADS = 1
TIMEOUT = 5
TIMEOUT = 5
DELAY = 1
188 changes: 181 additions & 7 deletions application/mainwindow.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,204 @@
# -*- coding: UTF-8 -*-
# !/usr/bin/env python

import csv
import json
import os
import platform
import webbrowser
from queue import Queue

from PyQt5 import uic, QtWidgets
from PyQt5.QtCore import (Qt, QSettings, QThread, QTimer, pyqtSlot, pyqtSignal)
from PyQt5.QtGui import (QFont, QStandardItem, QStandardItemModel)
from PyQt5 import (
uic, QtWidgets
)
from PyQt5.QtCore import (
Qt, QSettings, QThread, QTimer, pyqtSlot, pyqtSignal, QT_VERSION_STR,
PYQT_VERSION_STR
)
from PyQt5.QtGui import (
QFont, QStandardItem, QStandardItemModel
)

from .conf import ROOT
from .defaults import THREADS, TIMEOUT
from .conf import __author__, __title__, __description__, ROOT
from .defaults import DELAY, THREADS, TIMEOUT
from .helpers import readTextFile
from .utils import check_alexa, split_list
from .version import __version__
from .workers import CheckAlexaWorker

ui = uic.loadUiType(os.path.join(ROOT, "assets", "ui", "mainwindow.ui"))[0]

class MainWindow(QtWidgets.QMainWindow, ui):
def __init__(self, parent=None, title=""):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.setWindowTitle("{} {}".format(title, __version__))
self.setWindowTitle("{} - {}".format(__title__, __version__))
self._settingsFile = os.path.join(ROOT, "data", "settings.ini")
self._threads = []
self._workers = []
self._progressDone = 0
self._progressTotal = 0
# self.searchModel = QStandardItemModel(self)
# self.searchEdit = QtWidgets.QLineEdit()
# self.searchEdit.setPlaceholderText("Search urls ...")
# self.searchEdit.setClearButtonEnabled(True)
# self.toolBar.addWidget(self.searchEdit)
self.sitesModel = QStandardItemModel()
self.sitesModel.setHorizontalHeaderLabels(["URL", "Rank", "Status"])
self.sitesTableView.setModel(self.sitesModel)
self.actionExport_results.triggered.connect(self.exportResults)
self.actionQuit.triggered.connect(lambda: QtWidgets.QApplication.quit())
self.actionAbout.triggered.connect(self.helpAbout)
self.actionImport_URLs.triggered.connect(self.importUrls)
self.actionClear_table.triggered.connect(self.clearTable)
self.sitesTableView.doubleClicked.connect(self.sitesTableView_doubleClicked)
self.startButton.clicked.connect(self.start)
self.stopButton.clicked.connect(self.stop)
self.resizeEvent = self.onResize
self.closeEvent = self.onClose
self.showEvent = self.onShow
self.loadSettings()
self.centerWindow()
# text = readTextFile("data/sites.txt")
# i = 0
# for url in text.strip().splitlines():
# self.sitesModel.appendRow([QStandardItem(url), QStandardItem(""),QStandardItem("")])
# i += 1
# if i > 1:
# break

def centerWindow(self):
fg = self.frameGeometry()
c = QtWidgets.QDesktopWidget().availableGeometry().center()
fg.moveCenter(c)
self.move(fg.topLeft())

def loadSettings(self):
if os.path.isfile(self._settingsFile):
settings = QSettings(self._settingsFile, QSettings.IniFormat)
self.restoreGeometry(settings.value("geometry", ''))
self.restoreState(settings.value("windowState", ''))
# self._tableViewWidth = int(settings.value("tableViewWidth", ''))
self.threadsSpin.setValue(settings.value("threadsCount", THREADS, type=int))
self.timeoutSpin.setValue(settings.value("timeoutSpin", TIMEOUT, type=int))

def saveSettings(self):
settings = QSettings(self._settingsFile, QSettings.IniFormat)
settings.setValue("geometry", self.saveGeometry())
settings.setValue("windowState", self.saveState())
# settings.setValue("tableViewWidth", self.sitesTableView.frameGeometry().width())
settings.setValue("threadsCount", self.threadsSpin.value())
settings.setValue("timeout", self.timeoutSpin.value())

def onResize(self, event):
self.resizeTableColumns()
QtWidgets.QMainWindow.resizeEvent(self, event)

def onClose(self, event):
self.saveSettings()
QtWidgets.QMainWindow.closeEvent(self, event)

def onShow(self, event):
self.resizeTableColumns()
QtWidgets.QMainWindow.showEvent(self, event)

def resizeTableColumns(self):
self.sitesTableView.setColumnWidth(0, int(self.sitesTableView.frameGeometry().width() * 0.6))
self.sitesTableView.setColumnWidth(1, int(self.sitesTableView.frameGeometry().width() * 0.1))

def importUrls(self):
filePath, fileType = QtWidgets.QFileDialog.getOpenFileName(self, "Import URLs", filter="Text files (*.txt)")
if filePath:
text = readTextFile(filePath)
for url in text.strip().splitlines():
self.sitesModel.appendRow([QStandardItem(url), QStandardItem(""),QStandardItem("")])

def sitesTableView_doubleClicked(self, modelIndex):
model = self.sitesModel
row = modelIndex.row()
url = model.data(model.index(row, 0))
webbrowser.open(url)

def clearTable(self):
self.tableRemoveAllRows(self.sitesModel)

def tableRemoveAllRows(self, model):
for i in reversed(range(model.rowCount())):
model.removeRow(i)

@pyqtSlot()
def start(self):
model = self.sitesModel
queues = split_list(range(self.sitesModel.rowCount()), self.threadsSpin.value())
self._progressTotal = self.sitesModel.rowCount()
for i, rows in enumerate(queues):
self._threads.append(QThread())
queue = Queue()
for row in rows:
url = model.data(model.index(row, 0))
queue.put((row, url))
self._workers.append(CheckAlexaWorker(check_alexa, delay=self.delaySpin.value(), timeout=self.timeoutSpin.value(), queue=queue))
self._workers[i].moveToThread(self._threads[i])
self._threads[i].started.connect(self._workers[i].start)
self._threads[i].finished.connect(self._threads[i].deleteLater)
self._workers[i].status.connect(self.onStatus)
self._workers[i].result.connect(self.onResult)
self._workers[i].finished.connect(self._threads[i].quit)
self._workers[i].finished.connect(self._workers[i].deleteLater)
for i in range(self.threadsSpin.value()):
self._threads[i].start()

@pyqtSlot()
def stop(self):
for i, _ in enumerate(self._workers):
self._workers[i]._running = False

@pyqtSlot(tuple)
def onStatus(self, tuple_):
i, status = tuple_
self.sitesModel.setData(self.sitesModel.index(i, 2), status)

@pyqtSlot(object)
def onResult(self, result):
if result["status"]:
self.sitesModel.setData(self.sitesModel.index(result["row"], 1), result["rank"])
self.sitesModel.item(result["row"], 1).setBackground(Qt.green)
elif result["status"] is None:
self.sitesModel.setData(self.sitesModel.index(result["row"], 1), "No data")
else:
self.sitesModel.setData(self.sitesModel.index(result["row"], 1), "Fail")
self.sitesModel.item(result["row"], 1).setBackground(Qt.red)
self._progressDone += 1
self.progressBar.setValue(int(float(self._progressDone) / self._progressTotal * 100))

def helpAbout(self):
QtWidgets.QMessageBox.about(self, "About {}".format(__title__),
"""<b>{} v{}</b>
<p>Copyright &copy; 2017 .
All rights reserved.
<p>{}
<p>Python {} - Qt {} - PyQt {} on {}""".format(
__title__, __version__, __description__,
platform.python_version(), QT_VERSION_STR, PYQT_VERSION_STR,
platform.system())
)

def exportResults(self):
filePath, fileType = QtWidgets.QFileDialog.getSaveFileName(self, "Export results", "/mnt/ramdisk/results", filter="CSV files (*.csv);;JSON files (*.json)")
data = []
model = self.sitesModel
for i in range(model.rowCount()):
url = model.data(model.index(i, 0))
rank = model.data(model.index(i, 1))
data.append({
"URL": url,
"Rank": rank,
})
if "csv" in fileType:
with open(filePath + ".csv", 'w') as f:
w = csv.DictWriter(f, ["URL", "Rank"])
w.writeheader()
w.writerows(data)
else:
with open(filePath + ".json", 'w') as f:
f.write(json.dumps(data))
42 changes: 42 additions & 0 deletions application/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# -*- coding: UTF-8 -*-
#!/usr/bin/env python

from time import sleep
from urllib.parse import urljoin, urlparse

from lxml import etree
import requests

from .conf import HEADERS
from .defaults import TIMEOUT

def check_alexa(url, timeout=TIMEOUT):
rank = None
status = None
msg = ''
try:
url = "http://data.alexa.com/data?cli=10&url=" + extract_domain(url)
r = requests.get(url, headers=HEADERS, allow_redirects=True, timeout=timeout)
if r.status_code == 200:
doc = etree.fromstring(r.content)
for el in doc.xpath("//POPULARITY"):
if "TEXT" in el.attrib:
rank = el.attrib["TEXT"]
status = True
break
except requests.exceptions.ReadTimeout as e:
status = False
msg = str(e)
except Exception as e:
status = False
msg = str(e)

return rank, status, msg

def extract_domain(url):
return urlparse(url).netloc

def split_list(li, n):
k, m = divmod(len(li), n)

return (li[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n))
2 changes: 1 addition & 1 deletion application/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# -*- coding: UTF-8 -*-
# !/usr/bin/env python

__version__ = "0.4.20.1"
__version__ = "0.4.26.1"
57 changes: 57 additions & 0 deletions application/workers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# -*- coding: UTF-8 -*-
#!/usr/bin/env python

from time import sleep

from PyQt5.QtCore import QThread, pyqtSlot, pyqtSignal, QObject

from .utils import check_alexa

class Worker(QObject):
start = pyqtSignal()
stop = pyqtSignal()
finished = pyqtSignal()
result = pyqtSignal(object)

def __init__(self, func, *args, **kwargs):
super(Worker, self).__init__()
self._func = func
self._args = args
self._kwargs = kwargs
self._running = True
self.start.connect(self.run)
self.stop.connect(self.onStop)

@pyqtSlot()
def run(self):
result = self.doWork(*self._args, **self._kwargs)
self.finished.emit()

@pyqtSlot()
def onStop(self):
self._running = False

def doWork(self, *args, **kwargs):
raise NotImplementedError

class CheckAlexaWorker(Worker):
status = pyqtSignal(tuple)

def doWork(self, *args, **kwargs):
queue = kwargs["queue"]
timeout = kwargs["timeout"]
delay = kwargs["delay"]
while self._running and not queue.empty():
row, url = queue.get()
self.status.emit((row, "Checking ..."))
rank, status, msg = check_alexa(url, timeout)
result = True if rank else False
self.result.emit({
"row": row,
"url": url,
"result": result,
"rank": rank,
"status": status,
})
self.status.emit((row, "Done"))
sleep(delay)
12 changes: 12 additions & 0 deletions assets/assets.qrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<RCC>
<qresource prefix="icons">
<file>img/help-browser.png</file>
<file>img/document-open.png</file>
<file>img/document-save.png</file>
<file>img/edit-clear.png</file>
<file>img/system-shutdown.png</file>
<file>img/window-new.png</file>
<file>img/edit-clear.png</file>
<file>img/window-new.png</file>
</qresource>
</RCC>
Binary file added assets/img/document-open.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/document-save.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/edit-clear.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/help-browser.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/system-shutdown.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/window-new.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 1529743

Please sign in to comment.