Skip to content

Commit

Permalink
more app renames and removal of bottle & cherrypy
Browse files Browse the repository at this point in the history
  • Loading branch information
jheld committed Dec 12, 2023
1 parent 39cbdf4 commit 80587d2
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 200 deletions.
86 changes: 17 additions & 69 deletions diycrate/diycrate_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@
from boxsdk.object.folder import Folder
import httpx
import pyinotify
from bottle import ServerAdapter
from boxsdk import BoxAPIException, Client, exception
from cheroot import wsgi as wsgiserver
from cheroot.ssl.builtin import BuiltinSSLAdapter
import cherrypy


from diycrate.cache_utils import r_c
from diycrate.file_operations import (
Expand All @@ -39,17 +36,14 @@
from diycrate.path_utils import re_walk

from diycrate.log_utils import setup_logger
from diycrate.utils import Bottle, FastAPI
from diycrate.utils import FastAPI

setup_logger()

crate_logger = logging.getLogger(__name__)

cloud_provider_name = "Box"

bottle_app = Bottle()
bottle_app.processing_oauth_browser_lock = threading.Lock()
bottle_app.processing_oauth_refresh_lock = threading.Lock()

# The watch manager stores the watches and provides operations on watches

Expand All @@ -65,12 +59,6 @@
else:
wait_time = 1

handler = EventHandler(bottle_app=bottle_app, wait_time=wait_time)

file_notify_read_freq = 3

notifier = pyinotify.ThreadedNotifier(wm, handler, read_freq=file_notify_read_freq)
notifier.coalesce_events()

long_poll_thread = threading.Thread(target=long_poll_event_listener)
long_poll_thread.daemon = True
Expand All @@ -82,19 +70,25 @@
app.processing_oauth_browser_lock = threading.Lock()
app.processing_oauth_refresh_lock = threading.Lock()

handler = EventHandler(app=app, wait_time=wait_time)

file_notify_read_freq = 3

notifier = pyinotify.ThreadedNotifier(wm, handler, read_freq=file_notify_read_freq)
notifier.coalesce_events()


# only re-enable when wishing to test token refresh or access dance
# @bottle_app.route('/kill_tokens')
# @app.get('/kill_tokens/', response_class=fastapi.responses.PlainTextResponse)
# def kill_tokens():
# # will get all of the oauth instances, since they are all the same reference
# bottle_app.oauth._update_current_tokens(None, None)
# app.oauth._update_current_tokens(None, None)
# r_c.delete('diy_crate.auth.refresh_token', 'diy_crate.auth.access_token')
# return 'OK'

conf_obj = configparser.ConfigParser()


# @bottle_app.route("index")
@app.get("/index/", response_class=fastapi.responses.PlainTextResponse)
def index():
"""
Expand All @@ -104,15 +98,12 @@ def index():
return "Hello, World!"


# @bottle_app.route("/")
@app.get("/", response_class=fastapi.responses.PlainTextResponse)
def oauth_handler(state: str, code: str):
"""
RESTful end-point for the oauth handling
:return:
"""
# request = typing.cast(LocalRequest, bottle.request)
# assert bottle_app.csrf_token == request.GET["state"]
assert app.csrf_token == state
access_token, refresh_token = httpx.post(
conf_obj["box"]["authenticate_url"],
Expand All @@ -121,7 +112,7 @@ def oauth_handler(state: str, code: str):
).json()
store_tokens_callback(access_token, refresh_token)
app.oauth._update_current_tokens(access_token, refresh_token)
if not getattr(bottle_app, "started_cloud_threads", False):
if not getattr(app, "started_cloud_threads", False):
start_cloud_threads()
app.started_cloud_threads = True
app.processing_oauth_browser = False
Expand All @@ -138,7 +129,7 @@ def start_cloud_threads():
global oauth
client = Client(oauth)
handler.oauth = oauth
# bottle_app.oauth = oauth

app.oauth = oauth
wm.add_watch(BOX_DIR.as_posix(), mask, rec=True, auto_add=True)
client.auth._access_token = r_c.get("diy_crate.auth.access_token")
Expand Down Expand Up @@ -192,48 +183,6 @@ def start_cloud_threads():
walk_thread.start()


# Create our own sub-class of Bottle's ServerAdapter
# so that we can specify SSL. Using just server='cherrypy'
# uses the default cherrypy server, which doesn't use SSL
class SSLCherryPyServer(ServerAdapter):
"""
Custom server adapter using cherry-py with ssl
"""

def run(self, server_handler):
"""
Overrides super to setup Cherry py with ssl and start the server.
:param server_handler: originating server type
:type server_handler:
"""
server = wsgiserver.Server((self.host, self.port), server_handler)
# Uses the following github page's recommendation for setting up the cert:
# https://github.com/nickbabcock/bottle-ssl
server.ssl_adapter = BuiltinSSLAdapter(
conf_obj["ssl"]["cacert_pem_path"], conf_obj["ssl"]["privkey_pem_path"]
)
try:
server.start()
except KeyboardInterrupt:
walk_thread.join()
long_poll_thread.join()
notifier.join()
finally:
server.stop()

@cherrypy.tools.register("before_finalize", priority=60)
def secureheaders():
headers = cherrypy.response.headers
headers["X-Frame-Options"] = "DENY"
headers["X-XSS-Protection"] = "1; mode=block"
headers["Content-Security-Policy"] = "default-src 'self';"
if (
cherrypy.server.ssl_certificate is not None
and cherrypy.server.ssl_private_key is not None
):
headers["Strict-Transport-Security"] = "max-age=31536000" # one year


def main():
global oauth
global BOX_DIR
Expand Down Expand Up @@ -340,8 +289,7 @@ def main():
"web_server_port": args.port or args.web_server_port,
}
web_server_port: int = int(conf_obj["box"]["web_server_port"])
bottle_thread = threading.Thread(
# target=bottle_app.run,
web_server_thread = threading.Thread(
target=uvicorn.run,
kwargs=dict(
app=app,
Expand All @@ -351,14 +299,14 @@ def main():
ssl_keyfile=conf_obj["ssl"]["privkey_pem_path"],
),
)
bottle_thread.daemon = True
web_server_thread.daemon = True
with cloud_credentials_file_path.open("w") as fh:
conf_obj.write(fh)
BOX_DIR = Path(conf_obj["box"]["directory"]).expanduser().resolve()
if not BOX_DIR.is_dir():
BOX_DIR.mkdir()
if bottle_thread and not bottle_thread.is_alive():
bottle_thread.start()
if web_server_thread and not web_server_thread.is_alive():
web_server_thread.start()

if not (
r_c.exists("diy_crate.auth.access_token")
Expand Down
28 changes: 12 additions & 16 deletions diycrate/file_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from requests.exceptions import ConnectionError
from urllib3.exceptions import ProtocolError

from diycrate.utils import Bottle
from diycrate.utils import FastAPI

from .cache_utils import (
redis_key,
Expand Down Expand Up @@ -75,7 +75,7 @@ def my_init(self, **kargs):
Extends the super to add cloud storage state.
:return:
"""
our_keys = ["oauth", "bottle_app", "wait_time"]
our_keys = ["oauth", "app", "wait_time"]
our_kargs = {k: kargs.get(k) for k in our_keys}
for key in our_keys:
kargs.pop(key, None)
Expand All @@ -91,7 +91,7 @@ def my_init(self, **kargs):
self.operations_thread = threading.Thread(
target=self.incoming_operation_coalesce
)
self.bottle_app = kargs.get("bottle_app")
self.app = kargs.get("app")
self.operations_thread.daemon = True
self.operations_thread.start()

Expand Down Expand Up @@ -254,7 +254,7 @@ def process_real_close_event(self, event: pyinotify.Event):
crate_logger.debug(f"Real close...: {file_path.as_posix()}")
folders_to_traverse = self.folders_to_traverse(file_path.parent.as_posix())
client = Client(self.oauth)
cur_box_folder = get_box_folder(client, "0", 5, bottle_app=self.bottle_app)
cur_box_folder = get_box_folder(client, "0", 5, app=self.app)
# if we're modifying in root box dir, then we've already found the folder
try:
is_base = any(
Expand Down Expand Up @@ -362,9 +362,7 @@ def process_modify_event(self, event: pyinotify.Event, operation: str):

folder_id = "0"
retry_limit = 5
cur_box_folder = get_box_folder(
client, folder_id, retry_limit, bottle_app=self.bottle_app
)
cur_box_folder = get_box_folder(client, folder_id, retry_limit, app=self.app)
# if we're modifying in root box dir, then we've already found the folder
try:
is_base = any(
Expand Down Expand Up @@ -522,8 +520,8 @@ def process_create_event(self, event: pyinotify.Event):
box_folder = typing.cast(Folder, client.folder(folder_id="0").get()) # type: ignore
except BoxAPIException as e:
crate_logger.warning("Error getting box root folder.", exc_info=e)
get_access_token(client.auth.access_token, bottle_app=self.bottle_app)
client._auth = self.bottle_app.oauth
get_access_token(client.auth.access_token, app=self.app)
client._auth = self.app.oauth
time.sleep(min([pow(2, loop_index), 8]))
loop_index += 1
except Exception:
Expand Down Expand Up @@ -735,9 +733,7 @@ def process_move_event(self, event: pyinotify.Event):
r_c.set(redis_key(src_file.object_id), json.dumps(version_info))
r_c.set("diy_crate.last_save_time_stamp", int(time.time()))
path_builder = BOX_DIR
oauth = setup_remote_oauth(
r_c, conf=conf_obj, bottle_app=self.bottle_app
)
oauth = setup_remote_oauth(r_c, conf=conf_obj, app=self.app)
client = Client(oauth)
parent_folders: list[Folder] = (
client.file(file_obj.object_id)
Expand Down Expand Up @@ -1170,7 +1166,7 @@ def get_box_folder(
client: Client,
folder_id: str,
retry_limit: int,
bottle_app: Union[Bottle, None] = None,
app: Union[FastAPI, None] = None,
) -> Folder:
"""
Expand All @@ -1190,9 +1186,9 @@ def get_box_folder(
cur_box_folder = box_folder
break
except BoxAPIException as e:
get_access_token(client.auth._access_token, bottle_app=bottle_app)
if bottle_app:
client._auth = bottle_app.oauth
get_access_token(client.auth._access_token, app=app)
if app:
client._auth = app.oauth
if i == retry_limit - 1:
crate_logger.info("Bad box api response.", exc_info=e)
raise e
Expand Down
6 changes: 3 additions & 3 deletions diycrate/long_poll_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
)
from diycrate.log_utils import setup_logger
from diycrate.oauth_utils import get_access_token
from diycrate.utils import Bottle
from diycrate.utils import FastAPI

setup_logger()

Expand Down Expand Up @@ -886,7 +886,7 @@ def events(self):
return CustomBoxEvents(self._session)


def long_poll_event_listener(file_event_handler: EventHandler, bottle_app: Bottle):
def long_poll_event_listener(file_event_handler: EventHandler, app: FastAPI):
"""
Receive and process remote cloud item events in real-time
:return:
Expand Down Expand Up @@ -954,7 +954,7 @@ def long_poll_event_listener(file_event_handler: EventHandler, bottle_app: Bottl
)
except exception.BoxAPIException as e:
crate_logger.warning("Box or AttributeError occurred.", exc_info=e)
get_access_token(client.auth._access_token, bottle_app=bottle_app)
get_access_token(client.auth._access_token, app=app)
client.auth._update_current_tokens(oauth.access_token, oauth._refresh_token)
except (AttributeError):
crate_logger.warning("Box or AttributeError occurred.", exc_info=True)
Expand Down
24 changes: 12 additions & 12 deletions diycrate/oauth_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from pyinotify import ProcessEvent
from redis import Redis

from diycrate.utils import Bottle, FastAPI
from diycrate.utils import FastAPI
from diycrate.cache_utils import r_c


Expand Down Expand Up @@ -52,7 +52,7 @@ def setup_oauth(cache_client, conf_object, callback: typing.Callable[[str, str],

def get_access_token(
access_token: str | bytes | None,
app: Union[None, FastAPI, Bottle] = None,
app: Union[None, FastAPI] = None,
) -> str:
"""
Expand Down Expand Up @@ -161,7 +161,7 @@ def get_access_token(
raise e

else:
crate_logger.warn("Bad bottle_app object passed in, exiting", exc_info=e)
crate_logger.warn("Bad app object passed in, exiting", exc_info=e)
sys.exit(1)
else:
crate_logger.debug(
Expand All @@ -175,7 +175,7 @@ def setup_remote_oauth(
cache_client,
retrieve_access_token: typing.Callable = get_access_token,
conf: Union[ConfigParser, dict, None] = None,
app: Union[Bottle, FastAPI, None] = None,
app: Union[FastAPI, None] = None,
) -> RemoteOAuth2:
"""
sets up the oauth instance with credentials and runtime callback.
Expand Down Expand Up @@ -207,7 +207,7 @@ def setup_remote_oauth(
def wrapper_retrieve_access_token(access_token_arg):
return retrieve_access_token(
access_token_arg,
bottle_app=app,
app=app,
)

oauth = RemoteOAuth2(
Expand All @@ -220,11 +220,11 @@ def wrapper_retrieve_access_token(access_token_arg):
return oauth


def setup_remote_oauth_bais(
def setup_remote_oauth_initial_basic(
cache_client,
retrieve_access_token: typing.Callable = get_access_token,
conf: Union[ConfigParser, dict, None] = None,
bottle_app: Union[Bottle, None] = None,
app: Union[FastAPI, None] = None,
) -> RemoteOAuth2:
"""
sets up the oauth instance with credentials and runtime callback.
Expand Down Expand Up @@ -261,7 +261,7 @@ def wrapper_retrieve_access_token_function(access_token_arg):
)
return retrieve_access_token(
access_token_arg,
bottle_app=bottle_app,
app=app,
)

oauth_obj = RemoteOAuth2(
Expand Down Expand Up @@ -299,7 +299,7 @@ def oauth_dance(
conf: Union[ConfigParser, dict, None],
app: FastAPI,
file_event_handler: Union[ProcessEvent, None],
bottle_thread: Union[threading.Thread, None] = None,
web_server_thread: Union[threading.Thread, None] = None,
):
global oauth
if True:
Expand Down Expand Up @@ -336,9 +336,9 @@ def oauth_dance(
redirect_url=f"https://{redirect_url_domain}:{conf['box']['web_server_port']}/"
)
app.csrf_token = csrf_token
if bottle_thread and not bottle_thread.is_alive():
bottle_thread.start()
if web_server_thread and not web_server_thread.is_alive():
web_server_thread.start()
webbrowser.open_new_tab(auth_url) # make it easy for the end-user to start auth


oauth = setup_remote_oauth_bais(cache_client=r_c)
oauth = setup_remote_oauth_initial_basic(cache_client=r_c)
Loading

0 comments on commit 80587d2

Please sign in to comment.