Added repository server and APK sharing
This commit is contained in:
parent
9c802e0b70
commit
26aa9dc1bf
|
@ -6,6 +6,8 @@ clean:
|
||||||
@echo Cleaning...
|
@echo Cleaning...
|
||||||
-(rm ./__pycache__ -r)
|
-(rm ./__pycache__ -r)
|
||||||
-(rm ./app_storage -r)
|
-(rm ./app_storage -r)
|
||||||
|
-(rm ./share/pkg/* -r)
|
||||||
|
-(rm ./share/mirrors/* -r)
|
||||||
-(rm ./bin -r)
|
-(rm ./bin -r)
|
||||||
|
|
||||||
cleanlibs:
|
cleanlibs:
|
||||||
|
@ -53,15 +55,26 @@ else
|
||||||
@(sleep 2)
|
@(sleep 2)
|
||||||
endif
|
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:
|
release:
|
||||||
buildozer android release
|
buildozer android release
|
||||||
|
|
||||||
postbuild:
|
postbuild:
|
||||||
$(MAKE) cleanrns
|
$(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:
|
version:
|
||||||
@(echo $$(python ./gv.py))
|
@(echo $$(python ./gv.py))
|
||||||
|
|
|
@ -4,13 +4,13 @@ package.name = sideband
|
||||||
package.domain = io.unsigned
|
package.domain = io.unsigned
|
||||||
|
|
||||||
source.dir = .
|
source.dir = .
|
||||||
source.include_exts = py,png,jpg,jpeg,ttf,kv,pyi,typed,so,0,1,2,3,atlas,frag
|
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/*
|
source.include_patterns = assets/*,share/*
|
||||||
source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements,precompiled/*,parked/*,./setup.py,Makef*,./Makefile,Makefile
|
source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements,precompiled/*,parked/*,./setup.py,Makef*,./Makefile,Makefile
|
||||||
|
|
||||||
version.regex = __version__ = ['"](.*)['"]
|
version.regex = __version__ = ['"](.*)['"]
|
||||||
version.filename = %(source.dir)s/main.py
|
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
|
# Cryptography recipe is currently broken, using RNS-internal crypto for now
|
||||||
requirements = kivy==2.2.1,libbz2,pillow,qrcode==7.3.1,usb4a,usbserial4a
|
requirements = kivy==2.2.1,libbz2,pillow,qrcode==7.3.1,usb4a,usbserial4a
|
||||||
|
@ -25,7 +25,7 @@ android.presplash_color = #00000000
|
||||||
orientation = portrait
|
orientation = portrait
|
||||||
fullscreen = 0
|
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.api = 30
|
||||||
android.minapi = 24
|
android.minapi = 24
|
||||||
android.ndk = 25b
|
android.ndk = 25b
|
||||||
|
|
134
sbapp/main.py
134
sbapp/main.py
|
@ -110,6 +110,7 @@ class SidebandApp(MDApp):
|
||||||
self.settings_ready = False
|
self.settings_ready = False
|
||||||
self.connectivity_ready = False
|
self.connectivity_ready = False
|
||||||
self.hardware_ready = False
|
self.hardware_ready = False
|
||||||
|
self.repository_ready = False
|
||||||
self.hardware_rnode_ready = False
|
self.hardware_rnode_ready = False
|
||||||
self.hardware_modem_ready = False
|
self.hardware_modem_ready = False
|
||||||
self.hardware_serial_ready = False
|
self.hardware_serial_ready = False
|
||||||
|
@ -1673,6 +1674,139 @@ class SidebandApp(MDApp):
|
||||||
def close_connectivity_action(self, sender=None):
|
def close_connectivity_action(self, sender=None):
|
||||||
self.open_conversations(direction="right")
|
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
|
### Hardware screen
|
||||||
######################################
|
######################################
|
||||||
def hardware_action(self, sender=None, direction="left"):
|
def hardware_action(self, sender=None, direction="left"):
|
||||||
|
|
|
@ -95,6 +95,7 @@ class SidebandCore():
|
||||||
self.log_verbose = verbose
|
self.log_verbose = verbose
|
||||||
self.owner_app = owner_app
|
self.owner_app = owner_app
|
||||||
self.reticulum = None
|
self.reticulum = None
|
||||||
|
self.webshare_server = None
|
||||||
|
|
||||||
self.app_dir = plyer.storagepath.get_home_dir()+"/.config/sideband"
|
self.app_dir = plyer.storagepath.get_home_dir()+"/.config/sideband"
|
||||||
if self.app_dir.startswith("file://"):
|
if self.app_dir.startswith("file://"):
|
||||||
|
@ -130,6 +131,7 @@ class SidebandCore():
|
||||||
self.log_dir = self.app_dir+"/app_storage/"
|
self.log_dir = self.app_dir+"/app_storage/"
|
||||||
self.tmp_dir = self.app_dir+"/app_storage/tmp"
|
self.tmp_dir = self.app_dir+"/app_storage/tmp"
|
||||||
self.exports_dir = self.app_dir+"/exports"
|
self.exports_dir = self.app_dir+"/exports"
|
||||||
|
self.webshare_dir = "./share/"
|
||||||
|
|
||||||
self.first_run = True
|
self.first_run = True
|
||||||
self.saving_configuration = False
|
self.saving_configuration = False
|
||||||
|
@ -2056,6 +2058,67 @@ class SidebandCore():
|
||||||
self._db_setstate("core.started", True)
|
self._db_setstate("core.started", True)
|
||||||
RNS.log("Sideband Core "+str(self)+" started")
|
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):
|
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:
|
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)
|
self.message_router.request_messages_from_propagation_node(self.identity, max_messages = limit)
|
||||||
|
|
|
@ -1207,6 +1207,93 @@ MDNavigationLayout:
|
||||||
disabled: False
|
disabled: False
|
||||||
active: 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:
|
MDScreen:
|
||||||
name: "hardware_screen"
|
name: "hardware_screen"
|
||||||
|
|
||||||
|
@ -1880,6 +1967,15 @@ MDNavigationLayout:
|
||||||
on_release: root.ids.screen_manager.app.guide_action(self)
|
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:
|
OneLineIconListItem:
|
||||||
id: app_version_info
|
id: app_version_info
|
||||||
text: ""
|
text: ""
|
||||||
|
|
Loading…
Reference in New Issue