Skip to content

Commit

Permalink
Merge branch 'main' into feature/ookla-backend
Browse files Browse the repository at this point in the history
  • Loading branch information
Ketok4321 committed Sep 23, 2023
2 parents 732e7c0 + 8fcc071 commit d5235a5
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 43 deletions.
146 changes: 146 additions & 0 deletions \
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import time
import io
import os
import asyncio
import aiohttp

from urllib.parse import urljoin

DOWNLOAD_SIZE = 100
UPLOAD_SIZE = 20

DURATION = 15
DL_STREAMS = 6
UP_STREAMS = 3

garbage = os.urandom(UPLOAD_SIZE * 1000 * 1000)

class GarbageReader(io.IOBase):
def __init__(self, read_callback=None):
self.__read_callback = read_callback
super().__init__()
self.length = len(garbage)
self.pos = 0

def seekable(self):
return True

def writable(self):
return False

def readable(self):
return True

def tell(self):
return self.pos

def read(self, size=None):
if not size:
size = self.length - self.tell()

old_pos = self.tell()
self.pos = old_pos + size

if self.__read_callback:
self.__read_callback(size)

return garbage[old_pos:self.pos]

class LibrespeedServer:
def __init__(self, name, server, pingURL, dlURL, ulURL, **_):
if not (server.startswith("https:") or server.startswith("http:")):
server = "https:" + server

self.name = name
self.server = server
self.pingURL = urljoin(server + "/", pingURL)
self.downloadURL = urljoin(server + "/", dlURL)
self.uploadURL = urljoin(server + "/", ulURL)

class LibrespeedBackend:
def __init__(self, user_agent):
self.headers = {
"Accept-Encoding": "identity",
"User-Agent": user_agent,
}

async def get_servers(self): #TODO: Change how this works to take more time on worse connections and less on better ones, while returning the same amount of servers in both cases
async with aiohttp.ClientSession() as session:
async def check_server(server, results):
try:
start = time.time()
async with session.get(server.pingURL, timeout=aiohttp.ClientTimeout(total=1.0)) as _:
results.append((time.time() - start, server))
except (aiohttp.ClientError, asyncio.TimeoutError):
pass

async with session.get("https://librespeed.org/backend-servers/servers.php") as response:
servers = await response.json()
servers = list(map(lambda x: LibrespeedServer(**x), servers))

results = []

task = asyncio.gather(*[check_server(s, results) for s in servers])

while len(results) < 20 and not task.done():
await asyncio.sleep(0)

results.sort(key=lambda t: t[0])

return [s for _, s in results]

async def start(self, server, res, notify):
async def perform_test(test, streams, res):
tasks = []

timeout = asyncio.create_task(asyncio.sleep(DURATION))

for _ in range(streams):
tasks.append(asyncio.create_task(test(server, res)))
await asyncio.sleep(0.3)

await timeout

for t in tasks:
t.cancel()

res.ping, res.jitter = await self.ping(server)
notify("ping")

notify("download_start")
await perform_test(self.download, DL_STREAMS, res)
notify("download_end")

notify("upload_start")
await perform_test(self.upload, UP_STREAMS, res)
notify("upload_end")

async def ping(self, server):
async with aiohttp.ClientSession() as session:
pings = []
jitters = []
for i in range(10):
start = time.time()
async with session.get(server.pingURL, headers=self.headers) as _:
pings.append(time.time() - start)

if i != 0:
jitters.append(abs(pings[i] - pings[i - 1]))
return sum(pings) / len(pings) * 1000, sum(jitters) / len(jitters) * 1000

async def download(self, server, res):
async with aiohttp.ClientSession() as session:
while True:
async with session.get(server.downloadURL + "?ckSize=" + str(DOWNLOAD_SIZE), headers=self.headers) as response:
async for data in response.content.iter_any():
res.total_dl += len(data)

async def upload(self, server, res):
def callback(size):
res.total_up += size

async with aiohttp.ClientSession() as session:
while True:
reader = GarbageReader(callback)
async with session.post(server.uploadURL, headers=self.headers, data=reader) as response:
await response.read()
6 changes: 4 additions & 2 deletions po/POTFILES
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
data/xyz.ketok.Speedtest.desktop.in
data/xyz.ketok.Speedtest.metainfo.xml.in
data/xyz.ketok.Speedtest.gschema.xml
src/ui/views/offline.blp
src/ui/views/start.blp
src/ui/views/test.blp
src/ui/window.blp
src/main.py
src/window.py
src/window.ui
84 changes: 84 additions & 0 deletions po/speedtest.pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the speedtest package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: speedtest\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-22 18:20+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: data/xyz.ketok.Speedtest.desktop.in:3
#: data/xyz.ketok.Speedtest.metainfo.xml.in:5 src/ui/views/start.blp:9
#: src/ui/window.blp:8 src/main.py:63
msgid "Speedtest"
msgstr ""

#: data/xyz.ketok.Speedtest.metainfo.xml.in:6 src/ui/views/start.blp:10
msgid "Measure your internet connection speed"
msgstr ""

#: data/xyz.ketok.Speedtest.metainfo.xml.in:24
msgid "Desktop client for librespeed using gtk4+libadwaita"
msgstr ""

#: data/xyz.ketok.Speedtest.metainfo.xml.in:37
msgid "Improve the icon's contrast with the flathub website"
msgstr ""

#: data/xyz.ketok.Speedtest.metainfo.xml.in:38
msgid "Internal improvements"
msgstr ""

#: data/xyz.ketok.Speedtest.metainfo.xml.in:45
msgid "Initial release"
msgstr ""

#: src/ui/views/offline.blp:9
msgid "Couldn't connect to the speedtest servers"
msgstr ""

#: src/ui/views/offline.blp:10
msgid "Make sure you are connected to the internet"
msgstr ""

#: src/ui/views/offline.blp:17
msgid "Retry"
msgstr ""

#: src/ui/views/start.blp:28
msgid "Start"
msgstr ""

#: src/ui/views/test.blp:25
msgid "Ping:"
msgstr ""

#: src/ui/views/test.blp:39
msgid "Jitter:"
msgstr ""

#: src/ui/views/test.blp:66
msgid "Download:"
msgstr ""

#: src/ui/views/test.blp:73
msgid "Upload:"
msgstr ""

#: src/ui/window.blp:50
msgid "About Speedtest"
msgstr ""

#: src/main.py:72
msgid "Backend by"
msgstr ""
23 changes: 13 additions & 10 deletions src/backends/librespeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,30 @@ def __init__(self, user_agent):
"User-Agent": user_agent,
}

async def get_servers(self): #TODO: Change how this works to take more time on worse connections and less on better ones, while returning the same amount of servers in both cases
async def get_servers(self):
async with aiohttp.ClientSession() as session:
async def check_server(server):
async def check_server(server, results):
try:
start = time.time()
async with session.get(server.pingURL, timeout=aiohttp.ClientTimeout(total=0.75)) as _:
return time.time() - start
async with session.get(server.pingURL, timeout=aiohttp.ClientTimeout(total=2.0)) as _:
results.append((server, time.time() - start))
except (aiohttp.ClientError, asyncio.TimeoutError):
return -1
pass

async with session.get("https://librespeed.org/backend-servers/servers.php") as response:
servers = await response.json()
servers = list(map(lambda x: LibrespeedServer(**x), servers))

pings = await asyncio.gather(*[check_server(s) for s in servers])
results = []

task = asyncio.gather(*[check_server(s, results) for s in servers])

servers = list(zip(pings, servers))
servers.sort(key=lambda t: t[0])
servers = [s for p, s in servers if p != -1]
while len(results) < 15 and not task.done():
await asyncio.sleep(0)

results.sort(key=lambda t: t[1])

return servers
return [s for s, _ in results]

async def start(self, server, res, notify):
async def perform_test(test, streams, res):
Expand Down
21 changes: 13 additions & 8 deletions src/gauge.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,34 @@ def __init__(self, **kwargs):
def draw_background(self, da, ctx, width, height):
IS_LIGHT = not Adw.StyleManager.get_default().get_dark()

ARC_SIZE = min(width, height) * 0.9
ARC_SIZE = min(width, height) * 0.89

ARC_START = 0.75 * math.pi
ARC_LENGTH = 1.5 * math.pi
ARC_START = 0.8 * math.pi
ARC_LENGTH = 1.4 * math.pi

ARC_THICKNESS = ARC_SIZE * 0.125
ARC_THICKNESS = ARC_SIZE * 0.115

ARC_CENTER = height / 2 + ARC_THICKNESS / 2

UNFILLED_COLOR = 0, 0, 0, 0.1 if IS_LIGHT else 0.3

ctx.set_line_width(ARC_THICKNESS)
ctx.set_line_cap(cairo.LINE_CAP_ROUND)

ctx.set_source_rgba(*UNFILLED_COLOR)
ctx.arc(width / 2, ARC_CENTER, ARC_SIZE / 2, ARC_START, ARC_START + ARC_LENGTH)
ctx.stroke()

def draw_filled(self, da, ctx, width, height):
ARC_SIZE = min(width, height) * 0.9
if self.fill <= 0.01:
return

ARC_START = 0.75 * math.pi
ARC_LENGTH = 1.5 * math.pi
ARC_SIZE = min(width, height) * 0.89

ARC_THICKNESS = ARC_SIZE * 0.125
ARC_START = 0.8 * math.pi
ARC_LENGTH = 1.4 * math.pi

ARC_THICKNESS = ARC_SIZE * 0.115

ARC_CENTER = height / 2 + ARC_THICKNESS / 2

Expand All @@ -70,6 +74,7 @@ def gdk_color_to_tuple(color):
FILLED_COLOR_2 = gdk_color_to_tuple(self.gradient_2.get_style_context().get_color())

ctx.set_line_width(ARC_THICKNESS)
ctx.set_line_cap(cairo.LINE_CAP_ROUND)

filled = cairo.LinearGradient(0.0, 0.0, 0.0, height)
filled.add_color_stop_rgba(height, *FILLED_COLOR_1)
Expand Down
7 changes: 4 additions & 3 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
gi.require_foreign("cairo")

from gi.repository import GLib, Gio, Gtk, Adw
from gettext import gettext as _

from .window import SpeedtestWindow
from .gauge import Gauge # This class isn't used there but it the widget needs to be registered
Expand Down Expand Up @@ -57,9 +58,9 @@ def fetch_servers(self):
print(e)
GLib.idle_add(self.win.set_view, self.win.offline_view)

def on_about_action(self, widget, _):
def on_about_action(self, widget, __):
about = Adw.AboutWindow(transient_for=self.props.active_window,
application_name="Speedtest",
application_name=_("Speedtest"),
application_icon="xyz.ketok.Speedtest",
developer_name="Ketok",
version=self.version,
Expand All @@ -68,7 +69,7 @@ def on_about_action(self, widget, _):
copyright="© 2023 Ketok",
license_type=Gtk.License.GPL_3_0)

about.add_credit_section("Backend by", ["Librespeed"])
about.add_credit_section(_("Backend by"), ["Librespeed"])

about.present()

Expand Down
1 change: 1 addition & 0 deletions src/ui/gauge.blp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ template $Gauge : Box {
orientation: vertical;
valign: end;
halign: center;
margin-bottom: 24;

Label {
valign: center;
Expand Down
8 changes: 4 additions & 4 deletions src/ui/views/offline.blp
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ template $OfflineView : Box {
valign: center;

Adw.StatusPage {
title: "Couldn't connect to the speedtest servers";
description: "Make sure you are connected to the internet";
title: _("Couldn't connect to the speedtest servers");
description: _("Make sure you are connected to the internet");
icon-name: "network-offline-symbolic";
styles [ "compact" ]

Button {
halign: center;

label: "Retry";
label: _("Retry");
action-name: "app.retry_connect";
styles [ "pill", "suggested-action" ]
}
}
}
}
Loading

0 comments on commit d5235a5

Please sign in to comment.