Skip to content

Commit

Permalink
Merge pull request #95 from internxt/fix/show-full-storage-alert
Browse files Browse the repository at this point in the history
Fix/show full storage alert
  • Loading branch information
patricioxavier8 authored Sep 5, 2024
2 parents 855c2b1 + 034e727 commit a319392
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 30 deletions.
2 changes: 2 additions & 0 deletions InternxtDesktop.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
AAC188EC2C222F3A007E321A /* BackupProgressUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E56C212BC0423A00DBA0BF /* BackupProgressUpdate.swift */; };
AAC188EF2C2231B1007E321A /* MockBackupRealm.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC188EE2C2231B1007E321A /* MockBackupRealm.swift */; };
AAC188F12C2231E7007E321A /* MockBackupUploadService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC188F02C2231E7007E321A /* MockBackupUploadService.swift */; };
AAE7975C2C875F5000AEFDEF /* URLDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E16310562A8E66F70072989E /* URLDictionary.swift */; };
AAED64A12C23E85000CD971B /* JWTDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAED64A02C23E85000CD971B /* JWTDecoder.swift */; };
AAED64A22C23E93300CD971B /* JWTDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAED64A02C23E85000CD971B /* JWTDecoder.swift */; };
AAED64A32C23E93400CD971B /* JWTDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAED64A02C23E85000CD971B /* JWTDecoder.swift */; };
Expand Down Expand Up @@ -1462,6 +1463,7 @@
E1FD8C752BA989D900C257E0 /* LogService.swift in Sources */,
3120B5692B84F47700F524F1 /* Error.swift in Sources */,
3120B5682B84F46800F524F1 /* DecryptUtils.swift in Sources */,
AAE7975C2C875F5000AEFDEF /* URLDictionary.swift in Sources */,
3120B5672B84F45600F524F1 /* Device.swift in Sources */,
E15F8C432B7669F600C60EFA /* URL.swift in Sources */,
E15F8C412B762EFF00C60EFA /* BackupTreeNodeSyncStatus.swift in Sources */,
Expand Down
38 changes: 38 additions & 0 deletions InternxtDesktop/Colors.xcassets/TextRed.colorset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0x0D",
"red" : "0xFF"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x33",
"green" : "0x3D",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
8 changes: 7 additions & 1 deletion InternxtDesktop/Services/UsageManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class UsageManager: ObservableObject {
@Published var limit: Int64 = 1
@Published var driveUsage: Int64 = 0
@Published var backupsUsage: Int64 = 0

let storageThresholdPercentage: Int64 = 99
public func getUsedPercentage() -> String {
let totalUsed = driveUsage + backupsUsage
let percentage = (totalUsed * 100) / limit
Expand Down Expand Up @@ -97,4 +97,10 @@ class UsageManager: ObservableObject {
let suffix = suffixes[Int(i)]
return (numberString, suffix)
}

public func isStorageAlmostFull() -> Bool {
let totalUsed = driveUsage + backupsUsage
let percentage = (totalUsed * 100) / limit
return percentage >= storageThresholdPercentage
}
}
42 changes: 36 additions & 6 deletions InternxtDesktop/Views/Settings/AccountUsageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct AccountUsageView: View {
HStack(alignment: .center,spacing: 8) {
ProgressView().progressViewStyle(.circular).controlSize(.small)
AppText("USAGE_LOADING").font(.BaseMedium)
.foregroundColor(.Gray50)
.foregroundColor(.Gray50)
}.frame(height: 140)
} else {
HStack(alignment: .center, spacing: 0) {
Expand All @@ -40,6 +40,37 @@ struct AccountUsageView: View {
UsageLegendItem(label: "Drive", color: .Primary)
UsageLegendItem(label: "Backups", color: .Indigo)
}
if UsageManager.shared.isStorageAlmostFull(){
VStack(alignment: .leading, spacing: 8) {
HStack {
VStack{
Image(systemName: "exclamationmark.circle.fill")
.foregroundColor(.red)
.font(.system(size: 20))
Spacer()
Spacer()
}

VStack(alignment: .leading, spacing: 2) {
AppText("ACCOUNT_SETTINGS_BANNER_STORAGE_FULL_TITLE").font(.BaseSemibold)
.foregroundColor(.TextRed)

AppText("ACCOUNT_SETTINGS_BANNER_STORAGE_FULL_MESSAGE").font(.SMRegular)
.foregroundColor(.TextRed)
.lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
}
}
}
.padding(12)
.background(Color("TextRed").opacity(0.05))
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color("TextRed").opacity(0.50), lineWidth: 1)
)
.padding(.top,20)
}
}

}
Expand Down Expand Up @@ -123,18 +154,17 @@ struct AccountUsageView: View {
return Rectangle()
.fill(Color("Primary"))
.frame(maxWidth: CGFloat(getWidth()), maxHeight: .infinity)


.overlay(Rectangle()
.frame(width: 2, height: nil, alignment: .trailing).foregroundColor(Color("Surface")), alignment: .trailing)

}

func handleOpenUpgradePlan() {
URLDictionary.UPGRADE_PLAN.open()
}



}

struct AccountUsageView_Previews: PreviewProvider {
Expand Down
1 change: 1 addition & 0 deletions Shared/Extensions/Color.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ extension Color {
static let Secondary = Color("Secondary")
static let Surface = Color("Surface")
static let Red = Color("Red")
static let TextRed = Color("TextRed")
}
15 changes: 15 additions & 0 deletions Shared/Extensions/Error.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,18 @@ extension Error {
SentrySDK.capture(error: self)
}
}

extension NSAlert {
static func showStorageFullAlert() {
let alert = NSAlert()
alert.messageText = NSLocalizedString("ALERT_STORAGE_TITLE", comment: "")
alert.informativeText = NSLocalizedString("ALERT_STORAGE_SUBTITLE", comment: "")
alert.alertStyle = .warning
alert.addButton(withTitle: NSLocalizedString("ALERT_STORAGE_BUTTON_TITLE", comment: ""))
alert.addButton(withTitle: NSLocalizedString("COMMON_CANCEL", comment: ""))
let response = alert.runModal()
if response == .alertFirstButtonReturn {
URLDictionary.UPGRADE_PLAN.open()
}
}
}
14 changes: 13 additions & 1 deletion Shared/Services/Backups/BackupsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Foundation
import RealmSwift
import InternxtSwiftCore
import Combine
import AppKit


struct ItemBackup: Identifiable{
Expand All @@ -35,6 +36,7 @@ enum BackupError: Error {
case deviceHasNoName
case invalidDownloadURL
case missingNetworkAuth
case storageFull
}

enum BackupDevicesFetchingStatus {
Expand Down Expand Up @@ -417,7 +419,11 @@ class BackupsService: ObservableObject {

xpcBackupService.uploadDeviceBackup(backupAt: urlsStrings,networkAuth: networkAuth,deviceId: currentDevice.id, bucketId: bucketId, with: { response, error in
if let error = error {
self.propagateError(errorMessage: error)
if error == "storageFull"{
self.showAlert()
}else {
self.propagateError(errorMessage: error)
}
} else {
self.propagateUploadSuccess()
}
Expand Down Expand Up @@ -722,6 +728,12 @@ class BackupsService: ObservableObject {
backupsItemsInprogress.remove(at: index)
}
}

private func showAlert() {
DispatchQueue.main.async {
NSAlert.showStorageFullAlert()
}
}

}

Expand Down
11 changes: 11 additions & 0 deletions Shared/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@
"BACKUP_SETTINGS_ADD_FOLDERS" = "Click + to select the folders\nyou want to back up";
"BACKUP_SETTINGS_ALREADY_ADDED_FOLDER" = "This folder is already selected";

// Account Settings

"ACCOUNT_SETTINGS_BANNER_STORAGE_FULL_TITLE" = "Your storage is full";
"ACCOUNT_SETTINGS_BANNER_STORAGE_FULL_MESSAGE" = "You can't upload, sync or backup files. Upgrade now your plan or remove files to save up space.";


// Common
Expand Down Expand Up @@ -170,3 +174,10 @@
"SHARE_FEEDBACK_ACTION" = "Send feedback";
"SHARE_FEEDBACK_SUCCESS_TITLE" = "Thank you for sharing your feedback";
"SHARE_FEEDBACK_SUCCESS_SUBTITLE" = "We really appreciate your time and effort to help us improve our services.";

// Alert Full Storage
"ALERT_STORAGE_TITLE" = "Your storage is full";
"ALERT_STORAGE_SUBTITLE" = "You can’t upload, sync or backup files";
"ALERT_STORAGE_BUTTON_TITLE" = "Upgrade now";


10 changes: 10 additions & 0 deletions Shared/es.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@
"BACKUP_SETTINGS_ADD_FOLDERS" = "Haga clic en + para seleccionar las carpetas\nde las que desea hacer una copia de seguridad.";
"BACKUP_SETTINGS_ALREADY_ADDED_FOLDER" = "Esta carpeta ya se encuentra seleccionada";

// Account Settings

"ACCOUNT_SETTINGS_BANNER_STORAGE_FULL_TITLE" = "Tu almacenamiento está lleno";
"ACCOUNT_SETTINGS_BANNER_STORAGE_FULL_MESSAGE" = "No puedes cargar, sincronizar ni hacer copias de seguridad de archivos. Actualiza ahora tu plan o elimina archivos para ahorrar espacio.";

// Common
"COMMON_UPGRADE" = "Comprar espacio";
"COMMON_LOGOUT" = "Cerrar sesión";
Expand Down Expand Up @@ -170,3 +175,8 @@
"SHARE_FEEDBACK_ACTION" = "Enviar comentarios";
"SHARE_FEEDBACK_SUCCESS_TITLE" = "Gracias por compartir tus comentarios";
"SHARE_FEEDBACK_SUCCESS_SUBTITLE" = "Agradecemos mucho tu tiempo y esfuerzo para ayudarnos a mejorar nuestros servicios";

// Alert Full Storage
"ALERT_STORAGE_TITLE" = "Tu almacenamiento está lleno";
"ALERT_STORAGE_SUBTITLE" = "No puedes cargar, sincronizar ni realizar copias de seguridad de archivos";
"ALERT_STORAGE_BUTTON_TITLE" = "Actualizar ahora";
9 changes: 9 additions & 0 deletions Shared/fr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@
"BACKUP_MISSING_FOLDER_ERROR" = "Non trouvé";
"BACKUP_SETTINGS_ALREADY_ADDED_FOLDER" = "Ce dossier est déjà sélectionné";

// Account Settings

"ACCOUNT_SETTINGS_BANNER_STORAGE_FULL_TITLE" = "Votre stockage est plein";
"ACCOUNT_SETTINGS_BANNER_STORAGE_FULL_MESSAGE" = "Vous ne pouvez pas télécharger, synchroniser ou sauvegarder des fichiers. Mettez à niveau votre forfait maintenant ou supprimez des fichiers pour économiser de l'espace.";

// Common
"COMMON_UPGRADE" = "Mise à niveau";
"COMMON_LOGOUT" = "Déconnexion";
Expand Down Expand Up @@ -170,3 +175,7 @@
"SHARE_FEEDBACK_SUCCESS_TITLE" = "Merci d'avoir partagé votre commentaire";
"SHARE_FEEDBACK_SUCCESS_SUBTITLE" = "Nous apprécions vraiment le temps et les efforts pour nous aider à améliorer nos services";

// Alert Full Storage
"ALERT_STORAGE_TITLE" = "Votre stockage est plein";
"ALERT_STORAGE_SUBTITLE" = "Vous ne pouvez pas télécharger, synchroniser ou sauvegarder des fichiers";
"ALERT_STORAGE_BUTTON_TITLE" = "Mettre à jour maintenant";
29 changes: 17 additions & 12 deletions XPCBackupService/Entities/BackupTreeNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation
import RealmSwift
import InternxtSwiftCore

enum BackupTreeNodeError: Error {
case cannotGetPath
Expand Down Expand Up @@ -131,28 +132,25 @@ class BackupTreeNode {
return syncedNodeUnwrapped
}

func syncBelowNodes(withOperationQueue: OperationQueue, dependingOfOperation: BackupTreeNodeSyncOperation? = nil) throws -> Void {

func syncBelowNodes(withOperationQueue: OperationQueue, dependingOfOperation: BackupTreeNodeSyncOperation? = nil, onError: @escaping (Error) -> Void) throws -> Void {
let operation = BackupTreeNodeSyncOperation(backupTreeNode: self)
// If the node is already synced, we just update it, otherwise, move it
// to the queue, so it gets synced later

operation.onError = { error in
onError(error)

}
if(dependingOfOperation != nil) {
operation.addDependency(dependingOfOperation!)
}
withOperationQueue.addOperation(operation)


withOperationQueue.addOperation(operation)


for child in self.childs {
if(self.type == .folder) {
// If current node is a folder, make below sync operations dependent of the folder sync operation
try child.syncBelowNodes(withOperationQueue: withOperationQueue, dependingOfOperation: operation)
try child.syncBelowNodes(withOperationQueue: withOperationQueue, dependingOfOperation: operation, onError: onError)
} else {
// If current node is not a folder, below sync operations does not depend on parent node sync operation
try child.syncBelowNodes(withOperationQueue: withOperationQueue)
try child.syncBelowNodes(withOperationQueue: withOperationQueue, onError: onError)
}

}
}

Expand Down Expand Up @@ -191,6 +189,13 @@ class BackupTreeNode {
child.remoteParentId = remoteId
}
case .failure(let error):

if let startUploadError = error as? StartUploadError {
if let apiClientError = startUploadError.apiError, apiClientError.statusCode == 420 {
throw BackupError.storageFull
}
}

if case BackupUploadError.BackupStoppedManually = error {
// Noop, this was stopped
} else {
Expand Down
16 changes: 11 additions & 5 deletions XPCBackupService/Entities/BackupTreeNodeSyncOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ import Foundation

class BackupTreeNodeSyncOperation: AsyncOperation {
private let backupTreeNode: BackupTreeNode

init(backupTreeNode: BackupTreeNode) {
self.backupTreeNode = backupTreeNode
}
var onError: ((Error) -> Void)?

init(backupTreeNode: BackupTreeNode) {
self.backupTreeNode = backupTreeNode
}

override func performAsyncTask() async throws -> Void {
try await self.backupTreeNode.syncNode()
do {
try await self.backupTreeNode.syncNode()
} catch {
onError?(error)
throw error
}
}
}
5 changes: 5 additions & 0 deletions XPCBackupService/Services/BackupUploadService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,11 @@ class BackupUploadService: BackupUploadServiceProtocol, ObservableObject {
} catch {
self.logger.error("❌ Failed to create file \(node.name) in \(String(describing: node.remoteParentId)): \(self.getErrorDescription(error: error))")

if let startUploadError = error as? StartUploadError {
if let apiClientError = startUploadError.apiError, apiClientError.statusCode == 420 {
self.logger.error("❌ Failed to create file \(node.name) in \(String(describing: node.remoteParentId)): Max space used")
}
}


if encryptedContentURL != nil {
Expand Down
Loading

0 comments on commit a319392

Please sign in to comment.