Skip to content

Commit

Permalink
new: console version
Browse files Browse the repository at this point in the history
  • Loading branch information
uburuntu committed Oct 20, 2020
1 parent 0e5d292 commit 09ba14f
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 23 deletions.
37 changes: 32 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,24 @@ jobs:
include:
- os: ubuntu-20.04
artifact_name: DMonitor
asset_name: DMonitorLinux
asset_name: DMonitor-Linux
- os: ubuntu-20.04
artifact_name: DMonitor-Console
asset_name: DMonitor-Console-Linux

- os: windows-latest
artifact_name: DMonitor.exe
asset_name: DMonitorWin.exe
asset_name: DMonitor-Windows.exe
- os: windows-latest
artifact_name: DMonitor-Console.exe
asset_name: DMonitor-Console-Windows.exe

- os: macos-latest
artifact_name: DMonitor.zip
asset_name: DMonitorMacOS.zip
asset_name: DMonitor-MacOS.zip
- os: macos-latest
artifact_name: DMonitor-Console.zip
asset_name: DMonitor-Console-MacOS.zip

steps:
- uses: actions/checkout@v2
Expand All @@ -46,19 +57,35 @@ jobs:
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Build Artifact
- name: Build GUI Artifact
if: contains(matrix.asset_name, 'Console') == false
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: Build Console Artifact
if: contains(matrix.asset_name, 'Console')
env:
DMONITOR_STATHAT_KEY: ${{ secrets.DMONITOR_STATHAT_KEY }}
run: |
cd dmonitor/build
python make_hook.py
pyinstaller --console --noconfirm --clean --onefile --name DMonitor-Console --paths .. --icon icon.ico --runtime-hook hook.py ../main_console.py
- name: Zip Executable
if: startsWith(matrix.os, 'macos')
run: |
cd dmonitor/build/dist
zip -r -X DMonitor.zip DMonitor.app
zip -r -X ${{ matrix.artifact_name }} .
- name: Make chmod +x
if: startsWith(matrix.os, 'ubuntu')
run: |
cd dmonitor/build/dist
chmod +x ${{ matrix.artifact_name }}
- name: Upload a Build Artifact
uses: actions/upload-artifact@v2
Expand Down
45 changes: 30 additions & 15 deletions dmonitor/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import config
from pinger import Pinger
from stathat import StatHat
from utils import Timer, CheckMyIP
from utils import Timer, CheckMyIP, SingleInstance


def popup(text: str, blocking: bool = False, copiable: bool = False):
text = sg.MultilineOutput(text, size=(500, 300)) if copiable else sg.Text(text)
text = sg.MultilineOutput(text, size=(550, 300)) if copiable else sg.Text(text)
button = sg.Button if blocking else sg.DummyButton

window = sg.Window(
Expand All @@ -31,25 +31,34 @@ def popup(text: str, blocking: bool = False, copiable: bool = False):
def main():
sg.theme('DarkAmber')

s = SingleInstance()
if not s.try_lock():
return popup('Одновременно может быть запущена только одна версия программы', blocking=True)

timer = Timer(1 * 60.).start()
stathat = StatHat(config.stathat_key)
pinger = Pinger(stathat)
ip_checker = CheckMyIP()

try:
pinger.ping()
except PermissionError:
return popup('Нет доступа к сокетам, есть два варианта:\n'
'— запускайте программу с sudo\n'
'— дать разрешение программе ловить пакеты: sudo setcap cap_net_raw+ep ./DMonitor-Linux',
copiable=True, blocking=True)

if ip_checker.get_ip() is None:
return popup('Первый запуск программы должен быть при работающем интернете', blocking=True)

timer_notification_1 = Timer(60 * 60.)
timer_notification_2 = Timer(60 * 60.)
timer_notify_lost = Timer(60 * 60.)
timer_notify_net = Timer(60 * 60.)

menu = ['UNUSED', ['Информация', '---', 'Закрыть']]
last_send = 'Данные еще не отправлялись'
tooltip = 'Мониторинг интернета в общежитиях МГУ'
tray = sg.SystemTray(menu=menu, tooltip=f'{tooltip}\n\n{last_send}.', data_base64=config.icon)

popup('Мониторинг интернета запущен, выгрузка статистики начнётся через 1 минуту.\n\n'
'Это окно можно закрыть, я продолжу работать в фоновом режиме.\n\n'
'Спасибо за участие!')
tray = sg.SystemTray(menu=menu, tooltip=f'{tooltip}\n\n{last_send}', data_base64=config.icon)
tray.show_message('Мониторинг интернета запущен', 'Выгрузка статистики начнётся через 1 минуту.\n\nСпасибо за участие!', time=3000)

try:
while True:
Expand All @@ -61,13 +70,18 @@ def main():
if ok:
last_send = f'Последняя отправка данных: {time.ctime()}'
tray.update(tooltip=f'{tooltip}\n\n{last_send}')

timer_notify_lost.reset()
else:
if timer_notification_1.acquire():
popup(f'Проблема с доступом к одному из сайтов: {", ".join(config.domains)}.\n\n'
f'Информация об этом сохранена на диск и будет отправлена при первой возможности.')
if timer_notify_lost.acquire():
tray.show_message('Потерян доступ в интернет',
'Информация об этом сохранена и будет отправлена при первой возможности.')

timer_notify_net.reset()
else:
if timer_notification_2.acquire():
popup('Кажется, вы подключены не к сети МГУ.\n\nВыгрузка статистики приостановлена до переподключения к ней.')
if timer_notify_net.acquire():
tray.show_message('Текущее подключение не в сети МГУ',
'Выгрузка статистики приостановлена до переподключения к ней.')

if event == 'Закрыть':
break
Expand All @@ -76,7 +90,8 @@ def main():
popup(f'{config.text_about}\n\n{last_send}.', copiable=True)

except Exception as e:
popup(f'Exception: {repr(e)}\n\nTraceback: {traceback.format_exc()}', blocking=True)
popup(f'Exception: {repr(e)}\n\nTraceback: {traceback.format_exc()}\n\n'
f'You can send this logs to t.me/rm_bk, thanks.', copiable=True, blocking=True)
finally:
tray.close()

Expand Down
77 changes: 77 additions & 0 deletions dmonitor/main_console.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import logging
import sys
import traceback

from throttler import ExecutionTimer

import config
from pinger import Pinger
from stathat import StatHat
from utils import Timer, CheckMyIP, SingleInstance

logging.basicConfig(format='[%(asctime)s] [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=logging.INFO)


def close(status: int):
input('Нажмите Enter, чтобы закрыть программу: ')
sys.exit(status)


def main():
s = SingleInstance()
if not s.try_lock():
logging.critical('Одновременно может быть запущена только одна версия программы')
return close(1)

timer = Timer(1 * 60.).start()
stathat = StatHat(config.stathat_key)
pinger = Pinger(stathat)
ip_checker = CheckMyIP()
et = ExecutionTimer(60., align_sleep=True)

try:
pinger.ping()
except PermissionError:
return logging.critical('Нет доступа к сокетам, есть два варианта:\n'
'— запускайте программу с sudo\n'
'— единоразово дайте разрешение программе ловить пакеты: '
'sudo setcap cap_net_raw+ep ./DMonitor-Console-Linux')

if ip_checker.get_ip() is None:
logging.critical('Первый запуск программы должен быть при работающем интернете')
return close(2)

timer_notify_lost = Timer(30 * 60.)
timer_notify_net = Timer(30 * 60.)

logging.info('Мониторинг интернета запущен, выгрузка статистики начнётся через 1 минуту. Спасибо за участие!')

try:
while True:
with et:
if timer.acquire():
if provider := ip_checker.provider():
ok = pinger.analyze(provider)
if ok:
timer_notify_lost.reset()
else:
if timer_notify_lost.acquire():
logging.warning(f'Проблема с доступом к одному из сайтов: {", ".join(config.domains)}. '
f'Информация об этом сохранена на диск и будет отправлена при первой возможности.')

timer_notify_net.reset()
else:
if timer_notify_net.acquire():
logging.warning('Кажется, вы подключены не к сети МГУ. '
'Выгрузка статистики приостановлена до переподключения к ней.')

except (KeyboardInterrupt, SystemExit):
logging.info('Bye!')
sys.exit(0)
except Exception as e:
logging.error(f'\nException: {repr(e)}\n\nTraceback: {traceback.format_exc()}\n\nYou can send this logs to t.me/rm_bk, thanks.')
return close(3)


if __name__ == '__main__':
main()
51 changes: 51 additions & 0 deletions dmonitor/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import ipaddress
import os
import sys
import time
from enum import Enum
from pathlib import Path
from typing import Optional

import requests

if sys.platform != 'win32':
import fcntl


class IPNetworks(Enum):
umos = (
Expand Down Expand Up @@ -82,6 +87,10 @@ def start(self):
self.last = time.monotonic()
return self

def reset(self):
self.last = 0
return self

def acquire(self):
curr_ts = time.monotonic()
if self.last + self.interval > curr_ts:
Expand All @@ -90,6 +99,48 @@ def acquire(self):
return True


class SingleInstance:
def __init__(self, lock_file: Path = None):
self.locked = False
self.lock_file = lock_file or project_path('lock')
self.fd = None

def try_lock(self) -> bool:
if sys.platform == 'win32':
try:
self.lock_file.unlink(missing_ok=True)
self.fd = os.open(str(self.lock_file), os.O_CREAT | os.O_EXCL | os.O_RDWR)
except OSError:
return False

else:
self.fd = self.lock_file.open('w')
self.fd.flush()
try:
fcntl.lockf(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
return False

self.locked = True
return True

def unlock(self):
if not self.locked:
return

if sys.platform != 'win32':
fcntl.lockf(self.fd, fcntl.LOCK_UN)

if self.fd:
os.close(self.fd)

self.lock_file.unlink(missing_ok=True)
self.locked = False

def __del__(self):
self.unlock()


def project_path(name: str) -> Path:
file = Path.home() / Path(f'.dmonitor/{name}')
file.parent.mkdir(parents=True, exist_ok=True)
Expand Down
14 changes: 11 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,29 @@ https://internet.msut.me

https://github.com/uburuntu/dmonitor/releases/latest

### Тонкости Linux версии

Есть два варианта запуска:
- запускать с `sudo`
- единоразово дать разрешение программе трогать сокеты:
- `sudo setcap cap_net_raw+ep ./DMonitor-Linux` для GUI версии
- `sudo setcap cap_net_raw+ep ./DMonitor-Console-Linux` для консольной версии

### Настроить автозапуск

Для запуска программы при перезагрузке системы можно воспользоваться [скриптами](https://github.com/uburuntu/dmonitor/autorun_scripts) (потребуются администраторские права).

#### Windows

- Запустите `dmonitor_autorun.bat` от имени администратора
- Введите путь к `DMonitorWin.exe`
- Введите путь к `DMonitor-Windows.exe`

#### Linux

- Запустите `dmonitor_autorun.sh`
- Введите путь к `DMonitorLinux`
- Введите путь к `DMonitor-Linux`

#### MacOS

- Запустите `dmonitor_autorun.bash`
- Введите путь к `DMonitorMacOS.zip`
- Введите путь к `DMonitor-MacOS.zip`
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ ping3>=2.6
PySimpleGUIWx>=0.17
wxPython>=4.1
requests>=2.24
throttler>=1.2

0 comments on commit 09ba14f

Please sign in to comment.