@rsweeten/dropbox-sync
Version:
Reusable Dropbox synchronization module with framework adapters
245 lines (221 loc) • 8.66 kB
text/typescript
// Angular service implementation for Dropbox sync
import { Injectable } from '@angular/core'
import { BehaviorSubject, Observable, of, fromEvent } from 'rxjs'
import { catchError, map, tap } from 'rxjs/operators'
import { HttpClient } from '@angular/common/http'
// For a private module, you would import from your private registry or local path
// import { DropboxSyncService, getCredentialsFromEnvironment } from '@yourcompany/dropbox-sync';
import { DropboxSyncService, getCredentialsFromEnvironment } from 'dropbox-sync'
import { environment } from '../environments/environment'
export class DropboxService {
// State observables
private _connected = new BehaviorSubject<boolean>(false)
private _syncing = new BehaviorSubject<boolean>(false)
private _progress = new BehaviorSubject<number>(0)
private _message = new BehaviorSubject<string>('')
private _error = new BehaviorSubject<string | null>(null)
// Public observable properties
readonly connected$ = this._connected.asObservable()
readonly syncing$ = this._syncing.asObservable()
readonly progress$ = this._progress.asObservable()
readonly message$ = this._message.asObservable()
readonly error$ = this._error.asObservable()
private syncStats = {
total: 0,
uploads: 0,
downloads: 0,
completed: 0,
}
constructor(
private http: HttpClient,
private dropboxSyncService: DropboxSyncService
) {
// Initialize the Dropbox sync client
this.initialize()
}
/**
* Initialize the Dropbox sync client and check connection status
*/
initialize(): void {
// Get credentials from environment and localStorage
const credentials = getCredentialsFromEnvironment(environment)
// Initialize the service
this.dropboxSyncService.initialize(credentials)
// Check connection status
this.checkConnection().subscribe()
// Set up socket event listeners
this.setupSocketEventListeners()
}
/**
* Check if connected to Dropbox
*/
checkConnection(): Observable<boolean> {
return this.http
.get<{ connected: boolean }>('/api/dropbox/status')
.pipe(
map((response) => response.connected),
tap((connected) => this._connected.next(connected)),
catchError((error) => {
console.error('Error checking Dropbox connection:', error)
this._connected.next(false)
return of(false)
})
)
}
/**
* Start the OAuth flow to connect to Dropbox
*/
connect(): void {
window.location.href = '/api/dropbox/auth/start'
}
/**
* Disconnect from Dropbox
*/
disconnect(): Observable<boolean> {
return this.http
.post<{ success: boolean }>('/api/dropbox/logout', {})
.pipe(
tap((response) => {
if (response.success) {
this._connected.next(false)
localStorage.removeItem('dropbox_access_token')
localStorage.removeItem('dropbox_refresh_token')
localStorage.removeItem('dropbox_connected')
}
}),
map((response) => response.success),
catchError((error) => {
console.error('Error disconnecting from Dropbox:', error)
return of(false)
})
)
}
/**
* Start syncing files with Dropbox
*/
startSync(options?: { localDir?: string; dropboxDir?: string }): void {
try {
// Reset state
this._syncing.next(true)
this._progress.next(0)
this._message.next('Initializing Dropbox sync...')
this._error.next(null)
// Get socket from the sync service and emit sync event
const client = this.dropboxSyncService.getClient()
client.socket.connect()
client.socket.emit('dropbox:sync', options)
} catch (error: any) {
console.error('Error starting Dropbox sync:', error)
this._error.next(error.message || 'Error starting synchronization')
this._syncing.next(false)
}
}
/**
* Cancel ongoing sync process
*/
cancelSync(): void {
try {
const client = this.dropboxSyncService.getClient()
client.sync.cancelSync()
this._syncing.next(false)
this._message.next('Sync cancelled by user')
} catch (error: any) {
console.error('Error cancelling sync:', error)
}
}
/**
* Check which files need to be synced without performing sync
*/
checkSyncNeeded(options?: {
localDir?: string
dropboxDir?: string
}): Observable<{ upload: number; download: number }> {
try {
const client = this.dropboxSyncService.getClient()
return this.dropboxSyncService.createSyncQueue(options).pipe(
map(({ uploadQueue, downloadQueue }) => ({
upload: uploadQueue.length,
download: downloadQueue.length,
})),
catchError((error) => {
console.error('Error checking sync status:', error)
return of({ upload: 0, download: 0 })
})
)
} catch (error) {
console.error('Error creating sync queue:', error)
return of({ upload: 0, download: 0 })
}
}
/**
* Set up socket event listeners for real-time sync updates
*/
private setupSocketEventListeners(): void {
try {
const client = this.dropboxSyncService.getClient()
const socketProgress =
this.dropboxSyncService.setupSocketListeners()
// Subscribe to the socket events observable
socketProgress.subscribe({
next: (data: any) => {
switch (data.type) {
case 'progress':
this._progress.next(data.data.progress)
this._message.next(data.data.message)
break
case 'queue':
this.syncStats = {
total: data.data.total,
uploads: data.data.totalUploads,
downloads: data.data.totalDownloads,
completed: 0,
}
break
case 'complete':
this._progress.next(100)
this._message.next(data.data.message)
this._syncing.next(false)
if (data.data.stats) {
this.syncStats.completed = this.syncStats.total
}
break
case 'error':
this._error.next(data.data.message)
if (!data.data.continue) {
this._syncing.next(false)
}
break
}
},
error: (error) => {
this._error.next(
typeof error === 'string'
? error
: 'An error occurred during synchronization'
)
this._syncing.next(false)
},
complete: () => {
// Sync process completed successfully
console.log('Dropbox sync process completed')
},
})
} catch (error) {
console.error('Error setting up socket listeners:', error)
}
}
/**
* Get current sync statistics
*/
getSyncStats(): {
total: number
uploads: number
downloads: number
completed: number
} {
return { ...this.syncStats }
}
}