Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions containers/backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ def _fetch_branch_archive(ref: str, warmup: bool = False) -> str:
new_etag = response.headers.get("ETag")
except Exception as e:
if not warmup:
raise HTTPException(status_code=500, detail=f"Failed to check GitHub archive status: {e}")
if current_etag and os.path.exists(ref_cache_dir):
new_etag = current_etag
elif isinstance(e, urllib.error.HTTPError) and e.code == 404:
raise HTTPException(status_code=404, detail=f"GitHub archive not found for branch '{ref}'")
else:
raise HTTPException(status_code=500, detail=f"Failed to check GitHub archive status: {e}")
else:
return ""

Expand Down Expand Up @@ -142,7 +147,15 @@ def overlay_branch_data(ref: str, work_dir: str, warmup: bool = False):
Security: only data directories are copied. No executable code (src/, *.sh,
*.py) from the branch is ever used.
"""
cache_dir = _fetch_branch_archive(ref, warmup=warmup)
try:
cache_dir = _fetch_branch_archive(ref, warmup=warmup)
except HTTPException as e:
if e.status_code == 404 and ref != "main":
print(f"Branch {ref} not found on upstream, falling back to 'main' branch")
cache_dir = _fetch_branch_archive("main", warmup=warmup)
else:
raise e

if not cache_dir:
return

Expand Down Expand Up @@ -290,7 +303,9 @@ async def render_song_xml(request: XmlRequest, background_tasks: BackgroundTasks
job_id = f"job_{uuid.uuid4().hex[:8]}"
temp_dir = tempfile.mkdtemp(prefix="songbook_")
work_dir = setup_work_dir(temp_dir)
overlay_branch_data(request.branch, work_dir)
# We do not overlay branch data for a single song render
# because the song is fully provided in xml_content
# and all LaTeX templates are already bundled in the Docker image.

xml_path = os.path.join(work_dir, "custom.xml")
with open(xml_path, "w", encoding="utf-8") as f:
Expand Down Expand Up @@ -389,7 +404,11 @@ async def get_job_status(job_id: str):
raise HTTPException(status_code=500, detail=f"Failed to check storage: {e}")

@app.get("/api/jobs/{job_id}/download")
async def download_job(job_id: str):
async def download_job(job_id: str, filename: Optional[str] = None, disposition: str = "inline"):
dl_filename = filename if filename else f"{job_id}.pdf"
if not dl_filename.endswith(".pdf"):
dl_filename += ".pdf"

if STORAGE_URI.startswith("gs://"):
bucket_name = STORAGE_URI[5:]
storage_client = storage.Client()
Expand All @@ -398,13 +417,17 @@ async def download_job(job_id: str):
if blob.exists():
pdf_bytes = blob.download_as_bytes()
from fastapi import Response
return Response(content=pdf_bytes, media_type="application/pdf", headers={"Content-Disposition": f"attachment; filename={job_id}.pdf"})
import urllib.parse
encoded_filename = urllib.parse.quote(dl_filename)
headers = {"Content-Disposition": f"{disposition}; filename*=UTF-8''{encoded_filename}"}
return Response(content=pdf_bytes, media_type="application/pdf", headers=headers)
else:
local_dir = STORAGE_URI
if STORAGE_URI.startswith("file://"):
local_dir = STORAGE_URI[7:]
pdf_path = os.path.join(local_dir, f"{job_id}.pdf")
if os.path.exists(pdf_path):
return FileResponse(pdf_path, media_type="application/pdf", filename=f"{job_id}.pdf")
headers = {"Content-Disposition": f"{disposition}; filename=\"{dl_filename}\""}
return FileResponse(pdf_path, media_type="application/pdf", filename=dl_filename, headers=headers)

raise HTTPException(status_code=404, detail="File not found")
40 changes: 28 additions & 12 deletions editor/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -244,25 +244,41 @@ <h2>Musisz się zalogować</h2>
branch: branch || "main"
};

// Open blank window immediately to satisfy popup blocker
const pdfWindow = window.open('about:blank', '_blank');
if (pdfWindow) {
pdfWindow.document.write("<div style='font-family: sans-serif; padding: 20px;'>Trwa generowanie PDF... Proszę czekać (zwykle około 10-20 sekund).</div>");
}

renderPdfCloudRun(
payload,
"/api/render/song_xml",
() => notice("Generowanie PDF... Proszę czekać (może potrwać do kilkunastu sekund)", songbook),
(url) => {
notice("PDF wygenerowany!", songbook);
if (pdfWindow) pdfWindow.location.href = url;
else window.open(url, '_blank');
const dateStr = new Date().toISOString().split('T')[0];
const filename = encodeURIComponent(`${title || 'piosenka'}_${dateStr}.pdf`);
const finalUrl = `${url}?filename=${filename}&disposition=inline`;

const noticeDiv = document.createElement("div");
noticeDiv.classList.add("notice");
noticeDiv.innerHTML = `
<div style="text-align: center;">
<p style="margin-bottom: 15px; color: #28a745; font-weight: bold; font-size: 1.1em;">PDF wygenerowany!</p>
<a href="${finalUrl}" target="_blank" style="
display: inline-block;
background: #28a745;
color: white;
padding: 12px 24px;
text-decoration: none;
border-radius: 6px;
font-weight: bold;
font-size: 1.1em;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
transition: background 0.2s;
">Gotowe! Otwórz Podgląd PDF</a>
<div style="margin-top: 15px;">
<a href="#" onclick="this.parentNode.parentNode.parentNode.remove(); return false;" style="color: #6c757d; font-size: 0.9em; text-decoration: underline;">Zamknij to okienko</a>
</div>
</div>
`;
document.body.appendChild(noticeDiv);
},
(err, errUrl) => {
alert(err);
if (pdfWindow && errUrl) pdfWindow.location.href = errUrl;
else if (pdfWindow) pdfWindow.close();
alert("Błąd generowania: " + err);
}
);
}
Expand Down
5 changes: 4 additions & 1 deletion src/html/templates/songbook_edit/songbook_edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,10 @@ async function renderPDF() {
}
if (pdfStatusText) pdfStatusText.style.display = 'none';
if (pdfDownloadLink) {
pdfDownloadLink.href = url;
const songbookTitle = document.getElementById('songbookTitle').value.trim() || 'spiewnik';
const dateStr = new Date().toISOString().split('T')[0];
const filename = encodeURIComponent(`${songbookTitle}_${dateStr}.pdf`);
pdfDownloadLink.href = `${url}?filename=${filename}&disposition=inline`;
pdfDownloadLink.style.display = 'block';
}
},
Expand Down
Loading