From dc589211879ac7d32c67d6ea0502dfe12eb91400 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Nov 2015 19:01:20 -0800 Subject: [PATCH] Add docstrings to all classes and methods (fix #198) --- onionshare/helpers.py | 17 ++++++++++ onionshare/hs.py | 36 ++++++++++++++++++++++ onionshare/onionshare.py | 17 ++++++++++ onionshare/web.py | 41 ++++++++++++++++++++++++ onionshare_gui/downloads.py | 13 ++++++++ onionshare_gui/file_selection.py | 53 ++++++++++++++++++++++++++++++++ onionshare_gui/onionshare_gui.py | 38 +++++++++++++++++++++++ onionshare_gui/options.py | 6 ++++ onionshare_gui/server_status.py | 24 +++++++++++++++ 9 files changed, 245 insertions(+) diff --git a/onionshare/helpers.py b/onionshare/helpers.py index 250ef8da..1a48147f 100644 --- a/onionshare/helpers.py +++ b/onionshare/helpers.py @@ -120,6 +120,9 @@ def is_root(): def dir_size(start_path): + """ + Calculates the total size, in bytes, of all of the files in a directory. + """ total_size = 0 for dirpath, dirnames, filenames in os.walk(start_path): for f in filenames: @@ -130,6 +133,11 @@ def dir_size(start_path): 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): if zip_filename: self.zip_filename = zip_filename @@ -139,9 +147,15 @@ class ZipWriter(object): self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True) def add_file(self, filename): + """ + Add a file to the zip archive. + """ self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED) 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('/'))+'/' for dirpath, dirnames, filenames in os.walk(filename): for f in filenames: @@ -151,4 +165,7 @@ class ZipWriter(object): self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED) def close(self): + """ + Close the zip archive. + """ self.z.close() diff --git a/onionshare/hs.py b/onionshare/hs.py index 368a8669..8897ec0e 100644 --- a/onionshare/hs.py +++ b/onionshare/hs.py @@ -25,12 +25,35 @@ import socks import helpers, strings 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 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 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): 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' 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)) if self.supports_ephemeral: print strings._('using_ephemeral') @@ -104,6 +131,11 @@ class HS(object): return 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 print strings._('wait_for_hs') @@ -148,6 +180,10 @@ class HS(object): return True def cleanup(self): + """ + Stop hidden services that were created earlier, and delete any temporary + files that were created. + """ if self.supports_ephemeral: # cleanup the ephemeral hidden service if self.service_id: diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index 3e305672..aa50031a 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -22,6 +22,10 @@ import os, sys, subprocess, time, argparse, inspect, shutil, socket, threading import strings, helpers, web, hs 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): self.port = None self.hs = None @@ -45,6 +49,9 @@ class OnionShare(object): self.transparent_torification = transparent_torification def choose_port(self): + """ + Pick an un-used port to bind to. + """ # let the OS choose a port tmpsock = socket.socket() tmpsock.bind(("127.0.0.1", 0)) @@ -52,6 +59,9 @@ class OnionShare(object): tmpsock.close() def start_hidden_service(self, gui=False): + """ + Start the onionshare hidden service. + """ if not self.port: self.choose_port() @@ -65,6 +75,9 @@ class OnionShare(object): self.onion_host = self.hs.start(self.port) def cleanup(self): + """ + Shut everything down and clean up temporary files, etc. + """ # cleanup files for filename in self.cleanup_filenames: if os.path.isfile(filename): @@ -79,6 +92,10 @@ class OnionShare(object): def main(cwd=None): + """ + The main() function implements all of the logic that the command-line version of + onionshare uses. + """ strings.load_strings() # onionshare CLI in OSX needs to change current working directory (#132) diff --git a/onionshare/web.py b/onionshare/web.py index db52c049..8706c4d8 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -31,6 +31,11 @@ zip_filesize = None 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 # build file info list @@ -71,6 +76,9 @@ q = Queue.Queue() def add_request(request_type, path, data=None): + """ + Add a request to the queue, to communicate with the GUI. + """ global q q.put({ 'type': request_type, @@ -84,19 +92,34 @@ download_count = 0 stay_open = False def set_stay_open(new_stay_open): + """ + Set stay_open variable. + """ global stay_open stay_open = new_stay_open def get_stay_open(): + """ + Get stay_open variable. + """ return stay_open transparent_torification = False def set_transparent_torification(new_transparent_torification): + """ + Set transparent_torification variable. + """ global transparent_torification stay_open = new_transparent_torification def get_transparent_torification(): + """ + Get transparent_torification variable." + """ return transparent_torification def debug_mode(): + """ + Turn on debugging mode, which will log flask errors to a debug file. + """ import logging if platform.system() == 'Windows': @@ -111,6 +134,9 @@ def debug_mode(): @app.route("/") 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')): abort(404) @@ -128,6 +154,9 @@ def index(slug_candidate): @app.route("//download") def download(slug_candidate): + """ + Download the zip file. + """ global download_count if not helpers.constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')): abort(404) @@ -205,6 +234,9 @@ def download(slug_candidate): @app.errorhandler(404) def page_not_found(e): + """ + 404 error page. + """ add_request(REQUEST_OTHER, request.path) 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") 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')): abort(404) @@ -227,12 +262,18 @@ def shutdown(shutdown_slug_candidate): def start(port, stay_open=False, transparent_torification=False): + """ + Start the flask web server. + """ set_stay_open(stay_open) set_transparent_torification(transparent_torification) app.run(port=port, threaded=True) def stop(port): + """ + Stop the flask web server by loading /shutdown. + """ # to stop flask, load http://127.0.0.1://shutdown if transparent_torification: import socket diff --git a/onionshare_gui/downloads.py b/onionshare_gui/downloads.py index 85c25aad..0ecb49f0 100644 --- a/onionshare_gui/downloads.py +++ b/onionshare_gui/downloads.py @@ -24,6 +24,10 @@ from onionshare import strings, helpers class Downloads(QtGui.QVBoxLayout): + """ + The downloads chunk of the GUI. This lists all of the active download + progress bars. + """ def __init__(self): super(Downloads, self).__init__() @@ -37,6 +41,9 @@ class Downloads(QtGui.QVBoxLayout): self.addWidget(self.downloads_label) def add_download(self, download_id, total_bytes): + """ + Add a new download progress bar. + """ self.downloads_label.show() # make a new progress bar @@ -57,6 +64,9 @@ class Downloads(QtGui.QVBoxLayout): self.update_download(download_id, total_bytes, 0) 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: 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))) 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.setFormat(strings._('gui_canceled')) diff --git a/onionshare_gui/file_selection.py b/onionshare_gui/file_selection.py index 03127a21..441b3c90 100644 --- a/onionshare_gui/file_selection.py +++ b/onionshare_gui/file_selection.py @@ -25,6 +25,9 @@ from onionshare import strings, helpers class FileList(QtGui.QListWidget): + """ + The list of files and folders in the GUI. + """ files_dropped = QtCore.pyqtSignal() files_updated = QtCore.pyqtSignal() @@ -35,6 +38,10 @@ class FileList(QtGui.QListWidget): self.setSortingEnabled(True) 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): self.parent = parent super(DropHereLabel, self).__init__(parent=parent) @@ -61,6 +68,9 @@ class FileList(QtGui.QListWidget): self.update() def update(self): + """ + Update the GUI elements based on the current state. + """ # file list should have a background image if empty if len(self.filenames) == 0: self.drop_here_image.show() @@ -70,20 +80,32 @@ class FileList(QtGui.QListWidget): self.drop_here_text.hide() 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_text.setGeometry(0, 0, self.width(), self.height()) def dragEnterEvent(self, event): + """ + dragEnterEvent for dragging files and directories into the widget. + """ if event.mimeData().hasUrls: event.accept() else: event.ignore() def dragLeaveEvent(self, event): + """ + dragLeaveEvent for dragging files and directories into the widget. + """ event.accept() self.update() def dragMoveEvent(self, event): + """ + dragMoveEvent for dragging files and directories into the widget. + """ if event.mimeData().hasUrls: event.setDropAction(QtCore.Qt.CopyAction) event.accept() @@ -91,6 +113,9 @@ class FileList(QtGui.QListWidget): event.ignore() def dropEvent(self, event): + """ + dropEvent for dragging files and directories into the widget. + """ if event.mimeData().hasUrls: event.setDropAction(QtCore.Qt.CopyAction) event.accept() @@ -102,6 +127,9 @@ class FileList(QtGui.QListWidget): self.files_dropped.emit() def add_file(self, filename): + """ + Add a file or directory to this widget. + """ if filename not in self.filenames: # make filenames unicode-safe for Qt (#141) filename = filename.encode('utf-8').decode('utf-8', 'replace') @@ -128,6 +156,10 @@ class FileList(QtGui.QListWidget): 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): super(FileSelection, self).__init__() self.server_on = False @@ -156,6 +188,9 @@ class FileSelection(QtGui.QVBoxLayout): self.update() def update(self): + """ + Update the GUI elements based on the current state. + """ # all buttons should be disabled if the server is on if self.server_on: self.add_files_button.setEnabled(False) @@ -176,6 +211,9 @@ class FileSelection(QtGui.QVBoxLayout): self.file_list.update() def add_files(self): + """ + Add files button clicked. + """ filenames = QtGui.QFileDialog.getOpenFileNames( caption=strings._('gui_choose_files', True), options=QtGui.QFileDialog.ReadOnly) if filenames: @@ -184,6 +222,9 @@ class FileSelection(QtGui.QVBoxLayout): self.update() def add_dir(self): + """ + Add folder button clicked. + """ filename = QtGui.QFileDialog.getExistingDirectory( caption=strings._('gui_choose_folder', True), options=QtGui.QFileDialog.ReadOnly) if filename: @@ -191,20 +232,32 @@ class FileSelection(QtGui.QVBoxLayout): self.update() def delete_file(self): + """ + Delete button clicked + """ current_row = self.file_list.currentRow() self.file_list.filenames.pop(current_row) self.file_list.takeItem(current_row) self.update() def server_started(self): + """ + Gets called when the server starts. + """ self.server_on = True self.file_list.setAcceptDrops(False) self.update() def server_stopped(self): + """ + Gets called when the server stops. + """ self.server_on = False self.file_list.setAcceptDrops(True) self.update() def get_num_files(self): + """ + Returns the total number of files and folders in the list. + """ return len(self.file_list.filenames) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index ee5d1460..dc33a5b2 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -37,6 +37,10 @@ from options import Options 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): platform = helpers.get_platform() if platform == 'Linux': @@ -53,6 +57,10 @@ class Application(QtGui.QApplication): class OnionShareGui(QtGui.QWidget): + """ + OnionShareGui is the main window for the GUI that contains all of the + GUI elements. + """ start_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal() starting_server_step2 = QtCore.pyqtSignal() @@ -66,6 +74,10 @@ class OnionShareGui(QtGui.QWidget): self.setWindowIcon(window_icon) def send_files(self, filenames=None): + """ + Build the GUI in send files mode. + Note that this is the only mode currently implemented. + """ # file selection self.file_selection = FileSelection() if filenames: @@ -117,12 +129,20 @@ class OnionShareGui(QtGui.QWidget): self.timer.start(500) 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 if web.zip_filesize >= 157286400: # 150mb self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.show() 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 self.status_bar.showMessage(strings._('gui_starting_server1', True)) self.app.choose_port() @@ -162,6 +182,9 @@ class OnionShareGui(QtGui.QWidget): t.start() def stop_server(self): + """ + Stop the onionshare server. + """ if self.server_status.status == self.server_status.STATUS_STARTED: web.stop(self.app.port) self.app.cleanup() @@ -169,6 +192,9 @@ class OnionShareGui(QtGui.QWidget): self.stop_server_finished.emit() def check_for_requests(self): + """ + Check for messages communicated from the web app, and update the GUI accordingly. + """ self.update() # only check for requests if the server is running 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"])) 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) def clear_message(self): + """ + Clear messages from the status bar. + """ self.status_bar.clearMessage() def alert(msg, icon=QtGui.QMessageBox.NoIcon): + """ + Pop up a message in a dialog window. + """ dialog = QtGui.QMessageBox() dialog.setWindowTitle("OnionShare") dialog.setWindowIcon(window_icon) @@ -223,6 +258,9 @@ def alert(msg, icon=QtGui.QMessageBox.NoIcon): def main(): + """ + The main() function implements all of the logic that the GUI version of onionshare uses. + """ strings.load_strings() # start the Qt app diff --git a/onionshare_gui/options.py b/onionshare_gui/options.py index 9a098c83..f3a91d29 100644 --- a/onionshare_gui/options.py +++ b/onionshare_gui/options.py @@ -24,6 +24,9 @@ from onionshare import strings, helpers class Options(QtGui.QHBoxLayout): + """ + The extra onionshare options in the GUI. + """ def __init__(self, web): super(Options, self).__init__() @@ -42,6 +45,9 @@ class Options(QtGui.QHBoxLayout): self.addWidget(self.close_automatically) def stay_open_changed(self, state): + """ + When the 'close automatically' checkbox is toggled, let the web app know. + """ if state > 0: self.web.set_stay_open(False) else: diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 6d34b559..9753895d 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -25,6 +25,9 @@ from onionshare import strings, helpers class ServerStatus(QtGui.QVBoxLayout): + """ + The server status chunk of the GUI. + """ server_started = QtCore.pyqtSignal() server_stopped = QtCore.pyqtSignal() url_copied = QtCore.pyqtSignal() @@ -73,6 +76,9 @@ class ServerStatus(QtGui.QVBoxLayout): self.update() def update(self): + """ + Update the GUI elements based on the current state. + """ # set the status image if self.status == self.STATUS_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')) def server_button_clicked(self): + """ + Toggle starting or stopping the server. + """ if self.status == self.STATUS_STOPPED: self.start_server() elif self.status == self.STATUS_STARTED: self.stop_server() def start_server(self): + """ + Start the server. + """ self.status = self.STATUS_WORKING self.update() self.server_started.emit() def start_server_finished(self): + """ + The server has finished starting. + """ self.status = self.STATUS_STARTED self.copy_url() self.update() def stop_server(self): + """ + Stop the server. + """ self.status = self.STATUS_WORKING self.update() self.server_stopped.emit() def stop_server_finished(self): + """ + The server has finished stopping. + """ self.status = self.STATUS_STOPPED self.update() 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) if platform.system() == 'Windows':