Added repository server and APK sharing

This commit is contained in:
Mark Qvist 2023-09-20 20:12:47 +02:00
parent 9c802e0b70
commit 26aa9dc1bf
5 changed files with 312 additions and 6 deletions

View File

@ -6,6 +6,8 @@ clean:
@echo Cleaning...
-(rm ./__pycache__ -r)
-(rm ./app_storage -r)
-(rm ./share/pkg/* -r)
-(rm ./share/mirrors/* -r)
-(rm ./bin -r)
cleanlibs:
@ -53,15 +55,26 @@ else
@(sleep 2)
endif
fetchshare:
cp ../../dist_archive/rns-*-py3-none-any.whl ./share/pkg/
cp ../../dist_archive/rnspure-*-py3-none-any.whl ./share/pkg/
cp ../../dist_archive/lxmf-*-py3-none-any.whl ./share/pkg/
cp ../../dist_archive/nomadnet-*-py3-none-any.whl ./share/pkg/
cp ../../dist_archive/rnsh-*-py3-none-any.whl ./share/pkg/
cp ../../dist_archive/RNode_Firmware_1.64_Source.zip ./share/pkg/
cp -r ../../dist_archive/reticulum.network ./share/mirrors/
cp ../../dist_archive/Reticulum\ Manual.pdf ./share/mirrors/Reticulum_Manual.pdf
cp ../../dist_archive/Reticulum\ Manual.epub ./share/mirrors/Reticulum_Manual.epub
release:
buildozer android release
postbuild:
$(MAKE) cleanrns
apk: prepare prebake pacthfiles release postbuild
apk: prepare prebake pacthfiles fetchshare release postbuild
devapk: prepare prebake pacthfiles debug postbuild
devapk: prepare prebake pacthfiles fetchshare debug postbuild
version:
@(echo $$(python ./gv.py))

View File

@ -4,13 +4,13 @@ package.name = sideband
package.domain = io.unsigned
source.dir = .
source.include_exts = py,png,jpg,jpeg,ttf,kv,pyi,typed,so,0,1,2,3,atlas,frag
source.include_patterns = assets/*
source.include_exts = py,png,jpg,jpeg,webp,ttf,kv,pyi,typed,so,0,1,2,3,atlas,frag,html,css,js,whl,zip,gz,woff2,pdf,epub
source.include_patterns = assets/*,share/*
source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements,precompiled/*,parked/*,./setup.py,Makef*,./Makefile,Makefile
version.regex = __version__ = ['"](.*)['"]
version.filename = %(source.dir)s/main.py
android.numeric_version = 20230912
android.numeric_version = 20230920
# Cryptography recipe is currently broken, using RNS-internal crypto for now
requirements = kivy==2.2.1,libbz2,pillow,qrcode==7.3.1,usb4a,usbserial4a
@ -25,7 +25,7 @@ android.presplash_color = #00000000
orientation = portrait
fullscreen = 0
android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE,BLUETOOTH_CONNECT
android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE,BLUETOOTH_CONNECT,ACCESS_NETWORK_STATE
android.api = 30
android.minapi = 24
android.ndk = 25b

View File

@ -110,6 +110,7 @@ class SidebandApp(MDApp):
self.settings_ready = False
self.connectivity_ready = False
self.hardware_ready = False
self.repository_ready = False
self.hardware_rnode_ready = False
self.hardware_modem_ready = False
self.hardware_serial_ready = False
@ -1673,6 +1674,139 @@ class SidebandApp(MDApp):
def close_connectivity_action(self, sender=None):
self.open_conversations(direction="right")
### Repository screen
######################################
def repository_action(self, sender=None, direction="left"):
self.repository_init()
self.root.ids.screen_manager.transition.direction = direction
self.root.ids.screen_manager.current = "repository_screen"
self.root.ids.nav_drawer.set_state("closed")
if not RNS.vendor.platformutils.is_android():
self.widget_hide(self.root.ids.repository_enable_button)
self.widget_hide(self.root.ids.repository_disable_button)
self.widget_hide(self.root.ids.repository_download_button)
self.root.ids.repository_info.text = "\nThe [b]Repository Webserver[/b] feature is currently only available on mobile devices."
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
def repository_update_info(self, sender=None):
info = "Sideband includes a small repository of useful software and guides related to the Sideband and Reticulum ecosystem. You can start this repository to allow other people on your local network to download software and information directly from this device, without needing an Internet connection.\n\n"
info += "If you want to share the Sideband application itself via the repository server, you must first download it into the local repository, using the \"Update Content\" button below.\n\n"
info += "To make the repository available on your local network, simply start it below, and it will become browsable on a local IP address for anyone connected to the same WiFi or wired network.\n\n"
if self.sideband.webshare_server != None:
if RNS.vendor.platformutils.is_android():
def getIP():
adrs = []
try:
from jnius import autoclass
import ipaddress
mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
SystemProperties = autoclass('android.os.SystemProperties')
Context = autoclass('android.content.Context')
connectivity_manager = mActivity.getSystemService(Context.CONNECTIVITY_SERVICE)
ns = connectivity_manager.getAllNetworks()
if not ns == None and len(ns) > 0:
for n in ns:
lps = connectivity_manager.getLinkProperties(n)
las = lps.getLinkAddresses()
for la in las:
ina = la.getAddress()
ha = ina.getHostAddress()
if not ina.isLinkLocalAddress():
adrs.append(ha)
except Exception as e:
RNS.log("Error while getting repository IP address: "+str(e), RNS.LOG_ERROR)
return None
return adrs
ips = getIP()
if ips == None or len(ips) == 0:
info += "The repository server is running, but the local device IP address could not be determined.\n\nYou can access the repository by pointing a browser to: http://DEVICE_IP:4444/"
else:
ipstr = ""
for ip in ips:
ipstr += "http://"+str(ip)+":4444/\n"
ms = "" if len(ips) == 1 else "es"
info += "The repository server is running at the following address"+ms+":\n"+ipstr
self.root.ids.repository_enable_button.disabled = True
self.root.ids.repository_disable_button.disabled = False
else:
self.root.ids.repository_enable_button.disabled = False
self.root.ids.repository_disable_button.disabled = True
info += "\n"
self.root.ids.repository_info.text = info
def repository_start_action(self, sender=None):
self.sideband.start_webshare()
Clock.schedule_once(self.repository_update_info, 1.0)
def repository_stop_action(self, sender=None):
self.sideband.stop_webshare()
Clock.schedule_once(self.repository_update_info, 0.75)
def repository_download_action(self, sender=None):
def update_job(sender=None):
try:
import requests
# Get release info
apk_version = None
apk_url = None
pkgname = None
try:
release_url = "https://api.github.com/repos/markqvist/sideband/releases"
with requests.get(release_url) as response:
releases = response.json()
release = releases[0]
assets = release["assets"]
for asset in assets:
if asset["name"].lower().endswith(".apk"):
apk_url = asset["browser_download_url"]
pkgname = asset["name"]
apk_version = release["tag_name"]
RNS.log(f"Found version {apk_version} artefact {pkgname} at {apk_url}")
except Exception as e:
self.root.ids.repository_update.text = f"Downloading release info failed with the error:\n"+str(e)
return
self.root.ids.repository_update.text = "Downloading: "+str(apk_url)
with requests.get(apk_url, stream=True) as response:
with open("./dl_tmp", "wb") as tmp_file:
cs = 32*1024
tds = 0
for chunk in response.iter_content(chunk_size=cs):
tmp_file.write(chunk)
tds += cs
self.root.ids.repository_update.text = "Downloaded "+RNS.prettysize(tds)+" of "+str(pkgname)
os.rename("./dl_tmp", f"./share/pkg/{pkgname}")
self.root.ids.repository_update.text = f"Added {pkgname} to the repository!"
except Exception as e:
self.root.ids.repository_update.text = f"Downloading contents failed with the error:\n"+str(e)
self.root.ids.repository_update.text = "Starting package download..."
def start_update_job(sender=None):
threading.Thread(target=update_job, daemon=True).start()
Clock.schedule_once(start_update_job, 0.5)
def repository_init(self, sender=None):
if not self.repository_ready:
self.root.ids.hardware_scrollview.effect_cls = ScrollEffect
self.repository_update_info()
self.root.ids.repository_update.text = ""
self.repository_ready = True
def close_repository_action(self, sender=None):
self.open_conversations(direction="right")
### Hardware screen
######################################
def hardware_action(self, sender=None, direction="left"):

View File

@ -95,6 +95,7 @@ class SidebandCore():
self.log_verbose = verbose
self.owner_app = owner_app
self.reticulum = None
self.webshare_server = None
self.app_dir = plyer.storagepath.get_home_dir()+"/.config/sideband"
if self.app_dir.startswith("file://"):
@ -130,6 +131,7 @@ class SidebandCore():
self.log_dir = self.app_dir+"/app_storage/"
self.tmp_dir = self.app_dir+"/app_storage/tmp"
self.exports_dir = self.app_dir+"/exports"
self.webshare_dir = "./share/"
self.first_run = True
self.saving_configuration = False
@ -2056,6 +2058,67 @@ class SidebandCore():
self._db_setstate("core.started", True)
RNS.log("Sideband Core "+str(self)+" started")
def stop_webshare(self):
if self.webshare_server != None:
self.webshare_server.shutdown()
self.webshare_server = None
def start_webshare(self):
if self.webshare_server == None:
def webshare_job():
from http import server
import socketserver
import json
webshare_dir = self.webshare_dir
port = 4444
class RequestHandler(server.SimpleHTTPRequestHandler):
def do_GET(self):
serve_root = webshare_dir
if "?" in self.path:
self.path = self.path.split("?")[0]
path = serve_root + self.path
if self.path == "/":
path = serve_root + "/index.html"
if "/.." in self.path:
self.send_response(403)
self.end_headers()
self.write("Forbidden".encode("utf-8"))
elif self.path == "/pkglist":
try:
self.send_response(200)
self.send_header("Content-type", "text/json")
self.end_headers()
json_result = json.dumps(os.listdir(serve_root+"/pkg"))
self.wfile.write(json_result.encode("utf-8"))
except Exception as e:
self.send_response(500)
self.end_headers()
RNS.log("Error listing directory "+str(path)+": "+str(e), RNS.LOG_ERROR)
es = "Error"
self.wfile.write(es.encode("utf-8"))
else:
try:
with open(path, 'rb') as f:
data = f.read()
self.send_response(200)
self.end_headers()
self.wfile.write(data)
except Exception as e:
self.send_response(500)
self.end_headers()
RNS.log("Error serving file "+str(path)+": "+str(e), RNS.LOG_ERROR)
es = "Error"
self.wfile.write(es.encode("utf-8"))
with socketserver.TCPServer(("", port), RequestHandler) as webserver:
self.webshare_server = webserver
webserver.serve_forever()
self.webshare_server = None
RNS.log("Webshare server closed", RNS.LOG_DEBUG)
threading.Thread(target=webshare_job, daemon=True).start()
def request_lxmf_sync(self, limit = None):
if self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_IDLE or self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_COMPLETE:
self.message_router.request_messages_from_propagation_node(self.identity, max_messages = limit)

View File

@ -1207,6 +1207,93 @@ MDNavigationLayout:
disabled: False
active: False
MDScreen:
name: "repository_screen"
BoxLayout:
orientation: "vertical"
MDTopAppBar:
title: "Share Software & Guides"
anchor_title: "left"
elevation: 0
left_action_items:
[['menu', lambda x: nav_drawer.set_state("open")]]
right_action_items:
[
['close', lambda x: root.ids.screen_manager.app.close_repository_action(self)],
]
ScrollView:
id: repository_scrollview
MDBoxLayout:
orientation: "vertical"
spacing: "8dp"
size_hint_y: None
height: self.minimum_height
padding: [dp(28), dp(48), dp(28), dp(16)]
MDLabel:
text: "Repository Server\\n"
font_style: "H6"
MDLabel:
id: repository_info
markup: True
text: ""
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
MDBoxLayout:
orientation: "vertical"
spacing: "24dp"
size_hint_y: None
height: self.minimum_height
padding: [dp(0), dp(35), dp(0), dp(35)]
MDRectangleFlatIconButton:
id: repository_enable_button
icon: "wifi"
text: "Start Repository Server"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.ids.screen_manager.app.repository_start_action(self)
MDRectangleFlatIconButton:
id: repository_disable_button
icon: "wifi-off"
text: "Stop Repository Server"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.ids.screen_manager.app.repository_stop_action(self)
disabled: True
MDRectangleFlatIconButton:
id: repository_download_button
icon: "download-multiple"
text: "Update Contents"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.ids.screen_manager.app.repository_download_action(self)
disabled: False
MDLabel:
id: repository_update
markup: True
text: ""
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
MDScreen:
name: "hardware_screen"
@ -1880,6 +1967,15 @@ MDNavigationLayout:
on_release: root.ids.screen_manager.app.guide_action(self)
OneLineIconListItem:
text: "Repository"
on_release: root.ids.screen_manager.app.repository_action(self)
IconLeftWidget:
icon: "book-multiple"
on_release: root.ids.screen_manager.app.guide_action(self)
OneLineIconListItem:
id: app_version_info
text: ""