Fix image editing bug: ensure edits persist and upload correctly
Critical fixes for image editing workflow: 1. Keep local edited files as backup (don't delete after server upload) - Server may not process upload immediately - Keeps edits safe locally in case server fails - Prevents loss of edited images 2. Include original filename in metadata sent to server - Server needs to know which file was edited - Allows proper tracking and versioning 3. Improved error logging for server upload - Now logs detailed errors (404, 401, timeout, connection) - Shows clear messages when server doesn't support endpoint - Helps diagnose why edits aren't syncing to server 4. Better user feedback during save - Shows 'Saved to device' status first - Then 'Upload in progress' to show server sync happening - Clarifies local vs server save status Bug symptoms fixed: - Edited images now persist locally after restart - Server upload now sends correct file information - Clear error messages if server upload fails - User understands 'local save' vs 'server sync' steps
This commit is contained in:
1
.player_heartbeat
Normal file
1
.player_heartbeat
Normal file
@@ -0,0 +1 @@
|
||||
1768679079.4526675
|
||||
68
New.txt
68
New.txt
@@ -1,68 +0,0 @@
|
||||
[INFO ] ✓ Playlist is up to date
|
||||
[WARNING] Deprecated property "<BooleanProperty name=allow_stretch>" of object "<kivy.uix.image.AsyncImage object at 0x7f72df25f0>" has been set, it will be removed in a future version
|
||||
[WARNING] Deprecated property "<BooleanProperty name=keep_ratio>" of object "<kivy.uix.image.AsyncImage object at 0x7f72df25f0>" was accessed, it will be removed in a future version
|
||||
/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
|
||||
warnings.warn(
|
||||
[DEBUG ] [https ]//192.168.0.121:443 "POST /api/auth/verify HTTP/1.1" 200 None
|
||||
[INFO ] ✅ Auth code verified
|
||||
[INFO ] ✅ Using existing authentication
|
||||
/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
|
||||
warnings.warn(
|
||||
[DEBUG ] [https ]//192.168.0.121:443 "POST /api/player-feedback HTTP/1.1" 200 None
|
||||
start.sh: line 212: 16035 Killed python3 main.py
|
||||
[2026-01-17 21:31:57] ❌ Player process crashed or stopped (PID: 16035)
|
||||
[2026-01-17 21:31:57] ⏳ Waiting 5s before restart...
|
||||
[2026-01-17 21:32:02]
|
||||
[2026-01-17 21:32:02] ==========================================
|
||||
[2026-01-17 21:32:02] ▶️ Starting player (attempt #2)
|
||||
[2026-01-17 21:32:02] ==========================================
|
||||
[2026-01-17 21:32:02] Player PID: 16376
|
||||
[INFO ] [Logger ] Record log in /home/pi/.kivy/logs/kivy_26-01-17_43.txt
|
||||
[INFO ] [Kivy ] v2.3.1
|
||||
[INFO ] [Kivy ] Installed at "/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/kivy/__init__.py"
|
||||
[INFO ] [Python ] v3.13.5 (main, Jun 25 2025, 18:55:22) [GCC 14.2.0]
|
||||
[INFO ] [Python ] Interpreter at "/home/pi/Desktop/Kiwy-Signage/.venv/bin/python3"
|
||||
[INFO ] [Logger ] Purge log fired. Processing...
|
||||
[INFO ] [Logger ] Purge finished!
|
||||
/home/pi/Desktop/Kiwy-Signage/src/main.py:1868: DeprecationWarning: There is no current event loop
|
||||
loop = asyncio.get_event_loop()
|
||||
[DEBUG ] [Using selector] EpollSelector
|
||||
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
|
||||
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
|
||||
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
|
||||
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
|
||||
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
|
||||
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
|
||||
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
|
||||
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
|
||||
WARNING: running xinput against an Xwayland server. See the xinput man page for details.
|
||||
[ERROR ] [Image ] Error loading </home/pi/Desktop/Kiwy-Signage/config/resources/intro1.mp4>
|
||||
[WARNING] ⚠️ SSL verification disabled - NOT recommended for production!
|
||||
[DEBUG ] [Starting new HTTPS connection (1)] 192.168.0.121:443
|
||||
/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
|
||||
warnings.warn(
|
||||
[DEBUG ] [https ]//192.168.0.121:443 "POST /api/auth/verify HTTP/1.1" 200 None
|
||||
[INFO ] ✅ Auth code verified
|
||||
[INFO ] ✅ Using existing authentication
|
||||
[INFO ] [Fetching playlist from] https://192.168.0.121:443/api/playlists/1
|
||||
/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
|
||||
warnings.warn(
|
||||
[DEBUG ] [https ]//192.168.0.121:443 "GET /api/playlists/1 HTTP/1.1" 200 None
|
||||
[INFO ] [✅ Playlist received (version] 33)
|
||||
[INFO ] [📊 Playlist versions - Server] v33, Local: v33
|
||||
[INFO ] ✓ Playlist is up to date
|
||||
[WARNING] Deprecated property "<BooleanProperty name=allow_stretch>" of object "<kivy.uix.image.AsyncImage object at 0x7f6b7425f0>" has been set, it will be removed in a future version
|
||||
[WARNING] Deprecated property "<BooleanProperty name=keep_ratio>" of object "<kivy.uix.image.AsyncImage object at 0x7f6b7425f0>" was accessed, it will be removed in a future version
|
||||
/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
|
||||
warnings.warn(
|
||||
[DEBUG ] [https ]//192.168.0.121:443 "POST /api/auth/verify HTTP/1.1" 200 None
|
||||
[INFO ] ✅ Auth code verified
|
||||
[INFO ] ✅ Using existing authentication
|
||||
/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
|
||||
warnings.warn(
|
||||
[DEBUG ] [https ]//192.168.0.121:443 "POST /api/player-feedback HTTP/1.1" 200 None
|
||||
start.sh: line 212: 16376 Killed python3 main.py
|
||||
[2026-01-17 21:32:32] ❌ Player process crashed or stopped (PID: 16376)
|
||||
[2026-01-17 21:32:32] ⏳ Waiting 5s before restart...
|
||||
^C[2026-01-17 21:32:33] 🛑 Watchdog received stop signal
|
||||
pi@rpi-tvcanba1:~/Desktop/Kiwy-Signage $
|
||||
|
||||
BIN
media/edited_media/2026efvev-1428673176_e_v1.jpg
Normal file
BIN
media/edited_media/2026efvev-1428673176_e_v1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"time_of_modification": "2026-01-17T21:40:13.194566",
|
||||
"original_name": "2026efvev-1428673176.jpg",
|
||||
"new_name": "2026efvev-1428673176_e_v1.jpg",
|
||||
"original_path": "/home/pi/Desktop/Kiwy-Signage/media/2026efvev-1428673176.jpg",
|
||||
"version": 1,
|
||||
"user_card_data": "0007206239"
|
||||
}
|
||||
@@ -341,10 +341,14 @@ class EditPopup(Popup):
|
||||
|
||||
# NOW show saving popup AFTER everything is done
|
||||
def show_saving_and_dismiss(dt):
|
||||
# Create label with background
|
||||
# Create label with background showing detailed save status
|
||||
save_msg = (
|
||||
'Saved locally!\n'
|
||||
'Uploading to server...'
|
||||
)
|
||||
save_label = Label(
|
||||
text='Saved! Reloading player...',
|
||||
font_size='36sp',
|
||||
text=save_msg,
|
||||
font_size='24sp',
|
||||
color=(1, 1, 1, 1),
|
||||
bold=True
|
||||
)
|
||||
@@ -352,7 +356,7 @@ class EditPopup(Popup):
|
||||
saving_popup = Popup(
|
||||
title='',
|
||||
content=save_label,
|
||||
size_hint=(0.8, 0.3),
|
||||
size_hint=(0.85, 0.4),
|
||||
auto_dismiss=False,
|
||||
separator_height=0,
|
||||
background_color=(0.2, 0.7, 0.2, 0.95) # Green background
|
||||
@@ -360,13 +364,23 @@ class EditPopup(Popup):
|
||||
saving_popup.open()
|
||||
Logger.info("EditPopup: Saving confirmation popup opened")
|
||||
|
||||
# Dismiss both popups after 2 seconds
|
||||
# Update message after 3 seconds to show upload is happening
|
||||
def update_message(dt):
|
||||
if saving_popup:
|
||||
save_label.text = (
|
||||
'✓ Saved to device\n'
|
||||
'Upload in progress...'
|
||||
)
|
||||
|
||||
Clock.schedule_once(update_message, 3.0)
|
||||
|
||||
# Dismiss both popups after 4 seconds
|
||||
def dismiss_all(dt):
|
||||
saving_popup.dismiss()
|
||||
Logger.info(f"EditPopup: Dismissing to resume playback...")
|
||||
self.dismiss()
|
||||
|
||||
Clock.schedule_once(dismiss_all, 2.0)
|
||||
Clock.schedule_once(dismiss_all, 4.0)
|
||||
|
||||
# Small delay to ensure UI is ready, then show popup
|
||||
Clock.schedule_once(show_saving_and_dismiss, 0.1)
|
||||
@@ -420,7 +434,8 @@ class EditPopup(Popup):
|
||||
# Get authenticated instance
|
||||
auth = get_auth_instance()
|
||||
if not auth or not auth.is_authenticated():
|
||||
Logger.warning("EditPopup: Cannot upload - not authenticated (server will not receive edited media)")
|
||||
Logger.warning("EditPopup: Cannot upload - not authenticated (edited media saved locally only)")
|
||||
Logger.warning("EditPopup: Server will NOT receive this edit")
|
||||
return False
|
||||
|
||||
server_url = auth.auth_data.get('server_url')
|
||||
@@ -434,60 +449,76 @@ class EditPopup(Popup):
|
||||
with open(metadata_path, 'r') as meta_file:
|
||||
metadata = json.load(meta_file)
|
||||
|
||||
# Prepare upload URL
|
||||
# Prepare upload URL - send to the original file endpoint
|
||||
upload_url = f"{server_url}/api/player-edit-media"
|
||||
headers = {'Authorization': f'Bearer {auth_code}'}
|
||||
|
||||
# Add the original filename to metadata so server knows which file was edited
|
||||
metadata['original_filename'] = os.path.basename(metadata['original_path'])
|
||||
|
||||
# Prepare file and data for upload
|
||||
with open(image_path, 'rb') as img_file:
|
||||
files = {
|
||||
'image_file': (metadata['new_name'], img_file, 'image/jpeg')
|
||||
'image_file': (metadata['original_filename'], img_file, 'image/jpeg')
|
||||
}
|
||||
|
||||
# Send metadata as JSON string in form data
|
||||
data = {
|
||||
'metadata': json.dumps(metadata)
|
||||
'metadata': json.dumps(metadata),
|
||||
'original_file': metadata['original_filename']
|
||||
}
|
||||
|
||||
Logger.info(f"EditPopup: Uploading edited media to {upload_url}")
|
||||
response = requests.post(upload_url, headers=headers, files=files, data=data, timeout=30)
|
||||
Logger.info(f"EditPopup: 📤 Uploading edited media to {upload_url}")
|
||||
Logger.info(f"EditPopup: - Original file: {metadata['original_filename']}")
|
||||
Logger.info(f"EditPopup: - Edited image: {image_path}")
|
||||
Logger.info(f"EditPopup: - Metadata: {metadata_path}")
|
||||
|
||||
if response.status_code == 200:
|
||||
response_data = response.json()
|
||||
Logger.info(f"EditPopup: ✅ Successfully uploaded edited media to server: {response_data}")
|
||||
try:
|
||||
response = requests.post(upload_url, headers=headers, files=files, data=data, timeout=30)
|
||||
|
||||
# Delete local files after successful upload
|
||||
try:
|
||||
if os.path.exists(image_path):
|
||||
os.remove(image_path)
|
||||
Logger.info(f"EditPopup: Deleted local image file: {os.path.basename(image_path)}")
|
||||
if response.status_code == 200:
|
||||
response_data = response.json()
|
||||
Logger.info(f"EditPopup: ✅ Successfully uploaded edited media to server")
|
||||
Logger.info(f"EditPopup: Server response: {response_data}")
|
||||
|
||||
if os.path.exists(metadata_path):
|
||||
os.remove(metadata_path)
|
||||
Logger.info(f"EditPopup: Deleted local metadata file: {os.path.basename(metadata_path)}")
|
||||
# DO NOT delete local files - keep them as backup
|
||||
# In case the server doesn't process them, we want to keep the edits locally
|
||||
Logger.info(f"EditPopup: ✓ Keeping local edited files as backup:")
|
||||
Logger.info(f" - Image: {image_path}")
|
||||
Logger.info(f" - Metadata: {metadata_path}")
|
||||
|
||||
Logger.info("EditPopup: ✅ Local edited files cleaned up after successful upload")
|
||||
except Exception as e:
|
||||
Logger.warning(f"EditPopup: Could not delete local files: {e}")
|
||||
|
||||
return True
|
||||
elif response.status_code == 404:
|
||||
Logger.warning("EditPopup: ⚠️ Upload endpoint not available on server (404) - edited media saved locally only")
|
||||
return True
|
||||
elif response.status_code == 404:
|
||||
Logger.error("EditPopup: ❌ Upload endpoint not found on server (404)")
|
||||
Logger.error("EditPopup: Server may not support edited media uploads")
|
||||
Logger.error("EditPopup: Edited media is saved locally only")
|
||||
return False
|
||||
elif response.status_code == 401:
|
||||
Logger.error("EditPopup: ❌ Authentication failed (401) - check auth credentials")
|
||||
Logger.error("EditPopup: Edited media is saved locally only")
|
||||
return False
|
||||
else:
|
||||
Logger.error(f"EditPopup: ❌ Upload failed with status {response.status_code}")
|
||||
Logger.error(f"EditPopup: Response: {response.text}")
|
||||
Logger.error("EditPopup: Edited media is saved locally only")
|
||||
return False
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
Logger.error("EditPopup: ❌ Upload timed out after 30 seconds")
|
||||
Logger.error("EditPopup: Check network connection")
|
||||
Logger.error("EditPopup: Edited media is saved locally only")
|
||||
return False
|
||||
else:
|
||||
Logger.warning(f"EditPopup: ⚠️ Upload failed with status {response.status_code} - edited media saved locally only")
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
Logger.error(f"EditPopup: ❌ Cannot connect to server: {e}")
|
||||
Logger.error("EditPopup: Check server URL and network connection")
|
||||
Logger.error("EditPopup: Edited media is saved locally only")
|
||||
return False
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
Logger.warning("EditPopup: ⚠️ Upload timed out - edited media saved locally only")
|
||||
return False
|
||||
except requests.exceptions.ConnectionError:
|
||||
Logger.warning("EditPopup: ⚠️ Cannot connect to server - edited media saved locally only")
|
||||
return False
|
||||
except Exception as e:
|
||||
Logger.warning(f"EditPopup: ⚠️ Upload failed: {e} - edited media saved locally only")
|
||||
Logger.error(f"EditPopup: ❌ Unexpected error during upload: {e}")
|
||||
import traceback
|
||||
Logger.debug(f"EditPopup: Upload traceback: {traceback.format_exc()}")
|
||||
Logger.error(f"EditPopup: Traceback: {traceback.format_exc()}")
|
||||
Logger.error("EditPopup: Edited media is saved locally only")
|
||||
return False
|
||||
|
||||
def close_without_saving(self, instance):
|
||||
|
||||
Reference in New Issue
Block a user