diff --git a/bemani/common/__init__.py b/bemani/common/__init__.py
index 13db380..9179196 100644
--- a/bemani/common/__init__.py
+++ b/bemani/common/__init__.py
@@ -15,6 +15,7 @@ from bemani.common.aes import AESCipher
from bemani.common.time import Time
from bemani.common.parallel import Parallel
from bemani.common.pe import PEFile, InvalidOffsetException
+from bemani.common.cache import cache
__all__ = [
@@ -38,4 +39,5 @@ __all__ = [
"intish",
"PEFile",
"InvalidOffsetException",
+ "cache",
]
diff --git a/bemani/common/cache.py b/bemani/common/cache.py
new file mode 100644
index 0000000..1f61509
--- /dev/null
+++ b/bemani/common/cache.py
@@ -0,0 +1,17 @@
+from flask import Flask
+from flask_caching import Cache
+
+
+# This somewhat breaks convention of trying to keep flask stuff in only the application
+# routing layer, but flask-caching itself is a useful wrapper that supports a ton of
+# backends like in-memory, filesystem, redis, memcached, etc. So, we centralize the
+# object's ownership here and call into this to initialize it during application setup
+# so that anywhere in the codebase can assume an initialized cache object for decorators.
+app = Flask(__name__)
+
+cache = Cache(
+ app,
+ # We should overwrite this in any reasonable entrypoint to the system, but for simple
+ # utilities that don't want to set up the entire infrastructure, provide a sane default.
+ config={"CACHE_TYPE": "SimpleCache"},
+)
diff --git a/bemani/data/config.py b/bemani/data/config.py
index cabdae0..88cf45f 100644
--- a/bemani/data/config.py
+++ b/bemani/data/config.py
@@ -237,8 +237,18 @@ class Config(dict):
return str(self.get("email", "nobody@nowhere.com"))
@property
- def cache_dir(self) -> str:
- return os.path.abspath(str(self.get("cache_dir", "/tmp")))
+ def cache_dir(self) -> Optional[str]:
+ cache_dir = self.get("cache_dir")
+ if cache_dir is None:
+ return None
+ return os.path.abspath(str(cache_dir))
+
+ @property
+ def memcached_server(self) -> Optional[str]:
+ server = self.get("memcached_server")
+ if server is None:
+ return None
+ return str(server)
@property
def theme(self) -> str:
diff --git a/bemani/frontend/app.py b/bemani/frontend/app.py
index 8b85702..0c07acf 100644
--- a/bemani/frontend/app.py
+++ b/bemani/frontend/app.py
@@ -16,10 +16,9 @@ from flask import (
got_request_exception,
jsonify as flask_jsonify,
)
-from flask_caching import Cache
from functools import wraps
-from bemani.common import AESCipher, GameConstants
+from bemani.common import AESCipher, GameConstants, cache
from bemani.data import Config, Data
from bemani.frontend.types import g
from bemani.frontend.templates import templates_location
@@ -40,18 +39,14 @@ FRONTEND_CACHE_BUST: str = "site.1.3.react.16.14"
@app.before_request
def before_request() -> None:
global config
- g.cache = Cache(
- app,
- config={
- "CACHE_TYPE": "filesystem",
- "CACHE_DIR": config.cache_dir,
- },
- )
+
+ g.cache = cache
+ g.config = config
+
if request.endpoint in ["jsx", "static"]:
# This is just serving cached compiled frontends, skip loading from DB
return
- g.config = config
g.data = Data(config)
g.sessionID = None
g.userID = None
diff --git a/bemani/frontend/ddr/cache.py b/bemani/frontend/ddr/cache.py
index b339a7e..bbd57cb 100644
--- a/bemani/frontend/ddr/cache.py
+++ b/bemani/frontend/ddr/cache.py
@@ -1,19 +1,10 @@
-from flask_caching import Cache
-
+from bemani.common import cache
from bemani.data import Config, Data
-from bemani.frontend.app import app
from bemani.frontend.ddr.ddr import DDRFrontend
class DDRCache:
@classmethod
def preload(cls, data: Data, config: Config) -> None:
- cache = Cache(
- app,
- config={
- "CACHE_TYPE": "filesystem",
- "CACHE_DIR": config.cache_dir,
- },
- )
frontend = DDRFrontend(data, config, cache)
frontend.get_all_songs(force_db_load=True)
diff --git a/bemani/frontend/iidx/cache.py b/bemani/frontend/iidx/cache.py
index 0d5099a..7e1754c 100644
--- a/bemani/frontend/iidx/cache.py
+++ b/bemani/frontend/iidx/cache.py
@@ -1,19 +1,10 @@
-from flask_caching import Cache
-
+from bemani.common import cache
from bemani.data import Config, Data
-from bemani.frontend.app import app
from bemani.frontend.iidx.iidx import IIDXFrontend
class IIDXCache:
@classmethod
def preload(cls, data: Data, config: Config) -> None:
- cache = Cache(
- app,
- config={
- "CACHE_TYPE": "filesystem",
- "CACHE_DIR": config.cache_dir,
- },
- )
frontend = IIDXFrontend(data, config, cache)
frontend.get_all_songs(force_db_load=True)
diff --git a/bemani/frontend/jubeat/cache.py b/bemani/frontend/jubeat/cache.py
index 7ebe1aa..ae9880b 100644
--- a/bemani/frontend/jubeat/cache.py
+++ b/bemani/frontend/jubeat/cache.py
@@ -1,19 +1,10 @@
-from flask_caching import Cache
-
+from bemani.common import cache
from bemani.data import Config, Data
-from bemani.frontend.app import app
from bemani.frontend.jubeat.jubeat import JubeatFrontend
class JubeatCache:
@classmethod
def preload(cls, data: Data, config: Config) -> None:
- cache = Cache(
- app,
- config={
- "CACHE_TYPE": "filesystem",
- "CACHE_DIR": config.cache_dir,
- },
- )
frontend = JubeatFrontend(data, config, cache)
frontend.get_all_songs(force_db_load=True)
diff --git a/bemani/frontend/museca/cache.py b/bemani/frontend/museca/cache.py
index c6e6518..a5b6307 100644
--- a/bemani/frontend/museca/cache.py
+++ b/bemani/frontend/museca/cache.py
@@ -1,19 +1,10 @@
-from flask_caching import Cache
-
+from bemani.common import cache
from bemani.data import Config, Data
-from bemani.frontend.app import app
from bemani.frontend.museca.museca import MusecaFrontend
class MusecaCache:
@classmethod
def preload(cls, data: Data, config: Config) -> None:
- cache = Cache(
- app,
- config={
- "CACHE_TYPE": "filesystem",
- "CACHE_DIR": config.cache_dir,
- },
- )
frontend = MusecaFrontend(data, config, cache)
frontend.get_all_songs(force_db_load=True)
diff --git a/bemani/frontend/popn/cache.py b/bemani/frontend/popn/cache.py
index ab5d924..78f6f22 100644
--- a/bemani/frontend/popn/cache.py
+++ b/bemani/frontend/popn/cache.py
@@ -1,19 +1,10 @@
-from flask_caching import Cache
-
+from bemani.common import cache
from bemani.data import Config, Data
-from bemani.frontend.app import app
from bemani.frontend.popn.popn import PopnMusicFrontend
class PopnMusicCache:
@classmethod
def preload(cls, data: Data, config: Config) -> None:
- cache = Cache(
- app,
- config={
- "CACHE_TYPE": "filesystem",
- "CACHE_DIR": config.cache_dir,
- },
- )
frontend = PopnMusicFrontend(data, config, cache)
frontend.get_all_songs(force_db_load=True)
diff --git a/bemani/frontend/reflec/cache.py b/bemani/frontend/reflec/cache.py
index 55f56c7..cffb461 100644
--- a/bemani/frontend/reflec/cache.py
+++ b/bemani/frontend/reflec/cache.py
@@ -1,19 +1,10 @@
-from flask_caching import Cache
-
+from bemani.common import cache
from bemani.data import Config, Data
-from bemani.frontend.app import app
from bemani.frontend.reflec.reflec import ReflecBeatFrontend
class ReflecBeatCache:
@classmethod
def preload(cls, data: Data, config: Config) -> None:
- cache = Cache(
- app,
- config={
- "CACHE_TYPE": "filesystem",
- "CACHE_DIR": config.cache_dir,
- },
- )
frontend = ReflecBeatFrontend(data, config, cache)
frontend.get_all_songs(force_db_load=True)
diff --git a/bemani/frontend/sdvx/cache.py b/bemani/frontend/sdvx/cache.py
index 656e786..8cb21e0 100644
--- a/bemani/frontend/sdvx/cache.py
+++ b/bemani/frontend/sdvx/cache.py
@@ -1,19 +1,10 @@
-from flask_caching import Cache
-
+from bemani.common import cache
from bemani.data import Config, Data
-from bemani.frontend.app import app
from bemani.frontend.sdvx.sdvx import SoundVoltexFrontend
class SoundVoltexCache:
@classmethod
def preload(cls, data: Data, config: Config) -> None:
- cache = Cache(
- app,
- config={
- "CACHE_TYPE": "filesystem",
- "CACHE_DIR": config.cache_dir,
- },
- )
frontend = SoundVoltexFrontend(data, config, cache)
frontend.get_all_songs(force_db_load=True)
diff --git a/bemani/frontend/templates/admin/settings.html b/bemani/frontend/templates/admin/settings.html
index 9a66b0f..85ef5b9 100644
--- a/bemani/frontend/templates/admin/settings.html
+++ b/bemani/frontend/templates/admin/settings.html
@@ -37,6 +37,10 @@
Cache Directory
{{ config.cache_dir }}
+
+ - memcached Server
+ - {{ config.memcached_server }}
+
Frontend Settings
diff --git a/bemani/utils/api.py b/bemani/utils/api.py
index dfaaf81..287720b 100644
--- a/bemani/utils/api.py
+++ b/bemani/utils/api.py
@@ -1,7 +1,11 @@
import argparse
+from typing import Any
from bemani.api import app, config # noqa: F401
-from bemani.utils.config import load_config as base_load_config
+from bemani.utils.config import (
+ load_config as base_load_config,
+ instantiate_cache as base_instantiate_cache,
+)
def load_config(filename: str) -> None:
@@ -9,6 +13,11 @@ def load_config(filename: str) -> None:
base_load_config(filename, config)
+def instantiate_cache(app: Any) -> None:
+ global config
+ base_instantiate_cache(app, config)
+
+
def main() -> None:
parser = argparse.ArgumentParser(
description="An API services provider for eAmusement games, conforming to BEMAPI specs."
@@ -48,6 +57,7 @@ def main() -> None:
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, profile_dir=".") # type: ignore
# Run the app
+ instantiate_cache(app)
app.run(host="0.0.0.0", port=args.port, debug=True)
diff --git a/bemani/utils/config.py b/bemani/utils/config.py
index f092168..81b9bae 100644
--- a/bemani/utils/config.py
+++ b/bemani/utils/config.py
@@ -1,4 +1,5 @@
import yaml
+from flask import Flask
from typing import Set
from bemani.backend.iidx import IIDXFactory
@@ -10,7 +11,7 @@ from bemani.backend.sdvx import SoundVoltexFactory
from bemani.backend.reflec import ReflecBeatFactory
from bemani.backend.museca import MusecaFactory
from bemani.backend.mga import MetalGearArcadeFactory
-from bemani.common import GameConstants
+from bemani.common import GameConstants, cache
from bemani.data import Config, Data
@@ -26,6 +27,34 @@ def load_config(filename: str, config: Config) -> None:
config["support"] = supported_series
+def instantiate_cache(app: Flask, config: Config) -> None:
+ # This could easily be extended to add support for any other backend that flask-caching
+ # supports but right now the only demand is for in-memory, filesystem and memcached.
+ if config.memcached_server is not None:
+ cache.init_app(
+ app,
+ config={
+ "CACHE_TYPE": "MemcachedCache",
+ "CACHE_MEMCACHED_SERVERS": [config.memcached_server],
+ },
+ )
+ elif config.cache_dir is not None:
+ cache.init_app(
+ app,
+ config={
+ "CACHE_TYPE": "FileSystemCache",
+ "CACHE_DIR": config.cache_dir,
+ },
+ )
+ else:
+ cache.init_app(
+ app,
+ config={
+ "CACHE_TYPE": "SimpleCache",
+ },
+ )
+
+
def register_games(config: Config) -> None:
if GameConstants.POPN_MUSIC in config.support:
PopnMusicFactory.register_all()
diff --git a/bemani/utils/frontend.py b/bemani/utils/frontend.py
index 429cbea..e54d64a 100644
--- a/bemani/utils/frontend.py
+++ b/bemani/utils/frontend.py
@@ -1,4 +1,5 @@
import argparse
+from typing import Any
from bemani.common import GameConstants
from bemani.frontend import app, config # noqa: F401
@@ -17,6 +18,7 @@ from bemani.frontend.reflec import reflec_pages
from bemani.frontend.museca import museca_pages
from bemani.utils.config import (
load_config as base_load_config,
+ instantiate_cache as base_instantiate_cache,
register_games as base_register_games,
)
@@ -60,6 +62,11 @@ def load_config(filename: str) -> None:
app.secret_key = config.secret_key
+def instantiate_cache(app: Any) -> None:
+ global config
+ base_instantiate_cache(app, config)
+
+
def main() -> None:
parser = argparse.ArgumentParser(
description="A front end services provider for eAmusement games."
@@ -105,6 +112,7 @@ def main() -> None:
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, profile_dir=".") # type: ignore
# Run the app
+ instantiate_cache(app)
app.run(host="0.0.0.0", port=args.port, debug=True)
diff --git a/bemani/utils/scheduler.py b/bemani/utils/scheduler.py
index fbec82a..f9fb1d4 100644
--- a/bemani/utils/scheduler.py
+++ b/bemani/utils/scheduler.py
@@ -1,4 +1,5 @@
import argparse
+from flask import Flask
from typing import Any, List
from bemani.backend.popn import PopnMusicFactory
@@ -21,7 +22,7 @@ from bemani.frontend.reflec import ReflecBeatCache
from bemani.frontend.museca import MusecaCache
from bemani.common import GameConstants, Time
from bemani.data import Config, Data
-from bemani.utils.config import load_config
+from bemani.utils.config import load_config, instantiate_cache
def run_scheduled_work(config: Config) -> None:
@@ -99,5 +100,8 @@ if __name__ == "__main__":
if args.read_only:
config["database"]["read_only"] = True
+ # Set up production cache, with a dummy app context because flask-caching needs it.
+ instantiate_cache(Flask(__name__), config)
+
# Run out of band work
run_scheduled_work(config)
diff --git a/bemani/utils/services.py b/bemani/utils/services.py
index 7a70e75..180cd5a 100644
--- a/bemani/utils/services.py
+++ b/bemani/utils/services.py
@@ -1,12 +1,15 @@
import argparse
import traceback
from flask import Flask, request, redirect, Response, make_response
+from typing import Any
+
from bemani.protocol import EAmuseProtocol
from bemani.backend import Dispatch, UnrecognizedPCBIDException
from bemani.data import Config, Data
from bemani.utils.config import (
load_config as base_load_config,
+ instantiate_cache as base_instantiate_cache,
register_games as base_register_games,
)
@@ -125,6 +128,11 @@ def load_config(filename: str) -> None:
base_load_config(filename, config)
+def instantiate_cache(app: Any) -> None:
+ global config
+ base_instantiate_cache(app, config)
+
+
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="A backend services provider for eAmusement games"
@@ -171,4 +179,5 @@ if __name__ == "__main__":
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, profile_dir=".") # type: ignore
# Run the app
+ instantiate_cache(app)
app.run(host="0.0.0.0", port=args.port, debug=True)
diff --git a/bemani/wsgi/api.wsgi b/bemani/wsgi/api.wsgi
index 823a8a8..94dafe2 100644
--- a/bemani/wsgi/api.wsgi
+++ b/bemani/wsgi/api.wsgi
@@ -1,6 +1,7 @@
-from bemani.utils.api import app, load_config
+from bemani.utils.api import app, load_config, instantiate_cache
# Assumes a production server yaml in the same directory as this WSGI
# file. Also assumes that your uWSGI instance is configured with a
# virtualenv that includes the installed version of this repo.
load_config('server.yaml')
+instantiate_cache(app)
diff --git a/bemani/wsgi/frontend.wsgi b/bemani/wsgi/frontend.wsgi
index c688329..e597379 100644
--- a/bemani/wsgi/frontend.wsgi
+++ b/bemani/wsgi/frontend.wsgi
@@ -1,4 +1,4 @@
-from bemani.utils.frontend import app, load_config, register_blueprints, register_games
+from bemani.utils.frontend import app, load_config, instantiate_cache, register_blueprints, register_games
# Assumes a production server yaml in the same directory as this WSGI
# file. Also assumes that your uWSGI instance is configured with a
@@ -6,3 +6,4 @@ from bemani.utils.frontend import app, load_config, register_blueprints, registe
load_config('server.yaml')
register_blueprints()
register_games()
+instantiate_cache(app)
diff --git a/bemani/wsgi/proxy.wsgi b/bemani/wsgi/proxy.wsgi
index 770a6df..1771aaa 100644
--- a/bemani/wsgi/proxy.wsgi
+++ b/bemani/wsgi/proxy.wsgi
@@ -1,6 +1,7 @@
-from bemani.utils.proxy import app, load_config
+from bemani.utils.proxy import app, load_config, instantiate_cache
# Assumes a proxy yaml in the same directory as this WSGI
# file. Also assumes that your uWSGI instance is configured with a
# virtualenv that includes the installed version of this repo.
load_config('proxy.yaml')
+instantiate_cache(app)
diff --git a/bemani/wsgi/services.wsgi b/bemani/wsgi/services.wsgi
index 6839037..8249986 100644
--- a/bemani/wsgi/services.wsgi
+++ b/bemani/wsgi/services.wsgi
@@ -1,7 +1,8 @@
-from bemani.utils.services import app, load_config, register_games
+from bemani.utils.services import app, load_config, instantiate_cache, register_games
# Assumes a production server yaml in the same directory as this WSGI
# file. Also assumes that your uWSGI instance is configured with a
# virtualenv that includes the installed version of this repo.
load_config('server.yaml')
register_games()
+instantiate_cache(app)
diff --git a/config/server.yaml b/config/server.yaml
index ad03ff8..eb2b46c 100644
--- a/config/server.yaml
+++ b/config/server.yaml
@@ -94,8 +94,12 @@ secret_key: 'this_is_a_secret_please_change_me'
name: 'e-AMUSEMENT Network'
# Administrative contact for this network.
email: 'nobody@nowhere.com'
-# Cache DIR, should point somewhere other than /tmp for production instances.
+# Cache DIR, should point somewhere other than /tmp for production instances that wish
+# to use filesystem caching. For memcached, delete this value.
cache_dir: '/tmp'
+# memcached server, should point somewhere other than this bogus value for production
+# instances that wish to use memcached backend. For filesystem caching, delete this value.
+memcached_server: 1.2.3.4:5678
# Number of seconds to preserve event logs before deleting them.
# Set to zero or delete to disable deleting logs.
event_log_duration: 2592000