dangerzone/dangerzone/docker_installer.py

251 lines
8.4 KiB
Python

import os
import stat
import requests
import tempfile
import subprocess
import shutil
import time
import platform
from PySide2 import QtCore, QtGui, QtWidgets
from .container import container_runtime
class AuthorizationFailed(Exception):
pass
def is_docker_installed(global_common):
if platform.system() == "Darwin":
# Does the docker binary exist?
if os.path.isdir("/Applications/Docker.app") and os.path.exists(
container_runtime
):
# Is it executable?
st = os.stat(container_runtime)
return bool(st.st_mode & stat.S_IXOTH)
if platform.system() == "Windows":
return os.path.exists(container_runtime)
return False
def is_docker_ready(global_common):
# Run `docker image ls` without an error
with global_common.exec_dangerzone_container(["ls"]) as p:
p.communicate()
# The user canceled, or permission denied
if p.returncode == 126 or p.returncode == 127:
raise AuthorizationFailed
# Return true if it succeeds
if p.returncode == 0:
return True
else:
return False
def launch_docker_windows(global_common):
docker_desktop_path = "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"
subprocess.Popen(
[docker_desktop_path], startupinfo=global_common.get_subprocess_startupinfo()
)
class DockerInstaller(QtWidgets.QDialog):
def __init__(self, global_common):
super(DockerInstaller, self).__init__()
self.global_common = global_common
self.setWindowTitle("dangerzone")
self.setWindowIcon(self.global_common.get_window_icon())
# self.setMinimumHeight(170)
label = QtWidgets.QLabel()
if platform.system() == "Darwin":
label.setText("Dangerzone for macOS requires Docker")
elif platform.system() == "Windows":
label.setText("Dangerzone for Windows requires Docker")
label.setStyleSheet("QLabel { font-weight: bold; }")
label.setAlignment(QtCore.Qt.AlignCenter)
self.task_label = QtWidgets.QLabel()
self.task_label.setAlignment(QtCore.Qt.AlignCenter)
self.task_label.setWordWrap(True)
self.task_label.setOpenExternalLinks(True)
self.progress = QtWidgets.QProgressBar()
self.progress.setMinimum(0)
self.open_finder_button = QtWidgets.QPushButton()
if platform.system() == "Darwin":
self.open_finder_button.setText("Show in Finder")
else:
self.open_finder_button.setText("Show in Explorer")
self.open_finder_button.setStyleSheet("QPushButton { font-weight: bold; }")
self.open_finder_button.clicked.connect(self.open_finder_clicked)
self.open_finder_button.hide()
self.cancel_button = QtWidgets.QPushButton("Cancel")
self.cancel_button.clicked.connect(self.cancel_clicked)
self.ok_button = QtWidgets.QPushButton("OK")
self.ok_button.clicked.connect(self.ok_clicked)
buttons_layout = QtWidgets.QHBoxLayout()
buttons_layout.addStretch()
buttons_layout.addWidget(self.open_finder_button)
buttons_layout.addWidget(self.ok_button)
buttons_layout.addWidget(self.cancel_button)
buttons_layout.addStretch()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(label)
layout.addWidget(self.task_label)
layout.addWidget(self.progress)
layout.addLayout(buttons_layout)
layout.addStretch()
self.setLayout(layout)
if platform.system() == "Darwin":
self.installer_filename = os.path.join(
os.path.expanduser("~/Downloads"), "Docker.dmg"
)
else:
self.installer_filename = os.path.join(
os.path.expanduser("~\\Downloads"), "Docker for Windows Installer.exe"
)
# Threads
self.download_t = None
def update_progress(self, value, maximum):
self.progress.setMaximum(maximum)
self.progress.setValue(value)
def update_task_label(self, s):
self.task_label.setText(s)
def download_finished(self):
self.task_label.setText(
"Finished downloading Docker. Install it, make sure it's running, and then open Dangerzone again."
)
self.download_t = None
self.progress.hide()
self.cancel_button.hide()
self.open_finder_path = self.installer_filename
self.open_finder_button.show()
def download_failed(self, status_code):
print(f"Download failed: status code {status_code}")
self.download_t = None
def download(self):
self.task_label.setText("Downloading Docker")
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.start_download)
self.timer.setSingleShot(True)
self.timer.start(10)
def start_download(self):
self.download_t = Downloader(self.installer_filename)
self.download_t.download_finished.connect(self.download_finished)
self.download_t.download_failed.connect(self.download_failed)
self.download_t.update_progress.connect(self.update_progress)
self.download_t.start()
def cancel_clicked(self):
self.reject()
if self.download_t:
self.download_t.quit()
try:
os.remove(self.installer_filename)
except:
pass
def ok_clicked(self):
self.accept()
if self.download_t:
self.download_t.quit()
try:
os.remove(self.installer_filename)
except:
pass
def open_finder_clicked(self):
if platform.system() == "Darwin":
subprocess.call(["open", "-R", self.open_finder_path])
else:
subprocess.Popen(
f'explorer.exe /select,"{self.open_finder_path}"', shell=True
)
self.accept()
def start(self):
if platform.system() == "Darwin":
docker_app_path = "/Applications/Docker.app"
else:
docker_app_path = "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"
if not os.path.exists(docker_app_path):
if platform.system() == "Windows":
self.task_label.setText(
"<a href='https://docs.docker.com/docker-for-windows/install/'>Download Docker</a>, install it, and then run Dangerzone again."
)
self.task_label.setTextFormat(QtCore.Qt.RichText)
self.progress.hide()
self.cancel_button.hide()
else:
self.ok_button.hide()
self.download()
else:
self.task_label.setText(
"Docker is installed, but you must launch it first. Open Docker, make sure it's running, and then open Dangerzone again."
)
self.progress.hide()
self.ok_button.hide()
self.cancel_button.hide()
self.open_finder_path = docker_app_path
self.open_finder_button.show()
return self.exec_() == QtWidgets.QDialog.Accepted
class Downloader(QtCore.QThread):
download_finished = QtCore.Signal()
download_failed = QtCore.Signal(int)
update_progress = QtCore.Signal(int, int)
def __init__(self, installer_filename):
super(Downloader, self).__init__()
self.installer_filename = installer_filename
if platform.system() == "Darwin":
self.installer_url = "https://download.docker.com/mac/stable/Docker.dmg"
elif platform.system() == "Windows":
self.installer_url = "https://download.docker.com/win/stable/Docker%20for%20Windows%20Installer.exe"
def run(self):
print(f"Downloading docker to {self.installer_filename}")
with requests.get(self.installer_url, stream=True) as r:
if r.status_code != 200:
self.download_failed.emit(r.status_code)
return
total_bytes = int(r.headers.get("content-length"))
downloaded_bytes = 0
with open(self.installer_filename, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
if chunk: # filter out keep-alive new chunks
downloaded_bytes += f.write(chunk)
self.update_progress.emit(downloaded_bytes, total_bytes)
self.download_finished.emit()