From 555508ada327932270ce268662cd979655b61a91 Mon Sep 17 00:00:00 2001 From: uburuntu Date: Thu, 15 Oct 2020 02:31:01 +0300 Subject: [PATCH] new: github actions --- .github/workflows/build.yml | 77 +++++++++++++++++++++++++++++++++++++ .gitignore | 2 +- dmonitor/build/hook.py | 3 ++ dmonitor/build/make_hook.py | 14 +++++++ dmonitor/config.py | 2 +- dmonitor/main.py | 43 +++++++++++++-------- dmonitor/pinger.py | 10 ++--- dmonitor/utils.py | 58 ++++++++++++++++++++++++++++ readme.md | 13 ++++--- requirements.txt | 8 ++-- 10 files changed, 197 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 dmonitor/build/hook.py create mode 100644 dmonitor/build/make_hook.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..d6173fb --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,77 @@ +name: Build executables + +on: + push: + branches: [ main ] + + release: + types: [ published ] + + pull_request: + +jobs: + build: + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + include: + - os: ubuntu-20.04 + artifact_name: DMonitor + asset_name: DMonitorLinux + - os: windows-latest + artifact_name: DMonitor.exe + asset_name: DMonitorWin.exe + - os: macos-latest + artifact_name: DMonitor.zip + asset_name: DMonitorMacOS.zip + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install System Dependencies + if: startsWith(matrix.os, 'ubuntu') + run: | + pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-20.04 wxPython + + - name: Install Dependencies + run: | + pip install -U pip wheel + pip install -r requirements.txt + pip install -r requirements-dev.txt + + - name: Build Artifact + env: + DMONITOR_STATHAT_KEY: ${{ secrets.DMONITOR_STATHAT_KEY }} + run: | + cd dmonitor/build + python make_hook.py + pyinstaller --noconsole --noconfirm --clean --onefile --name DMonitor --paths .. --icon icon.ico --runtime-hook hook.py ../main.py + + - name: Zip Executable + if: startsWith(matrix.os, 'macos') + run: | + cd dmonitor/build/dist + zip -r -X DMonitor.zip DMonitor.app + rm -rf DMonitor.app + + - name: Upload a Build Artifact + uses: actions/upload-artifact@v2 + with: + path: dmonitor/build/dist/${{ matrix.artifact_name }} + + - name: Upload Binaries to Release + if: github.event_name == 'release' + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ github.ref }} + file: dmonitor/build/dist/${{ matrix.artifact_name }} + asset_name: ${{ matrix.asset_name }} + overwrite: true diff --git a/.gitignore b/.gitignore index 6ff869b..3efd499 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ __pycache__/ # Distribution / packaging .Python -# build/ +build/ develop-eggs/ dist/ downloads/ diff --git a/dmonitor/build/hook.py b/dmonitor/build/hook.py new file mode 100644 index 0000000..b4efdfc --- /dev/null +++ b/dmonitor/build/hook.py @@ -0,0 +1,3 @@ +import os + +os.environ['DMONITOR_STATHAT_KEY'] = '' diff --git a/dmonitor/build/make_hook.py b/dmonitor/build/make_hook.py new file mode 100644 index 0000000..0cf0c3f --- /dev/null +++ b/dmonitor/build/make_hook.py @@ -0,0 +1,14 @@ +import os +from pathlib import Path + + +def path(filename: str) -> Path: + return (Path(__file__).parent / filename).absolute() + + +hook = f'''import os + +os.environ['DMONITOR_STATHAT_KEY'] = '{os.getenv('DMONITOR_STATHAT_KEY', '')}' +''' + +path('hook.py').write_text(hook, encoding='utf-8') diff --git a/dmonitor/config.py b/dmonitor/config.py index 50290a1..2b3a433 100644 --- a/dmonitor/config.py +++ b/dmonitor/config.py @@ -2,7 +2,7 @@ stathat_key = os.getenv('DMONITOR_STATHAT_KEY') -if stathat_key is None: +if not stathat_key: raise Exception('You should set `DMONITOR_STATHAT_KEY` environment variable') domains = ('google.com', 'yandex.ru', 'vk.com') diff --git a/dmonitor/main.py b/dmonitor/main.py index d7c0a2a..444331e 100644 --- a/dmonitor/main.py +++ b/dmonitor/main.py @@ -3,38 +3,51 @@ import PySimpleGUIWx as sg import config - from pinger import Pinger from stathat import StatHat -from utils import Timer +from utils import Timer, CheckMyIP def main(): - updates_interval = 60. - timer = Timer(updates_interval).start() - timer_notification = Timer(30 * 60.) + timer = Timer(1 * 60.).start() stathat = StatHat(config.stathat_key) pinger = Pinger(stathat) + ip_checker = CheckMyIP() + + if ip_checker.get_ip() is None: + return sg.popup_ok('Первый запуск программы должен быть при работающем интернете', icon=config.icon) + + timer_notification_1 = Timer(30 * 60.) + timer_notification_2 = Timer(30 * 60.) menu = ['UNUSED', ['Информация', '---', 'Закрыть']] last_send = 'Данные еще не отправлялись' tooltip = 'Мониторинг интернета в общежитии ГЗ' tray = sg.SystemTray(menu=menu, tooltip=f'{tooltip}\n\n{last_send}', data_base64=config.icon) - while True: - event = tray.read(timeout=updates_interval * 1000) + sg.popup_no_wait('Мониторинг интернета запущен, выгрузка статистики начнётся через 1 минуту.\n\n' + 'Это окно можно закрыть, я продолжу работать в фоновом режиме.\n\n' + 'Спасибо за участие!', icon=config.icon) - if event == 'Закрыть': - break + while True: + event = tray.read(timeout=1000) if timer.acquire(): - ok = pinger.analyze() - if ok: - last_send = f'Последняя отправка данных: {time.ctime()}' - tray.update(tooltip=f'{tooltip}\n\n{last_send}') + if ip_checker.check(): + ok = pinger.analyze() + if ok: + last_send = f'Последняя отправка данных: {time.ctime()}' + tray.update(tooltip=f'{tooltip}\n\n{last_send}') + else: + if timer_notification_1.acquire(): + sg.popup_no_wait(f'Проблема с доступом к одному из сайтов: {", ".join(config.domains)}', icon=config.icon) else: - if timer_notification.acquire(): - sg.popup_no_wait(f'Проблема с доступом к одному из сайтов: {", ".join(config.domains)}', icon=config.icon) + if timer_notification_2.acquire(): + sg.popup_no_wait('Кажется, вы подключены не к сети Главного здания.\n\n' + 'Выгрузка статистики приостановлена до переподключения к ней.', icon=config.icon) + + if event == 'Закрыть': + break if event == 'Информация': sg.popup_no_wait(f'{config.text_about}\n\n{last_send}', icon=config.icon) diff --git a/dmonitor/pinger.py b/dmonitor/pinger.py index 260fd9c..fb27459 100644 --- a/dmonitor/pinger.py +++ b/dmonitor/pinger.py @@ -1,21 +1,19 @@ import json import time -from pathlib import Path from typing import Tuple +import ping3 + import config from stathat import StatHat -import ping3 +from utils import project_path class Pinger: def __init__(self, stathat: StatHat): self.stathat = stathat self.data = {} - self.data_file = Path.home() / Path('.dmonitor/data.json') - - self.data_file.parent.mkdir(parents=True, exist_ok=True) - self.data_file.touch() + self.data_file = project_path('data.json') self.load() def load(self): diff --git a/dmonitor/utils.py b/dmonitor/utils.py index 5827bf5..dd333f2 100644 --- a/dmonitor/utils.py +++ b/dmonitor/utils.py @@ -1,4 +1,55 @@ +import ipaddress import time +from pathlib import Path +from typing import Optional + +import requests + + +class CheckMyIP: + ip_start = '85.89.126.0' + ip_end = '85.89.127.255' + + def __init__(self): + self.session = requests.Session() + self.ip_file = project_path('ip.txt') + + def extract_ip(self) -> Optional[ipaddress.IPv4Address]: + return self.parse_ip(self.ip_file.read_text(encoding='utf-8')) + + def request_ip(self) -> Optional[ipaddress.IPv4Address]: + try: + response = self.session.get('https://checkip.amazonaws.com') + response.raise_for_status() + except Exception: + return None + + ip = self.parse_ip(response.text.strip()) + if ip is not None: + self.ip_file.write_text(str(ip), encoding='utf-8') + return ip + + @staticmethod + def parse_ip(ip: str) -> Optional[ipaddress.IPv4Address]: + try: + ip = ipaddress.ip_address(ip) + except ValueError: + return None + return ip + + def get_ip(self) -> Optional[ipaddress.IPv4Address]: + ip = self.request_ip() + if ip is None: + ip = self.extract_ip() + if ip is None: + return None + return ip + + def check(self) -> bool: + ip = self.get_ip() + if ip is None: + return False + return ipaddress.ip_address(self.ip_start) <= ip <= ipaddress.ip_address(self.ip_end) class Timer: @@ -16,3 +67,10 @@ def acquire(self): return False self.last = curr_ts return True + + +def project_path(name: str) -> Path: + file = Path.home() / Path(f'.dmonitor/{name}') + file.parent.mkdir(parents=True, exist_ok=True) + file.touch() + return file diff --git a/readme.md b/readme.md index f73a7e7..7e774f1 100644 --- a/readme.md +++ b/readme.md @@ -1,9 +1,10 @@ -# dmonitor +# DMonitor -Программа для мониторинга интернета в Главном здании МГУ. +#### Программа для мониторинга интернета в Главном здании МГУ -# Build +> ![](https://i.imgur.com/8fSJOVs.jpg) -``` -pyinstaller --noconsole -y --clean --onefile --name DMonitor main.py -``` +#### Графики + +https://www.stathat.com/cards/YSY4cWqWLV44 +> ![](https://i.imgur.com/6iNytqJ.jpg) diff --git a/requirements.txt b/requirements.txt index 0c949f7..ed2ad9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ping3==2.6 -PySimpleGUIQt==0.17 -wxPython==4.1 -requests==2.24 +ping3>=2.6 +PySimpleGUIWx>=0.17 +wxPython>=4.1 +requests>=2.24