Skip to content

Commit

Permalink
Merge pull request #36 from TheManWhoLikesToCode/TheManWhoLikesToCode…
Browse files Browse the repository at this point in the history
…-patch-1

Fix Issue #11
  • Loading branch information
TheManWhoLikesToCode authored Jan 17, 2024
2 parents 01960df + cf76031 commit d9458f8
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 258 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ frontend/.env
*.pyc
*.DS_Store
backend/support/.DS_Store
credentials.json
backend/credentials.json
.DS_Store
frontend/.DS_Store
backend/support/.DS_Store
frontend/.DS_Store
143 changes: 16 additions & 127 deletions backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@
import os
import threading
import time
import uuid

from dotenv import load_dotenv
from flask import Flask, abort, after_this_request, jsonify, request, send_from_directory
from flask_cors import CORS, cross_origin
from flask_apscheduler import APScheduler
import yaml

from blackboard_scraper import BlackboardSession
from file_management import clean_up_session_files, delete_session_files, list_files_in_drive_folder, update_drive_directory, clean_up_docs_files
from blackboard_session import BlackboardSession
from file_management import clean_up_session_files, delete_session_files, view_in_drive_folder, update_drive_directory, clean_up_docs_files, remove_file_safely, is_file_valid, authorize_drive, get_session_files_path, file_name_from_path
from blackboard_session_manager import BlackboardSessionManager
import config

from pydrive2.auth import GoogleAuth
from pydrive2.drive import GoogleDrive

app = Flask(__name__)
cors = CORS(app)
scheduler = APScheduler()
Expand All @@ -31,18 +27,6 @@
load_dotenv()


def is_file_valid(file_path):
return os.path.isfile(file_path) and not os.path.islink(file_path)


def remove_file_safely(file_path):
try:
if is_file_valid(file_path):
os.remove(file_path)
except OSError as error:
app.logger.error(f"Error removing file: {error}")


@scheduler.task('interval', id='clean_up', seconds=600)
def clean_up_and_upload_files_to_google_drive(file_path=None):

Expand All @@ -58,95 +42,17 @@ def clean_up_and_upload_files_to_google_drive(file_path=None):
app.logger.error(f"Error during post-download operations: {e}")


def authorize_drive():
current_directory = os.getcwd()

if 'backend' in current_directory:
settings_path = 'settings.yaml'
elif 'Archive-Me' in current_directory:
settings_path = 'backend/settings.yaml'
else:
raise Exception("Unable to locate settings file.")

with open(settings_path, 'r') as file:
settings = yaml.safe_load(file)

settings['client_config']['client_id'] = os.environ.get('GOOGLE_CLIENT_ID')
settings['client_config']['client_secret'] = os.environ.get(
'GOOGLE_CLIENT_SECRET')

gauth = GoogleAuth(settings=settings)

if os.path.isfile("credentials.json"):
gauth.LoadCredentialsFile("credentials.json")
else:
url = gauth.GetAuthUrl()
logging.info("Please visit this URL and get the auth code: " + url)
code = input("Enter the auth code: ")
gauth.Auth(code)
gauth.SaveCredentialsFile("credentials.json")

if gauth.access_token_expired:
gauth.Refresh()
gauth.SaveCredentialsFile("credentials.json")

drive = GoogleDrive(gauth)
return drive


bb_sessions = {}


def get_bb_session(username):
if 'bb_sessions' not in bb_sessions:
bb_sessions['bb_sessions'] = {}

if username not in bb_sessions['bb_sessions']:
session_id = str(uuid.uuid4()) # Generate a unique session ID
bb_sessions['bb_sessions'][username] = session_id
# Store the session object
bb_sessions[session_id] = BlackboardSession()

return bb_sessions[bb_sessions['bb_sessions'][username]]


def put_bb_session(username, bb_session):
session_id = bb_sessions['bb_sessions'].get(username)
if session_id:
bb_sessions[session_id] = bb_session


def retrieve_bb_session(username):
if 'bb_sessions' not in bb_sessions:
bb_sessions['bb_sessions'] = {}

session_id = bb_sessions['bb_sessions'].get(username)
if session_id:
return bb_sessions.get(session_id)

return None


def delete_bb_session(username):
session_id = bb_sessions['bb_sessions'].get(username)
if session_id:
session_to_delete = bb_sessions.pop(session_id, None)
if session_to_delete:
del bb_sessions['bb_sessions'][username]
bb_session_manager = BlackboardSessionManager()


@scheduler.task('interval', id='delete_sessions', seconds=60)
def delete_inactive_bb_sessions(inactivity_threshold_seconds=180):
current_time = time.time()

# Check if 'bb_sessions' key exists
if 'bb_sessions' not in bb_sessions:
return # No sessions exist yet

# Collect usernames with inactive sessions for deletion
usernames_to_delete = []
for username, session_id in bb_sessions['bb_sessions'].items():
session = bb_sessions.get(session_id)
for username, session_id in bb_session_manager.user_session_map.items():
session = bb_session_manager.bb_sessions.get(session_id)
if session:
last_activity_time = session.last_activity_time
inactive_duration = current_time - last_activity_time
Expand All @@ -155,13 +61,10 @@ def delete_inactive_bb_sessions(inactivity_threshold_seconds=180):

# Delete collected usernames' sessions
for username in usernames_to_delete:
delete_bb_session(username)
bb_session_manager.delete_bb_session(username)

print("Deleting inactive sessions at:", time.time())

session_id = bb_sessions['bb_sessions'].get(username)
return bb_sessions.get(session_id)


@app.route('/')
@cross_origin()
Expand All @@ -181,14 +84,14 @@ def login():

try:
# Retrieve or create a session for the user
bb_session = get_bb_session(username)
bb_session = bb_session_manager.get_bb_session(username)
bb_session.username = username
bb_session.password = password

bb_session.login()
response = bb_session.get_response()
if response == 'Login successful.':
put_bb_session(username, bb_session)
bb_session_manager.put_bb_session(username, bb_session)
return jsonify({'message': 'Logged in successfully'})
else:
return jsonify({'error': response}), 401
Expand All @@ -203,7 +106,7 @@ def scrape():
return jsonify({'error': 'Username is required'}), 400

try:
bb_session = retrieve_bb_session(username)
bb_session = bb_session_manager.retrieve_bb_session(username)

if not bb_session:
return jsonify({'error': 'Session not found'}), 400
Expand Down Expand Up @@ -254,19 +157,14 @@ def list_directory(path):

if path is None:
path = team_drive_id
items = list_files_in_drive_folder(drive, path, team_drive_id)

if len(items) == 1:
item = items[0]
item_type, file_name, file_id = item[3], item[0], item[2]
folders, files = view_in_drive_folder(drive, path, team_drive_id)

if item_type == 'FILE':
return handle_single_file(file_id, file_name)
elif item_type == 'FOLDER':
return jsonify({'error': 'Cannot download a folder.'}), 400

return jsonify(items)
items = folders + files
if not items:
file_name = file_name_from_path(drive, path)
return handle_single_file(path, file_name)

return jsonify({'folders': folders, 'files': files})

def handle_single_file(file_id, file_name):
session_files_path = get_session_files_path()
Expand All @@ -287,15 +185,6 @@ def trigger_post_download_operations(response):

return send_from_directory(session_files_path, file_name, as_attachment=True)


def get_session_files_path():
current_dir = os.path.dirname(os.path.abspath(__file__))
if os.path.basename(current_dir) != 'backend':
return os.path.join(current_dir, 'backend', 'Session Files')
else:
return os.path.join(current_dir, 'Session Files')


@app.route('/browse')
def list_root_directory():
return list_directory(None)
Expand Down
35 changes: 14 additions & 21 deletions backend/blackboard_scraper.py → backend/blackboard_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,28 +381,22 @@ def get_courses(self):

def download_and_save_file(self):
"""
Downloads and saves the taks passed from the get dwonload tasks function.
Downloads and saves the tasks passed from the get download tasks function.
self modifies:
zipFound -- A boolean value indicating if the zip file was found.
last_activity_time -- The time of the last activity.
response -- The response of the download and save file attempt.
"""

if self.is_logged_in == False:
if not self.is_logged_in:
self.response = "Not logged in."
return

current_dir = os.path.dirname(os.path.abspath(__file__))
if os.path.basename(current_dir) != 'backend':
session_files_path = os.path.join(
current_dir, 'backend', 'Session Files')
else:
session_files_path = os.path.join(current_dir, 'Session Files')
session_files_path = os.path.join(current_dir, 'backend', 'Session Files') if os.path.basename(current_dir) != 'backend' else os.path.join(current_dir, 'Session Files')

zip_file_name = self.username + '_downloaded_content.zip'
zip_file_name = f'{self.username}_downloaded_content.zip'
zip_file_path = os.path.join(current_dir, zip_file_name)

download_tasks = getattr(self, 'download_tasks', [])
Expand All @@ -421,17 +415,17 @@ def download_task(task):
name, current_extension = os.path.splitext(assignment_name)

if current_extension:
mime_of_current_extension = mimetypes.guess_type(assignment_name)[
0]
if mime_of_current_extension == content_type:
extension = current_extension
else:
extension = guessed_extension or current_extension
mime_of_current_extension = mimetypes.guess_type(assignment_name)[0]
extension = current_extension if mime_of_current_extension == content_type else guessed_extension or current_extension
else:
if 'html' in content_type or b'<html' in response.content or b'<!DOCTYPE HTML' in response.content or b'<html lang="en-US">' in response.content:
return
else:
extension = guessed_extension or '.bin'
extension = guessed_extension or '.bin'

# Skip download if file type is None
if extension is None:
print(f"Skipped downloading {assignment_name} as file type could not be determined.")
return

file_path = os.path.join(base_directory, name + extension)

Expand All @@ -448,16 +442,15 @@ def download_task(task):
for file in files:
if file.endswith('.pdf') or file.endswith('.docx'):
file_path = os.path.join(root, file)
arcname = os.path.relpath(
file_path, session_files_path)
arcname = os.path.relpath(file_path, session_files_path)
zipf.write(file_path, arcname=arcname)

# Return the relative path of the zip file
self.zipFound = True
self.last_activity_time = time.time()

return os.path.relpath(zip_file_path, os.getcwd())


def get_download_tasks(self):
"""
Expand Down
35 changes: 35 additions & 0 deletions backend/blackboard_session_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import uuid
from blackboard_session import BlackboardSession

class BlackboardSessionManager:
def __init__(self):
self.bb_sessions = {}
self.user_session_map = {}

def get_bb_session(self, username):
if username not in self.user_session_map:
session_id = str(uuid.uuid4()) # Generate a unique session ID
self.user_session_map[username] = session_id
# Store the session object
self.bb_sessions[session_id] = BlackboardSession()

return self.bb_sessions[self.user_session_map[username]]

def put_bb_session(self, username, bb_session):
session_id = self.user_session_map.get(username)
if session_id:
self.bb_sessions[session_id] = bb_session

def retrieve_bb_session(self, username):
session_id = self.user_session_map.get(username)
if session_id:
return self.bb_sessions.get(session_id)

return None

def delete_bb_session(self, username):
session_id = self.user_session_map.get(username)
if session_id:
session_to_delete = self.bb_sessions.pop(session_id, None)
if session_to_delete:
del self.user_session_map[username]
2 changes: 1 addition & 1 deletion backend/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

if env == 'dev':
PORT = 5003
DEBUG = True
DEBUG = False
elif env == 'prod':
PORT = 5001
DEBUG = False
Expand Down
2 changes: 1 addition & 1 deletion backend/features/steps/blackboard_session_steps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from behave import given, when, then
from blackboard_scraper import BlackboardSession
from blackboard_session import BlackboardSession
import os
from unittest.mock import patch
from dotenv import load_dotenv
Expand Down
Loading

0 comments on commit d9458f8

Please sign in to comment.