Support handling events by monitoring an events folder for changes

This commit is contained in:
Micah Lee 2019-11-28 12:24:26 -08:00
parent d78d67adf6
commit e7c683528d
No known key found for this signature in database
GPG Key ID: 403C2657CD994F73
7 changed files with 152 additions and 23 deletions

View File

@ -14,13 +14,13 @@ Install the needed dependencies:
For Debian-like distros:
```
apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python python3-flask-httpauth python3-distutils python3-psutil
apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python python3-flask-httpauth python3-distutils python3-psutil python3-watchdog
```
For Fedora-like distros:
```
dnf install -y python3-flask python3-flask-httpauth python3-stem python3-qt5 python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build python3-psutil
dnf install -y python3-flask python3-flask-httpauth python3-stem python3-qt5 python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build python3-psutil python3-watchdog
```
After that you can try both the CLI and the GUI version of OnionShare:

View File

@ -21,3 +21,4 @@ requests==2.22.0
stem==1.7.1
urllib3==1.25.3
Werkzeug==0.15.6
watchdog==0.9.0

View File

@ -23,9 +23,11 @@ import sys
import platform
import argparse
import signal
import psutil
from PyQt5 import QtCore, QtWidgets
if platform.system() == "Linux":
import psutil
from onionshare.common import Common
from .gui_common import GuiCommon
@ -122,28 +124,30 @@ def main():
sys.exit()
# Is there another onionshare-gui running?
existing_pid = None
for proc in psutil.process_iter(attrs=["pid", "name", "cmdline"]):
if proc.info["pid"] == os.getpid():
continue
if common.platform == "Linux":
existing_pid = None
for proc in psutil.process_iter(attrs=["pid", "name", "cmdline"]):
if proc.info["pid"] == os.getpid():
continue
if proc.info["name"] == "onionshare-gui":
existing_pid = proc.info["pid"]
break
else:
# Dev mode onionshare?
if proc.info["cmdline"] and len(proc.info["cmdline"]) >= 2:
if (
os.path.basename(proc.info["cmdline"][0]).lower() == "python"
and os.path.basename(proc.info["cmdline"][1]) == "onionshare-gui"
):
existing_pid = proc.info["pid"]
break
if proc.info["name"] == "onionshare-gui":
existing_pid = proc.info["pid"]
break
else:
# Dev mode onionshare?
if proc.info["cmdline"] and len(proc.info["cmdline"]) >= 2:
if (
os.path.basename(proc.info["cmdline"][0]).lower() == "python"
and os.path.basename(proc.info["cmdline"][1])
== "onionshare-gui"
):
existing_pid = proc.info["pid"]
break
if existing_pid:
print(f"Opening tab in existing OnionShare window (pid {proc.info['pid']})")
# TODO: open tab
return
if existing_pid:
print(f"Opening tab in existing OnionShare window (pid {proc.info['pid']})")
# TODO: open tab
return
# Attach the GUI common parts to the common object
common.gui = GuiCommon(common, qtapp, local_only)

View File

@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
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 <http://www.gnu.org/licenses/>.
"""
import json
import os
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler, FileModifiedEvent
from PyQt5 import QtCore
class EventHandler(FileSystemEventHandler, QtCore.QObject):
"""
To trigger an event, write a JSON line to the events file. When that file changes,
each line will be handled as an event. Valid events are:
{"type": "new_tab"}
{"type": "new_share_tab", "filenames": ["file1", "file2"]}
"""
new_tab = QtCore.pyqtSignal()
new_share_tab = QtCore.pyqtSignal(list)
def __init__(self, common):
super(EventHandler, self).__init__()
self.common = common
def on_modified(self, event):
if (
type(event) == FileModifiedEvent
and event.src_path == self.common.gui.events_filename
):
# Read all the lines in the events, then delete it
with open(self.common.gui.events_filename, "r") as f:
lines = f.readlines()
os.remove(self.common.gui.events_filename)
self.common.log(
"EventHandler", "on_modified", f"processing {len(lines)} lines"
)
for line in lines:
try:
obj = json.loads(line)
if "type" not in obj:
self.common.log(
"EventHandler",
"on_modified",
f"event does not have a type: {obj}",
)
continue
except json.decoder.JSONDecodeError:
self.common.log(
"EventHandler", "on_modified", f"ignoring invalid line: {line}"
)
continue
if obj["type"] == "new_tab":
self.common.log("EventHandler", "on_modified", "new_tab event")
self.new_tab.emit()
elif obj["type"] == "new_share_tab":
if "filenames" in obj and type(obj["filenames"]) is list:
self.new_share_tab.emit(obj["filenames"])
else:
self.common.log(
"EventHandler",
"on_modified",
f"invalid new_share_tab event: {obj}",
)
else:
self.common.log(
"EventHandler", "on_modified", f"invalid event type: {obj}"
)

View File

@ -17,6 +17,8 @@ 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 <http://www.gnu.org/licenses/>.
"""
import os
from onionshare import strings
from onionshare.onion import Onion
@ -44,6 +46,12 @@ class GuiCommon:
# Start the Onion
self.onion = Onion(common)
# Directory to watch for events
self.events_dir = os.path.join(self.common.build_data_dir(), "events")
if not os.path.exists(self.events_dir):
os.makedirs(self.events_dir, 0o700, True)
self.events_filename = os.path.join(self.events_dir, "events")
self.css = {
# OnionShareGui styles
"tab_widget_new_tab_button": """

View File

@ -119,6 +119,7 @@ class MainWindow(QtWidgets.QMainWindow):
# Tabs
self.tabs = TabWidget(self.common, self.system_tray, self.status_bar)
self.tabs.bring_to_front.connect(self.bring_to_front)
# If we have saved persistent tabs, try opening those
if len(self.common.settings.get("persistent_tabs")) > 0:
@ -281,6 +282,11 @@ class MainWindow(QtWidgets.QMainWindow):
self.update_thread.update_available.connect(update_available)
self.update_thread.start()
def bring_to_front(self):
self.common.log("MainWindow", "bring_to_front")
self.raise_()
self.activateWindow()
def closeEvent(self, e):
self.common.log("MainWindow", "closeEvent")

View File

@ -18,11 +18,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from PyQt5 import QtCore, QtWidgets, QtGui
from watchdog.observers import Observer
from onionshare import strings
from onionshare.mode_settings import ModeSettings
from .tab import Tab
from .event_handler import EventHandler
class TabWidget(QtWidgets.QTabWidget):
@ -30,6 +32,8 @@ class TabWidget(QtWidgets.QTabWidget):
A custom tab widget, that has a "+" button for adding new tabs
"""
bring_to_front = QtCore.pyqtSignal()
def __init__(self, common, system_tray, status_bar):
super(TabWidget, self).__init__()
self.common = common
@ -67,6 +71,14 @@ class TabWidget(QtWidgets.QTabWidget):
self.move_new_tab_button()
# Watch the events file for changes
self.event_handler = EventHandler(common)
self.event_handler.new_tab.connect(self.add_tab)
self.event_handler.new_share_tab.connect(self.new_share_tab)
self.observer = Observer()
self.observer.schedule(self.event_handler, self.common.gui.events_dir)
self.observer.start()
def move_new_tab_button(self):
# Find the width of all tabs
tabs_width = sum(
@ -95,6 +107,12 @@ class TabWidget(QtWidgets.QTabWidget):
mode_settings = ModeSettings(self.common, id=mode_settings_id)
self.add_tab(mode_settings)
def new_share_tab(self, filenames):
mode_settings = ModeSettings(self.common)
mode_settings.set("persistent", "mode", "share")
mode_settings.set("share", "filenames", filenames)
self.add_tab(mode_settings)
def add_tab(self, mode_settings=None):
tab = Tab(self.common, self.current_tab_id, self.system_tray, self.status_bar)
tab.change_title.connect(self.change_title)
@ -111,6 +129,9 @@ class TabWidget(QtWidgets.QTabWidget):
# If it's persistent, set the persistent image in the tab
self.change_persistent(tab.tab_id, tab.settings.get("persistent", "enabled"))
# Bring the window to front, in case this is being added by an event
self.bring_to_front.emit()
def change_title(self, tab_id, title):
index = self.indexOf(self.tabs[tab_id])
self.setTabText(index, title)