עידכון!
קוד חדש עם הורדה בשיטה שתופסת פחות זיכרון (מיוחד למערכות גדולות), וכן עם מד מהירות ההורדה.
הקוד המעודכן
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<title>גיבוי מערכות ישירות למחשב האישי</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<style>
body { font-family: 'Segoe UI', Tahoma, sans-serif; margin: 20px; background-color: #f4f7f6; text-align: right; direction: rtl; }
.container { max-width: 1100px; margin: auto; background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
.section { border: 1px solid #e0e0e0; padding: 15px; margin-bottom: 15px; border-radius: 8px; background: #fafafa; }
#logArea {
background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px;
height: 250px; overflow-y: auto; font-family: 'Consolas', monospace; font-size: 13px; margin-top: 10px;
}
.log-info { color: #4fc3f7; }
.log-success { color: #00ff00; font-weight: bold; }
.log-error { color: #ff5252; }
button { padding: 10px 20px; background: #3498db; color: white; border: none; cursor: pointer; border-radius: 5px; font-weight: bold; transition: background 0.3s; }
button:hover { background: #2980b9; }
button:disabled { background: #bdc3c7; }
input { padding: 10px; margin: 5px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }
.stats-bar { font-weight: bold; color: #2c3e50; margin-bottom: 5px; font-size: 16px; background: #e8f4fd; padding: 10px; border-radius: 5px; display: inline-block; }
.progress-container { background: #e0e0e0; border-radius: 20px; height: 25px; margin: 15px 0; overflow: hidden; display: none; }
.progress-bar { width: 0%; height: 100%; background: linear-gradient(90deg, #2ecc71, #27ae60); transition: width 0.3s; color: white; text-align: center; line-height: 25px; font-weight: bold; }
table { width: 100%; border-collapse: collapse; margin-top: 15px; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: right; }
tr:nth-child(even) { background: #f9f9f9; }
</style>
</head>
<body>
<div class="container">
<h2>גיבוי מערכות ישירות למחשב האישי</h2>
<div class="section">
<strong>⚙️ הגדרות:</strong>
סרוק שלוחות בעלות <input type="number" id="digitCount" style="width:50px;" value="2"> ספרות.
שם קובץ ZIP: <input type="text" id="fileNameInput" value="backup_ivr">
</div>
<div class="section">
<strong>1. פרטי המערכת</strong><br>
<input type="text" id="srcToken" style="width:350px;" placeholder="הכנס טוקן...">
<input type="text" id="srcPath" value="/">
<button onclick="loadFolder(document.getElementById('srcPath').value)">טען רשימת קבצים</button>
</div>
<div class="section">
<strong>2. ביצוע הורדה</strong><br>
<div id="statsBar" class="stats-bar">מהירות: 0 KB/s | סה"כ ירד: 0 MB</div><br><br>
<button id="downloadBtn" onclick="startDownload()" disabled style="background:#27ae60;">בחר מיקום ושמור כקובץ ZIP</button>
<div class="progress-container" id="progContainer">
<div id="progBar" class="progress-bar">0%</div>
</div>
<div id="logArea">מוכן...</div>
</div>
<div id="fileArea" style="display:none;">
<table>
<thead>
<tr style="background:#eee">
<th style="width: 40px;"><input type="checkbox" id="masterCheck" checked onclick="toggleAll(this)"></th>
<th>סוג</th>
<th>שם</th>
</tr>
</thead>
<tbody id="fileTableBody"></tbody>
</table>
</div>
</div>
<script>
let currentViewPath = "/";
let totalBytes = 0;
let startTime = 0;
// פונקציית "נשימה" - משחררת את הזיכרון של הדפדפן ומאפשרת לממשק להתעדכן
const breathe = () => new Promise(resolve => setTimeout(resolve, 10));
function addLog(msg, type = '') {
const logArea = document.getElementById('logArea');
const div = document.createElement('div');
div.className = `log-${type}`;
div.innerHTML = `[${new Date().toLocaleTimeString()}] ${msg}`;
logArea.appendChild(div);
logArea.scrollTop = logArea.scrollHeight;
}
function updateStats(newBytes) {
totalBytes += newBytes;
const duration = (Date.now() - startTime) / 1000;
const speed = duration > 0 ? (totalBytes / 1024 / duration).toFixed(2) : 0;
const mb = (totalBytes / 1024 / 1024).toFixed(2);
document.getElementById('statsBar').innerText = `מהירות: ${speed} KB/s | סה"כ ירד: ${mb} MB`;
}
async function loadFolder(path) {
const token = document.getElementById('srcToken').value;
const digits = parseInt(document.getElementById('digitCount').value) || 2;
if (!token) { alert("נא להזין טוקן"); return; }
currentViewPath = path;
const tbody = document.getElementById('fileTableBody');
tbody.innerHTML = '<tr><td colspan="3">סורק שלוחות...</td></tr>';
try {
const res = await fetch(`https://www.call2all.co.il/ym/api/GetIVR2Dir?token=${token}&path=${path}`);
const data = await res.json();
let filesMap = new Map();
if (data.files) data.files.forEach(f => filesMap.set(f.name, f));
const maxRange = Math.pow(10, digits) - 1;
const scanPromises = [];
for (let i = 0; i <= maxRange; i++) {
const n = i.toString();
if (filesMap.has(n)) continue;
scanPromises.push(
fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${token}&what=ivr2:${path}/${n}/ext.ini`)
.then(r => r.json())
.then(d => { if (d.contents !== undefined) filesMap.set(n, { name: n, fileType: "DIR" }); })
);
}
await Promise.all(scanPromises);
tbody.innerHTML = '';
filesMap.forEach(f => {
tbody.insertAdjacentHTML('beforeend', `
<tr>
<td><input type="checkbox" class="file-check" data-name="${f.name}" data-type="${f.fileType}" checked></td>
<td>${f.fileType === "DIR" || !isNaN(f.name) ? "שלוחה" : "קובץ"}</td>
<td>${f.name}</td>
</tr>`);
});
document.getElementById('fileArea').style.display = 'block';
document.getElementById('downloadBtn').disabled = false;
addLog("סריקה הושלמה.", "info");
} catch (e) { addLog("שגיאה בסריקה", "error"); }
}
async function downloadRecursive(token, path, name, type, zipFolder) {
const sPath = `${path}/${name}`.replace(/\/+/g, '/');
const digits = parseInt(document.getElementById('digitCount').value) || 2;
if (type === "DIR" || !isNaN(name)) {
addLog(`מעבד שלוחה: ${sPath}`, "info");
const newFolder = zipFolder.folder(name);
try {
const iniRes = await fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${token}&what=ivr2:${sPath}/ext.ini`);
const iniData = await iniRes.json();
if (iniData.contents !== undefined) {
newFolder.file("ext.ini.txt", iniData.contents);
updateStats(new TextEncoder().encode(iniData.contents).length);
}
} catch(e) {}
const res = await fetch(`https://www.call2all.co.il/ym/api/GetIVR2Dir?token=${token}&path=${sPath}`);
const data = await res.json();
let children = data.files || [];
const subScan = [];
for(let i=0; i <= Math.pow(10, digits)-1; i++) {
const n = i.toString();
if(!children.find(c => c.name === n)) {
subScan.push(fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${token}&what=ivr2:${sPath}/${n}/ext.ini`).then(r => r.json()).then(d => { if(d.contents !== undefined) children.push({name: n, fileType: "DIR"}); }));
}
}
await Promise.all(subScan);
for (const f of children) {
if (f.name === "ext.ini") continue;
await downloadRecursive(token, sPath, f.name, f.fileType, newFolder);
await breathe(); // הפסקה קצרה לשחרור זיכרון ברמת התיקייה
}
} else {
try {
let finalName = name;
if (!name.toLowerCase().match(/\.(mp3|wav|txt)$/)) finalName += ".txt";
const dl = await fetch(`https://www.call2all.co.il/ym/api/DownloadFile?token=${token}&path=ivr2:${sPath}`);
const arrayBuffer = await dl.arrayBuffer();
updateStats(arrayBuffer.byteLength);
zipFolder.file(finalName, arrayBuffer);
// שחרור מהיר של ה-Buffer מהזיכרון הראשי
await breathe();
} catch(e) { addLog(`שגיאה ב-${name}`, "error"); }
}
}
async function startDownload() {
const token = document.getElementById('srcToken').value;
const userFileName = document.getElementById('fileNameInput').value || 'backup';
const selected = Array.from(document.querySelectorAll('.file-check:checked'));
if (selected.length === 0) return;
let fileHandle = null;
if ('showSaveFilePicker' in window) {
try {
fileHandle = await window.showSaveFilePicker({
suggestedName: `${userFileName}.zip`,
types: [{ description: 'ZIP Archive', accept: {'application/zip': ['.zip']} }]
});
} catch (e) { return; }
}
const zip = new JSZip();
document.getElementById('downloadBtn').disabled = true;
document.getElementById('progContainer').style.display = 'block';
totalBytes = 0;
startTime = Date.now();
for (let i = 0; i < selected.length; i++) {
await downloadRecursive(token, currentViewPath, selected[i].dataset.name, selected[i].dataset.type, zip);
let p = Math.round(((i + 1) / selected.length) * 100);
document.getElementById('progBar').style.width = p + '%';
document.getElementById('progBar').innerText = p + '%';
await breathe(); // הפסקה קריטית בין שלוחות ראשיות
}
addLog("יוצר ZIP סופי... במערכות גדולות זה יכול לקחת כמה דקות", "success");
// יצירת ה-ZIP עם אופטימיזציה למהירות על פני דחיסה במערכות ענק
const content = await zip.generateAsync({
type: "blob",
compression: "STORE", // STORE אומר "אל תדחוס", זה הרבה יותר מהר וחוסך CPU
streamFiles: true
});
if (fileHandle) {
const writable = await fileHandle.createWritable();
await writable.write(content);
await writable.close();
} else {
const link = document.createElement('a');
link.href = URL.createObjectURL(content);
link.download = `${userFileName}.zip`;
link.click();
}
addLog("✅ הגיבוי הסתיים בהצלחה!", "success");
document.getElementById('downloadBtn').disabled = false;
}
function toggleAll(source) {
document.querySelectorAll('.file-check').forEach(cb => cb.checked = source.checked);
}
</script>
</body>
</html>