Skip to content

Commit

Permalink
Add update notification
Browse files Browse the repository at this point in the history
  • Loading branch information
cbenhagen committed Nov 7, 2019
1 parent a9a47e4 commit c125ecb
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 4 deletions.
2 changes: 2 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ verify_ssl = true
pytest = "*"
pytest-cov = "*"
pytest-mock = "*"
requests-mock = "*"

[packages]
lxml = "*"
xxhash = "*"
click = "*"
defusedxml = "*"
requests = "*"

[requires]
python_version = "3.7"
Expand Down
82 changes: 81 additions & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion ocopy/cli/ocopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from typing import List

import click
import pkg_resources

from ocopy.cli.update import Updater
from ocopy.copy import CopyJob
from ocopy.utils import folder_size, get_mount

Expand Down Expand Up @@ -42,6 +42,8 @@ def cli(overwrite: bool, verify: bool, skip_existing: bool, source: str, destina
Copy SOURCE directory to DESTINATIONS
"""
updater = Updater()

size = folder_size(source)
for destination in destinations:
if shutil.disk_usage(destination).free < size:
Expand Down Expand Up @@ -76,6 +78,9 @@ def cli(overwrite: bool, verify: bool, skip_existing: bool, source: str, destina

sys.exit(1)

if updater.needs_update:
click.secho(f"Please update to the latest o/COPY version using `pip3 install -U ocopy`.", fg="blue")


if __name__ == "__main__":
cli() # pragma: no cover
41 changes: 41 additions & 0 deletions ocopy/cli/update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from distutils.version import LooseVersion
from threading import Thread

import pkg_resources
import requests


class Updater(Thread):
def __init__(self):
super().__init__()
self.daemon = True
self.latest_version = None
self.installed_version = None
self.finished = False
self.start()

def run(self):
self._get_latest_version()
self._get_installed_version()
self.finished = True

@property
def needs_update(self) -> bool:
if not self.latest_version or not self.installed_version:
return False

return self.latest_version > self.installed_version

def _get_latest_version(self):
try:
r = requests.get("https://api.github.com/repos/OTTOMATIC-IO/ocopy/releases/latest")
r.raise_for_status()
self.latest_version = LooseVersion(r.json().get("tag_name"))
except requests.exceptions.RequestException:
self.finished = True

def _get_installed_version(self):
try:
self.installed_version = LooseVersion(pkg_resources.get_distribution("ocopy").version)
except pkg_resources.DistributionNotFound:
self.finished = True
11 changes: 9 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,21 @@
description="Securely copy files to multiple destinations using source and destination verification.",
entry_points={"console_scripts": ["ocopy = ocopy.cli.ocopy:cli"]},
include_package_data=True,
install_requires=["click>=7.0", "lxml>=4.4.1", "sh>=1.12.14", "xxhash>=1.4.2", "defusedxml>=0.6.0"],
install_requires=[
"click>=7.0",
"lxml>=4.4.1",
"sh>=1.12.14",
"xxhash>=1.4.2",
"defusedxml>=0.6.0",
"requests>=2.22.0",
],
dependency_links=[],
long_description=long_description,
long_description_content_type="text/markdown",
name="ocopy",
packages=find_packages(),
setup_requires=["pytest-runner", "setuptools_scm"],
tests_require=["pytest"],
tests_require=["pytest", "requests-mock", "pytest-mock"],
url="https://github.com/ottomatic-io/ocopy",
use_scm_version=True,
python_requires="~=3.7",
Expand Down
28 changes: 28 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,24 @@
from pathlib import Path
from shutil import copystat
from time import sleep
from unittest import mock

import pytest
from click.testing import CliRunner

from ocopy.cli.ocopy import cli


@pytest.fixture(scope="session", autouse=True)
def package():
class Distribution:
def __init__(self, _):
self.version = "0.0.1"

with mock.patch("pkg_resources.get_distribution", Distribution) as _fixture:
yield _fixture


def test_help():
runner = CliRunner()
result = runner.invoke(cli, "--help")
Expand Down Expand Up @@ -159,3 +171,19 @@ def fake_folder_size(*args):
assert "Failed to copy" in result.output
assert rename_mock.call_count == 21 # Only good files get renamed
assert unlink_mock.call_count == 24 # Unlink is tried for all temporary files


def test_update(requests_mock, mocker, card):
class Distribution:
def __init__(self, _):
self.version = "0.0.1"

requests_mock.get("https://api.github.com/repos/OTTOMATIC-IO/ocopy/releases/latest", json={"tag_name": "0.6.5"})
mocker.patch("pkg_resources.get_distribution", Distribution, create=True)

src_dir, destinations = card

runner = CliRunner()
result = runner.invoke(cli, [src_dir.as_posix(), *[d.as_posix() for d in destinations]])
assert result.exit_code == 0
assert "update" in result.output
50 changes: 50 additions & 0 deletions tests/test_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import time
from distutils.version import LooseVersion

import requests


def test_updater(requests_mock, mocker):
class Distribution:
def __init__(self, _):
self.version = "0.0.1"

mocker.patch("pkg_resources.get_distribution", Distribution)
requests_mock.get("https://api.github.com/repos/OTTOMATIC-IO/ocopy/releases/latest", json={"tag_name": "0.6.5"})

from importlib import reload
import ocopy.cli.update

reload(ocopy.cli.update)

updater = ocopy.cli.update.Updater()
while not updater.finished:
time.sleep(0.1)

assert updater.latest_version == LooseVersion("0.6.5")
assert updater.installed_version == LooseVersion("0.0.1")
assert updater.needs_update is True


def test_updater_timeout(requests_mock, mocker):
class Distribution:
def __init__(self, _):
self.version = "0.0.1"

mocker.patch("pkg_resources.get_distribution", Distribution)

requests_mock.get(
"https://api.github.com/repos/OTTOMATIC-IO/ocopy/releases/latest", exc=requests.exceptions.ConnectTimeout
)

from importlib import reload
import ocopy.cli.update

reload(ocopy.cli.update)

updater = ocopy.cli.update.Updater()
while not updater.finished:
time.sleep(0.1)

assert updater.latest_version is None
assert updater.needs_update is False

0 comments on commit c125ecb

Please sign in to comment.