Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Update app.py
Browse files
app.py
CHANGED
|
@@ -70,17 +70,21 @@ def save_pdf_metadata():
|
|
| 70 |
logger.error(f"메타데이터 저장 오류: {e}")
|
| 71 |
|
| 72 |
# PDF ID 생성 (파일명 + 타임스탬프 기반)
|
|
|
|
| 73 |
def generate_pdf_id(filename: str) -> str:
|
| 74 |
# 파일명에서 확장자 제거
|
| 75 |
base_name = os.path.splitext(filename)[0]
|
| 76 |
-
# URL
|
| 77 |
-
|
|
|
|
| 78 |
# 타임스탬프 추가로 고유성 보장
|
| 79 |
timestamp = int(time.time())
|
| 80 |
# 짧은 임의 문자열 추가
|
| 81 |
random_suffix = uuid.uuid4().hex[:6]
|
| 82 |
return f"{safe_name}_{timestamp}_{random_suffix}"
|
| 83 |
|
|
|
|
|
|
|
| 84 |
# PDF 파일 목록 가져오기 (메인 디렉토리용)
|
| 85 |
def get_pdf_files():
|
| 86 |
pdf_files = []
|
|
@@ -297,15 +301,21 @@ async def cache_pdf(pdf_path: str):
|
|
| 297 |
pdf_cache[pdf_name]["error"] = str(e)
|
| 298 |
|
| 299 |
# PDF ID로 PDF 경로 찾기
|
|
|
|
| 300 |
def get_pdf_path_by_id(pdf_id: str) -> str:
|
|
|
|
|
|
|
|
|
|
| 301 |
if pdf_id in pdf_metadata:
|
| 302 |
path = pdf_metadata[pdf_id]
|
| 303 |
# 파일 존재 확인
|
| 304 |
if os.path.exists(path):
|
| 305 |
return path
|
| 306 |
|
| 307 |
-
#
|
| 308 |
filename = os.path.basename(path)
|
|
|
|
|
|
|
| 309 |
perm_path = PERMANENT_PDF_DIR / filename
|
| 310 |
if perm_path.exists():
|
| 311 |
# 메타데이터 업데이트
|
|
@@ -313,7 +323,7 @@ def get_pdf_path_by_id(pdf_id: str) -> str:
|
|
| 313 |
save_pdf_metadata()
|
| 314 |
return str(perm_path)
|
| 315 |
|
| 316 |
-
# 메인 디렉토리에서
|
| 317 |
main_path = PDF_DIR / filename
|
| 318 |
if main_path.exists():
|
| 319 |
# 메타데이터 업데이트
|
|
@@ -321,6 +331,34 @@ def get_pdf_path_by_id(pdf_id: str) -> str:
|
|
| 321 |
save_pdf_metadata()
|
| 322 |
return str(main_path)
|
| 323 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
return None
|
| 325 |
|
| 326 |
# 시작 시 모든 PDF 파일 캐싱
|
|
@@ -378,6 +416,28 @@ async def init_cache_all_pdfs():
|
|
| 378 |
# 백그라운드 작업 시작 함수
|
| 379 |
@app.on_event("startup")
|
| 380 |
async def startup_event():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
# 백그라운드 태스크로 캐싱 실행
|
| 382 |
asyncio.create_task(init_cache_all_pdfs())
|
| 383 |
|
|
@@ -685,6 +745,22 @@ async def unfeature_pdf(path: str):
|
|
| 685 |
async def view_pdf_by_id(pdf_id: str):
|
| 686 |
# PDF ID 유효한지 확인
|
| 687 |
pdf_path = get_pdf_path_by_id(pdf_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 688 |
if not pdf_path:
|
| 689 |
return HTMLResponse(
|
| 690 |
content=f"<html><body><h1>PDF를 찾을 수 없습니다</h1><p>ID: {pdf_id}</p><a href='/'>홈으로 돌아가기</a></body></html>",
|
|
@@ -1591,38 +1667,47 @@ HTML = """
|
|
| 1591 |
showError("PDF 업로드 중 오류가 발생했습니다.");
|
| 1592 |
}
|
| 1593 |
}
|
| 1594 |
-
|
| 1595 |
-
|
| 1596 |
-
|
| 1597 |
-
|
| 1598 |
-
|
| 1599 |
-
|
| 1600 |
-
|
| 1601 |
-
|
| 1602 |
-
|
| 1603 |
-
|
| 1604 |
-
|
| 1605 |
-
|
| 1606 |
-
|
| 1607 |
-
|
| 1608 |
-
|
| 1609 |
-
|
| 1610 |
-
|
| 1611 |
-
|
| 1612 |
-
|
| 1613 |
-
|
| 1614 |
-
|
| 1615 |
-
|
| 1616 |
-
|
| 1617 |
-
|
| 1618 |
-
|
| 1619 |
-
|
| 1620 |
-
|
| 1621 |
-
|
| 1622 |
-
|
| 1623 |
-
|
| 1624 |
-
$
|
| 1625 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1626 |
|
| 1627 |
/* ── 프로젝트 저장 ── */
|
| 1628 |
function save(pages, title, isCached = false, pdfId = null) {
|
|
|
|
| 70 |
logger.error(f"메타데이터 저장 오류: {e}")
|
| 71 |
|
| 72 |
# PDF ID 생성 (파일명 + 타임스탬프 기반)
|
| 73 |
+
# PDF ID 생성 (파일명 + 타임스탬프 기반) - 더 단순하고 안전한 방식으로 변경
|
| 74 |
def generate_pdf_id(filename: str) -> str:
|
| 75 |
# 파일명에서 확장자 제거
|
| 76 |
base_name = os.path.splitext(filename)[0]
|
| 77 |
+
# 안전한 문자열로 변환 (URL 인코딩 대신 직접 변환)
|
| 78 |
+
import re
|
| 79 |
+
safe_name = re.sub(r'[^\w\-_]', '_', base_name.replace(" ", "_"))
|
| 80 |
# 타임스탬프 추가로 고유성 보장
|
| 81 |
timestamp = int(time.time())
|
| 82 |
# 짧은 임의 문자열 추가
|
| 83 |
random_suffix = uuid.uuid4().hex[:6]
|
| 84 |
return f"{safe_name}_{timestamp}_{random_suffix}"
|
| 85 |
|
| 86 |
+
|
| 87 |
+
|
| 88 |
# PDF 파일 목록 가져오기 (메인 디렉토리용)
|
| 89 |
def get_pdf_files():
|
| 90 |
pdf_files = []
|
|
|
|
| 301 |
pdf_cache[pdf_name]["error"] = str(e)
|
| 302 |
|
| 303 |
# PDF ID로 PDF 경로 찾기
|
| 304 |
+
# PDF ID로 PDF 경로 찾기 (개선된 검색 로직)
|
| 305 |
def get_pdf_path_by_id(pdf_id: str) -> str:
|
| 306 |
+
logger.info(f"PDF ID로 파일 조회: {pdf_id}")
|
| 307 |
+
|
| 308 |
+
# 1. 메타데이터에서 직접 ID로 검색
|
| 309 |
if pdf_id in pdf_metadata:
|
| 310 |
path = pdf_metadata[pdf_id]
|
| 311 |
# 파일 존재 확인
|
| 312 |
if os.path.exists(path):
|
| 313 |
return path
|
| 314 |
|
| 315 |
+
# 파일이 이동했을 수 있으므로 파일명으로 검색
|
| 316 |
filename = os.path.basename(path)
|
| 317 |
+
|
| 318 |
+
# 영구 저장소에서 검색
|
| 319 |
perm_path = PERMANENT_PDF_DIR / filename
|
| 320 |
if perm_path.exists():
|
| 321 |
# 메타데이터 업데이트
|
|
|
|
| 323 |
save_pdf_metadata()
|
| 324 |
return str(perm_path)
|
| 325 |
|
| 326 |
+
# 메인 디렉토리에서 검색
|
| 327 |
main_path = PDF_DIR / filename
|
| 328 |
if main_path.exists():
|
| 329 |
# 메타데이터 업데이트
|
|
|
|
| 331 |
save_pdf_metadata()
|
| 332 |
return str(main_path)
|
| 333 |
|
| 334 |
+
# 2. 파일명 부분만 추출하여 모든 PDF 파일 검색
|
| 335 |
+
try:
|
| 336 |
+
# ID 형식: filename_timestamp_random
|
| 337 |
+
# 파일명 부분만 추출
|
| 338 |
+
name_part = pdf_id.split('_')[0] if '_' in pdf_id else pdf_id
|
| 339 |
+
|
| 340 |
+
# 모든 PDF 파일 검색
|
| 341 |
+
for file_path in get_pdf_files() + get_permanent_pdf_files():
|
| 342 |
+
# 파일명이 ID의 시작 부분과 일치하면
|
| 343 |
+
file_basename = os.path.basename(file_path)
|
| 344 |
+
if file_basename.startswith(name_part) or file_path.stem.startswith(name_part):
|
| 345 |
+
# ID 매핑 업데이트
|
| 346 |
+
pdf_metadata[pdf_id] = str(file_path)
|
| 347 |
+
save_pdf_metadata()
|
| 348 |
+
return str(file_path)
|
| 349 |
+
except Exception as e:
|
| 350 |
+
logger.error(f"파일명 검색 중 오류: {e}")
|
| 351 |
+
|
| 352 |
+
# 3. 모든 PDF 파일에 대해 메타데이터 확인
|
| 353 |
+
for pid, path in pdf_metadata.items():
|
| 354 |
+
if os.path.exists(path):
|
| 355 |
+
file_basename = os.path.basename(path)
|
| 356 |
+
# 유사한 파일명을 가진 경우
|
| 357 |
+
if pdf_id in pid or pid in pdf_id:
|
| 358 |
+
pdf_metadata[pdf_id] = path
|
| 359 |
+
save_pdf_metadata()
|
| 360 |
+
return path
|
| 361 |
+
|
| 362 |
return None
|
| 363 |
|
| 364 |
# 시작 시 모든 PDF 파일 캐싱
|
|
|
|
| 416 |
# 백그라운드 작업 시작 함수
|
| 417 |
@app.on_event("startup")
|
| 418 |
async def startup_event():
|
| 419 |
+
# PDF 메타데이터 로드
|
| 420 |
+
load_pdf_metadata()
|
| 421 |
+
|
| 422 |
+
# 누락된 PDF 파일에 대한 메타데이터 생성
|
| 423 |
+
for pdf_file in get_pdf_files() + get_permanent_pdf_files():
|
| 424 |
+
found = False
|
| 425 |
+
for pid, path in pdf_metadata.items():
|
| 426 |
+
if os.path.basename(path) == pdf_file.name:
|
| 427 |
+
found = True
|
| 428 |
+
# 경로 업데이트
|
| 429 |
+
if not os.path.exists(path):
|
| 430 |
+
pdf_metadata[pid] = str(pdf_file)
|
| 431 |
+
break
|
| 432 |
+
|
| 433 |
+
if not found:
|
| 434 |
+
# 새 ID 생성 및 메타데이터에 추가
|
| 435 |
+
pdf_id = generate_pdf_id(pdf_file.name)
|
| 436 |
+
pdf_metadata[pdf_id] = str(pdf_file)
|
| 437 |
+
|
| 438 |
+
# 변경사항 저장
|
| 439 |
+
save_pdf_metadata()
|
| 440 |
+
|
| 441 |
# 백그라운드 태스크로 캐싱 실행
|
| 442 |
asyncio.create_task(init_cache_all_pdfs())
|
| 443 |
|
|
|
|
| 745 |
async def view_pdf_by_id(pdf_id: str):
|
| 746 |
# PDF ID 유효한지 확인
|
| 747 |
pdf_path = get_pdf_path_by_id(pdf_id)
|
| 748 |
+
|
| 749 |
+
if not pdf_path:
|
| 750 |
+
# 일단 모든 PDF 메타데이터를 다시 로드하고 재시도
|
| 751 |
+
load_pdf_metadata()
|
| 752 |
+
pdf_path = get_pdf_path_by_id(pdf_id)
|
| 753 |
+
|
| 754 |
+
if not pdf_path:
|
| 755 |
+
# 모든 PDF 파일을 직접 스캔��여 유사한 이름 찾기
|
| 756 |
+
for file_path in get_pdf_files() + get_permanent_pdf_files():
|
| 757 |
+
name_part = pdf_id.split('_')[0] if '_' in pdf_id else pdf_id
|
| 758 |
+
if file_path.stem.startswith(name_part):
|
| 759 |
+
pdf_metadata[pdf_id] = str(file_path)
|
| 760 |
+
save_pdf_metadata()
|
| 761 |
+
pdf_path = str(file_path)
|
| 762 |
+
break
|
| 763 |
+
|
| 764 |
if not pdf_path:
|
| 765 |
return HTMLResponse(
|
| 766 |
content=f"<html><body><h1>PDF를 찾을 수 없습니다</h1><p>ID: {pdf_id}</p><a href='/'>홈으로 돌아가기</a></body></html>",
|
|
|
|
| 1667 |
showError("PDF 업로드 중 오류가 발생했습니다.");
|
| 1668 |
}
|
| 1669 |
}
|
| 1670 |
+
|
| 1671 |
+
function addCard(i, thumb, title, isCached = false, pdfId = null) {
|
| 1672 |
+
const d = document.createElement('div');
|
| 1673 |
+
d.className = 'card fade-in';
|
| 1674 |
+
d.onclick = () => open(i);
|
| 1675 |
+
|
| 1676 |
+
// PDF ID가 있으면 데이터 속성으로 저장
|
| 1677 |
+
if (pdfId) {
|
| 1678 |
+
d.dataset.pdfId = pdfId;
|
| 1679 |
+
}
|
| 1680 |
+
|
| 1681 |
+
// 제목 처리
|
| 1682 |
+
const displayTitle = title ?
|
| 1683 |
+
(title.length > 15 ? title.substring(0, 15) + '...' : title) :
|
| 1684 |
+
'프로젝트 ' + (i+1);
|
| 1685 |
+
|
| 1686 |
+
// 캐시 상태 뱃지 추가
|
| 1687 |
+
const cachedBadge = isCached ?
|
| 1688 |
+
'<div class="cached-status">캐시됨</div>' : '';
|
| 1689 |
+
|
| 1690 |
+
// 바로가기 링크 추가 (PDF ID가 있는 경우에만)
|
| 1691 |
+
const linkHtml = pdfId ?
|
| 1692 |
+
`<div style="position: absolute; bottom: 55px; left: 50%; transform: translateX(-50%); z-index:5;">
|
| 1693 |
+
<a href="/view/${pdfId}" target="_blank" style="color:#4a6ee0; font-size:11px;">바로가기 링크</a>
|
| 1694 |
+
</div>` : '';
|
| 1695 |
+
|
| 1696 |
+
d.innerHTML = `
|
| 1697 |
+
<div class="card-inner">
|
| 1698 |
+
${cachedBadge}
|
| 1699 |
+
<img src="${thumb}" alt="${displayTitle}" loading="lazy">
|
| 1700 |
+
${linkHtml}
|
| 1701 |
+
<p title="${title || '프로젝트 ' + (i+1)}">${displayTitle}</p>
|
| 1702 |
+
</div>
|
| 1703 |
+
`;
|
| 1704 |
+
grid.appendChild(d);
|
| 1705 |
+
|
| 1706 |
+
// 프로젝트가 있으면 '프로젝트 없음' 메시지 숨기기
|
| 1707 |
+
$id('noProjects').style.display = 'none';
|
| 1708 |
+
}
|
| 1709 |
+
|
| 1710 |
+
|
| 1711 |
|
| 1712 |
/* ── 프로젝트 저장 ── */
|
| 1713 |
function save(pages, title, isCached = false, pdfId = null) {
|