Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement resumable upload for google drive #117

Merged
merged 14 commits into from
Jan 22, 2025
Merged

Conversation

cp-pratik-k
Copy link
Collaborator

@cp-pratik-k cp-pratik-k commented Jan 13, 2025

Summary by CodeRabbit

  • New Features

    • Added pause and resume functionality for uploads
    • Introduced upload session management for Google Drive and Dropbox
    • Enhanced upload process tracking and control
  • Bug Fixes

    • Improved error handling for media uploads
    • Added more precise status tracking for upload processes
  • Documentation

    • Updated localization messages for upload statuses
    • Added error messages for Google Drive access issues
  • Chores

    • Refactored media content and process models
    • Updated API configuration constants

Copy link

coderabbitai bot commented Jan 13, 2025

Walkthrough

This pull request introduces comprehensive enhancements to media upload functionality across multiple components of the application. The changes focus on implementing session-based uploads for Google Drive and Dropbox, adding pause and resume capabilities for upload processes, and refining error handling and user feedback mechanisms. The modifications span UI components, data models, services, and localization, providing a more robust and interactive media transfer experience.

Changes

File Change Summary
app/lib/ui/flow/media_transfer/components/transfer_item.dart Added pause and resume callbacks to UploadProcessItem
app/lib/ui/flow/media_transfer/media_transfer_screen.dart Integrated pause and resume callbacks in upload list
app/lib/ui/flow/media_transfer/media_transfer_view_model.dart Added methods for pausing and resuming upload processes
data/lib/apis/dropbox/dropbox_content_endpoints.dart Introduced new endpoints for Dropbox upload sessions
data/lib/apis/google_drive/google_drive_endpoint.dart Added endpoints for Google Drive resumable uploads
data/lib/models/media_process/media_process.dart Updated process status enum, added upload session ID
data/lib/services/dropbox_services.dart Implemented session-based upload methods
data/lib/services/google_drive_service.dart Added methods for starting and appending upload sessions
app/assets/locales/app_en.arb Added new localization messages for upload statuses and errors

Sequence Diagram

sequenceDiagram
    participant User
    participant UI
    participant ViewModel
    participant Service
    participant CloudService

    User->>UI: Initiate Upload
    UI->>ViewModel: startUploadProcess
    ViewModel->>Service: uploadMedia
    Service->>CloudService: startUploadSession
    CloudService-->>Service: uploadSessionId
    Service->>Service: appendUploadSession
    Service-->>ViewModel: uploadProgress
    ViewModel-->>UI: updateUploadStatus
    
    alt Pause Upload
        User->>UI: Pause Upload
        UI->>ViewModel: onPauseUploadProcess
        ViewModel->>Service: pauseUploadProcess
        Service-->>UI: uploadPaused
    end

    alt Resume Upload
        User->>UI: Resume Upload
        UI->>ViewModel: onResumeUploadProcess
        ViewModel->>Service: resumeUploadProcess
        Service->>CloudService: continueUploadSession
        Service-->>UI: uploadResumed
    end
Loading

Possibly related PRs

Poem

🐰 Uploads dance, now smooth and light,
Pause and resume with pure delight!
Dropbox, Drive, in perfect sync,
Our rabbit code makes transfers blink! 🚀
Chunked uploads, no more delay! 🌈


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🔭 Outside diff range comments (1)
data/lib/repositories/media_process_repository.dart (1)

Line range hint 721-723: Fix incorrect method call for updating upload process status

In the catch block of _uploadInDropbox, the method updateDownloadProcessStatus is incorrectly called instead of updateUploadProcessStatus. This needs to be corrected to update the upload queue appropriately upon failure.

Apply this diff to correct the method call:

           await updateDownloadProcessStatus(
+          await updateUploadProcessStatus(
             status: MediaQueueProcessStatus.failed,
             id: process.id,
           );
🧹 Nitpick comments (6)
data/lib/models/media_process/media_process.freezed.dart (1)

731-731: Consider adding documentation for the upload_session_id field.

While the toString implementation is correct, it would be helpful to add documentation explaining the purpose and lifecycle of the upload_session_id field in the original class definition.

data/lib/repositories/media_process_repository.dart (1)

454-468: Prevent multiple resumptions of the same upload process

In resumeUploadProcess, ensure that an upload process is not started multiple times if resumeUploadProcess is called repeatedly.

Add a check to see if the process is already running before initiating the upload.

if (process.status.isUploading) {
  // The upload is already in progress.
  return;
}
data/lib/domain/config.dart (1)

22-28: LGTM! The configuration values are well-chosen.

  • uploadRequestByteSize of 256KB (262144 bytes) is a good balance between network efficiency and memory usage for resumable uploads.
  • processProgressUpdateDuration of 300ms provides smooth progress updates without overwhelming the UI.

Consider making these values configurable through environment variables or build configuration for different environments (development, production) if needed.

app/lib/ui/flow/media_transfer/media_transfer_screen.dart (1)

125-130: Add error handling for pause/resume actions

While the implementation of pause/resume callbacks is correct, consider adding error handling to gracefully handle failures during these operations.

 onResumeTap: () {
+  try {
     notifier.onResumeUploadProcess(uploadProcesses[index].id);
+  } catch (e) {
+    context.showErrorSnackBar(message: 'Failed to resume upload: $e');
+  }
 },
 onPausedTap: () {
+  try {
     notifier.onPauseUploadProcess(uploadProcesses[index].id);
+  } catch (e) {
+    context.showErrorSnackBar(message: 'Failed to pause upload: $e');
+  }
 },
data/lib/apis/google_drive/google_drive_endpoint.dart (1)

141-144: Add validation for required headers

Consider adding validation to ensure that required headers (Content-Type, Content-Length, Content-Range) are not null or empty before making the request.

 @override
 Map<String, dynamic> get headers => {
+    if (content.type.isEmpty) throw ArgumentError('Content-Type cannot be empty'),
+    if (content.length == null) throw ArgumentError('Content-Length is required'),
+    if (content.range == null) throw ArgumentError('Content-Range is required for resumable uploads'),
     'Content-Type': content.type,
     'Content-Length': content.length.toString(),
     'Content-Range': content.range,
   };
app/lib/ui/flow/media_transfer/components/transfer_item.dart (1)

141-142: Consider using localization for the pause message.

Other status messages in this method use localization (context.l10n), but the pause message is hardcoded. Consider using localization for consistency.

-      return "Upload paused";
+      return context.l10n.upload_status_paused;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6fd0c09 and 4e5551f.

📒 Files selected for processing (15)
  • app/lib/ui/flow/media_transfer/components/transfer_item.dart (4 hunks)
  • app/lib/ui/flow/media_transfer/media_transfer_screen.dart (1 hunks)
  • app/lib/ui/flow/media_transfer/media_transfer_view_model.dart (1 hunks)
  • data/.flutter-plugins-dependencies (1 hunks)
  • data/lib/apis/dropbox/dropbox_content_endpoints.dart (1 hunks)
  • data/lib/apis/google_drive/google_drive_endpoint.dart (3 hunks)
  • data/lib/domain/config.dart (1 hunks)
  • data/lib/models/media_content/media_content.dart (1 hunks)
  • data/lib/models/media_content/media_content.freezed.dart (10 hunks)
  • data/lib/models/media_process/media_process.dart (3 hunks)
  • data/lib/models/media_process/media_process.freezed.dart (14 hunks)
  • data/lib/models/media_process/media_process.g.dart (3 hunks)
  • data/lib/repositories/media_process_repository.dart (12 hunks)
  • data/lib/services/dropbox_services.dart (5 hunks)
  • data/lib/services/google_drive_service.dart (6 hunks)
✅ Files skipped from review due to trivial changes (1)
  • data/.flutter-plugins-dependencies
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (21)
data/lib/models/media_process/media_process.freezed.dart (2)

417-417: LGTM! The upload_session_id field is properly integrated.

The new optional String? field is correctly implemented across all necessary locations in the Freezed-generated code, including:

  • Field declarations
  • Constructor parameters
  • copyWith method
  • toString, equality, and hashCode implementations

Also applies to: 450-450, 509-512, 682-682, 705-706, 731-731, 746-747, 771-771, 804-804, 827-828


417-417: Verify session ID persistence across app restarts.

Since upload_session_id is used for resumable uploads, ensure that it's properly persisted and can survive app restarts to maintain upload progress.

Also applies to: 705-706

✅ Verification successful

Upload session ID is properly persisted for resumable uploads

The implementation uses SQLite database storage which ensures upload_session_id persists across app restarts. The session ID is stored in a dedicated column in the upload_queue_table and is properly managed through the updateUploadSessionId method. This allows for reliable resumption of uploads from their last position.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if upload_session_id is being persisted in any storage implementations
rg -A 5 "upload_session_id.*persist|persist.*upload_session_id"

# Check if there are any database schema or migration files
fd -e sql -e dart ".*schema.*|.*migration.*"

Length of output: 112


Script:

#!/bin/bash
# Find the original MediaProcess class file
fd -e dart "media_process" --exclude "*.freezed.dart" --exclude "*.g.dart"

# Search for all occurrences of upload_session_id
rg -l "upload_session_id"

# Look for storage/persistence related files
fd -e dart "storage|repository|dao|database"

Length of output: 509


Script:

#!/bin/bash
# Check the repository implementation
cat data/lib/repositories/media_process_repository.dart

# Look for any persistence-related code in the model
cat data/lib/models/media_process/media_process.dart

Length of output: 35603

data/lib/apis/dropbox/dropbox_content_endpoints.dart (3)

164-199: Well-structured implementation of DropboxStartUploadEndpoint

The DropboxStartUploadEndpoint class is well-implemented, properly setting up the initial upload session with correct headers and data handling.


200-245: Effective implementation of DropboxAppendUploadEndpoint

The DropboxAppendUploadEndpoint class efficiently handles appending data to an ongoing upload session, with appropriate management of headers and content.


246-312: Comprehensive implementation of DropboxFinishUploadEndpoint

Apart from the minor issue with the endpoint path, the DropboxFinishUploadEndpoint class is thoroughly implemented, correctly finalizing the upload session with proper handling of commit details and cursor information.

data/lib/services/google_drive_service.dart (1)

220-220: Correct property name for content type

Ensure consistency in property naming. The AppMediaContent class uses type as the property, but if any part of the code still references contentType, it should be updated to type to prevent errors.

Run the following script to verify all usages of contentType:

✅ Verification successful

Property naming is correct and consistent

The codebase maintains proper separation between HTTP-standard contentType in the network layer and type in application models. The mapping between these conventions is handled correctly in media_content.dart. No changes needed.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all references to 'contentType' in the codebase.

# Search for 'contentType' in Dart files
rg --glob '*.dart' 'contentType'

Length of output: 702

data/lib/models/media_content/media_content.dart (2)

11-12: LGTM! The changes support resumable uploads.

The addition of the range property and renaming of contentType to type align well with implementing resumable uploads. The range property will be useful for specifying byte ranges during resumable uploads.


18-18: Verify type compatibility with Google Drive API.

The factory method correctly maps Google Drive's contentType to the new type property.

Let's verify the type compatibility:

✅ Verification successful

Type compatibility verified successfully

The contentType from Google Drive API is correctly mapped to the type property, as both represent MIME types and are used consistently in Content-Type headers across the API implementations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any other usages of media.contentType to ensure consistency
rg "media\.contentType" --type dart

Length of output: 119


Script:

#!/bin/bash
# Find MediaContent class definition and type property
ast-grep --pattern 'class MediaContent {
  $$$
}'

# Search for type-related definitions
rg "(?i)content.*type" --type dart -A 3

# Look for Google Drive API related files
fd "drive.*\.dart$"

Length of output: 4871

data/lib/models/media_process/media_process.dart (3)

20-20: LGTM! The paused status supports resumable uploads.

The addition of the paused status is essential for implementing resumable uploads.


37-37: LGTM! The isPaused getter is well-implemented.

The getter provides a clean way to check for paused status.


125-125: Verify session ID persistence.

The upload_session_id is crucial for resumable uploads. Ensure it's properly persisted and recovered after app restarts.

Let's check the persistence implementation:

✅ Verification successful

Upload session ID persistence implementation verified.

The upload_session_id is properly persisted in the SQLite database and recovered through JSON serialization, ensuring it survives app restarts for resumable uploads.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for upload_session_id usage in database operations
rg "upload_session_id" --type dart -A 5

# Search for session-related operations
ast-grep --pattern 'class $_ {
  $$$
  upload_session_id
  $$$
}'

Length of output: 12168

data/lib/models/media_process/media_process.g.dart (2)

56-56: LGTM: Status enum updated for pause functionality

The addition of MediaQueueProcessStatus.paused status is correctly implemented and aligns with the resumable upload feature.


67-67: LGTM: Upload session tracking implemented

The upload_session_id field is properly integrated into both JSON serialization and deserialization methods, enabling proper tracking of resumable upload sessions.

Also applies to: 91-91

data/lib/models/media_content/media_content.freezed.dart (1)

21-22: LGTM: Media content model enhanced for resumable uploads

The model has been correctly updated to support resumable uploads:

  • Replaced contentType with a more specific type field
  • Added optional range field for tracking upload progress
  • All related methods are properly updated

Also applies to: 147-149

data/lib/apis/google_drive/google_drive_endpoint.dart (2)

87-115: LGTM: Start upload endpoint implementation

The GoogleDriveStartUploadEndpoint correctly implements the resumable upload initiation as per Google Drive API specifications.


117-160: Verify Content-Range header format

The GoogleDriveAppendUploadEndpoint implementation looks good, but ensure that the Content-Range header follows the format specified by Google Drive API:

  • For first chunk: bytes 0-524287/2000000
  • For final chunk: bytes 524288-2000000/2000000
✅ Verification successful

Content-Range header format is correctly implemented

The Content-Range header follows the required Google Drive API format. The implementation in google_drive_service.dart correctly generates the range as bytes start-end/total with proper zero-based byte ranges.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if Content-Range header format matches Google Drive API specs
ast-grep --pattern 'content.range = "bytes $_-$_/$_"'

Length of output: 55


Script:

#!/bin/bash
# Find AppMediaContent class definition and implementation
ast-grep --pattern 'class AppMediaContent {
  $$$
}'

# Also search for any mentions of range property or method
rg -A 5 'range' --type dart

Length of output: 9948

app/lib/ui/flow/media_transfer/components/transfer_item.dart (3)

14-15: LGTM! Well-structured implementation of pause/resume callbacks.

The addition of onPausedTap and onResumeTap callbacks provides the necessary control for implementing resumable uploads.

Also applies to: 22-23


93-101: LGTM! Clean implementation of the pause button.

The pause button is appropriately shown only when the upload is running, using platform-specific icons.


84-92: LGTM! Well-designed state-specific resume buttons.

The implementation thoughtfully uses different icons based on the upload state:

  • Play icon for paused uploads
  • Refresh icon for terminated/failed uploads
    This provides clear visual feedback to users about the available actions.

Also applies to: 113-121

data/lib/services/dropbox_services.dart (2)

295-295: LGTM! Consistent renaming of content type property.

The change from contentType to type is applied consistently across all AppMediaContent instantiations.

Also applies to: 348-349, 389-390, 409-410, 539-539, 564-564, 598-598


328-361: LGTM! Robust implementation of upload session initialization.

The implementation includes:

  • File existence validation
  • Proper error handling
  • Clear session ID return for subsequent operations

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
data/lib/services/google_drive_service.dart (1)

256-258: ⚠️ Potential issue

Add null safety checks when parsing headers

The code is vulnerable to null pointer exceptions when accessing the 'location' header and parsing the URI.

Apply this diff to enhance null safety:

-      return Uri.parse(res.headers.value('location')!)
-          .queryParameters['upload_id']!;
+      final locationHeader = res.headers.value('location');
+      if (locationHeader == null) {
+        throw SomethingWentWrongError(
+          message: 'Missing Location header in response',
+          statusCode: res.statusCode,
+        );
+      }
+      final uploadId = Uri.parse(locationHeader).queryParameters['upload_id'];
+      if (uploadId == null) {
+        throw SomethingWentWrongError(
+          message: 'Missing upload_id in Location header',
+          statusCode: res.statusCode,
+        );
+      }
+      return uploadId;
data/lib/apis/dropbox/dropbox_content_endpoints.dart (1)

281-281: ⚠️ Potential issue

Fix missing leading slash in the endpoint path

The path property is missing a leading slash. This could lead to incorrect API endpoint resolution.

Apply this diff to correct the path:

-  String get path => '/files/upload_session/finish';
+  String get path => '/files/upload_session/finish';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e5551f and f6fc9ac.

📒 Files selected for processing (4)
  • data/lib/apis/dropbox/dropbox_content_endpoints.dart (2 hunks)
  • data/lib/repositories/media_process_repository.dart (21 hunks)
  • data/lib/services/dropbox_services.dart (5 hunks)
  • data/lib/services/google_drive_service.dart (7 hunks)
🧰 Additional context used
🪛 GitHub Actions: Analyze
data/lib/repositories/media_process_repository.dart

[info] 705-705: Missing a required trailing comma. Try adding a trailing comma.

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (13)
data/lib/services/google_drive_service.dart (3)

95-95: LGTM! Good separation of concerns

The query modification correctly excludes album files from media listing, maintaining a clean separation between media and album management.


220-220: LGTM! Consistent parameter naming

The parameter rename from contentType to type is applied consistently across all methods, improving code consistency.

Also applies to: 459-459, 485-485, 562-562, 636-636


267-315: LGTM! Robust implementation of appendUploadSession

The implementation includes:

  • Proper file existence validation
  • Correct byte range handling
  • Comprehensive error handling including resume status (308)
data/lib/apis/dropbox/dropbox_content_endpoints.dart (3)

150-150: LGTM! Property name change from contentType to type

The change aligns with the updated AppMediaContent model.


164-197: LGTM! Well-structured upload session initialization endpoint

The implementation follows the endpoint pattern consistently and includes all necessary configurations for starting an upload session.


199-242: LGTM! Well-implemented append operation endpoint

The implementation correctly handles session continuation with proper offset tracking and content streaming.

data/lib/services/dropbox_services.dart (4)

295-295: LGTM! Consistent property name updates

The changes from contentType to type are applied consistently across all occurrences.

Also applies to: 561-561, 586-586, 620-620


328-364: LGTM! Well-implemented upload session initialization

The method includes proper error handling and file existence validation.


366-403: LGTM! Well-structured append operation

The method follows consistent error handling patterns and properly manages upload session continuation.


405-450: LGTM! Well-implemented session finalization

The method properly handles session completion with appropriate error handling.

data/lib/repositories/media_process_repository.dart (3)

15-15: LGTM! Consistent integration of UniqueIdGenerator

The UniqueIdGenerator is properly injected and used consistently across the repository.

Also applies to: 31-31, 81-81, 246-246, 298-298, 388-388


141-141: LGTM! Database schema update for session management

The upload_session_id column is properly added to support resumable uploads.


463-490: LGTM! Well-implemented pause/resume functionality

The implementation properly handles upload process state transitions between paused and resumed states.

data/lib/repositories/media_process_repository.dart Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
data/lib/repositories/media_process_repository.dart (3)

463-473: 🛠️ Refactor suggestion

Implement request cancellation in pauseUploadProcess.

The pauseUploadProcess method only updates the status but doesn't cancel ongoing upload requests. This could lead to unnecessary network usage.


626-628: ⚠️ Potential issue

Fix incorrect method call in error handling.

The error handling block incorrectly calls updateDownloadProcessStatus instead of updateUploadProcessStatus.


705-705: ⚠️ Potential issue

Add missing trailing comma.

A trailing comma is required for consistent formatting.

🧰 Tools
🪛 GitHub Actions: Analyze

[info] 705-705: Missing a required trailing comma. Try adding a trailing comma.

🧹 Nitpick comments (3)
data/lib/repositories/media_process_repository.dart (3)

15-15: LGTM! Consider adding documentation for the new dependency.

The addition of UniqueIdGenerator as a dependency is a good practice for better testability and maintainability.

Consider adding a documentation comment explaining the purpose of _uniqueIdGenerator:

/// Generator for creating unique identifiers used in media processes
final UniqueIdGenerator _uniqueIdGenerator;

Also applies to: 81-81, 109-109


141-141: Consider adding an index for the upload_session_id column.

Since upload_session_id will be frequently queried during upload operations, adding an index could improve query performance.

CREATE INDEX idx_upload_session_id ON ${LocalDatabaseConstants.uploadQueueTable} (upload_session_id);

571-624: Extract common upload functionality into a shared method.

The upload implementations for Google Drive and Dropbox share similar logic for chunking, progress tracking, and notifications. Consider extracting these into a shared method to reduce code duplication.

Future<void> _handleChunkedUpload({
  required UploadMediaProcess process,
  required Future<void> Function(int startByte, int endByte) uploadChunk,
  required Future<void> Function(String message) showNotification,
  required AppMedia? response,
}) async {
  int chunk = process.chunk;
  final total = await File(process.path).length();

  while (chunk < total) {
    if (process.status.isTerminated || process.status.isPaused) {
      showNotification(process.status.isTerminated ? 'Upload Cancelled' : 'Upload Paused');
      break;
    }

    final end = (chunk + ApiConfigs.uploadRequestByteSize).clamp(0, total);
    await uploadChunk(chunk, end);
    chunk = end;

    await updateUploadProcessProgress(
      id: process.id,
      chunk: chunk,
      total: total,
    );

    await showNotification(
      '${chunk.formatBytes} / ${total.formatBytes} - ${(chunk / total * 100).round()}%',
    );
  }

  if (chunk >= total) {
    await updateUploadProcessStatus(
      status: MediaQueueProcessStatus.completed,
      response: response,
      id: process.id,
    );
    await showNotification('Upload completed successfully');
    await clearUploadProcessResponse(id: process.id);
  }
}

Also applies to: 664-749

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f6fc9ac and f54b014.

📒 Files selected for processing (1)
  • data/lib/repositories/media_process_repository.dart (21 hunks)
🧰 Additional context used
🪛 GitHub Actions: Analyze
data/lib/repositories/media_process_repository.dart

[info] 705-705: Missing a required trailing comma. Try adding a trailing comma.

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
data/lib/repositories/media_process_repository.dart (2)

111-111: Consider adding error handling for database initialization.

The database initialization is called directly in the constructor without any error handling. If the initialization fails, it could leave the repository in an invalid state.

Consider wrapping the initialization in a try-catch block and handling potential database errors:

-    _initializeLocalDatabase();
+    try {
+      await _initializeLocalDatabase();
+    } catch (e) {
+      debugPrint('Failed to initialize database: $e');
+      // Consider throwing a custom exception or handling the error appropriately
+    }

557-624: Add cleanup for abandoned upload sessions.

When an upload is terminated or fails, the upload session is not cleaned up on Google Drive's side. This could lead to orphaned upload sessions.

Consider adding cleanup logic:

   if (process.status.isTerminated) {
+    try {
+      await _googleDriveService.deleteUploadSession(uploadSessionId);
+    } catch (e) {
+      debugPrint('Failed to cleanup upload session: $e');
+    }
     showNotification('Upload to Google Drive Cancelled');
     break;
   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f54b014 and 7b44127.

📒 Files selected for processing (1)
  • data/lib/repositories/media_process_repository.dart (21 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (2)
data/lib/repositories/media_process_repository.dart (2)

591-596: Implement cancellation support during upload.

The appendUploadSession call should support cancellation to prevent unnecessary data transfer when the upload is paused or terminated.


Line range hint 124-164: Consider implementing database migration strategy.

The database schema has been updated with a new column upload_session_id, but there's no migration strategy for existing installations. This could cause issues for users upgrading from a previous version.

Consider implementing a version-based migration strategy. Also, verify if there are any existing installations that need migration:

data/lib/repositories/media_process_repository.dart Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (4)
data/lib/repositories/media_process_repository.dart (4)

477-491: 🛠️ Refactor suggestion

Improve resumeUploadProcess implementation.

The method has several potential issues:

  1. No status update before resuming the upload
  2. No validation of current status
  3. Potential race condition when accessing the process

Consider this improved implementation:

   Future<void> resumeUploadProcess(String id) async {
+    bool updated = false;
+    await database.transaction((txn) async {
+      final result = await txn.rawUpdate(
+        "UPDATE ${LocalDatabaseConstants.uploadQueueTable} SET status = ? WHERE id = ? AND status = ?",
+        [MediaQueueProcessStatus.waiting.value, id, MediaQueueProcessStatus.paused.value],
+      );
+      updated = result > 0;
+    });
+
+    if (!updated) return;
+
     final process = _uploadQueue.firstWhere(
       (element) => element.id == id,
     );

     if (process.provider == MediaProvider.googleDrive) {
       _uploadInGoogleDrive(process);
     } else if (process.provider == MediaProvider.dropbox) {
       _uploadInDropbox(process);
     } else {
       updateUploadProcessStatus(
         status: MediaQueueProcessStatus.failed,
         id: process.id,
       );
     }
   }

592-597: 🛠️ Refactor suggestion

Implement cancellation support during upload.

The appendUploadSession call should support cancellation to properly handle paused or terminated uploads.

Add a CancelToken to cancel ongoing requests when the upload is paused or terminated:

+    final cancelToken = CancelToken();
     final res = await _googleDriveService.appendUploadSession(
       startByte: chunk,
       endByte: end,
       uploadId: uploadSessionId,
       path: process.path,
+      cancelToken: cancelToken,
     );

And cancel it when needed:

     if (process.status.isTerminated) {
+      cancelToken.cancel();
       showNotification('Upload to Google Drive Cancelled');
       break;
     } else if (process.status.isPaused) {
+      cancelToken.cancel();
       showNotification('Upload to Google Drive Paused');
       break;
     }

734-743: 🛠️ Refactor suggestion

Add retry limit to prevent infinite loop.

The upload session restart logic could potentially loop indefinitely if there are persistent failures.

Add a retry limit and exponential backoff:

   if (chunk != 0) {
+    static const maxRetries = 3;
+    int retryCount = 0;
+    while (retryCount < maxRetries) {
       await updateUploadProcessProgress(
         id: process.id,
         chunk: 0,
         total: total,
       );
       process = _uploadQueue.firstWhere((e) => e.id == process.id);
-      _uploadInDropbox(process);
-      return;
+      try {
+        await _uploadInDropbox(process);
+        return;
+      } catch (e) {
+        retryCount++;
+        if (retryCount >= maxRetries) throw e;
+        await Future.delayed(Duration(seconds: math.pow(2, retryCount).toInt()));
+      }
+    }
   }

637-639: ⚠️ Potential issue

Fix incorrect method call for updating upload process status.

The catch block incorrectly calls updateDownloadProcessStatus instead of updateUploadProcessStatus.

Apply this fix:

-          await updateDownloadProcessStatus(
+          await updateUploadProcessStatus(
             status: MediaQueueProcessStatus.failed,
             id: process.id,
           );
🧹 Nitpick comments (4)
app/lib/ui/flow/media_transfer/components/transfer_item.dart (3)

14-23: Maintain consistent naming convention for callback functions

The callback naming is inconsistent: onPausedTap vs onResumeTap. One uses past tense while the other doesn't.

Consider using consistent naming:

-  final void Function() onPausedTap;
+  final void Function() onPauseTap;
   final void Function() onResumeTap;

84-92: Maintain consistent icon usage for resume action

The resume action uses different icons depending on the state:

  • Play icon (CupertinoIcons.play) for paused state
  • Refresh icon (CupertinoIcons.refresh) for failed state

This might confuse users as both actions resume the upload.

Consider using the same icon for consistency:

// For failed state
-                CupertinoIcons.refresh,
+                CupertinoIcons.play,

Also applies to: 113-121


88-90: Maintain consistent color scheme for action buttons

The resume action uses different colors depending on the state:

  • textPrimary for paused state
  • textSecondary for failed state

Consider using the same color for consistency:

// For failed state
-                color: context.colorScheme.textSecondary,
+                color: context.colorScheme.textPrimary,

Also applies to: 117-119

data/lib/repositories/media_process_repository.dart (1)

417-426: Add validation for uploadSessionId parameter.

The updateUploadSessionId method doesn't validate the uploadSessionId parameter. A null or empty session ID could cause issues.

Consider adding parameter validation:

   Future<void> updateUploadSessionId({
     required String id,
     required String uploadSessionId,
   }) async {
+    if (uploadSessionId.isEmpty) {
+      throw ArgumentError.value(uploadSessionId, 'uploadSessionId', 'Upload session ID cannot be empty');
+    }
+
     await database.rawUpdate(
       "UPDATE ${LocalDatabaseConstants.uploadQueueTable} SET upload_session_id = ? WHERE id = ?",
       [uploadSessionId, id],
     );
     await updateQueue(database);
   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d22e8cb and 3581f68.

📒 Files selected for processing (4)
  • .github/workflows/ios_deploy.yml (1 hunks)
  • app/lib/ui/flow/media_transfer/components/transfer_item.dart (4 hunks)
  • data/lib/domain/config.dart (1 hunks)
  • data/lib/repositories/media_process_repository.dart (21 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • data/lib/domain/config.dart
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: ios_deploy
  • GitHub Check: build
🔇 Additional comments (1)
app/lib/ui/flow/media_transfer/components/transfer_item.dart (1)

Line range hint 84-121: Well-implemented pause/resume functionality

The implementation provides clear visual feedback with appropriate controls for different upload states. The conditional rendering ensures users see relevant actions based on the current state of the upload.

.github/workflows/ios_deploy.yml Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
data/lib/errors/app_error.dart (1)

25-28: Avoid hardcoding API domains.

The Google Drive API domain is hardcoded. Consider moving it to a constant or configuration file for better maintainability.

+ // At the top of the file
+ const googleApisHost = 'www.googleapis.com';

- if (error.response?.statusCode == 403 &&
-     error.requestOptions.uri.host == 'www.googleapis.com') {
+ if (error.response?.statusCode == 403 &&
+     error.requestOptions.uri.host == googleApisHost) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3581f68 and d8952a4.

📒 Files selected for processing (4)
  • app/assets/locales/app_en.arb (1 hunks)
  • app/lib/domain/extensions/app_error_extensions.dart (1 hunks)
  • data/lib/errors/app_error.dart (3 hunks)
  • data/lib/errors/l10n_error_codes.dart (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • data/lib/errors/l10n_error_codes.dart
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: ios_deploy
  • GitHub Check: build
  • GitHub Check: analyze
🔇 Additional comments (4)
app/lib/domain/extensions/app_error_extensions.dart (1)

24-25: LGTM! Error handling properly integrated.

The new error case for NoGoogleDriveAccessError is well-integrated with the existing error handling pattern and localization system.

data/lib/errors/app_error.dart (2)

80-87: LGTM! Well-structured error class.

The NoGoogleDriveAccessError class is well-implemented with a clear error message and proper integration with the localization system.


21-22: Consider more specific PlatformException handling.

While consolidating network error handling is good, the current implementation might be too broad. Consider checking additional properties of PlatformException to ensure it's specifically a network error.

Run this script to analyze other potential PlatformException codes in the codebase:

app/assets/locales/app_en.arb (1)

43-43: LGTM! Clear and actionable error message.

The localization message is well-written, clearly explains the issue, and provides actionable steps for users to resolve it.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
app/lib/ui/flow/accounts/accounts_screen.dart (2)

144-158: Add error handling and loading state for sign-out operation.

While the async implementation and mounted check are good, consider these improvements:

  1. Add error handling in case sign-out fails
  2. Show loading state during the sign-out process

Consider this implementation:

 onPressed: () async {
+  try {
+    notifier.setLoading(true);
     await notifier.signOutWithGoogle();
     if (context.mounted) {
       showSnackBar(
         context: context,
         text: context.l10n.successfully_sign_out_from_google_drive,
         icon: SvgPicture.asset(
           Assets.images.icGoogleDrive,
           height: 22,
           width: 22,
         ),
       );
     }
+  } catch (e) {
+    if (context.mounted) {
+      showErrorSnackBar(context: context, error: e.toString());
+    }
+  } finally {
+    if (context.mounted) {
+      notifier.setLoading(false);
+    }
+  }
 },

233-246: Extract common sign-out logic to reduce duplication.

The sign-out implementation is duplicated between Google Drive and Dropbox. Consider extracting this into a reusable method.

Here's a suggested implementation:

+ Future<void> _handleSignOut({
+   required BuildContext context,
+   required Future<void> Function() signOutAction,
+   required String successMessage,
+   required String iconAsset,
+ }) async {
+   try {
+     notifier.setLoading(true);
+     await signOutAction();
+     if (context.mounted) {
+       showSnackBar(
+         context: context,
+         text: successMessage,
+         icon: SvgPicture.asset(
+           iconAsset,
+           height: 22,
+           width: 22,
+         ),
+       );
+     }
+   } catch (e) {
+     if (context.mounted) {
+       showErrorSnackBar(context: context, error: e.toString());
+     }
+   } finally {
+     if (context.mounted) {
+       notifier.setLoading(false);
+     }
+   }
+ }

// Usage in Google Drive sign-out
onPressed: () => _handleSignOut(
  context: context,
  signOutAction: notifier.signOutWithGoogle,
  successMessage: context.l10n.successfully_sign_out_from_google_drive,
  iconAsset: Assets.images.icGoogleDrive,
),

// Usage in Dropbox sign-out
onPressed: () => _handleSignOut(
  context: context,
  signOutAction: notifier.signOutWithDropbox,
  successMessage: context.l10n.successfully_sign_out_from_dropbox,
  iconAsset: Assets.images.icDropbox,
),
app/lib/ui/flow/media_transfer/components/transfer_item.dart (2)

14-15: Maintain consistent naming convention for callback parameters.

The callback parameter names have inconsistent tense usage:

  • onPausedTap uses past tense
  • onResumeTap uses base form

Apply this diff to maintain consistency:

-  final void Function() onPausedTap;
+  final void Function() onPauseTap;
   final void Function() onResumeTap;

   const UploadProcessItem({
     super.key,
     required this.process,
     required this.onCancelTap,
     required this.onRemoveTap,
-    required this.onPausedTap,
+    required this.onPauseTap,
     required this.onResumeTap,
   });

Also applies to: 22-23


Line range hint 153-251: Consider adding pause/resume functionality to downloads for consistency.

While uploads now support pause/resume functionality, downloads don't have similar controls. This might create an inconsistent user experience, as users would expect similar controls for both operations.

Consider extending the pause/resume functionality to the DownloadProcessItem class to maintain feature parity between upload and download operations.

Would you like me to help implement the pause/resume functionality for downloads?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8952a4 and 5ea77ac.

📒 Files selected for processing (3)
  • app/assets/locales/app_en.arb (2 hunks)
  • app/lib/ui/flow/accounts/accounts_screen.dart (2 hunks)
  • app/lib/ui/flow/media_transfer/components/transfer_item.dart (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/assets/locales/app_en.arb
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (2)
app/lib/ui/flow/media_transfer/components/transfer_item.dart (2)

84-92: Well-implemented UI controls for pause/resume functionality!

The implementation shows good attention to detail:

  • Appropriate button states for running, paused, and failed states
  • Consistent styling with existing UI elements
  • Logical button ordering and visibility conditions

Also applies to: 93-101, 102-104, 113-121


141-142: Good job implementing the paused state message with localization!

The implementation properly follows the existing pattern and addresses the previous review comment about using localization for consistency.

@cp-pratik-k cp-pratik-k merged commit 3117a21 into main Jan 22, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant