Skip to content

Commit

Permalink
Add app instance management
Browse files Browse the repository at this point in the history
  • Loading branch information
crysxd committed Nov 25, 2023
1 parent 221b4e1 commit eb5a2ad
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 113 deletions.
27 changes: 27 additions & 0 deletions moonraker_octoapp/moonrakerappstorage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import time
from octoapp.sentry import Sentry
from .moonrakerdatabase import MoonrakerDatabase
from octoapp.appsstorage import AppInstance, AppStorageHelper

class MoonrakerAppStorage:

def __init__(self, database):
self.First = False
self.Database = database


# !! Platform Command Handler Interface Function !!
#
# This must return a list of AppInstance
#
def GetAllApps(self) -> [AppInstance]:
apps = self.Database.GetAppsEntry()
return list(map(lambda app: AppInstance.FromDict(app), apps))

# !! Platform Command Handler Interface Function !!
#
# This must receive a lsit of AppInstnace
#
def RemoveApps(self, apps:[AppInstance]):
apps = list(map(lambda app: app.FcmToken, apps))
self.Database.RemoveAppEntries(apps)
12 changes: 9 additions & 3 deletions moonraker_octoapp/moonrakerclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,16 @@ class MoonrakerClient:


@staticmethod
def Init(logger, isObserverMode:bool, moonrakerConfigFilePath:str, observerConfigPath:str, printerId, connectionStatusHandler, pluginVersionStr):
MoonrakerClient._Instance = MoonrakerClient(logger, isObserverMode, moonrakerConfigFilePath, observerConfigPath, printerId, connectionStatusHandler, pluginVersionStr)
def Init(logger, isObserverMode:bool, moonrakerConfigFilePath:str, observerConfigPath:str, printerId, connectionStatusHandler, pluginVersionStr, moonrakerDatabase):
MoonrakerClient._Instance = MoonrakerClient(logger, isObserverMode, moonrakerConfigFilePath, observerConfigPath, printerId, connectionStatusHandler, pluginVersionStr, moonrakerDatabase)


@staticmethod
def Get():
return MoonrakerClient._Instance


def __init__(self, logger:logging.Logger, isObserverMode:bool, moonrakerConfigFilePath:str, observerConfigPath:str, printerId:str, connectionStatusHandler, pluginVersionStr:str) -> None:
def __init__(self, logger:logging.Logger, isObserverMode:bool, moonrakerConfigFilePath:str, observerConfigPath:str, printerId:str, connectionStatusHandler, pluginVersionStr:str, moonrakerDatabase) -> None:
self.Logger = logger
self.IsObserverMode = isObserverMode
self.MoonrakerConfigFilePath = moonrakerConfigFilePath
Expand All @@ -81,6 +81,7 @@ def __init__(self, logger:logging.Logger, isObserverMode:bool, moonrakerConfigFi
self.PrinterId = printerId
self.ConnectionStatusHandler = connectionStatusHandler
self.PluginVersionStr = pluginVersionStr
self.MoonrakerDatabase = moonrakerDatabase

# Setup the json-rpc vars
self.JsonRpcIdLock = threading.Lock()
Expand Down Expand Up @@ -799,6 +800,11 @@ def OnPrintStart(self, fileName):
# Only process notifications when ready, aka after state sync.
if self.IsReadyToProcessNotifications is False:
return

# Get our name
name = MoonrakerClient.Get().MoonrakerDatabase.GetPrinterName()
self.NotificationHandler.NotificationSender.PrinterName = name
self.Logger.info("Printer is called %s" % name)

# Since this is a new print, reset the cache. The file name might be the same as the last, but have
# different props, so we will always reset. We know when we are printing the same file name will have the same props.
Expand Down
67 changes: 58 additions & 9 deletions moonraker_octoapp/moonrakerdatabase.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging

from octoapp.sentry import Sentry
from octoapp.appsstorage import AppInstance

from .moonrakerclient import MoonrakerClient

Expand All @@ -13,6 +14,63 @@ def __init__(self, logger:logging.Logger, printerId:str, pluginVersion:str) -> N
self.PrinterId = printerId
self.PluginVersion = pluginVersion

def GetAppsEntry(self):
self.Logger.info("Getting apps")
result = MoonrakerClient.Get().SendJsonRpcRequest("server.database.get_item",
{
"namespace": "octoapp",
"key": "apps",
})
if result.GetErrorCode() == 404 or result.GetErrorCode() == -32601:
self.Logger.error("No apps set")
return []

if result.HasError():
self.Logger.error("Ensure database entry item post failed. "+result.GetLoggingErrorStr())
raise Exception("Unable to fetch apps: %s" % result.GetLoggingErrorStr())

out = []
value = result.GetResult()["value"]
for key in value.keys(): out.append(value[key])
return out

def GetPrinterName(self) -> str:
mainsailResult = MoonrakerClient.Get().SendJsonRpcRequest("server.database.get_item",
{
"namespace": "mainsail",
"key": "general.printername",
})
if mainsailResult.HasError() is False and mainsailResult.GetResult() is not None:
return mainsailResult.GetResult()["value"]

fluiddResult = MoonrakerClient.Get().SendJsonRpcRequest("server.database.get_item",
{
"namespace": "fluidd",
"key": "uiSettings.general.instanceName",
})
if fluiddResult.HasError() is False and mainsailResult.GetResult() is not None:
return mainsailResult.GetResult()["value"]

if mainsailResult.HasError() is False & mainsailResult.GetErrorCode() != 404 or mainsailResult.GetErrorCode() != -3260:
self.Logger.error("Failed to load Mainsail printer name"+mainsailResult.GetLoggingErrorStr())

if fluiddResult.HasError() is False & fluiddResult.GetErrorCode() != 404 or fluiddResult.GetErrorCode() != -3260:
self.Logger.error("Failed to load Fluidd printer name"+fluiddResult.GetLoggingErrorStr())

return "Klipper"

def RemoveAppEntries(self, apps: []):
self.Logger.info("Removing apps: %s" % apps)

for appId in apps:
result = MoonrakerClient.Get().SendJsonRpcRequest("server.database.delete_item",
{
"namespace": "octoapp",
"key": "apps.%s" % appId,
})
if result.HasError():
self.Logger.error("Unable to remove app %s: %s" % (appId, result.GetLoggingErrorStr()))


def EnsureOctoAppDatabaseEntry(self):
# Useful for debugging.
Expand All @@ -21,15 +79,6 @@ def EnsureOctoAppDatabaseEntry(self):
# We use a few database entries under our own name space to share information with apps and other plugins.
# Note that since these are used by 3rd party systems, they must never change. We also use this for our frontend.
result = MoonrakerClient.Get().SendJsonRpcRequest("server.database.post_item",
{
"namespace": "octoapp",
"key": "public.printerId",
"value": self.PrinterId
})
if result.HasError():
self.Logger.error("Ensure database entry item post failed. "+result.GetLoggingErrorStr())
return
result = MoonrakerClient.Get().SendJsonRpcRequest("server.database.post_item",
{
"namespace": "octoapp",
"key": "public.pluginVersion",
Expand Down
11 changes: 7 additions & 4 deletions moonraker_octoapp/moonrakerhost.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from octoapp.Proto.ServerHost import ServerHost
from octoapp.localip import LocalIpHelper
from octoapp.compat import Compat
from octoapp.appsstorage import AppStorageHelper

from .config import Config
from .secrets import Secrets
Expand All @@ -28,6 +29,7 @@
from .filemetadatacache import FileMetadataCache
from .uiinjector import UiInjector
from .observerconfigfile import ObserverConfigFile
from .moonrakerappstorage import MoonrakerAppStorage

# This file is the main host for the moonraker service.
class MoonrakerHost:
Expand Down Expand Up @@ -114,6 +116,10 @@ def RunBlocking(self, klipperConfigDir, isObserverMode, localStorageDir, service
# Setup the database helper
self.MoonrakerDatabase = MoonrakerDatabase(self.Logger, printerId, pluginVersionStr)

# Setup app storage
moonrakerAppStorage = MoonrakerAppStorage(self.MoonrakerDatabase)
AppStorageHelper.Init(moonrakerAppStorage)

# Setup the credential manager.
MoonrakerCredentialManager.Init(self.Logger, moonrakerConfigFilePath, isObserverMode)

Expand Down Expand Up @@ -146,7 +152,7 @@ def RunBlocking(self, klipperConfigDir, isObserverMode, localStorageDir, service
# When everything is setup, start the moonraker client object.
# This also creates the Notifications Handler and Gadget objects.
# This doesn't start the moon raker connection, we don't do that until OE connects.
MoonrakerClient.Init(self.Logger, isObserverMode, moonrakerConfigFilePath, observerConfigFilePath, printerId, self, pluginVersionStr)
MoonrakerClient.Init(self.Logger, isObserverMode, moonrakerConfigFilePath, observerConfigFilePath, printerId, self, pluginVersionStr, self.MoonrakerDatabase)

# Init our file meta data cache helper
FileMetadataCache.Init(self.Logger, MoonrakerClient.Get())
Expand All @@ -167,9 +173,6 @@ def RunBlocking(self, klipperConfigDir, isObserverMode, localStorageDir, service

# Now start the main runner!
MoonrakerClient.Get().RunBlocking()

while 1:
time.sleep(5)
except Exception as e:
Sentry.Exception("!! Exception thrown out of main host run function.", e)

Expand Down
14 changes: 7 additions & 7 deletions moonraker_octoapp/uiinjector.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,13 @@ def _DoInject(self, staticHtmlRootPath) -> bool:

# Searches the text for our special tag.
def _FindSpecialJsTagIndex(self, htmlLower) -> int:
c_jsTagSearch = "src=\"/oe/ui."
c_jsTagSearch = "src=\"/octoapp/ui."
return htmlLower.find(c_jsTagSearch)


# Searches the text for our special tag.
def _FindSpecialCssTagIndex(self, htmlLower) -> int:
c_cssTagSearch = "href=\"/oe/ui."
c_cssTagSearch = "href=\"/octoapp/ui."
return htmlLower.find(c_cssTagSearch)


Expand Down Expand Up @@ -238,10 +238,10 @@ def _InjectIntoHtml(self, indexHtmlFilePath) -> bool:
# We add some indents to re-create the about correct whitespace.
# Note that since the update logic needs to find these file names, they can't change!
# Especially the parts we search for, or there will be multiple tags showing up.
# "src=\"/oe/ui."
# "href=\"/oe/ui."
# The string "oe/ui.js?hash=" and "oe/ui.css?hash=" are important not to change.
tags = f"\r\n<!-- OctoApp Injected UI --><script async crossorigin src=\"/oe/ui.{self.StaticFileHash}.js\"></script><link crossorigin rel=\"stylesheet\" href=\"/oe/ui.{self.StaticFileHash}.css\">\r\n"
# "src=\"/octoapp/ui."
# "href=\"/octoapp/ui."
# The string "octoapp/ui.js?hash=" and "octoapp/ui.css?hash=" are important not to change.
tags = f"\r\n<!-- OctoApp Injected UI --><script async crossorigin src=\"/octoapp/ui.{self.StaticFileHash}.js\"></script><link crossorigin rel=\"stylesheet\" href=\"/octoapp/ui.{self.StaticFileHash}.css\">\r\n"

# Inject the tags into the html
htmlText = htmlText[:headEndTag] + tags + htmlText[headEndTag:]
Expand Down Expand Up @@ -355,7 +355,7 @@ def _UpdateSwHash(self, staticHtmlRootPath) -> None:
def _UpdateStaticFilesIntoRootIfNeeded(self, staticHtmlRootPath):
try:
# Ensure the dir exists.
oeStaticFileRoot = os.path.join(staticHtmlRootPath, "oe")
oeStaticFileRoot = os.path.join(staticHtmlRootPath, "octoapp")
if os.path.exists(oeStaticFileRoot) is False:
os.makedirs(oeStaticFileRoot)

Expand Down
88 changes: 21 additions & 67 deletions octoapp/appsstorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,16 @@ def ToDict(self):
def FromDict(dict:dict):
return AppInstance(
fcmToken=dict["fcmToken"],
fcmFallbackToken=dict["fcmTokenFallback"],
fcmFallbackToken=dict.get("fcmTokenFallback", None),
instanceId=dict["instanceId"],
displayName=dict["displayName"],
displayDescription=dict["displayDescription"],
model=dict["model"],
appVersion=dict["appVersion"],
appBuild=dict["appBuild"],
appLanguage=dict["appLanguage"],
lastSeenAt=dict["lastSeenAt"],
expireAt=dict["expireAt"],
displayName=dict.get("displayName", "Unknown"),
displayDescription=dict.get("displayDescription", ""),
model=dict.get("model", "Unknown"),
appVersion=dict.get("appVersion", "Unknown"),
appBuild=dict.get("appBuild", 1),
appLanguage=dict.get("appLanguage", "en"),
lastSeenAt=dict.get("lastSeenAt", 0),
expireAt=dict.get("expireAt", 0),
)


Expand All @@ -81,61 +81,11 @@ def Get():
def __init__(self, appStoragePlatformHelper):
self.AppStoragePlatformHelper = appStoragePlatformHelper


def continuously_check_activities_expired(self):
t = threading.Thread(
target=self.do_continuously_check_activities_expired,
args=[]
)
t.daemon = True
t.start()

def do_continuously_check_activities_expired(self):
Sentry.Debug("APPS", "Checking for expired apps every 60s")
while True:
time.sleep(60)

try:
expired = self.get_expired_apps(self.get_activities(self.get_apps()))
if len(expired):
Sentry.Debug("APPS", "Found %s expired apps" % len(expired))
self.LogApps()

expired_activities = self.GetActivities(expired)
if len(expired_activities):
# This will end the live activity, we currently do not send a notification to inform
# the user, we can do so by setting is_end=False and the apnsData as below
apnsData=self.create_activity_content_state(
is_end=True,
liveActivityState="expired",
state=self.print_state
)
# apnsData["alert"] = {
# "title": "Updates paused for %s" % self.print_state.get("name", ""),
# "body": "Live activities expire after 8h, open OctoApp to renew"
# }
self.send_notification_blocking_raw(
targets=expired_activities,
high_priority=True,
apnsData=apnsData,
androidData="none"
)

filtered_apps = list(filter(lambda app: any(app.fcmToken != x.fcmToken for x in expired), self.get_apps()))
self.SetAllApps(filtered_apps)
self.LogApps()
Sentry.Debug("APPS", "Cleaned up expired apps")


except Exception as e:
Sentry.ExceptionNoSend("Failed to retire expired", e)


def GetAndroidApps(self, apps):
return list(filter(lambda app: not app.FcmToken.startswith("activity:") and not app.FcmToken.startswith("ios:"), apps))

def GetExpiredApps(self, apps):
return list(filter(lambda app: app.ExpiresAt is not None and time.time() > app.ExpiresAt, apps))
return list(filter(lambda app: app.ExpireAt is not None and time.time() > app.ExpireAt, apps))

def GetIosApps(self, apps):
return list(filter(lambda app: app.FcmToken.startswith("ios:"), apps))
Expand All @@ -147,25 +97,29 @@ def GetDefaultExpirationFromNow(self):
return (time.time() + 2592000)

def LogApps(self):
self.AppStoragePlatformHelper.LogAllApps()
apps = self.GetAllApps()
Sentry.Debug("APPS", "Now %s apps registered" % len(apps))
for app in apps:
Sentry.Debug("APPS", " => %s" % app.FcmToken[0:100])

def RemoveTemporaryApps(self, for_instance_id=None):
apps = self.GetAllApps()

if for_instance_id is None:
apps = list(filter(lambda app: not app.FcmToken.startswith("activity:") ,apps))
apps = list(filter(lambda app: app.FcmToken.startswith("activity:") ,apps))
Sentry.Debug("APPS", "Removed all temporary apps")
else:
apps = list(filter(lambda app: not app.FcmToken.startswith("activity:") or app.instanceId != for_instance_id ,apps))
apps = list(filter(lambda app: app.FcmToken.startswith("activity:") and app.instanceId == for_instance_id , apps))
Sentry.Debug("APPS", "Removed all temporary apps for %s" % for_instance_id)

self.SetAllApps(apps)
self.RemoveApps(apps)

def GetAllApps(self) -> [AppInstance]:
apps = self.AppStoragePlatformHelper.GetAllApps()
Sentry.Debug("APPS", "Loading %s apps" % len(apps))
return apps

def SetAllApps(self, apps:[AppInstance]):
Sentry.Debug("APPS", "Storing %s apps" % len(apps))
self.AppStoragePlatformHelper.SetAllApps(apps)
def RemoveApps(self, apps: [AppInstance]):
Sentry.Debug("APPS", "Removing %s apps" % len(apps))
self.AppStoragePlatformHelper.RemoveApps(apps)
self.LogApps()
Loading

0 comments on commit eb5a2ad

Please sign in to comment.