modificato deploy verso contabo
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import urllib.request
|
||||
import json
|
||||
|
||||
url = "https://libretto.mmcinet.eu/canti/api/v3/get_all_app_tables?uuid=pwa-cc-uuid&email=&platform=browser&version=1.0&gruppo=123456&all_song=false"
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
||||
print("Fetching...")
|
||||
with urllib.request.urlopen(req) as res:
|
||||
data = json.loads(res.read().decode("utf-8"))
|
||||
|
||||
settings = data.get("canti_settings", {}).get("data", [])
|
||||
print(f"Total settings: {len(settings)}")
|
||||
for s in settings[:10]:
|
||||
print(s)
|
||||
@@ -0,0 +1,154 @@
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
from ftplib import FTP, error_perm
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
thread_local = threading.local()
|
||||
|
||||
def load_env():
|
||||
env = {}
|
||||
if os.path.exists('.env'):
|
||||
with open('.env', 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
if '=' in line:
|
||||
key, val = line.split('=', 1)
|
||||
env[key.strip()] = val.strip()
|
||||
return env
|
||||
|
||||
def get_ftp_connection(ftp_host, ftp_user, ftp_pass):
|
||||
if not hasattr(thread_local, "ftp") or thread_local.ftp is None:
|
||||
ftp = FTP(ftp_host, timeout=30)
|
||||
ftp.login(ftp_user, ftp_pass)
|
||||
ftp.passive = True
|
||||
thread_local.ftp = ftp
|
||||
return thread_local.ftp
|
||||
|
||||
def close_thread_connection():
|
||||
if hasattr(thread_local, "ftp") and thread_local.ftp is not None:
|
||||
try:
|
||||
thread_local.ftp.quit()
|
||||
except:
|
||||
try:
|
||||
thread_local.ftp.close()
|
||||
except:
|
||||
pass
|
||||
thread_local.ftp = None
|
||||
|
||||
def ensure_remote_dir(ftp, path):
|
||||
parts = path.replace('\\', '/').strip('/').split('/')
|
||||
current = ""
|
||||
for part in parts:
|
||||
if not part:
|
||||
continue
|
||||
current += "/" + part
|
||||
try:
|
||||
ftp.mkd(current)
|
||||
print(f"\nCreated remote dir: {current}")
|
||||
except error_perm:
|
||||
# Directory already exists or permission error (which is normal if it exists)
|
||||
pass
|
||||
|
||||
def upload_file_task(local_path, rel_path, ftp_host, ftp_user, ftp_pass, created_dirs, created_dirs_lock):
|
||||
ftp_path = rel_path.replace('\\', '/')
|
||||
remote_dir = os.path.dirname(ftp_path)
|
||||
|
||||
# Ensure remote directory exists
|
||||
if remote_dir:
|
||||
with created_dirs_lock:
|
||||
if remote_dir not in created_dirs:
|
||||
try:
|
||||
ftp = get_ftp_connection(ftp_host, ftp_user, ftp_pass)
|
||||
ensure_remote_dir(ftp, remote_dir)
|
||||
created_dirs.add(remote_dir)
|
||||
except Exception as e:
|
||||
thread_local.ftp = None
|
||||
try:
|
||||
ftp = get_ftp_connection(ftp_host, ftp_user, ftp_pass)
|
||||
ensure_remote_dir(ftp, remote_dir)
|
||||
created_dirs.add(remote_dir)
|
||||
except Exception as err2:
|
||||
return False, ftp_path, f"Failed directory creation: {err2}"
|
||||
|
||||
# Upload the file
|
||||
for attempt in range(3):
|
||||
try:
|
||||
ftp = get_ftp_connection(ftp_host, ftp_user, ftp_pass)
|
||||
with open(local_path, 'rb') as f:
|
||||
ftp.storbinary(f"STOR {ftp_path}", f)
|
||||
return True, ftp_path, None
|
||||
except Exception as e:
|
||||
thread_local.ftp = None # Force reconnect on next attempt
|
||||
if attempt == 2:
|
||||
return False, ftp_path, str(e)
|
||||
return False, ftp_path, "Unknown error"
|
||||
|
||||
def main():
|
||||
env = load_env()
|
||||
ftp_host = "ftp.canticristiani.it"
|
||||
ftp_user = "canticristiani.it"
|
||||
ftp_pass = env.get("FTP_PASSWORD")
|
||||
|
||||
if not ftp_pass:
|
||||
print("❌ Error: FTP_PASSWORD not found in .env")
|
||||
sys.exit(1)
|
||||
|
||||
local_dir = "www"
|
||||
if not os.path.isdir(local_dir):
|
||||
print(f"❌ Error: Local dir '{local_dir}' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
files_to_upload = []
|
||||
for root, dirs, files in os.walk(local_dir):
|
||||
for file in files:
|
||||
local_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(local_path, local_dir)
|
||||
files_to_upload.append((local_path, rel_path))
|
||||
|
||||
total = len(files_to_upload)
|
||||
uploaded = 0
|
||||
errors = []
|
||||
|
||||
created_dirs = set()
|
||||
created_dirs_lock = threading.Lock()
|
||||
progress_lock = threading.Lock()
|
||||
|
||||
# Thread limit: default to 100 as requested
|
||||
num_threads = int(os.environ.get("FTP_THREADS", env.get("FTP_THREADS", "100")))
|
||||
print(f"🚀 Starting parallel upload of {total} files using {num_threads} threads...")
|
||||
|
||||
def run_task(item):
|
||||
nonlocal uploaded
|
||||
local_path, rel_path = item
|
||||
success, ftp_path, err_msg = upload_file_task(
|
||||
local_path, rel_path, ftp_host, ftp_user, ftp_pass, created_dirs, created_dirs_lock
|
||||
)
|
||||
with progress_lock:
|
||||
uploaded += 1
|
||||
print(f"\rUploading [{uploaded}/{total}] {ftp_path}...", end="", flush=True)
|
||||
if not success:
|
||||
errors.append((ftp_path, err_msg))
|
||||
|
||||
with ThreadPoolExecutor(max_workers=num_threads) as executor:
|
||||
futures = [executor.submit(run_task, item) for item in files_to_upload]
|
||||
for future in as_completed(futures):
|
||||
pass
|
||||
|
||||
# Cleanup connections
|
||||
with ThreadPoolExecutor(max_workers=num_threads) as cleanup_executor:
|
||||
cleanups = [cleanup_executor.submit(close_thread_connection) for _ in range(num_threads)]
|
||||
for future in as_completed(cleanups):
|
||||
pass
|
||||
|
||||
if errors:
|
||||
print(f"\n⚠️ Deploy completed with {len(errors)} errors:")
|
||||
for path, err in errors:
|
||||
print(f" {path}: {err}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\n\n✅ Deploy completed successfully with 100% files uploaded without errors!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,34 @@
|
||||
import urllib.request
|
||||
import json
|
||||
|
||||
url = "https://libretto.mmcinet.eu/canti/api/v3/get_all_app_tables?uuid=pwa-cc-uuid&email=&platform=browser&version=1.0&gruppo=123456&all_song=false"
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
||||
print("Fetching...")
|
||||
with urllib.request.urlopen(req) as res:
|
||||
data = json.loads(res.read().decode("utf-8"))
|
||||
|
||||
canti_pers = data.get("canti_personali", {}).get("data", [])
|
||||
states = {}
|
||||
for cp in canti_pers:
|
||||
state = cp.get("stato")
|
||||
states[state] = states.get(state, 0) + 1
|
||||
|
||||
print("States found in canti_personali:")
|
||||
for state, count in states.items():
|
||||
print(f"- State {state}: {count} songs")
|
||||
|
||||
# Let's also check if there are any duplicate IDs between canti and canti_personali
|
||||
canti_ids = {c["id_canti"] for c in data.get("canti", {}).get("data", [])}
|
||||
canti_pers_ids = {cp["id_canti"] for cp in canti_pers}
|
||||
intersection = canti_ids.intersection(canti_pers_ids)
|
||||
print(f"Number of duplicate song IDs between global catalog and canti_personali: {len(intersection)}")
|
||||
if len(intersection) > 0:
|
||||
print("First 5 duplicate IDs:")
|
||||
print(list(intersection)[:5])
|
||||
# Find one example of duplicate
|
||||
dup_id = list(intersection)[0]
|
||||
global_dup = next(c for c in data["canti"]["data"] if c["id_canti"] == dup_id)
|
||||
pers_dup = next(cp for cp in canti_pers if cp["id_canti"] == dup_id)
|
||||
print(f"\nExample Duplicate (ID {dup_id}):")
|
||||
print(f"Global: Title='{global_dup.get('titolo')}', Stato={global_dup.get('stato')}")
|
||||
print(f"Personal: Title='{pers_dup.get('titolo')}', Stato={pers_dup.get('stato')}")
|
||||
@@ -0,0 +1,11 @@
|
||||
import urllib.request
|
||||
|
||||
url = "https://app.canticristiani.it/js/db.js"
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
||||
with urllib.request.urlopen(req) as res:
|
||||
content = res.read().decode("utf-8")
|
||||
lines = content.split("\n")
|
||||
|
||||
print("================== js/db.js lines 1260 to 1310 ==================")
|
||||
for i in range(1259, min(1310, len(lines))):
|
||||
print(f"{i+1}: {lines[i]}")
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
.chord-segment {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
vertical-align: bottom;
|
||||
margin-right: 0.2em;
|
||||
border: 1px solid blue;
|
||||
}
|
||||
.chord {
|
||||
font-size: 0.75em;
|
||||
font-weight: 700;
|
||||
color: red;
|
||||
height: 1.2em;
|
||||
margin-bottom: -0.2em;
|
||||
border: 1px solid red;
|
||||
}
|
||||
.seg-text {
|
||||
white-space: pre;
|
||||
border: 1px solid green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
ti amerò come
|
||||
<span class="chord-segment"><span class="chord">LA</span><span class="seg-text"> </span></span>
|
||||
<span class="chord-segment"><span class="chord">MI</span><span class="seg-text"></span></span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,67 @@
|
||||
const textWords = [
|
||||
{ text: 'come', bbox: { x0: 40, x1: 60 } },
|
||||
{ text: 'sei', bbox: { x0: 70, x1: 90 } }
|
||||
];
|
||||
|
||||
const chordWords = [
|
||||
{ text: 'la', bbox: { x0: 65, x1: 75 } },
|
||||
{ text: 'mi', bbox: { x0: 95, x1: 105 } }
|
||||
];
|
||||
|
||||
function mergeChordsAndLyrics(chordWords: any[], textWords: any[]): string {
|
||||
let result = '';
|
||||
const chordAssignments = new Map<any, any[]>();
|
||||
|
||||
chordWords.forEach(chord => {
|
||||
const chordX = (chord.bbox.x0 + chord.bbox.x1) / 2;
|
||||
let closestWord: any = null;
|
||||
let minDistance = Infinity;
|
||||
|
||||
textWords.forEach(textWord => {
|
||||
const wordXCenter = (textWord.bbox.x0 + textWord.bbox.x1) / 2;
|
||||
const dist = Math.abs(chordX - wordXCenter);
|
||||
if (dist < minDistance) {
|
||||
minDistance = dist;
|
||||
closestWord = textWord;
|
||||
}
|
||||
});
|
||||
|
||||
if (closestWord) {
|
||||
if (!chordAssignments.has(closestWord)) {
|
||||
chordAssignments.set(closestWord, []);
|
||||
}
|
||||
chordAssignments.get(closestWord)!.push(chord);
|
||||
}
|
||||
});
|
||||
|
||||
textWords.forEach((textWord, index) => {
|
||||
const assignedChords = chordAssignments.get(textWord) || [];
|
||||
|
||||
// Split chords into before and after the word
|
||||
const chordsBefore = assignedChords.filter(c => (c.bbox.x0 + c.bbox.x1)/2 <= textWord.bbox.x1);
|
||||
const chordsAfter = assignedChords.filter(c => (c.bbox.x0 + c.bbox.x1)/2 > textWord.bbox.x1);
|
||||
|
||||
chordsBefore.sort((a, b) => a.bbox.x0 - b.bbox.x0);
|
||||
chordsAfter.sort((a, b) => a.bbox.x0 - b.bbox.x0);
|
||||
|
||||
chordsBefore.forEach(chord => {
|
||||
const cleanChord = chord.text.replace(/[\(\)\[\]]/g, '').toUpperCase();
|
||||
result += `[${cleanChord}]`;
|
||||
});
|
||||
|
||||
result += textWord.text;
|
||||
|
||||
chordsAfter.forEach(chord => {
|
||||
const cleanChord = chord.text.replace(/[\(\)\[\]]/g, '').toUpperCase();
|
||||
result += `[${cleanChord}]`;
|
||||
});
|
||||
|
||||
if (index < textWords.length - 1) {
|
||||
result += ' ';
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
console.log(mergeChordsAndLyrics(chordWords, textWords));
|
||||
Reference in New Issue
Block a user