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):
"""
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()

View File

@ -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:

View File

@ -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)

View File

@ -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("/<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')):
abort(404)
@ -128,6 +154,9 @@ def index(slug_candidate):
@app.route("/<slug_candidate>/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_slug_candidate>/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:<port>/<shutdown_slug>/shutdown
if transparent_torification:
import socket

View File

@ -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'))

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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':