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) {
|