server can be started and stopped from the GUI

This commit is contained in:
Micah Lee 2014-08-27 17:52:45 -07:00
parent 9cb1871b02
commit 349ca67cc9
2 changed files with 41 additions and 254 deletions

View file

@ -154,6 +154,22 @@ def page_not_found(e):
add_request(REQUEST_OTHER, request.path) add_request(REQUEST_OTHER, request.path)
return render_template_string(open('{0}/404.html'.format(helpers.get_onionshare_dir())).read()) return render_template_string(open('{0}/404.html'.format(helpers.get_onionshare_dir())).read())
# shutting down the server only works within the context of flask, so the easiest way to do it is over http
shutdown_slug = helpers.random_string(16)
@app.route("/<shutdown_slug_candidate>/shutdown")
def shutdown(shutdown_slug_candidate):
if not helpers.constant_time_compare(shutdown_slug.encode('ascii'), shutdown_slug_candidate.encode('ascii')):
abort(404)
# shutdown the flask service
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
raise RuntimeError('Not running with the Werkzeug Server')
func()
return ""
def start(port, stay_open=False): def start(port, stay_open=False):
set_stay_open(stay_open) set_stay_open(stay_open)
app.run(port=port) app.run(port=port)

View file

@ -1,5 +1,5 @@
from __future__ import division from __future__ import division
import os, sys, subprocess, inspect, platform, argparse, threading, time, math, inspect, platform import os, sys, subprocess, inspect, platform, argparse, threading, time, math, inspect, platform, urllib2
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
import common import common
@ -31,7 +31,7 @@ class OnionShareGui(QtGui.QWidget):
self.setWindowTitle('OnionShare') self.setWindowTitle('OnionShare')
self.setWindowIcon(window_icon) self.setWindowIcon(window_icon)
def start_send(self, filenames=None): def send_files(self, filenames=None):
# file selection # file selection
file_selection = FileSelection() file_selection = FileSelection()
if filenames: if filenames:
@ -39,10 +39,12 @@ class OnionShareGui(QtGui.QWidget):
file_selection.file_list.add_file(filename) file_selection.file_list.add_file(filename)
# server status # server status
server_status = ServerStatus(file_selection) self.server_status = ServerStatus(file_selection)
server_status.server_started.connect(file_selection.server_started) self.server_status.server_started.connect(file_selection.server_started)
server_status.server_stopped.connect(file_selection.server_stopped) self.server_status.server_started.connect(self.start_server)
file_selection.file_list.files_updated.connect(server_status.update) self.server_status.server_stopped.connect(file_selection.server_stopped)
self.server_status.server_stopped.connect(self.stop_server)
file_selection.file_list.files_updated.connect(self.server_status.update)
# downloads # downloads
downloads = Downloads() downloads = Downloads()
@ -53,254 +55,33 @@ class OnionShareGui(QtGui.QWidget):
# main layout # main layout
self.layout = QtGui.QVBoxLayout() self.layout = QtGui.QVBoxLayout()
self.layout.addLayout(file_selection) self.layout.addLayout(file_selection)
self.layout.addLayout(server_status) self.layout.addLayout(self.server_status)
self.layout.addLayout(downloads) self.layout.addLayout(downloads)
self.layout.addLayout(options) self.layout.addLayout(options)
self.setLayout(self.layout) self.setLayout(self.layout)
self.show() self.show()
""" def start_server(self):
# initialize ui # start the hidden service
self.init_ui(filename, basename) try:
# check for requests every 1000ms self.app.start_hidden_service(gui=True)
self.timer = QtCore.QTimer() except onionshare.NoTor as e:
QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.check_for_requests) alert(e.args[0], QtGui.QMessageBox.Warning)
self.timer.start(1000) self.server_status.stop_server()
# copy url to clipboard return
self.copy_to_clipboard() except onionshare.TailsError as e:
alert(e.args[0], QtGui.QMessageBox.Warning)
def init_ui(self, filename, basename): self.server_status.stop_server()
# window return
self.setWindowTitle(u"{0} | OnionShare".format(basename.decode("utf-8")))
self.resize(580, 400)
self.setMinimumSize(580, 400)
self.setMaximumSize(580, 400)
palette = QtGui.QPalette()
palette.setColor(QtGui.QPalette.Background, QtCore.Qt.white)
self.setPalette(palette)
# icon
self.setWindowIcon(window_icon)
# widget
self.widget = QtGui.QWidget(self)
self.widget.setGeometry(QtCore.QRect(5, 5, 570, 390))
# wrapper
self.wrapper = QtGui.QVBoxLayout(self.widget)
self.wrapper.setMargin(0)
self.wrapper.setObjectName("wrapper")
# header
self.header = QtGui.QHBoxLayout()
# logo
self.logoLabel = QtGui.QLabel(self.widget)
self.logo = QtGui.QPixmap("{0}/static/logo.png".format(common.onionshare_gui_dir))
self.logoLabel.setPixmap(self.logo)
self.header.addWidget(self.logoLabel)
# fileinfo
self.fileinfo = QtGui.QVBoxLayout()
# filename
self.filenameLabel = QtGui.QLabel(self.widget)
self.filenameLabel.setStyleSheet("font-family: sans-serif; font-size: 22px; font-weight: bold; color: #000000; white-space: nowrap")
self.filenameLabel.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.fileinfo.addWidget(self.filenameLabel)
# checksum
self.checksumLabel = QtGui.QLabel(self.widget)
self.checksumLabel.setStyleSheet("font-family: arial; text-align: left; color: #666666")
self.checksumLabel.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.fileinfo.addWidget(self.checksumLabel)
# filesize
self.filesizeLabel = QtGui.QLabel(self.widget)
self.filesizeLabel.setStyleSheet("font-family: arial; text-align: left; color: #666666")
self.filesizeLabel.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.fileinfo.addWidget(self.filesizeLabel)
self.header.addLayout(self.fileinfo)
fileinfoSpacer = QtGui.QSpacerItem(20, 50, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Maximum)
self.header.addItem(fileinfoSpacer)
self.wrapper.addLayout(self.header)
# header seperator
self.headerSeperator = QtGui.QFrame(self.widget)
self.headerSeperator.setFrameShape(QtGui.QFrame.HLine)
self.headerSeperator.setFrameShadow(QtGui.QFrame.Plain)
self.wrapper.addWidget(self.headerSeperator)
# log
self.log = QtGui.QVBoxLayout()
self.log.setAlignment(QtCore.Qt.AlignTop)
self.wrapper.addLayout(self.log)
spacerItem2 = QtGui.QSpacerItem(1, 400, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Maximum)
self.wrapper.addItem(spacerItem2)
# footer seperator
self.footerSeperator = QtGui.QFrame(self.widget)
self.footerSeperator.setFrameShape(QtGui.QFrame.HLine)
self.footerSeperator.setFrameShadow(QtGui.QFrame.Plain)
self.wrapper.addWidget(self.footerSeperator)
# footer
self.footer = QtGui.QHBoxLayout()
# close automatically checkbox
self.closeAutomatically = QtGui.QCheckBox(self.widget)
self.closeAutomatically.setCheckState(QtCore.Qt.Checked)
if web.get_stay_open():
self.closeAutomatically.setCheckState(QtCore.Qt.Unchecked)
self.closeAutomatically.setStyleSheet("font-size: 12px")
self.connect(self.closeAutomatically, QtCore.SIGNAL('stateChanged(int)'), self.stay_open_changed)
self.footer.addWidget(self.closeAutomatically)
# footer spacer
spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.footer.addItem(spacerItem1)
# copy url button
self.copyURL = QtGui.QPushButton(self.widget)
self.connect(self.copyURL, QtCore.SIGNAL("clicked()"), self.copy_to_clipboard)
self.footer.addWidget(self.copyURL)
self.wrapper.addLayout(self.footer)
url = 'http://{0}/{1}'.format(self.app.onion_host, web.slug)
filehash, filesize = helpers.file_crunching(filename)
web.set_file_info(filename, filehash, filesize)
# start onionshare service in new thread # start onionshare service in new thread
t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open)) t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open))
t.daemon = True t.daemon = True
t.start() t.start()
# show url to share def stop_server(self):
loaded = QtGui.QLabel(strings._("give_this_url") + "<br /><strong>" + url + "</strong>") # to stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
loaded.setStyleSheet("color: #000000; font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;") urllib2.urlopen('http://127.0.0.1:{0}/{1}/shutdown'.format(self.app.port, web.shutdown_slug)).read()
loaded.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.log.addWidget(loaded)
# translate
self.filenameLabel.setText(basename)
self.checksumLabel.setText(strings._("sha1_checksum") + ": <strong>" + filehash + "</strong>")
self.filesizeLabel.setText(strings._("filesize") + ": <strong>" + helpers.human_readable_filesize(filesize) + "</strong>")
self.closeAutomatically.setText(strings._("close_on_finish"))
self.copyURL.setText(strings._("copy_url"))
# show dialog
self.show()
def update_log(self, event, msg):
global progress
if event["type"] == web.REQUEST_LOAD:
label = QtGui.QLabel(msg)
label.setStyleSheet("color: #009900; font-weight: bold; font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;")
label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.log.addWidget(label)
elif event["type"] == web.REQUEST_DOWNLOAD:
download = QtGui.QLabel(msg)
download.setStyleSheet("color: #009900; font-weight: bold; font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;")
download.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.log.addWidget(download)
progress = QtGui.QLabel()
progress.setStyleSheet("color: #0000cc; font-weight: bold; font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;")
progress.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.log.addWidget(progress)
elif event["type"] == web.REQUEST_PROGRESS:
progress.setText(msg)
elif event["path"] != '/favicon.ico':
other = QtGui.QLabel(msg)
other.setStyleSheet("color: #009900; font-weight: bold; font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;")
other.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.log.addWidget(other)
return
def check_for_requests(self):
events = []
done = False
while not done:
try:
r = web.q.get(False)
events.append(r)
except web.Queue.Empty:
done = True
for event in events:
if event["type"] == web.REQUEST_LOAD:
self.update_log(event, strings._("download_page_loaded"))
elif event["type"] == web.REQUEST_DOWNLOAD:
self.update_log(event, strings._("download_started"))
elif event["type"] == web.REQUEST_PROGRESS:
# is the download complete?
if event["data"]["bytes"] == web.filesize:
self.update_log(event, strings._("download_finished"))
# close on finish?
if not web.get_stay_open():
time.sleep(1)
def close_countdown(i):
if i > 0:
QtGui.QApplication.quit()
else:
time.sleep(1)
i -= 1
closing.setText(strings._("close_countdown").format(str(i)))
print strings._("close_countdown").format(str(i))
close_countdown(i)
closing = QtGui.QLabel(self.widget)
closing.setStyleSheet("font-weight: bold; font-style: italic; font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;")
closing.setText(strings._("close_countdown").format("3"))
closing.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.log.addWidget(closing)
close_countdown(3)
# still in progress
else:
percent = math.floor((event["data"]["bytes"] / web.filesize) * 100)
self.update_log(event, " " + helpers.human_readable_filesize(event["data"]["bytes"]) + ', ' + str(percent) +'%')
elif event["path"] != '/favicon.ico':
self.update_log(event, strings._("other_page_loaded"))
def copy_to_clipboard(self):
url = 'http://{0}/{1}'.format(self.app.onion_host, web.slug)
if platform.system() == 'Windows':
# Qt's QClipboard isn't working in Windows
# https://github.com/micahflee/onionshare/issues/46
import ctypes
GMEM_DDESHARE = 0x2000
ctypes.windll.user32.OpenClipboard(None)
ctypes.windll.user32.EmptyClipboard()
hcd = ctypes.windll.kernel32.GlobalAlloc(GMEM_DDESHARE, len(bytes(url))+1)
pch_data = ctypes.windll.kernel32.GlobalLock(hcd)
ctypes.cdll.msvcrt.strcpy(ctypes.c_char_p(pch_data), bytes(url))
ctypes.windll.kernel32.GlobalUnlock(hcd)
ctypes.windll.user32.SetClipboardData(1, hcd)
ctypes.windll.user32.CloseClipboard()
else:
clipboard = qtapp.clipboard()
clipboard.setText(url)
copied = QtGui.QLabel(strings._("copied_url"))
copied.setStyleSheet("font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;")
copied.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.log.addWidget(copied)
return
def stay_open_changed(self, state):
if state > 0:
web.set_stay_open(False)
else:
web.set_stay_open(True)
return
"""
def alert(msg, icon=QtGui.QMessageBox.NoIcon): def alert(msg, icon=QtGui.QMessageBox.NoIcon):
dialog = QtGui.QMessageBox() dialog = QtGui.QMessageBox()
@ -352,16 +133,6 @@ def main():
web.set_stay_open(stay_open) web.set_stay_open(stay_open)
app = onionshare.OnionShare(debug, local_only, stay_open) app = onionshare.OnionShare(debug, local_only, stay_open)
"""try:
app.start_hidden_service(gui=True)
except onionshare.NoTor as e:
alert(e.args[0], QtGui.QMessageBox.Warning)
sys.exit()
except onionshare.TailsError as e:
alert(e.args[0], QtGui.QMessageBox.Warning)
sys.exit()
"""
# clean up when app quits # clean up when app quits
def shutdown(): def shutdown():
app.cleanup() app.cleanup()
@ -369,7 +140,7 @@ def main():
# launch the gui # launch the gui
gui = OnionShareGui(app) gui = OnionShareGui(app)
gui.start_send(filenames) gui.send_files(filenames)
# all done # all done
sys.exit(qtapp.exec_()) sys.exit(qtapp.exec_())