Add docstrings to all classes and methods (fix #198)

This commit is contained in:
Micah Lee 2015-11-15 19:01:20 -08:00
parent a5aff46050
commit dc58921187
9 changed files with 245 additions and 0 deletions

View File

@ -120,6 +120,9 @@ def is_root():
def dir_size(start_path): def dir_size(start_path):
"""
Calculates the total size, in bytes, of all of the files in a directory.
"""
total_size = 0 total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path): for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames: for f in filenames:
@ -130,6 +133,11 @@ def dir_size(start_path):
class ZipWriter(object): class ZipWriter(object):
"""
ZipWriter accepts files and directories and compresses them into a zip file
with. If a zip_filename is not passed in, it will use the default onionshare
filename.
"""
def __init__(self, zip_filename=None): def __init__(self, zip_filename=None):
if zip_filename: if zip_filename:
self.zip_filename = zip_filename self.zip_filename = zip_filename
@ -139,9 +147,15 @@ class ZipWriter(object):
self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True) self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True)
def add_file(self, filename): def add_file(self, filename):
"""
Add a file to the zip archive.
"""
self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED) self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED)
def add_dir(self, filename): def add_dir(self, filename):
"""
Add a directory, and all of its children, to the zip archive.
"""
dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/' dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/'
for dirpath, dirnames, filenames in os.walk(filename): for dirpath, dirnames, filenames in os.walk(filename):
for f in filenames: for f in filenames:
@ -151,4 +165,7 @@ class ZipWriter(object):
self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED) self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED)
def close(self): def close(self):
"""
Close the zip archive.
"""
self.z.close() self.z.close()

View File

@ -25,12 +25,35 @@ import socks
import helpers, strings import helpers, strings
class NoTor(Exception): class NoTor(Exception):
"""
This exception is raised if onionshare can't find a Tor control port
to connect to, or if it can't find a Tor socks5 proxy to proxy though.
"""
pass pass
class HSDirError(Exception): class HSDirError(Exception):
"""
This exception is raised when onionshare tries create a non-ephemeral
hidden service and does not have permission to create or write to
the hidden service directory.
"""
pass pass
class HS(object): class HS(object):
"""
HS is an abstraction layer for connecting to the Tor control port and
creating hidden services. Onionshare supports creating hidden services
using two methods:
- Modifying the Tor configuration through the control port is the old
method, and will be deprecated in favor of ephemeral hidden services.
- Using the control port to create ephemeral hidden servers is the
preferred method.
This class detects the versions of Tor and stem to determine if ephemeral
hidden services are supported. If not, it falls back to modifying the
Tor configuration.
"""
def __init__(self, transparent_torification=False): def __init__(self, transparent_torification=False):
self.transparent_torification = transparent_torification self.transparent_torification = transparent_torification
@ -57,6 +80,10 @@ class HS(object):
self.supports_ephemeral = callable(list_ephemeral_hidden_services) and tor_version >= '0.2.7.1' self.supports_ephemeral = callable(list_ephemeral_hidden_services) and tor_version >= '0.2.7.1'
def start(self, port): def start(self, port):
"""
Start a hidden service on port 80, pointing to the given port, and
return the onion hostname.
"""
print strings._("connecting_ctrlport").format(int(port)) print strings._("connecting_ctrlport").format(int(port))
if self.supports_ephemeral: if self.supports_ephemeral:
print strings._('using_ephemeral') print strings._('using_ephemeral')
@ -104,6 +131,11 @@ class HS(object):
return onion_host return onion_host
def wait_for_hs(self, onion_host): def wait_for_hs(self, onion_host):
"""
This function is only required when using non-ephemeral hidden services. After
creating a hidden service, continually attempt to connect to it until it
successfully connects..
"""
# legacy only, this function is no longer required with ephemeral hidden services # legacy only, this function is no longer required with ephemeral hidden services
print strings._('wait_for_hs') print strings._('wait_for_hs')
@ -148,6 +180,10 @@ class HS(object):
return True return True
def cleanup(self): def cleanup(self):
"""
Stop hidden services that were created earlier, and delete any temporary
files that were created.
"""
if self.supports_ephemeral: if self.supports_ephemeral:
# cleanup the ephemeral hidden service # cleanup the ephemeral hidden service
if self.service_id: if self.service_id:

View File

@ -22,6 +22,10 @@ import os, sys, subprocess, time, argparse, inspect, shutil, socket, threading
import strings, helpers, web, hs import strings, helpers, web, hs
class OnionShare(object): class OnionShare(object):
"""
OnionShare is the main application class. Pass in options and run
start_hidden_service and it will do the magic.
"""
def __init__(self, debug=False, local_only=False, stay_open=False, transparent_torification=False): def __init__(self, debug=False, local_only=False, stay_open=False, transparent_torification=False):
self.port = None self.port = None
self.hs = None self.hs = None
@ -45,6 +49,9 @@ class OnionShare(object):
self.transparent_torification = transparent_torification self.transparent_torification = transparent_torification
def choose_port(self): def choose_port(self):
"""
Pick an un-used port to bind to.
"""
# let the OS choose a port # let the OS choose a port
tmpsock = socket.socket() tmpsock = socket.socket()
tmpsock.bind(("127.0.0.1", 0)) tmpsock.bind(("127.0.0.1", 0))
@ -52,6 +59,9 @@ class OnionShare(object):
tmpsock.close() tmpsock.close()
def start_hidden_service(self, gui=False): def start_hidden_service(self, gui=False):
"""
Start the onionshare hidden service.
"""
if not self.port: if not self.port:
self.choose_port() self.choose_port()
@ -65,6 +75,9 @@ class OnionShare(object):
self.onion_host = self.hs.start(self.port) self.onion_host = self.hs.start(self.port)
def cleanup(self): def cleanup(self):
"""
Shut everything down and clean up temporary files, etc.
"""
# cleanup files # cleanup files
for filename in self.cleanup_filenames: for filename in self.cleanup_filenames:
if os.path.isfile(filename): if os.path.isfile(filename):
@ -79,6 +92,10 @@ class OnionShare(object):
def main(cwd=None): def main(cwd=None):
"""
The main() function implements all of the logic that the command-line version of
onionshare uses.
"""
strings.load_strings() strings.load_strings()
# onionshare CLI in OSX needs to change current working directory (#132) # onionshare CLI in OSX needs to change current working directory (#132)

View File

@ -31,6 +31,11 @@ zip_filesize = None
def set_file_info(filenames): def set_file_info(filenames):
"""
Using the list of filenames being shared, fill in details that the web
page will need to display. This includes zipping up the file in order to
get the zip file's name and size.
"""
global file_info, zip_filename, zip_filesize global file_info, zip_filename, zip_filesize
# build file info list # build file info list
@ -71,6 +76,9 @@ q = Queue.Queue()
def add_request(request_type, path, data=None): def add_request(request_type, path, data=None):
"""
Add a request to the queue, to communicate with the GUI.
"""
global q global q
q.put({ q.put({
'type': request_type, 'type': request_type,
@ -84,19 +92,34 @@ download_count = 0
stay_open = False stay_open = False
def set_stay_open(new_stay_open): def set_stay_open(new_stay_open):
"""
Set stay_open variable.
"""
global stay_open global stay_open
stay_open = new_stay_open stay_open = new_stay_open
def get_stay_open(): def get_stay_open():
"""
Get stay_open variable.
"""
return stay_open return stay_open
transparent_torification = False transparent_torification = False
def set_transparent_torification(new_transparent_torification): def set_transparent_torification(new_transparent_torification):
"""
Set transparent_torification variable.
"""
global transparent_torification global transparent_torification
stay_open = new_transparent_torification stay_open = new_transparent_torification
def get_transparent_torification(): def get_transparent_torification():
"""
Get transparent_torification variable."
"""
return transparent_torification return transparent_torification
def debug_mode(): def debug_mode():
"""
Turn on debugging mode, which will log flask errors to a debug file.
"""
import logging import logging
if platform.system() == 'Windows': if platform.system() == 'Windows':
@ -111,6 +134,9 @@ def debug_mode():
@app.route("/<slug_candidate>") @app.route("/<slug_candidate>")
def index(slug_candidate): def index(slug_candidate):
"""
Render the template for the onionshare landing page.
"""
if not helpers.constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')): if not helpers.constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')):
abort(404) abort(404)
@ -128,6 +154,9 @@ def index(slug_candidate):
@app.route("/<slug_candidate>/download") @app.route("/<slug_candidate>/download")
def download(slug_candidate): def download(slug_candidate):
"""
Download the zip file.
"""
global download_count global download_count
if not helpers.constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')): if not helpers.constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')):
abort(404) abort(404)
@ -205,6 +234,9 @@ def download(slug_candidate):
@app.errorhandler(404) @app.errorhandler(404)
def page_not_found(e): def page_not_found(e):
"""
404 error page.
"""
add_request(REQUEST_OTHER, request.path) add_request(REQUEST_OTHER, request.path)
return render_template_string(open(helpers.get_html_path('404.html')).read()) return render_template_string(open(helpers.get_html_path('404.html')).read())
@ -214,6 +246,9 @@ shutdown_slug = helpers.random_string(16)
@app.route("/<shutdown_slug_candidate>/shutdown") @app.route("/<shutdown_slug_candidate>/shutdown")
def shutdown(shutdown_slug_candidate): def shutdown(shutdown_slug_candidate):
"""
Stop the flask web server.
"""
if not helpers.constant_time_compare(shutdown_slug.encode('ascii'), shutdown_slug_candidate.encode('ascii')): if not helpers.constant_time_compare(shutdown_slug.encode('ascii'), shutdown_slug_candidate.encode('ascii')):
abort(404) abort(404)
@ -227,12 +262,18 @@ def shutdown(shutdown_slug_candidate):
def start(port, stay_open=False, transparent_torification=False): def start(port, stay_open=False, transparent_torification=False):
"""
Start the flask web server.
"""
set_stay_open(stay_open) set_stay_open(stay_open)
set_transparent_torification(transparent_torification) set_transparent_torification(transparent_torification)
app.run(port=port, threaded=True) app.run(port=port, threaded=True)
def stop(port): def stop(port):
"""
Stop the flask web server by loading /shutdown.
"""
# to stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown # to stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
if transparent_torification: if transparent_torification:
import socket import socket

View File

@ -24,6 +24,10 @@ from onionshare import strings, helpers
class Downloads(QtGui.QVBoxLayout): class Downloads(QtGui.QVBoxLayout):
"""
The downloads chunk of the GUI. This lists all of the active download
progress bars.
"""
def __init__(self): def __init__(self):
super(Downloads, self).__init__() super(Downloads, self).__init__()
@ -37,6 +41,9 @@ class Downloads(QtGui.QVBoxLayout):
self.addWidget(self.downloads_label) self.addWidget(self.downloads_label)
def add_download(self, download_id, total_bytes): def add_download(self, download_id, total_bytes):
"""
Add a new download progress bar.
"""
self.downloads_label.show() self.downloads_label.show()
# make a new progress bar # make a new progress bar
@ -57,6 +64,9 @@ class Downloads(QtGui.QVBoxLayout):
self.update_download(download_id, total_bytes, 0) self.update_download(download_id, total_bytes, 0)
def update_download(self, download_id, total_bytes, downloaded_bytes): def update_download(self, download_id, total_bytes, downloaded_bytes):
"""
Update the progress of a download progress bar.
"""
if download_id not in self.progress_bars: if download_id not in self.progress_bars:
self.add_download(download_id, total_bytes) self.add_download(download_id, total_bytes)
@ -68,5 +78,8 @@ class Downloads(QtGui.QVBoxLayout):
pb.setFormat("{0:s}, %p%".format(helpers.human_readable_filesize(downloaded_bytes))) pb.setFormat("{0:s}, %p%".format(helpers.human_readable_filesize(downloaded_bytes)))
def cancel_download(self, download_id): def cancel_download(self, download_id):
"""
Update a download progress bar to show that it has been canceled.
"""
pb = self.progress_bars[download_id] pb = self.progress_bars[download_id]
pb.setFormat(strings._('gui_canceled')) pb.setFormat(strings._('gui_canceled'))

View File

@ -25,6 +25,9 @@ from onionshare import strings, helpers
class FileList(QtGui.QListWidget): class FileList(QtGui.QListWidget):
"""
The list of files and folders in the GUI.
"""
files_dropped = QtCore.pyqtSignal() files_dropped = QtCore.pyqtSignal()
files_updated = QtCore.pyqtSignal() files_updated = QtCore.pyqtSignal()
@ -35,6 +38,10 @@ class FileList(QtGui.QListWidget):
self.setSortingEnabled(True) self.setSortingEnabled(True)
class DropHereLabel(QtGui.QLabel): class DropHereLabel(QtGui.QLabel):
"""
When there are no files or folders in the FileList yet, display the
'drop files here' message and graphic.
"""
def __init__(self, parent, image=False): def __init__(self, parent, image=False):
self.parent = parent self.parent = parent
super(DropHereLabel, self).__init__(parent=parent) super(DropHereLabel, self).__init__(parent=parent)
@ -61,6 +68,9 @@ class FileList(QtGui.QListWidget):
self.update() self.update()
def update(self): def update(self):
"""
Update the GUI elements based on the current state.
"""
# file list should have a background image if empty # file list should have a background image if empty
if len(self.filenames) == 0: if len(self.filenames) == 0:
self.drop_here_image.show() self.drop_here_image.show()
@ -70,20 +80,32 @@ class FileList(QtGui.QListWidget):
self.drop_here_text.hide() self.drop_here_text.hide()
def resizeEvent(self, event): def resizeEvent(self, event):
"""
When the widget is resized, resize the drop files image and text.
"""
self.drop_here_image.setGeometry(0, 0, self.width(), self.height()) self.drop_here_image.setGeometry(0, 0, self.width(), self.height())
self.drop_here_text.setGeometry(0, 0, self.width(), self.height()) self.drop_here_text.setGeometry(0, 0, self.width(), self.height())
def dragEnterEvent(self, event): def dragEnterEvent(self, event):
"""
dragEnterEvent for dragging files and directories into the widget.
"""
if event.mimeData().hasUrls: if event.mimeData().hasUrls:
event.accept() event.accept()
else: else:
event.ignore() event.ignore()
def dragLeaveEvent(self, event): def dragLeaveEvent(self, event):
"""
dragLeaveEvent for dragging files and directories into the widget.
"""
event.accept() event.accept()
self.update() self.update()
def dragMoveEvent(self, event): def dragMoveEvent(self, event):
"""
dragMoveEvent for dragging files and directories into the widget.
"""
if event.mimeData().hasUrls: if event.mimeData().hasUrls:
event.setDropAction(QtCore.Qt.CopyAction) event.setDropAction(QtCore.Qt.CopyAction)
event.accept() event.accept()
@ -91,6 +113,9 @@ class FileList(QtGui.QListWidget):
event.ignore() event.ignore()
def dropEvent(self, event): def dropEvent(self, event):
"""
dropEvent for dragging files and directories into the widget.
"""
if event.mimeData().hasUrls: if event.mimeData().hasUrls:
event.setDropAction(QtCore.Qt.CopyAction) event.setDropAction(QtCore.Qt.CopyAction)
event.accept() event.accept()
@ -102,6 +127,9 @@ class FileList(QtGui.QListWidget):
self.files_dropped.emit() self.files_dropped.emit()
def add_file(self, filename): def add_file(self, filename):
"""
Add a file or directory to this widget.
"""
if filename not in self.filenames: if filename not in self.filenames:
# make filenames unicode-safe for Qt (#141) # make filenames unicode-safe for Qt (#141)
filename = filename.encode('utf-8').decode('utf-8', 'replace') filename = filename.encode('utf-8').decode('utf-8', 'replace')
@ -128,6 +156,10 @@ class FileList(QtGui.QListWidget):
class FileSelection(QtGui.QVBoxLayout): class FileSelection(QtGui.QVBoxLayout):
"""
The list of files and folders in the GUI, as well as buttons to add and
delete the files and folders.
"""
def __init__(self): def __init__(self):
super(FileSelection, self).__init__() super(FileSelection, self).__init__()
self.server_on = False self.server_on = False
@ -156,6 +188,9 @@ class FileSelection(QtGui.QVBoxLayout):
self.update() self.update()
def update(self): def update(self):
"""
Update the GUI elements based on the current state.
"""
# all buttons should be disabled if the server is on # all buttons should be disabled if the server is on
if self.server_on: if self.server_on:
self.add_files_button.setEnabled(False) self.add_files_button.setEnabled(False)
@ -176,6 +211,9 @@ class FileSelection(QtGui.QVBoxLayout):
self.file_list.update() self.file_list.update()
def add_files(self): def add_files(self):
"""
Add files button clicked.
"""
filenames = QtGui.QFileDialog.getOpenFileNames( filenames = QtGui.QFileDialog.getOpenFileNames(
caption=strings._('gui_choose_files', True), options=QtGui.QFileDialog.ReadOnly) caption=strings._('gui_choose_files', True), options=QtGui.QFileDialog.ReadOnly)
if filenames: if filenames:
@ -184,6 +222,9 @@ class FileSelection(QtGui.QVBoxLayout):
self.update() self.update()
def add_dir(self): def add_dir(self):
"""
Add folder button clicked.
"""
filename = QtGui.QFileDialog.getExistingDirectory( filename = QtGui.QFileDialog.getExistingDirectory(
caption=strings._('gui_choose_folder', True), options=QtGui.QFileDialog.ReadOnly) caption=strings._('gui_choose_folder', True), options=QtGui.QFileDialog.ReadOnly)
if filename: if filename:
@ -191,20 +232,32 @@ class FileSelection(QtGui.QVBoxLayout):
self.update() self.update()
def delete_file(self): def delete_file(self):
"""
Delete button clicked
"""
current_row = self.file_list.currentRow() current_row = self.file_list.currentRow()
self.file_list.filenames.pop(current_row) self.file_list.filenames.pop(current_row)
self.file_list.takeItem(current_row) self.file_list.takeItem(current_row)
self.update() self.update()
def server_started(self): def server_started(self):
"""
Gets called when the server starts.
"""
self.server_on = True self.server_on = True
self.file_list.setAcceptDrops(False) self.file_list.setAcceptDrops(False)
self.update() self.update()
def server_stopped(self): def server_stopped(self):
"""
Gets called when the server stops.
"""
self.server_on = False self.server_on = False
self.file_list.setAcceptDrops(True) self.file_list.setAcceptDrops(True)
self.update() self.update()
def get_num_files(self): def get_num_files(self):
"""
Returns the total number of files and folders in the list.
"""
return len(self.file_list.filenames) return len(self.file_list.filenames)

View File

@ -37,6 +37,10 @@ from options import Options
class Application(QtGui.QApplication): class Application(QtGui.QApplication):
"""
This is Qt's QApplication class. It has been overridden to support threads
and the quick keyboard shortcut.
"""
def __init__(self): def __init__(self):
platform = helpers.get_platform() platform = helpers.get_platform()
if platform == 'Linux': if platform == 'Linux':
@ -53,6 +57,10 @@ class Application(QtGui.QApplication):
class OnionShareGui(QtGui.QWidget): class OnionShareGui(QtGui.QWidget):
"""
OnionShareGui is the main window for the GUI that contains all of the
GUI elements.
"""
start_server_finished = QtCore.pyqtSignal() start_server_finished = QtCore.pyqtSignal()
stop_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal()
starting_server_step2 = QtCore.pyqtSignal() starting_server_step2 = QtCore.pyqtSignal()
@ -66,6 +74,10 @@ class OnionShareGui(QtGui.QWidget):
self.setWindowIcon(window_icon) self.setWindowIcon(window_icon)
def send_files(self, filenames=None): def send_files(self, filenames=None):
"""
Build the GUI in send files mode.
Note that this is the only mode currently implemented.
"""
# file selection # file selection
self.file_selection = FileSelection() self.file_selection = FileSelection()
if filenames: if filenames:
@ -117,12 +129,20 @@ class OnionShareGui(QtGui.QWidget):
self.timer.start(500) self.timer.start(500)
def start_server_step2(self): def start_server_step2(self):
"""
Step 2 in starting the onionshare server. This displays the large filesize
warning, if applicable.
"""
# warn about sending large files over Tor # warn about sending large files over Tor
if web.zip_filesize >= 157286400: # 150mb if web.zip_filesize >= 157286400: # 150mb
self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.setText(strings._("large_filesize", True))
self.filesize_warning.show() self.filesize_warning.show()
def start_server(self): def start_server(self):
"""
Start the onionshare server. This uses multiple threads to start the Tor hidden
server and the web app.
"""
# start the hidden service # start the hidden service
self.status_bar.showMessage(strings._('gui_starting_server1', True)) self.status_bar.showMessage(strings._('gui_starting_server1', True))
self.app.choose_port() self.app.choose_port()
@ -162,6 +182,9 @@ class OnionShareGui(QtGui.QWidget):
t.start() t.start()
def stop_server(self): def stop_server(self):
"""
Stop the onionshare server.
"""
if self.server_status.status == self.server_status.STATUS_STARTED: if self.server_status.status == self.server_status.STATUS_STARTED:
web.stop(self.app.port) web.stop(self.app.port)
self.app.cleanup() self.app.cleanup()
@ -169,6 +192,9 @@ class OnionShareGui(QtGui.QWidget):
self.stop_server_finished.emit() self.stop_server_finished.emit()
def check_for_requests(self): def check_for_requests(self):
"""
Check for messages communicated from the web app, and update the GUI accordingly.
"""
self.update() self.update()
# only check for requests if the server is running # only check for requests if the server is running
if self.server_status.status != self.server_status.STATUS_STARTED: if self.server_status.status != self.server_status.STATUS_STARTED:
@ -207,13 +233,22 @@ class OnionShareGui(QtGui.QWidget):
self.status_bar.showMessage('{0:s}: {1:s}'.format(strings._('other_page_loaded', True), event["path"])) self.status_bar.showMessage('{0:s}: {1:s}'.format(strings._('other_page_loaded', True), event["path"]))
def copy_url(self): def copy_url(self):
"""
When the URL gets copied to the clipboard, display this in the status bar.
"""
self.status_bar.showMessage(strings._('gui_copied_url', True), 2000) self.status_bar.showMessage(strings._('gui_copied_url', True), 2000)
def clear_message(self): def clear_message(self):
"""
Clear messages from the status bar.
"""
self.status_bar.clearMessage() self.status_bar.clearMessage()
def alert(msg, icon=QtGui.QMessageBox.NoIcon): def alert(msg, icon=QtGui.QMessageBox.NoIcon):
"""
Pop up a message in a dialog window.
"""
dialog = QtGui.QMessageBox() dialog = QtGui.QMessageBox()
dialog.setWindowTitle("OnionShare") dialog.setWindowTitle("OnionShare")
dialog.setWindowIcon(window_icon) dialog.setWindowIcon(window_icon)
@ -223,6 +258,9 @@ def alert(msg, icon=QtGui.QMessageBox.NoIcon):
def main(): def main():
"""
The main() function implements all of the logic that the GUI version of onionshare uses.
"""
strings.load_strings() strings.load_strings()
# start the Qt app # start the Qt app

View File

@ -24,6 +24,9 @@ from onionshare import strings, helpers
class Options(QtGui.QHBoxLayout): class Options(QtGui.QHBoxLayout):
"""
The extra onionshare options in the GUI.
"""
def __init__(self, web): def __init__(self, web):
super(Options, self).__init__() super(Options, self).__init__()
@ -42,6 +45,9 @@ class Options(QtGui.QHBoxLayout):
self.addWidget(self.close_automatically) self.addWidget(self.close_automatically)
def stay_open_changed(self, state): def stay_open_changed(self, state):
"""
When the 'close automatically' checkbox is toggled, let the web app know.
"""
if state > 0: if state > 0:
self.web.set_stay_open(False) self.web.set_stay_open(False)
else: else:

View File

@ -25,6 +25,9 @@ from onionshare import strings, helpers
class ServerStatus(QtGui.QVBoxLayout): class ServerStatus(QtGui.QVBoxLayout):
"""
The server status chunk of the GUI.
"""
server_started = QtCore.pyqtSignal() server_started = QtCore.pyqtSignal()
server_stopped = QtCore.pyqtSignal() server_stopped = QtCore.pyqtSignal()
url_copied = QtCore.pyqtSignal() url_copied = QtCore.pyqtSignal()
@ -73,6 +76,9 @@ class ServerStatus(QtGui.QVBoxLayout):
self.update() self.update()
def update(self): def update(self):
"""
Update the GUI elements based on the current state.
"""
# set the status image # set the status image
if self.status == self.STATUS_STOPPED: if self.status == self.STATUS_STOPPED:
self.status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.status_image_stopped)) self.status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.status_image_stopped))
@ -110,31 +116,49 @@ class ServerStatus(QtGui.QVBoxLayout):
self.server_button.setText(strings._('gui_please_wait')) self.server_button.setText(strings._('gui_please_wait'))
def server_button_clicked(self): def server_button_clicked(self):
"""
Toggle starting or stopping the server.
"""
if self.status == self.STATUS_STOPPED: if self.status == self.STATUS_STOPPED:
self.start_server() self.start_server()
elif self.status == self.STATUS_STARTED: elif self.status == self.STATUS_STARTED:
self.stop_server() self.stop_server()
def start_server(self): def start_server(self):
"""
Start the server.
"""
self.status = self.STATUS_WORKING self.status = self.STATUS_WORKING
self.update() self.update()
self.server_started.emit() self.server_started.emit()
def start_server_finished(self): def start_server_finished(self):
"""
The server has finished starting.
"""
self.status = self.STATUS_STARTED self.status = self.STATUS_STARTED
self.copy_url() self.copy_url()
self.update() self.update()
def stop_server(self): def stop_server(self):
"""
Stop the server.
"""
self.status = self.STATUS_WORKING self.status = self.STATUS_WORKING
self.update() self.update()
self.server_stopped.emit() self.server_stopped.emit()
def stop_server_finished(self): def stop_server_finished(self):
"""
The server has finished stopping.
"""
self.status = self.STATUS_STOPPED self.status = self.STATUS_STOPPED
self.update() self.update()
def copy_url(self): def copy_url(self):
"""
Copy the onionshare URL to the clipboard.
"""
url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug) url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)
if platform.system() == 'Windows': if platform.system() == 'Windows':