-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFTPsyncScriptAl.py
More file actions
195 lines (166 loc) · 6.81 KB
/
FTPsyncScriptAl.py
File metadata and controls
195 lines (166 loc) · 6.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#!/usr/bin/env python3
"""
FTP Sync Script for InfinityFree by Al (http://al.page.gd)
Syncs local files to InfinityFree hosting
LOCAL IS SOURCE OF TRUTH - deleting local files removes from server
Usage: python3 sync.py [upload|download|sync]
"""
import ftplib
import os
import sys
from pathlib import Path
# Configuration
FTP_HOST = "ftpupload.net"
FTP_PORT = 21
FTP_USER = "USERNAME_HERE"
FTP_PASS = "PASSWORD_HERE"
FTP_PATH = "/htdocs"
LOCAL_DIR = Path(__file__).parent.parent
IGNORE_FILES = {'.vscode', '.git', '.DS_Store', 'node_modules', '.gitignore', 'sync.py', '.ftpkr.json', 'vscode', '.gitkeep', '.prettierrc'}
IGNORE_DIRS = {'.vscode', '.git', '__pycache__', 'node_modules', 'vscode'}
def get_local_files():
"""Get all files in local directory to sync"""
files = []
for root, dirs, filenames in os.walk(LOCAL_DIR):
# Filter ignored directories
dirs[:] = [d for d in dirs if d not in IGNORE_DIRS]
for filename in filenames:
if filename not in IGNORE_FILES:
full_path = Path(root) / filename
rel_path = full_path.relative_to(LOCAL_DIR)
files.append((full_path, str(rel_path).replace('\\', '/')))
return files
def get_remote_files(ftp):
"""Get list of remote files to sync (fast, non-recursive)"""
remote_files = set()
try:
# Only list root level
for item in ftp.nlst('.'):
if item not in ('.', '..', './.', './..'):
# Clean up FTP path artifacts
clean_item = item.replace('./', '')
if clean_item and clean_item not in ('.', '..'):
remote_files.add(clean_item)
# Check for imgs folder
try:
ftp.cwd('imgs')
for img in ftp.nlst('.'):
if img not in ('.', '..', './.', './..'):
clean_img = img.replace('./', '')
if clean_img and clean_img not in ('.', '..'):
remote_files.add(f'imgs/{clean_img}')
ftp.cwd('..')
except ftplib.error_perm:
pass
except ftplib.all_errors:
pass
return remote_files
def upload():
"""Upload local files to server and delete server files not in local"""
try:
ftp = ftplib.FTP()
print(f"Connecting to {FTP_HOST}:{FTP_PORT}...")
ftp.connect(FTP_HOST, FTP_PORT, timeout=10)
ftp.set_pasv(True)
ftp.login(FTP_USER, FTP_PASS)
print(f"✓ Connected and logged in")
ftp.cwd(FTP_PATH)
print(f"✓ Changed to {FTP_PATH}")
# Get local files
local_files = get_local_files()
local_paths = {rel_path for _, rel_path in local_files}
print(f"\n📤 Uploading {len(local_files)} file(s):")
# Upload all local files
for local_path, rel_path in local_files:
# Create remote directories if needed
remote_dir = '/'.join(rel_path.split('/')[:-1])
if remote_dir:
# Build directory path incrementally from /htdocs
dirs_to_create = remote_dir.split('/')
current_path = FTP_PATH
for dir_part in dirs_to_create:
current_path = current_path + '/' + dir_part
try:
ftp.mkd(current_path)
print(f" 📁 Created directory: {dir_part}")
except ftplib.error_perm:
pass # Directory already exists
# Upload file
try:
# Use absolute path from FTP_PATH
full_remote_path = f"{FTP_PATH}/{rel_path}"
with open(local_path, 'rb') as f:
ftp.storbinary(f'STOR {full_remote_path}', f)
print(f" ✓ {rel_path}")
except Exception as e:
print(f" ✗ {rel_path}: {e}")
# Get remote files and delete those not in local
print(f"\n🗑️ Checking for removed files...")
remote_files = get_remote_files(ftp)
remote_files_set = set(remote_files)
files_to_delete = remote_files_set - local_paths
if files_to_delete:
print(f"Removing {len(files_to_delete)} orphaned file(s) from server:")
for remote_file in sorted(files_to_delete):
try:
ftp.delete(remote_file)
print(f" ✓ Deleted {remote_file}")
except ftplib.error_perm as e:
print(f" - Skip {remote_file}: Permission denied")
except Exception as e:
print(f" - Skip {remote_file}: {e}")
else:
print(f"✓ No orphaned files to remove")
ftp.quit()
print(f"\n✅ Sync complete! (Local is source of truth)")
except Exception as e:
print(f"✗ Error: {e}", file=sys.stderr)
sys.exit(1)
def download():
"""Download files from server (server overwrites local)"""
try:
ftp = ftplib.FTP()
print(f"Connecting to {FTP_HOST}:{FTP_PORT}...")
ftp.connect(FTP_HOST, FTP_PORT, timeout=10)
ftp.set_pasv(True)
ftp.login(FTP_USER, FTP_PASS)
print(f"✓ Connected and logged in")
ftp.cwd(FTP_PATH)
print(f"✓ Changed to {FTP_PATH}")
# Simple download of main HTML files
files_to_download = ['index.html', 'index2.html']
print(f"\n📥 Downloading files:")
for filename in files_to_download:
try:
local_file = LOCAL_DIR / filename
with open(local_file, 'wb') as f:
ftp.retrbinary(f'RETR {filename}', f.write)
print(f" ✓ {filename}")
except ftplib.error_perm:
print(f" - {filename} (not found on server)")
except Exception as e:
print(f" - {filename}: {e}")
ftp.quit()
print(f"\n✅ Download complete!")
except Exception as e:
print(f"✗ Error: {e}", file=sys.stderr)
sys.exit(1)
def sync():
"""Smart sync: upload + delete (local is source of truth)"""
print("🔄 Running SMART SYNC (local is source of truth)...")
upload()
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python3 sync.py [upload|download|sync]")
print(" upload - Upload local files, delete remote files not in local")
print(" download - Download from server")
print(" sync - Smart sync (same as upload)")
sys.exit(1)
command = sys.argv[1].lower()
if command == 'upload' or command == 'sync':
upload()
elif command == 'download':
download()
else:
print(f"Unknown command: {command}")
sys.exit(1)