Commit
·
bd41193
1
Parent(s):
9d4e272
printing answers
Browse files- main.py +83 -7
- templates/index.html +12 -0
main.py
CHANGED
@@ -6,12 +6,15 @@ import sys
|
|
6 |
import builtins
|
7 |
from datetime import datetime
|
8 |
from flask_cors import CORS
|
9 |
-
from flask import Flask, request, jsonify, render_template, make_response
|
10 |
from werkzeug.utils import secure_filename
|
11 |
from dotenv import load_dotenv
|
12 |
import json
|
13 |
import shutil
|
14 |
from threading import Thread, Event
|
|
|
|
|
|
|
15 |
|
16 |
# Load environment variables
|
17 |
load_dotenv()
|
@@ -30,6 +33,7 @@ log_file = os.path.join(log_dir, 'app.log') # Add log file path
|
|
30 |
# Global variables for model caching and initialization status
|
31 |
global_models = {}
|
32 |
initialization_complete = Event()
|
|
|
33 |
|
34 |
def ensure_directory(path):
|
35 |
"""Create directory and ensure full permissions with better error handling"""
|
@@ -327,6 +331,35 @@ def validate_folder_structure(files):
|
|
327 |
except Exception as e:
|
328 |
return False, f"Error validating folder structure: {str(e)}"
|
329 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
330 |
@app.route('/compute_marks', methods=['POST'])
|
331 |
def compute_marks():
|
332 |
try:
|
@@ -415,6 +448,7 @@ def compute_marks():
|
|
415 |
# Dictionary to store results by student folder and image name
|
416 |
results = {}
|
417 |
failed_files = []
|
|
|
418 |
|
419 |
try:
|
420 |
# Process each file
|
@@ -484,6 +518,18 @@ def compute_marks():
|
|
484 |
|
485 |
log_print(f"Extracted text: {extracted_text[:100]}...")
|
486 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
487 |
# Clean the extracted text for JSON
|
488 |
extracted_text = extracted_text.encode('ascii', 'ignore').decode('ascii')
|
489 |
|
@@ -580,7 +626,11 @@ def compute_marks():
|
|
580 |
"failed_files": [{
|
581 |
"file": f["file"].encode('ascii', 'ignore').decode('ascii'),
|
582 |
"error": f["error"].encode('ascii', 'ignore').decode('ascii')
|
583 |
-
} for f in failed_files]
|
|
|
|
|
|
|
|
|
584 |
}
|
585 |
|
586 |
log_print(f"Sending response: {response_data}")
|
@@ -675,25 +725,51 @@ def ensure_initialization():
|
|
675 |
wait_for_initialization()
|
676 |
|
677 |
def cleanup_temp_files():
|
678 |
-
"""Clean up temporary files with proper error handling"""
|
679 |
try:
|
680 |
-
# Clean up the temporary processing directory
|
681 |
temp_processing_dir = os.path.join(BASE_DIR, 'temp_processing')
|
682 |
if os.path.exists(temp_processing_dir):
|
683 |
-
|
|
|
|
|
|
|
|
|
684 |
|
685 |
-
# Clean up the images directory
|
686 |
if os.path.exists(images_dir):
|
687 |
for file in os.listdir(images_dir):
|
688 |
try:
|
689 |
file_path = os.path.join(images_dir, file)
|
690 |
if os.path.isfile(file_path):
|
691 |
-
|
|
|
|
|
|
|
692 |
except Exception as e:
|
693 |
log_print(f"Warning: Could not delete file {file_path}: {e}", "WARNING")
|
694 |
except Exception as e:
|
695 |
log_print(f"Error cleaning up temporary files: {e}", "ERROR")
|
696 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
697 |
if __name__ == '__main__':
|
698 |
try:
|
699 |
# Create essential directories
|
|
|
6 |
import builtins
|
7 |
from datetime import datetime
|
8 |
from flask_cors import CORS
|
9 |
+
from flask import Flask, request, jsonify, render_template, make_response, Response
|
10 |
from werkzeug.utils import secure_filename
|
11 |
from dotenv import load_dotenv
|
12 |
import json
|
13 |
import shutil
|
14 |
from threading import Thread, Event
|
15 |
+
import queue
|
16 |
+
import time
|
17 |
+
from contextlib import contextmanager
|
18 |
|
19 |
# Load environment variables
|
20 |
load_dotenv()
|
|
|
33 |
# Global variables for model caching and initialization status
|
34 |
global_models = {}
|
35 |
initialization_complete = Event()
|
36 |
+
notification_queue = queue.Queue()
|
37 |
|
38 |
def ensure_directory(path):
|
39 |
"""Create directory and ensure full permissions with better error handling"""
|
|
|
331 |
except Exception as e:
|
332 |
return False, f"Error validating folder structure: {str(e)}"
|
333 |
|
334 |
+
@app.route('/notifications')
|
335 |
+
def notifications():
|
336 |
+
def generate():
|
337 |
+
start_time = time.time()
|
338 |
+
timeout = 300 # 5 minutes timeout
|
339 |
+
|
340 |
+
while True:
|
341 |
+
try:
|
342 |
+
# Check timeout
|
343 |
+
if time.time() - start_time > timeout:
|
344 |
+
yield "data: {\"type\": \"timeout\", \"message\": \"Connection timed out\"}\n\n"
|
345 |
+
break
|
346 |
+
|
347 |
+
# Get notification from queue (non-blocking)
|
348 |
+
try:
|
349 |
+
notification = notification_queue.get_nowait()
|
350 |
+
yield f"data: {json.dumps(notification)}\n\n"
|
351 |
+
except queue.Empty:
|
352 |
+
# If no notification, yield empty to keep connection alive
|
353 |
+
yield "data: {}\n\n"
|
354 |
+
time.sleep(0.5) # Increased delay to reduce server load
|
355 |
+
|
356 |
+
except Exception as e:
|
357 |
+
log_print(f"Error in notification stream: {str(e)}", "ERROR")
|
358 |
+
yield f"data: {{\"type\": \"error\", \"message\": \"{str(e)}\"}}\n\n"
|
359 |
+
break
|
360 |
+
|
361 |
+
return Response(generate(), mimetype='text/event-stream')
|
362 |
+
|
363 |
@app.route('/compute_marks', methods=['POST'])
|
364 |
def compute_marks():
|
365 |
try:
|
|
|
448 |
# Dictionary to store results by student folder and image name
|
449 |
results = {}
|
450 |
failed_files = []
|
451 |
+
extracted_texts = {} # Add this line to store extracted texts
|
452 |
|
453 |
try:
|
454 |
# Process each file
|
|
|
518 |
|
519 |
log_print(f"Extracted text: {extracted_text[:100]}...")
|
520 |
|
521 |
+
# Send notification for extracted text immediately
|
522 |
+
notification_data = {
|
523 |
+
"type": "extracted_text",
|
524 |
+
"filename": filename,
|
525 |
+
"text": extracted_text[:200] + ("..." if len(extracted_text) > 200 else "")
|
526 |
+
}
|
527 |
+
notification_queue.put(notification_data)
|
528 |
+
log_print(f"Notification queued for {filename}")
|
529 |
+
|
530 |
+
# Store extracted text for final response
|
531 |
+
extracted_texts[filename] = extracted_text
|
532 |
+
|
533 |
# Clean the extracted text for JSON
|
534 |
extracted_text = extracted_text.encode('ascii', 'ignore').decode('ascii')
|
535 |
|
|
|
626 |
"failed_files": [{
|
627 |
"file": f["file"].encode('ascii', 'ignore').decode('ascii'),
|
628 |
"error": f["error"].encode('ascii', 'ignore').decode('ascii')
|
629 |
+
} for f in failed_files],
|
630 |
+
"extracted_texts": { # Add this section to include extracted texts
|
631 |
+
filename: text.encode('ascii', 'ignore').decode('ascii')
|
632 |
+
for filename, text in extracted_texts.items()
|
633 |
+
}
|
634 |
}
|
635 |
|
636 |
log_print(f"Sending response: {response_data}")
|
|
|
725 |
wait_for_initialization()
|
726 |
|
727 |
def cleanup_temp_files():
|
728 |
+
"""Clean up temporary files with proper error handling and timeout"""
|
729 |
try:
|
730 |
+
# Clean up the temporary processing directory with timeout
|
731 |
temp_processing_dir = os.path.join(BASE_DIR, 'temp_processing')
|
732 |
if os.path.exists(temp_processing_dir):
|
733 |
+
try:
|
734 |
+
with timeout(30): # 30 second timeout for cleanup
|
735 |
+
shutil.rmtree(temp_processing_dir, ignore_errors=True)
|
736 |
+
except TimeoutError:
|
737 |
+
log_print("Warning: Cleanup of temp_processing_dir timed out", "WARNING")
|
738 |
|
739 |
+
# Clean up the images directory with timeout
|
740 |
if os.path.exists(images_dir):
|
741 |
for file in os.listdir(images_dir):
|
742 |
try:
|
743 |
file_path = os.path.join(images_dir, file)
|
744 |
if os.path.isfile(file_path):
|
745 |
+
with timeout(5): # 5 second timeout per file
|
746 |
+
os.unlink(file_path)
|
747 |
+
except TimeoutError:
|
748 |
+
log_print(f"Warning: Cleanup of file {file_path} timed out", "WARNING")
|
749 |
except Exception as e:
|
750 |
log_print(f"Warning: Could not delete file {file_path}: {e}", "WARNING")
|
751 |
except Exception as e:
|
752 |
log_print(f"Error cleaning up temporary files: {e}", "ERROR")
|
753 |
|
754 |
+
# Add timeout context manager
|
755 |
+
class timeout:
|
756 |
+
def __init__(self, seconds):
|
757 |
+
self.seconds = seconds
|
758 |
+
|
759 |
+
def __enter__(self):
|
760 |
+
import signal
|
761 |
+
def timeout_handler(signum, frame):
|
762 |
+
raise TimeoutError("Timed out!")
|
763 |
+
# Set the signal handler and a 5-second alarm
|
764 |
+
self.original_handler = signal.signal(signal.SIGALRM, timeout_handler)
|
765 |
+
signal.alarm(self.seconds)
|
766 |
+
return self
|
767 |
+
|
768 |
+
def __exit__(self, type, value, traceback):
|
769 |
+
import signal
|
770 |
+
signal.alarm(0)
|
771 |
+
signal.signal(signal.SIGALRM, self.original_handler)
|
772 |
+
|
773 |
if __name__ == '__main__':
|
774 |
try:
|
775 |
# Create essential directories
|
templates/index.html
CHANGED
@@ -1303,6 +1303,15 @@
|
|
1303 |
keepalive: true
|
1304 |
});
|
1305 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1306 |
let result;
|
1307 |
const contentType = response.headers.get('content-type');
|
1308 |
if (contentType && contentType.includes('application/json')) {
|
@@ -1311,6 +1320,9 @@
|
|
1311 |
throw new Error('Server returned non-JSON response');
|
1312 |
}
|
1313 |
|
|
|
|
|
|
|
1314 |
if (!response.ok) {
|
1315 |
throw new Error(result.message || result.error || `Server error: ${response.status}`);
|
1316 |
}
|
|
|
1303 |
keepalive: true
|
1304 |
});
|
1305 |
|
1306 |
+
// Set up event source for real-time notifications
|
1307 |
+
const eventSource = new EventSource('/notifications');
|
1308 |
+
eventSource.onmessage = function(event) {
|
1309 |
+
const data = JSON.parse(event.data);
|
1310 |
+
if (data.type === 'extracted_text') {
|
1311 |
+
notificationSystem.info(`Extracted text from ${data.filename}:\n${data.text}`, 8000);
|
1312 |
+
}
|
1313 |
+
};
|
1314 |
+
|
1315 |
let result;
|
1316 |
const contentType = response.headers.get('content-type');
|
1317 |
if (contentType && contentType.includes('application/json')) {
|
|
|
1320 |
throw new Error('Server returned non-JSON response');
|
1321 |
}
|
1322 |
|
1323 |
+
// Close event source after getting final response
|
1324 |
+
eventSource.close();
|
1325 |
+
|
1326 |
if (!response.ok) {
|
1327 |
throw new Error(result.message || result.error || `Server error: ${response.status}`);
|
1328 |
}
|