Add NextSnap PWA with photo gallery viewer and continuous capture
Offline-first photo capture app for Nextcloud with: - Camera capture with continuous mode (auto-reopens after each photo) - File browser with fullscreen image gallery, swipe navigation, and rename - Upload queue with background sync engine - Admin panel for Nextcloud user management - Service worker for offline-first caching (v13) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
242
app/static/js/sync_broken_backup.js
Normal file
242
app/static/js/sync_broken_backup.js
Normal file
@@ -0,0 +1,242 @@
|
||||
// SYNC.JS VERSION 8 - LOADING
|
||||
console.log("[SYNC] Loading sync.js v8...");
|
||||
// NextSnap - Sync Engine with Upload Queue and Retry Logic
|
||||
'use strict';
|
||||
|
||||
const Sync = {
|
||||
currentUsername: null,
|
||||
isOnline: navigator.onLine,
|
||||
isSyncing: false,
|
||||
currentUpload: null,
|
||||
retryTimeouts: {},
|
||||
|
||||
// Exponential backoff delays (in milliseconds)
|
||||
retryDelays: [5000, 15000, 45000, 120000, 300000], // 5s, 15s, 45s, 2m, 5m
|
||||
maxRetryDelay: 300000, // Cap at 5 minutes
|
||||
|
||||
init(username) {
|
||||
this.currentUsername = username;
|
||||
this.setupEventListeners();
|
||||
|
||||
// Check for pending uploads on init
|
||||
if (this.isOnline) {
|
||||
this.triggerSync();
|
||||
}
|
||||
},
|
||||
|
||||
setupEventListeners() {
|
||||
// Listen for online/offline events
|
||||
window.addEventListener('online', () => {
|
||||
console.log('Network online - triggering sync');
|
||||
this.isOnline = true;
|
||||
this.updateConnectivityUI();
|
||||
this.triggerSync();
|
||||
});
|
||||
|
||||
window.addEventListener('offline', () => {
|
||||
console.log('Network offline');
|
||||
this.isOnline = false;
|
||||
this.isSyncing = false;
|
||||
this.updateConnectivityUI();
|
||||
});
|
||||
},
|
||||
|
||||
async triggerSync() {
|
||||
if (!this.isOnline || this.isSyncing) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Starting sync...');
|
||||
this.isSyncing = true;
|
||||
this.updateConnectivityUI();
|
||||
|
||||
try {
|
||||
await this.processQueue();
|
||||
} catch (error) {
|
||||
console.error('Sync error:', error);
|
||||
} finally {
|
||||
this.isSyncing = false;
|
||||
this.updateConnectivityUI();
|
||||
}
|
||||
},
|
||||
|
||||
async processQueue() {
|
||||
// Get pending and uploading photos (retry stalled uploads)
|
||||
const pendingPhotos = await Storage.db.photos
|
||||
.where('username').equals(this.currentUsername)
|
||||
.and(photo => photo.status === 'pending' || photo.status === 'uploading')
|
||||
.sortBy('timestamp');
|
||||
|
||||
if (pendingPhotos.length === 0) {
|
||||
console.log('No pending photos to upload');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found ${pendingPhotos.length} photos to upload`);
|
||||
|
||||
// Process uploads sequentially (one at a time)
|
||||
for (const photo of pendingPhotos) {
|
||||
if (!this.isOnline) {
|
||||
console.log('Lost connection - stopping sync');
|
||||
break;
|
||||
}
|
||||
|
||||
await this.uploadPhoto(photo);
|
||||
}
|
||||
|
||||
// Update UI
|
||||
this.updatePendingCount();
|
||||
this.updateRecentPhotos();
|
||||
},
|
||||
|
||||
async uploadPhoto(photo) {
|
||||
this.currentUpload = photo;
|
||||
|
||||
try {
|
||||
console.log(`Uploading ${photo.filename}...`);
|
||||
|
||||
// Update status to uploading
|
||||
await Storage.updatePhoto(photo.id, { status: 'uploading' });
|
||||
this.updatePendingCount();
|
||||
|
||||
// Upload file
|
||||
const formData = new FormData();
|
||||
formData.append('file', photo.blob, photo.filename);
|
||||
|
||||
const uploadUrl = `/api/files/upload?path=${encodeURIComponent(photo.targetPath)}`;
|
||||
|
||||
const uploadResponse = await fetch(uploadUrl, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!uploadResponse.ok) {
|
||||
const error = await uploadResponse.json();
|
||||
throw new Error(error.error || 'Upload failed');
|
||||
}
|
||||
|
||||
const uploadResult = await uploadResponse.json();
|
||||
console.log(`Upload successful: ${uploadResult.path}`);
|
||||
|
||||
// Update status to uploaded
|
||||
await Storage.updatePhoto(photo.id, { status: 'uploaded' });
|
||||
|
||||
// Verify file exists on server
|
||||
const verifyUrl = `/api/files/verify?path=${encodeURIComponent(uploadResult.path)}`;
|
||||
|
||||
const verifyResponse = await fetch(verifyUrl);
|
||||
|
||||
if (!verifyResponse.ok) {
|
||||
throw new Error('Verification failed - file not found on server');
|
||||
}
|
||||
|
||||
const verifyResult = await verifyResponse.json();
|
||||
|
||||
if (!verifyResult.exists) {
|
||||
throw new Error('Verification failed - file does not exist');
|
||||
}
|
||||
|
||||
console.log(`Verification successful: ${uploadResult.path}`);
|
||||
|
||||
// Update status to verified
|
||||
await Storage.updatePhoto(photo.id, { status: 'verified' });
|
||||
|
||||
// Delete from IndexedDB (only after verification!)
|
||||
await Storage.deletePhoto(photo.id);
|
||||
console.log(`Deleted photo ${photo.id} from IndexedDB`);
|
||||
|
||||
// Clear any pending retry
|
||||
if (this.retryTimeouts[photo.id]) {
|
||||
clearTimeout(this.retryTimeouts[photo.id]);
|
||||
delete this.retryTimeouts[photo.id];
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error uploading ${photo.filename}:`, error);
|
||||
|
||||
// Handle upload failure
|
||||
await this.handleUploadFailure(photo, error.message);
|
||||
} finally {
|
||||
this.currentUpload = null;
|
||||
}
|
||||
},
|
||||
|
||||
async handleUploadFailure(photo, errorMessage) {
|
||||
const retryCount = (photo.retryCount || 0) + 1;
|
||||
|
||||
// Update photo with error info
|
||||
await Storage.updatePhoto(photo.id, {
|
||||
status: 'pending',
|
||||
retryCount: retryCount,
|
||||
lastError: errorMessage
|
||||
});
|
||||
|
||||
// Calculate retry delay using exponential backoff
|
||||
const delayIndex = Math.min(retryCount - 1, this.retryDelays.length - 1);
|
||||
const delay = this.retryDelays[delayIndex];
|
||||
|
||||
console.log(`Scheduling retry #${retryCount} in ${delay / 1000}s for ${photo.filename}`);
|
||||
|
||||
// Schedule retry
|
||||
if (this.retryTimeouts[photo.id]) {
|
||||
clearTimeout(this.retryTimeouts[photo.id]);
|
||||
}
|
||||
|
||||
this.retryTimeouts[photo.id] = setTimeout(() => {
|
||||
delete this.retryTimeouts[photo.id];
|
||||
|
||||
if (this.isOnline) {
|
||||
console.log(`Retrying upload for ${photo.filename}`);
|
||||
this.uploadPhoto(photo);
|
||||
}
|
||||
}, delay);
|
||||
},
|
||||
|
||||
|
||||
|
||||
async updateRecentPhotos() {
|
||||
if (typeof Camera !== 'undefined' && Camera.updateRecentPhotos) {
|
||||
await Camera.updateRecentPhotos();
|
||||
}
|
||||
},
|
||||
updateConnectivityUI() {
|
||||
const indicator = document.querySelector(".connectivity-indicator");
|
||||
if (!indicator) return;
|
||||
indicator.classList.remove("online", "offline", "syncing");
|
||||
if (!this.isOnline) {
|
||||
indicator.classList.add("offline");
|
||||
indicator.title = "Offline";
|
||||
} else if (this.isSyncing) {
|
||||
indicator.classList.add("syncing");
|
||||
indicator.title = "Syncing...";
|
||||
} else {
|
||||
indicator.classList.add("online");
|
||||
indicator.title = "Online";
|
||||
}
|
||||
},
|
||||
|
||||
async updatePendingCount() {
|
||||
if (!this.currentUsername) return;
|
||||
const countElement = document.getElementById("pendingCount");
|
||||
const countValueElement = document.getElementById("pendingCountValue");
|
||||
if (!countElement || !countValueElement) return;
|
||||
const count = await Storage.getPhotoCount(this.currentUsername, "pending");
|
||||
if (count > 0) {
|
||||
countValueElement.textContent = count;
|
||||
countElement.style.display = "block";
|
||||
} else {
|
||||
countElement.style.display = "none";
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
getState() {
|
||||
return {
|
||||
isOnline: this.isOnline,
|
||||
isSyncing: this.isSyncing,
|
||||
};
|
||||
}
|
||||
|
||||
// Make Sync globally available as SyncEngine
|
||||
window.SyncEngine = Sync;
|
||||
console.log("[SYNC] SyncEngine exported:", typeof window.SyncEngine);
|
||||
Reference in New Issue
Block a user