Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type hints and refactor most *Common methods to be static or functional #166

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c088951
Support Python 3.10
gmarmstrong May 28, 2022
851124e
Upgrade from PySide2 (Qt 5) to PySide6 (Qt 6)
gmarmstrong May 31, 2022
bd33f16
Fix TypeError in ConvertThread.run
gmarmstrong Jun 2, 2022
fc8d2af
Unwrap QApplication
gmarmstrong Jun 4, 2022
16dfa47
Fix circular import
gmarmstrong Jun 4, 2022
bdc08d7
Fix missing SysTray
gmarmstrong Jun 4, 2022
616ba55
Add more type annotations
gmarmstrong Jun 4, 2022
3006537
Refactor GlobalCommon methods to be static
gmarmstrong Jun 4, 2022
42a1f01
Access methods statically in dangerzone.gui.common
gmarmstrong Jun 4, 2022
17ff3a8
Refactor static methods as functions in util module
gmarmstrong Jun 5, 2022
815c2ef
Fix typo in CONTAINER_COMMAND value
gmarmstrong Jun 6, 2022
4978ab7
Add typing extensions to developer dependencies for mypy support
gmarmstrong Jun 7, 2022
2da6ce0
Make GlobalCommon completely static except for __init__
gmarmstrong Jun 5, 2022
bbce13d
Use pathlib internally in dzutil
gmarmstrong Jun 7, 2022
7eb1023
Inline container.exec into container.exec_container
gmarmstrong Jun 7, 2022
2f48e8a
Remove unused dangerzone.gui.common.Alert
gmarmstrong Jun 7, 2022
519b8fd
Remove all instantiations of GlobalCommon, move Settings to GuiCommon
gmarmstrong Jun 7, 2022
ca5d175
Remove GlobalCommon, convert its methods (all static) to functions
gmarmstrong Jun 7, 2022
ba51647
Delete global_common.py, move its functions to dzutil
gmarmstrong Jun 7, 2022
8eefbef
Run `black dangerzone` to fix up style
gmarmstrong Jun 7, 2022
4fd233e
Undo accidental tweak of platform_args
gmarmstrong Jun 7, 2022
52e5154
Convert relative imports to absolute
gmarmstrong Jun 7, 2022
c655d3a
Have GuiCommon subclass Common
gmarmstrong Jun 7, 2022
1042c51
Move is_container_installed and install_container to container module
gmarmstrong Jun 7, 2022
801d98f
Expand mypy compliance, ignore unsupported packages
gmarmstrong Jun 8, 2022
ce95dff
Don't hardcode "Docker" in help text
gmarmstrong Jun 8, 2022
4e92a22
Use xdg.BaseDirectory in _find_pdf_viewers
gmarmstrong Jun 8, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Install Xcode from the App Store.

Install [Docker Desktop](https://www.docker.com/products/docker-desktop). Make sure to choose your correct CPU, either Intel Chip or Apple Chip.

Install Python 3.9.9 [from python.org](https://www.python.org/downloads/release/python-399/).
Install Python 3.10.4 [from python.org](https://www.python.org/downloads/release/python-3104/).

Install Python dependencies:

Expand Down Expand Up @@ -110,7 +110,7 @@ The output is in the `dist` folder.

Install [Docker Desktop](https://www.docker.com/products/docker-desktop).

Install Python 3.9.9 (x86) [from python.org](https://www.python.org/downloads/release/python-399/). When installing it, make sure to check the "Add Python 3.9 to PATH" checkbox on the first page of the installer.
Install Python 3.10.4 (x86) [from python.org](https://www.python.org/downloads/release/python-3104/). When installing it, make sure to check the "Add Python 3.10 to PATH" checkbox on the first page of the installer.

Install [poetry](https://python-poetry.org/). Open PowerShell, and run:

Expand Down Expand Up @@ -155,7 +155,7 @@ Open a command prompt, cd into the dangerzone directory, and run:
poetry run python .\setup-windows.py build
```

In `build\exe.win32-3.9\` you will find `dangerzone.exe`, `dangerzone-cli.exe`, and all supporting files.
In `build\exe.win32-3.10\` you will find `dangerzone.exe`, `dangerzone-cli.exe`, and all supporting files.

### To build the installer

Expand Down
28 changes: 14 additions & 14 deletions dangerzone/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
import sys
import json
import click
from colorama import Fore, Style
import colorama # type: ignore
from colorama import Fore, Style # type: ignore

from .global_common import GlobalCommon
from .common import Common
from .container import convert
from dangerzone import container
from dangerzone.common import Common
import dangerzone.util as dzutil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing directly to make the code easier to read. This is also done in the securedrop-client.

Suggested change
import dangerzone.util as dzutil
from dangerzone.util import OCR_LANGUAGES

This refactor also needs to be propagated.



def print_header(s):
Expand All @@ -19,10 +20,9 @@ def print_header(s):
@click.option("--ocr-lang", help="Language to OCR, defaults to none")
@click.argument("filename", required=True)
def cli_main(output_filename, ocr_lang, filename):
global_common = GlobalCommon()
colorama.init(autoreset=True)
common = Common()

global_common.display_banner()
dzutil.display_banner()

# Validate filename
valid = True
Expand Down Expand Up @@ -73,23 +73,23 @@ def cli_main(output_filename, ocr_lang, filename):
# Validate OCR language
if ocr_lang:
valid = False
for lang in global_common.ocr_languages:
if global_common.ocr_languages[lang] == ocr_lang:
for lang in dzutil.OCR_LANGUAGES:
if dzutil.OCR_LANGUAGES[lang] == ocr_lang:
valid = True
break
if not valid:
click.echo("Invalid OCR language code. Valid language codes:")
for lang in global_common.ocr_languages:
click.echo(f"{global_common.ocr_languages[lang]}: {lang}")
for lang in dzutil.OCR_LANGUAGES:
click.echo(f"{dzutil.OCR_LANGUAGES[lang]}: {lang}")
return

# Ensure container is installed
global_common.install_container()
container.install_container()

# Convert the document
print_header("Converting document to safe PDF")

def stdout_callback(line):
def stdout_callback(line: str) -> None:
try:
status = json.loads(line)
s = Style.BRIGHT + Fore.CYAN + f"{status['percentage']}% "
Expand All @@ -101,7 +101,7 @@ def stdout_callback(line):
except:
click.echo(f"Invalid JSON returned from container: {line}")

if convert(
if container.convert(
common.input_filename,
common.output_filename,
ocr_lang,
Expand Down
9 changes: 1 addition & 8 deletions dangerzone/common.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import os
import stat
import platform
import tempfile
import appdirs


class Common(object):
"""
The Common class is a singleton of shared functionality throughout an open dangerzone window
The Common class is a singleton of shared functionality throughout a dangerzone process
"""

def __init__(self):
Expand Down
133 changes: 94 additions & 39 deletions dangerzone/container.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
from __future__ import annotations

import gzip
import platform
import subprocess
import pipes
import shutil
import os
import tempfile
import appdirs

# What container tech is used for this platform?
if platform.system() == "Linux":
container_tech = "podman"
else:
# Windows, Darwin, and unknown use docker for now, dangerzone-vm eventually
container_tech = "docker"

# Define startupinfo for subprocesses
if platform.system() == "Windows":
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
else:
startupinfo = None
from typing import Callable

import dangerzone.util as dzutil

# Name of the dangerzone container
container_name = "dangerzone.rocks/dangerzone"


def exec(args, stdout_callback=None):
args_str = " ".join(pipes.quote(s) for s in args)
def exec_container(args, stdout_callback: Callable[[str], None]) -> int:
args = [dzutil.CONTAINER_RUNTIME] + args
args_str = " ".join(pipes.quote(arg) for arg in args)
print("> " + args_str)

with subprocess.Popen(
Expand All @@ -36,35 +24,26 @@ def exec(args, stdout_callback=None):
stderr=subprocess.STDOUT,
bufsize=1,
universal_newlines=True,
startupinfo=startupinfo,
startupinfo=dzutil.get_subprocess_startupinfo(),
) as p:
if stdout_callback:
if p.stdout:
for line in p.stdout:
stdout_callback(line)

p.communicate()
return p.returncode


def exec_container(args, stdout_callback=None):
if container_tech == "podman":
container_runtime = shutil.which("podman")
else:
container_runtime = shutil.which("docker")

args = [container_runtime] + args
return exec(args, stdout_callback)


def convert(input_filename, output_filename, ocr_lang, stdout_callback):
def convert(
input_filename, output_filename, ocr_lang, stdout_callback: Callable[[str], None]
):
success = False

if ocr_lang:
ocr = "1"
else:
ocr = "0"

dz_tmp = os.path.join(appdirs.user_config_dir("dangerzone"), "tmp")
dz_tmp = os.path.join(dzutil.APPDATA_PATH, "tmp")
os.makedirs(dz_tmp, exist_ok=True)

tmpdir = tempfile.TemporaryDirectory(dir=dz_tmp)
Expand All @@ -73,8 +52,9 @@ def convert(input_filename, output_filename, ocr_lang, stdout_callback):
os.makedirs(pixel_dir, exist_ok=True)
os.makedirs(safe_dir, exist_ok=True)

if container_tech == "docker":
platform_args = ["--platform", "linux/amd64"]
platform_args: list[str] = []
if dzutil.CONTAINER_COMMAND == "docker":
platform_args += ["--platform", "linux/amd64"]
else:
platform_args = []

Expand All @@ -87,7 +67,7 @@ def convert(input_filename, output_filename, ocr_lang, stdout_callback):
f"{input_filename}:/tmp/input_file",
"-v",
f"{pixel_dir}:/dangerzone",
container_name,
dzutil.CONTAINER_NAME,
"/usr/bin/python3",
"/usr/local/bin/dangerzone.py",
"document-to-pixels",
Expand All @@ -112,7 +92,7 @@ def convert(input_filename, output_filename, ocr_lang, stdout_callback):
f"OCR={ocr}",
"-e",
f"OCR_LANGUAGE={ocr_lang}",
container_name,
dzutil.CONTAINER_NAME,
"/usr/bin/python3",
"/usr/local/bin/dangerzone.py",
"pixels-to-pdf",
Expand Down Expand Up @@ -210,3 +190,78 @@ def convert(input_filename, output_filename, ocr_lang, stdout_callback):
# return False, f"Page {i} has an invalid RGB file size"

# return True, True
def is_container_installed():
"""
See if the podman container is installed. Linux only.
"""
# Get the image id
with open(dzutil.get_resource_path("image-id.txt")) as f:
expected_image_id = f.read().strip()

# See if this image is already installed
installed = False
found_image_id = subprocess.check_output(
[
dzutil.CONTAINER_RUNTIME,
"image",
"list",
"--format",
"{{.ID}}",
dzutil.CONTAINER_NAME,
],
text=True,
startupinfo=dzutil.get_subprocess_startupinfo(),
)
found_image_id = found_image_id.strip()

if found_image_id == expected_image_id:
installed = True
elif found_image_id == "":
pass
else:
print("Deleting old dangerzone container image")

try:
subprocess.check_output(
[dzutil.CONTAINER_RUNTIME, "rmi", "--force", found_image_id],
startupinfo=dzutil.get_subprocess_startupinfo(),
)
except:
print("Couldn't delete old container image, so leaving it there")

return installed


def install_container():
"""
Make sure the podman container is installed. Linux only.
"""
if is_container_installed():
return

# Load the container into podman
print("Installing Dangerzone container image...")

p = subprocess.Popen(
[dzutil.CONTAINER_RUNTIME, "load"],
stdin=subprocess.PIPE,
startupinfo=dzutil.get_subprocess_startupinfo(),
)

chunk_size = 10240
compressed_container_path = dzutil.get_resource_path("container.tar.gz")
with gzip.open(compressed_container_path) as f:
while True:
chunk = f.read(chunk_size)
if len(chunk) > 0:
p.stdin.write(chunk)
else:
break
p.communicate()

if not is_container_installed():
print("Failed to install the container image")
return False

print("Container image installed")
return True
Loading