Skip to content

Commit

Permalink
Initial implementation for shared library usage.
Browse files Browse the repository at this point in the history
  • Loading branch information
elgatito committed Jan 9, 2023
1 parent 1d581b6 commit 18916c1
Show file tree
Hide file tree
Showing 3 changed files with 461 additions and 59 deletions.
196 changes: 140 additions & 56 deletions resources/site-packages/elementum/daemon.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import io
import os
import errno
import stat
Expand All @@ -11,7 +12,9 @@
from kodi_six import xbmc, xbmcgui
from six.moves import urllib_request
from kodi_six.utils import py2_decode
from ctypes import c_char_p, c_int, cdll, Structure, c_longlong

from elementum.taller import follow
from elementum.logger import log
from elementum.osarch import PLATFORM, get_platform
from elementum.config import ELEMENTUMD_HOST
Expand All @@ -29,9 +32,14 @@
except:
hasSubprocess = False

log_path = ""
last_exit_code = -1
binary_platform = {}
repo = "elgatito/elementum-binaries"

class GoString(Structure):
_fields_ = [("p", c_char_p), ("n", c_longlong)]

def ensure_exec_perms(file_):
st = os.stat(file_)
os.chmod(file_, st.st_mode | stat.S_IEXEC)
Expand Down Expand Up @@ -197,6 +205,8 @@ def jsonrpc_enabled(notify=False):
return False

def start_elementumd(**kwargs):
global log_path

jsonrpc_failures = 0
while jsonrpc_enabled() is False:
jsonrpc_failures += 1
Expand All @@ -211,6 +221,7 @@ def start_elementumd(**kwargs):
time.sleep(3)

elementum_dir, elementum_binary = get_elementum_binary()
elementum_library = "elementum.so"

log.info("Binary dir: %s, item: %s " % (elementum_dir, elementum_binary))
if elementum_dir is False or elementum_binary is False:
Expand All @@ -224,7 +235,7 @@ def start_elementumd(**kwargs):
pid = int(lf.read().rstrip(" \t\r\n\0"))
if pid != os.getpid():
log.info("Killing process id %s" % pid)
os.kill(pid, 9)
os.kill(pid, 9)
except OSError as e:
if e.errno != 3 and e.errno != 22:
# Ignore: OSError: [Errno 3] No such process
Expand All @@ -235,21 +246,26 @@ def start_elementumd(**kwargs):
if binary_platform["os"] == "windows":
try:
library_lockfile = os.path.join(py2_decode(translatePath(ADDON.getAddonInfo("profile"))), "library.db.lock")
log.warning("Removing library.db.lock file at %s ..." % library_lockfile)
os.remove(library_lockfile)
if os.path.exists(library_lockfile):
log.warning("Removing library.db.lock file at %s ..." % library_lockfile)
os.remove(library_lockfile)
except Exception as e:
log.error(repr(e))

SW_HIDE = 0
STARTF_USESHOWWINDOW = 1

args = [elementum_binary]
shared_args = ""
if ADDON.getSetting("local_port") != "":
args.append("-remotePort=" + ADDON.getSetting("local_port"))
shared_args += " -remotePort=" + ADDON.getSetting("local_port")
if ADDON.getSetting("remote_host") != "":
args.append("-localHost=" + ADDON.getSetting("remote_host"))
shared_args += " -localHost=" + ADDON.getSetting("remote_host")
if ADDON.getSetting("remote_port") != "":
args.append("-localPort=" + ADDON.getSetting("remote_port"))
shared_args += " -localPort=" + ADDON.getSetting("remote_port")
kwargs["cwd"] = elementum_dir

if binary_platform["os"] == "windows":
Expand All @@ -260,6 +276,7 @@ def start_elementumd(**kwargs):
si.wShowWindow = SW_HIDE
clear_fd_inherit_flags()
kwargs["startupinfo"] = si
elementum_library = "elementum.dll"
else:
env = os.environ.copy()
env["LD_LIBRARY_PATH"] = "%s:%s" % (elementum_dir, env.get("LD_LIBRARY_PATH", ""))
Expand All @@ -282,9 +299,56 @@ def start_elementumd(**kwargs):
log.info("elementumd: sleeping %d seconds before startup" % (delay))
time.sleep(delay)

if hasSubprocess:
return subprocess.Popen(args, **kwargs)
return False
proc = None
try:
proc = subprocess.Popen(args, **kwargs)
except:
pass
if not proc:
try:
log_dir = os.path.join(translatePath("special://temp/"), "elementum")
log_path = os.path.join(log_dir, "elementum.log")

# Create and truncate log file before starting the library
try:
if not os.path.exists(log_dir):
os.makedirs(log_dir)

if not os.path.exists(log_path):
open(log_path, 'w').close()
else:
with open(log_path, 'r+') as f:
f.truncate()
except IOError as e:
log.error("Could not truncate the log: %s" % (e))
pass

library_path = os.path.join(elementum_dir, elementum_library)
log.info("elementumd: loading shared library from %s" % library_path)
lib = cdll.LoadLibrary(library_path)

library_thread = threading.Thread(target=start_library, args=[lib, log_path, shared_args])
library_thread.start()

return library_thread
except Exception as e:
log.error("Unable to start library: %s" % e)
return None

return proc


def start_library(lib, log_path, args):
global last_exit_code

log.info("Preparing start with args '%s' and log path: %s" % (args, log_path))

lib.startWithLog.argtypes = [GoString, GoString]
lib.startWithLog.restype = c_int

last_exit_code = lib.startWithLog(GoString(c_char_p(log_path.encode('utf-8')), len(log_path)), GoString(c_char_p(args.encode('utf-8')), len(args)))
log.info("Shared library execution stopped with code %d" % (last_exit_code))
time.sleep(1)

def shutdown():
try:
Expand All @@ -296,7 +360,7 @@ def wait_for_abortRequested(proc, monitor):
monitor.closing.wait()
log.info("elementumd: exiting elementumd daemon")
try:
if proc is not None:
if not isinstance(proc, threading.Thread):
proc.terminate()
except OSError:
pass # Process already exited, nothing to terminate
Expand Down Expand Up @@ -324,61 +388,81 @@ def elementumd_thread(monitor):
continue

log.info("elementumd: starting elementumd")
proc = None
if hasSubprocess:
proc = start_elementumd(stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if not proc:
break
else:
log.info("elementumd: current system is unable to run the binary")
break

threading.Thread(target=wait_for_abortRequested, args=[proc, monitor]).start()

if not hasSubprocess:
proc = start_elementumd(stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if proc is None:
break

if binary_platform["os"] == "windows":
while proc.poll() is None:
log.info(toUtf8(proc.stdout.readline().rstrip()))
else:
# Kodi hangs on some Android (sigh...) systems when doing a blocking
# read. We count on the fact that Elementum daemon flushes its log
# output on \n, creating a pretty clean output
import fcntl
import select
fd = proc.stdout.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
while proc.poll() is None:
try:
to_read, _, _ = select.select([proc.stdout], [], [])
for ro in to_read:
line = ro.readline()
if line == "": # write end is closed
break
threading.Thread(target=wait_for_abortRequested, args=[proc, monitor]).start()

if isinstance(proc, threading.Thread):
while not os.path.exists(log_path):
time.sleep(1)

for line in follow(io.open(log_path, 'r', encoding='utf8'), delay=0.5, selector=proc.is_alive):
try:
log.info(toUtf8(line.rstrip()))
except UnicodeDecodeError:
log.info(line.rstrip())
if not proc.is_alive():
break
last_code = last_exit_code
else:
if binary_platform["os"] == "windows":
while proc.poll() is None:
log.info(toUtf8(proc.stdout.readline().rstrip()))
else:
# Kodi hangs on some Android (sigh...) systems when doing a blocking
# read. We count on the fact that Elementum daemon flushes its log
# output on \n, creating a pretty clean output
import fcntl
import select
fd = proc.stdout.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
while proc.poll() is None:
try:
log.info(toUtf8(line.rstrip()))
except TypeError:
pass
except IOError:
time.sleep(1) # nothing to read, sleep

last_code = proc.returncode
if monitor_abort.abortRequested():
break
if proc.returncode == 0 or proc.returncode == -9 or proc.returncode == -1:
continue

if proc.returncode == 5:
restart_count = 0
notify(getLocalizedString(30332), time=3000)
else:
restart_count += 1
notify(getLocalizedString(30100), time=3000)
to_read, _, _ = select.select([proc.stdout], [], [])
for ro in to_read:
line = ro.readline()
if line == "": # write end is closed
break
try:
log.info(toUtf8(line.rstrip()))
except TypeError:
pass
except IOError:
time.sleep(1) # nothing to read, sleep

if not isinstance(proc, threading.Thread):
last_code = proc.returncode
if monitor_abort.abortRequested():
break
if proc.returncode == 0 or proc.returncode == -9 or proc.returncode == -1:
continue

if proc.returncode == 5:
restart_count = 0
notify(getLocalizedString(30332), time=3000)
else:
restart_count += 1
notify(getLocalizedString(30100), time=3000)
else:
log.info("Joining library thread")
proc.join()
if monitor_abort.abortRequested():
break

if last_exit_code == 5:
restart_count = 0
notify(getLocalizedString(30332), time=3000)
else:
restart_count += 1
notify(getLocalizedString(30100), time=3000)

xbmc.executebuiltin("Dialog.Close(all, true)")
system_information()

log.info("Sleeping before next restart attempt")
time.sleep(5)

if restart_count >= max_restart:
Expand Down
11 changes: 8 additions & 3 deletions resources/site-packages/elementum/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from six.moves import urllib_request

from elementum.daemon import shutdown
from elementum.logger import log
from elementum.config import init, ELEMENTUMD_HOST, ONLY_CLIENT
from kodi_six.utils import PY2

Expand All @@ -26,8 +27,9 @@ def closing(self):

def restart(self):
try:
log.info("Triggering Elementum restart")
init()
urllib_request.urlopen("%s/restart" % ELEMENTUMD_HOST)
urllib_request.urlopen("%s/restart" % ELEMENTUMD_HOST, timeout=1)
except:
pass

Expand All @@ -41,21 +43,24 @@ def onAbortRequested(self):
# Only when closing Kodi
if self.abortRequested():
xbmc.executebuiltin("Dialog.Close(all, true)")
log.info("onAbortRequested")
if not ONLY_CLIENT:
shutdown()
try:
self._closing.set()
self._closing.clear()
except SystemExit as e:
log.info("Exit %d" % (e.code))
if e.code != 0:
os._exit(0)
pass

def onSettingsChanged(self):
try:
log.info("Triggering Elementum reload")
init()
urllib_request.urlopen("%s/reload" % ELEMENTUMD_HOST)
urllib_request.urlopen("%s/cmd/clear_page_cache" % ELEMENTUMD_HOST)
urllib_request.urlopen("%s/reload" % ELEMENTUMD_HOST, timeout=1)
urllib_request.urlopen("%s/cmd/clear_page_cache" % ELEMENTUMD_HOST, timeout=1)
except:
pass

Expand Down
Loading

0 comments on commit 18916c1

Please sign in to comment.