testdeep123 commited on
Commit
56a75e7
·
verified ·
1 Parent(s): aa851b7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -188
app.py CHANGED
@@ -1,202 +1,68 @@
1
  import os
2
- import shutil
3
- import zipfile
4
- import threading
5
- import time
6
- import humanize
7
- from flask import Flask, request, jsonify, render_template_string
8
- import gdown
9
- from huggingface_hub import HfApi, login, upload_folder, hf_hub_url
10
- from huggingface_hub.utils import HfHubHTTPError
11
 
12
- # --- Configuration & Initialization ---
13
- os.environ["HF_HOME"] = "/tmp/hf_home"
14
- DOWNLOAD_DIR = "/tmp/backups"
15
- EXTRACT_DIR = "/tmp/extracted_backups"
16
-
17
- # Environment variables
18
- FOLDER_URL = os.getenv("FOLDER_URL")
19
  REPO_ID = os.getenv("REPO_ID")
20
  TOKEN = os.getenv("HF_TOKEN")
21
 
22
- # --- Global State Management ---
23
- app_state = {
24
- "backup_status": "idle",
25
- "backup_log": ["Awaiting first run."],
26
- "last_backup_time": "Never",
27
- "next_backup_time": "Scheduler disabled",
28
- "schedule_interval_minutes": 0,
29
- "scheduler_thread": None
30
- }
31
-
32
- # --- Flask App Setup ---
33
  app = Flask(__name__)
34
  api = HfApi()
35
 
36
- # --- HTML, CSS, JS Template ---
37
- HTML_TEMPLATE = """
38
- <!DOCTYPE html>
39
- <html lang="en" data-bs-theme="dark">
40
- <head>
41
- <meta charset="utf-8">
42
- <meta name="viewport" content="width=device-width, initial-scale=1">
43
- <title>Backup & Dataset Controller</title>
44
- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
45
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
46
- <style>
47
- body{background-color:#212529}.log-box{height:300px;overflow-y:auto;font-family:'Courier New',Courier,monospace;font-size:.85rem;color:#f8f9fa;background-color:#111315!important;border-top:1px solid #495057}.log-box div{padding:2px 5px;border-bottom:1px solid #343a40}.status-badge{padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:background-color .3s ease-in-out}.status-idle{background-color:#6c757d}.status-running{background-color:#0d6efd}.status-success{background-color:#198754}.status-error{background-color:#dc3545}#files-list-container{max-height:450px;overflow-y:auto}.btn i,.btn .spinner-border{pointer-events:none}.card{border:1px solid rgba(255,255,255,.1)}
48
- </style>
49
- </head>
50
- <body>
51
- <div class="container my-4">
52
- <header class="d-flex align-items-center pb-3 mb-4 border-bottom border-secondary">
53
- <i class="fas fa-server fa-2x me-3 text-info"></i>
54
- <span class="fs-4">Minecraft Backup & Dataset Controller</span>
55
- </header>
56
- <div class="row g-4">
57
- <div class="col-lg-6">
58
- <div class="card h-100 shadow-sm">
59
- <div class="card-header d-flex justify-content-between align-items-center">
60
- <h5 class="mb-0"><i class="fas fa-shield-alt me-2"></i>Backup Controls</h5>
61
- <div id="backup-status-indicator" class="status-badge" data-bs-toggle="tooltip" title="Current Status">Idle</div>
62
- </div>
63
- <div class="card-body">
64
- <div class="d-grid gap-2 mb-4"><button id="run-now-btn" class="btn btn-lg btn-success"><i class="fas fa-play-circle me-2"></i>Run Backup Now</button></div>
65
- <form id="schedule-form" class="row g-2 align-items-center">
66
- <div class="col"><label for="interval-input" class="form-label">Schedule Interval (minutes)</label><input type="number" class="form-control" id="interval-input" placeholder="0 to disable" min="0"></div>
67
- <div class="col-auto align-self-end"><button type="submit" class="btn btn-primary"><i class="fas fa-save me-2"></i>Set</button></div>
68
- </form>
69
- <ul class="list-group list-group-flush mt-4">
70
- <li class="list-group-item d-flex justify-content-between bg-transparent"><span>Last Backup:</span><strong id="last-run-time">Never</strong></li>
71
- <li class="list-group-item d-flex justify-content-between bg-transparent"><span>Next Scheduled:</span><strong id="next-run-time">N/A</strong></li>
72
- </ul>
73
- </div>
74
- <div class="card-footer"><strong><i class="fas fa-clipboard-list me-2"></i>Live Log</strong></div>
75
- <div id="log-output" class="log-box card-body"></div>
76
- </div>
77
- </div>
78
- <div class="col-lg-6">
79
- <div class="card h-100 shadow-sm">
80
- <div class="card-header d-flex justify-content-between align-items-center">
81
- <h5 class="mb-0"><i class="fas fa-database me-2"></i>Dataset Management</h5>
82
- <a href="https://huggingface.co/datasets/{{ repo_id }}" target="_blank" class="btn btn-sm btn-outline-info">View on Hub <i class="fas fa-external-link-alt"></i></a>
83
- </div>
84
- <div class="card-body">
85
- <div class="d-flex justify-content-between align-items-center mb-3">
86
- <p class="text-muted mb-0">Files in <strong>{{ repo_id }}</strong></p>
87
- <button id="refresh-files-btn" class="btn btn-sm btn-secondary"><i class="fas fa-sync-alt me-1"></i> Refresh</button>
88
- </div>
89
- <div id="files-list-container">
90
- <div id="files-loader" class="text-center p-4" style="display: none;"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div></div>
91
- <table class="table table-hover">
92
- <thead><tr><th>File Path</th><th>Size</th><th>Actions</th></tr></thead>
93
- <tbody id="files-list"></tbody>
94
- </table>
95
- </div>
96
- </div>
97
- </div>
98
- </div>
99
- </div>
100
- </div>
101
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
102
- <script>
103
- document.addEventListener('DOMContentLoaded',()=>{const e=document.getElementById("run-now-btn"),t=document.getElementById("schedule-form"),n=document.getElementById("interval-input"),o=document.getElementById("log-output"),i=document.getElementById("backup-status-indicator"),s=document.getElementById("last-run-time"),a=document.getElementById("next-run-time"),l=document.getElementById("refresh-files-btn"),c=document.getElementById("files-list"),d=document.getElementById("files-loader");async function r(e,t={}){try{const n=await fetch(e,t);if(!n.ok){const e=await n.json().catch(()=>({message:`HTTP error! Status: ${n.status}`}));throw new Error(e.message)}return n.json()}catch(e){return console.error(`API call to ${e} failed:`,e),alert(`Error: ${e.message}`),null}}function u(e){o.innerHTML=e.map(e=>`<div>${e.replace(/</g,"<").replace(/>/g,">")}</div>`).join(""),o.scrollTop=o.scrollHeight}function p(t){i.textContent=t.backup_status.charAt(0).toUpperCase()+t.backup_status.slice(1),i.className="status-badge",i.classList.add(`status-${t.backup_status}`),s.textContent=t.last_backup_time,a.textContent=t.next_backup_time,document.activeElement!==n&&(n.value=t.schedule_interval_minutes>0?t.schedule_interval_minutes:""),e.disabled="running"===t.backup_status,e.innerHTML="running"===t.backup_status?'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Running...':'<i class="fas fa-play-circle me-2"></i>Run Backup Now'}async function m(){const e=await r("/api/status");e&&(u(e.backup_log),p(e))}async function h(){if(e.disabled)return;await r("/api/run-backup",{method:"POST"})&&m()}async function f(e){e.preventDefault();const t=n.value;await r("/api/set-schedule",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({interval:parseInt(t,10)||0})}),m()}async function g(){d.style.display="block",c.innerHTML="",l.disabled=!0;const e=await r("/api/list-files");d.style.display="none",l.disabled=!1,e&&e.files&&(0===e.files.length?c.innerHTML='<tr><td colspan="3" class="text-center text-muted">No files found in repository.</td></tr>':e.files.forEach(e=>{const t=document.createElement("tr");t.innerHTML=`\n <td class="text-break">\n <a href="${e.url}" target="_blank" title="${e.name}">${e.name}</a>\n </td>\n <td>${e.size}</td>\n <td>\n <button class="btn btn-sm btn-outline-danger delete-btn" data-filename="${e.name}" title="Delete File">\n <i class="fas fa-trash-alt"></i>\n </button>\n </td>\n `,c.appendChild(t)}))}async function y(e){const t=e.target.closest(".delete-btn");if(!t)return;const n=t.dataset.filename;if(!confirm(`Are you sure you want to permanently delete "${n}"?`))return;t.disabled=!0,t.innerHTML='<span class="spinner-border spinner-border-sm"></span>';await r("/api/delete-file",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({filename:n})})?g():(t.disabled=!1,t.innerHTML='<i class="fas fa-trash-alt"></i>')}e.addEventListener("click",h),t.addEventListener("submit",f),l.addEventListener("click",g),c.addEventListener("click",y);const b=[].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));b.map(function(e){return new bootstrap.Tooltip(e)}),m(),g(),setInterval(m,3e3)});
104
- </script>
105
- </body>
106
- </html>
107
  """
108
 
109
- # --- Core Backup Logic ---
110
- def run_backup_job():
111
- global app_state
112
- app_state["backup_status"] = "running"
113
- app_state["backup_log"] = ["Starting backup process..."]
114
- def log(message):
115
- print(message)
116
- app_state["backup_log"].append(message)
117
- try:
118
- log("Resetting temporary directories..."); shutil.rmtree(DOWNLOAD_DIR,ignore_errors=True); shutil.rmtree(EXTRACT_DIR,ignore_errors=True); os.makedirs(DOWNLOAD_DIR,exist_ok=True); os.makedirs(EXTRACT_DIR,exist_ok=True)
119
- log("Downloading from Google Drive folder..."); gdown.download_folder(url=FOLDER_URL,output=DOWNLOAD_DIR,use_cookies=False,quiet=True); log("Download finished.")
120
- log("Extracting zip archives..."); extracted_count = 0
121
- for r,_,f_list in os.walk(DOWNLOAD_DIR):
122
- for f in f_list:
123
- if f.endswith(".zip"):
124
- zp=os.path.join(r,f); z=zipfile.ZipFile(zp); z.extractall(EXTRACT_DIR); z.close(); log(f"Extracted: {f}"); extracted_count += 1
125
- if extracted_count == 0: log("Warning: No .zip files found to extract.")
126
- bad_p=os.path.join(EXTRACT_DIR,"world_nither"); good_p=os.path.join(EXTRACT_DIR,"world_nether")
127
- if os.path.exists(bad_p) and not os.path.exists(good_p): os.rename(bad_p,good_p); log("Fixed typo: 'world_nither'->'world_nether'")
128
- log("Logging into Hugging Face Hub..."); login(token=TOKEN)
129
- log(f"Ensuring repo '{REPO_ID}' exists..."); api.create_repo(repo_id=REPO_ID,repo_type="dataset",private=False,exist_ok=True); log("Repo ready.")
130
- s_up={"world":os.path.join(EXTRACT_DIR,"world"),"world_nether":os.path.join(EXTRACT_DIR,"world_nether"),"world_the_end":os.path.join(EXTRACT_DIR,"world_the_end"),"plugins":os.path.join(EXTRACT_DIR,"plugins")}
131
- for n,p in s_up.items():
132
- if os.path.exists(p): log(f"Uploading '{n}'..."); upload_folder(repo_id=REPO_ID,folder_path=p,repo_type="dataset",path_in_repo=n,commit_message=f"Backup: {n}"); log(f"'{n}' uploaded.")
133
- else: log(f"Skipping '{n}': not found.")
134
- app_state["last_backup_time"]=time.strftime("%Y-%m-%d %H:%M:%S %Z"); log(f"Backup done at {app_state['last_backup_time']}."); app_state["backup_status"]="success"
135
- except Exception as e: log(f"AN ERROR OCCURRED: {str(e)}"); app_state["backup_status"]="error"
136
-
137
- # --- Scheduler Thread ---
138
- def scheduler_loop():
139
- while True:
140
- interval=app_state["schedule_interval_minutes"]
141
- if interval>0:
142
- if app_state["backup_status"]!="running": run_backup_job()
143
- next_run=time.time()+interval*60; app_state["next_backup_time"]=time.strftime("%Y-%m-%d %H:%M:%S %Z",time.localtime(next_run)); time.sleep(interval*60)
144
- else: app_state["next_backup_time"]="Scheduler disabled"; time.sleep(5)
145
-
146
- # --- Flask Routes (API Endpoints) ---
147
  @app.route("/")
148
- def index(): return render_template_string(HTML_TEMPLATE,repo_id=REPO_ID)
149
-
150
- @app.route("/api/status", methods=["GET"])
151
- def get_status():
152
- state_for_json = {k:v for k,v in app_state.items() if k!="scheduler_thread"}
153
- return jsonify(state_for_json)
154
-
155
- @app.route("/api/run-backup", methods=["POST"])
156
- def start_backup():
157
- if app_state["backup_status"]=="running": return jsonify({"status":"error","message":"Backup already in progress."}),409
158
- threading.Thread(target=run_backup_job,daemon=True).start()
159
- return jsonify({"status":"ok","message":"Backup started."})
160
-
161
- @app.route("/api/set-schedule", methods=["POST"])
162
- def set_schedule():
163
- try:
164
- interval=int(request.json.get("interval",0)); app_state["schedule_interval_minutes"]=interval
165
- if interval>0: next_run=time.time()+interval*60; app_state["next_backup_time"]=time.strftime("%Y-%m-%d %H:%M:%S %Z",time.localtime(next_run))
166
- else: app_state["next_backup_time"]="Scheduler disabled"
167
- return jsonify({"status":"ok","message":f"Schedule set to {interval} minutes."})
168
- except(ValueError,TypeError): return jsonify({"status":"error","message":"Invalid interval."}),400
169
 
170
- @app.route("/api/list-files", methods=["GET"])
171
- def list_repo_files():
172
- try:
173
- repo_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
174
- files_details = []
175
- for filename in repo_files:
176
- try:
177
- # ==================================
178
- # THIS IS THE CORRECTED LINE
179
- # ==================================
180
- info = api.file_info(repo_id=REPO_ID, path_in_repo=filename, repo_type="dataset")
181
- size = humanize.naturalsize(info.size) if info.size else "0 B"
182
- except HfHubHTTPError: size = "N/A"
183
- files_details.append({
184
- "name": filename, "size": size,
185
- "url": hf_hub_url(repo_id=REPO_ID, filename=filename, repo_type="dataset")
186
- })
187
- return jsonify({"status": "ok", "files": files_details})
188
- except Exception as e: return jsonify({"status": "error", "message": str(e)}), 500
189
 
190
- @app.route("/api/delete-file", methods=["POST"])
191
- def delete_repo_file():
192
- filename = request.json.get("filename")
193
- if not filename: return jsonify({"status":"error","message":"Filename not provided."}),400
194
- try:
195
- api.delete_file(repo_id=REPO_ID,path_in_repo=filename,repo_type="dataset",commit_message=f"Deleted file: {filename}")
196
- return jsonify({"status":"ok","message":f"Successfully deleted '{filename}'."})
197
- except Exception as e: return jsonify({"status":"error","message":str(e)}),500
 
 
 
 
 
 
198
 
199
- # --- Main Execution ---
200
- if __name__=="__main__":
201
- app_state["scheduler_thread"]=threading.Thread(target=scheduler_loop,daemon=True); app_state["scheduler_thread"].start()
202
- app.run(host="0.0.0.0",port=7860)
 
1
  import os
2
+ from flask import Flask, request, send_file, redirect, url_for, render_template_string
3
+ from huggingface_hub import HfApi, upload_file
4
+ import tempfile
 
 
 
 
 
 
5
 
6
+ # Get credentials from environment
 
 
 
 
 
 
7
  REPO_ID = os.getenv("REPO_ID")
8
  TOKEN = os.getenv("HF_TOKEN")
9
 
 
 
 
 
 
 
 
 
 
 
 
10
  app = Flask(__name__)
11
  api = HfApi()
12
 
13
+ # Basic HTML template with upload form
14
+ TEMPLATE = """
15
+ <!doctype html>
16
+ <title>Hugging Face Dataset Browser</title>
17
+ <h2>Files in {{ repo_id }}</h2>
18
+ <ul>
19
+ {% for file in files %}
20
+ <li>
21
+ {{ file }} —
22
+ <a href="{{ url_for('download_file', filename=file) }}">Download</a>
23
+ </li>
24
+ {% endfor %}
25
+ </ul>
26
+ <h3>Upload File</h3>
27
+ <form method="POST" action="/upload" enctype="multipart/form-data">
28
+ <input type="file" name="file">
29
+ <input type="submit" value="Upload">
30
+ </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  """
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  @app.route("/")
34
+ def index():
35
+ files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=TOKEN)
36
+ return render_template_string(TEMPLATE, files=files, repo_id=REPO_ID)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ @app.route("/download/<path:filename>")
39
+ def download_file(filename):
40
+ url = api.hf_hub_url(REPO_ID, filename, repo_type="dataset")
41
+ import requests
42
+ headers = {"Authorization": f"Bearer {TOKEN}"}
43
+ r = requests.get(url, headers=headers)
44
+ if r.status_code == 200:
45
+ tmp = tempfile.NamedTemporaryFile(delete=False)
46
+ tmp.write(r.content)
47
+ tmp.close()
48
+ return send_file(tmp.name, as_attachment=True, download_name=os.path.basename(filename))
49
+ else:
50
+ return f"Failed to download {filename}", 404
 
 
 
 
 
 
51
 
52
+ @app.route("/upload", methods=["POST"])
53
+ def upload():
54
+ file = request.files["file"]
55
+ if file:
56
+ temp_path = os.path.join(tempfile.gettempdir(), file.filename)
57
+ file.save(temp_path)
58
+ upload_file(
59
+ path_or_fileobj=temp_path,
60
+ path_in_repo=file.filename,
61
+ repo_id=REPO_ID,
62
+ repo_type="dataset",
63
+ token=TOKEN
64
+ )
65
+ return redirect(url_for("index"))
66
 
67
+ if __name__ == "__main__":
68
+ app.run(debug=True, host="0.0.0.0", port=7860)