diff --git a/bindings/ios/MEGADelegate.h b/bindings/ios/MEGADelegate.h index e814423a09..bbafaaa683 100644 --- a/bindings/ios/MEGADelegate.h +++ b/bindings/ios/MEGADelegate.h @@ -240,6 +240,8 @@ NS_ASSUME_NONNULL_BEGIN * 300: suspension only for multiple copyright violations. * 400: the subuser account has been disabled. * 401: the subuser account has been removed. + * 500: The account needs to be verified by an SMS code. + * 700: the account is supended for Weak Account Protection. * * - EventStorage: when the status of the storage changes. * diff --git a/bindings/ios/MEGAGlobalDelegate.h b/bindings/ios/MEGAGlobalDelegate.h index f7b59ac031..16957a650b 100644 --- a/bindings/ios/MEGAGlobalDelegate.h +++ b/bindings/ios/MEGAGlobalDelegate.h @@ -142,6 +142,8 @@ NS_ASSUME_NONNULL_BEGIN * 300: suspension only for multiple copyright violations. * 400: the subuser account has been disabled. * 401: the subuser account has been removed. + * 500: The account needs to be verified by an SMS code. + * 700: the account is supended for Weak Account Protection. * * - EventStorage: when the status of the storage changes. * diff --git a/bindings/ios/MEGANode.mm b/bindings/ios/MEGANode.mm index ff7f94451a..950dabfaa7 100644 --- a/bindings/ios/MEGANode.mm +++ b/bindings/ios/MEGANode.mm @@ -178,7 +178,7 @@ - (BOOL)isRemoved { } - (BOOL)hasChangedType:(MEGANodeChangeType)changeType { - return self.megaNode ? self.megaNode->hasChanged(changeType) : NO; + return self.megaNode ? self.megaNode->hasChanged(int(changeType)) : NO; } - (MEGANodeChangeType)getChanges { diff --git a/bindings/ios/MEGARequest.h b/bindings/ios/MEGARequest.h index b3c1e84302..03061bebe7 100644 --- a/bindings/ios/MEGARequest.h +++ b/bindings/ios/MEGARequest.h @@ -143,6 +143,14 @@ typedef NS_ENUM (NSInteger, MEGARequestType) { MEGARequestTypePublicLinkInformation, MEGARequestTypeGetBackgroundUploadURL, MEGARequestTypeCompleteBackgroundUpload, + MEGARequestTypeCloudStorageUsed, + MEGARequestTypeSendSMSVerificationCode, + MEGARequestTypeCheckSMSVerificationCode, + MEGARequestTypeGetRegisteredContacts, + MEGARequestTypeGetCountryCallingCodes, + MEGARequestTypeVerifyCredentials, + MEGARequestTypeGetMiscFlags, + MEGARequestTypeResendVerificationEmail, TotalOfRequestTypes }; diff --git a/bindings/ios/MEGASDK.xcodeproj/project.pbxproj b/bindings/ios/MEGASDK.xcodeproj/project.pbxproj index 4bdfb66e77..8ab1935820 100644 --- a/bindings/ios/MEGASDK.xcodeproj/project.pbxproj +++ b/bindings/ios/MEGASDK.xcodeproj/project.pbxproj @@ -89,6 +89,7 @@ A8827A5C1F178A0D0097B5DE /* DelegateMEGATreeProcessorListener.mm in Sources */ = {isa = PBXBuildFile; fileRef = A8827A5B1F178A0D0097B5DE /* DelegateMEGATreeProcessorListener.mm */; }; A88722DC1FFE6A8B00E3F443 /* mediafileattribute.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A88722DB1FFE6A8A00E3F443 /* mediafileattribute.cpp */; }; A8A86BD51F559EDA00C214DA /* mega_zxcvbn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A8A86BD41F559EDA00C214DA /* mega_zxcvbn.cpp */; }; + A8C45FD7237AB61A00342F36 /* testhooks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A8C45FD6237AB61A00342F36 /* testhooks.cpp */; }; A8FD7334230ABA400070A5E8 /* MEGACancelToken.mm in Sources */ = {isa = PBXBuildFile; fileRef = A8FD7333230ABA400070A5E8 /* MEGACancelToken.mm */; }; A8FD7B641E93B40E0031FC50 /* osxutils.mm in Sources */ = {isa = PBXBuildFile; fileRef = A8FD7B631E93B40E0031FC50 /* osxutils.mm */; }; B6657E9C225C2B6200EF8D91 /* raid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B6657E9B225C2B6200EF8D91 /* raid.cpp */; }; @@ -307,6 +308,8 @@ A8A86BD21F559C0100C214DA /* mega_utf8proc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mega_utf8proc.h; sourceTree = ""; }; A8A86BD31F559C0100C214DA /* mega_zxcvbn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mega_zxcvbn.h; sourceTree = ""; }; A8A86BD41F559EDA00C214DA /* mega_zxcvbn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mega_zxcvbn.cpp; path = ../../src/mega_zxcvbn.cpp; sourceTree = ""; }; + A8C45FD5237AB5FE00342F36 /* testhooks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = testhooks.h; sourceTree = ""; }; + A8C45FD6237AB61A00342F36 /* testhooks.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = testhooks.cpp; path = ../../src/testhooks.cpp; sourceTree = ""; }; A8EBFDD21EFAE14C00DF89DA /* MEGAEvent+init.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MEGAEvent+init.h"; sourceTree = ""; }; A8FD7332230ABA400070A5E8 /* MEGACancelToken.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGACancelToken.h; sourceTree = ""; }; A8FD7333230ABA400070A5E8 /* MEGACancelToken.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MEGACancelToken.mm; sourceTree = ""; }; @@ -380,6 +383,7 @@ 940BEFAE19ED92C2007E7FA2 /* share.cpp */, 940BEFAF19ED92C2007E7FA2 /* sharenodekeys.cpp */, 940BEFB019ED92C2007E7FA2 /* sync.cpp */, + A8C45FD6237AB61A00342F36 /* testhooks.cpp */, 940BEFB119ED92C2007E7FA2 /* transfer.cpp */, 940BEFB219ED92C2007E7FA2 /* transferslot.cpp */, 940BEFB319ED92C2007E7FA2 /* treeproc.cpp */, @@ -562,6 +566,7 @@ 940BF06619EDBCAD007E7FA2 /* share.h */, 940BF06719EDBCAD007E7FA2 /* sharenodekeys.h */, 940BF06819EDBCAD007E7FA2 /* sync.h */, + A8C45FD5237AB5FE00342F36 /* testhooks.h */, 940BF06919EDBCAD007E7FA2 /* thread */, 940BF06D19EDBCAD007E7FA2 /* thread.h */, 940BF06E19EDBCAD007E7FA2 /* transfer.h */, @@ -778,6 +783,7 @@ A8A86BD51F559EDA00C214DA /* mega_zxcvbn.cpp in Sources */, A8FD7B641E93B40E0031FC50 /* osxutils.mm in Sources */, 940BEFB819ED92C2007E7FA2 /* backofftimer.cpp in Sources */, + A8C45FD7237AB61A00342F36 /* testhooks.cpp in Sources */, BFD7F5172341B3A60039A6EE /* MEGAPushNotificationSettings.mm in Sources */, 940BEFF419ED9351007E7FA2 /* net.cpp in Sources */, 940BF01119ED97B9007E7FA2 /* MEGANode.mm in Sources */, diff --git a/bindings/ios/MEGASdk.h b/bindings/ios/MEGASdk.h index b4adb21606..2b73593f8b 100644 --- a/bindings/ios/MEGASdk.h +++ b/bindings/ios/MEGASdk.h @@ -107,7 +107,8 @@ typedef NS_ENUM(NSInteger, MEGAUserAttribute) { MEGAUserAttributeGeolocation = 22, // private - byte array MEGAUserAttributeCameraUploadsFolder = 23, // private - byte array MEGAUserAttributeMyChatFilesFolder = 24, // private - byte array - MEGAUserAttributePushSettings = 25 // private - char array + MEGAUserAttributePushSettings = 25, // private - char array + MEGAUserAttributeAlias = 27 // private - char array }; typedef NS_ENUM(NSInteger, MEGANodeAttribute) { @@ -1177,6 +1178,88 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { */ - (BOOL)checkPassword:(NSString *)password; +/** + * @brief Returns the credentials of the currently open account + * + * If the MEGASdk object isn't logged in or there's no signing key available, + * this function returns nil + * + * @return Fingerprint of the signing key of the current account + */ +- (NSString *)myCredentials; + +/** + * Returns the credentials of a given user + * + * The associated request type with this request is MEGARequestTypeGetAttrUser + * Valid data in the MEGARequest object received on callbacks: + * - [MEGARequest paramType] - Returns MEGAUserAttributeED25519PublicKey + * - [MEGARequest flag] - Returns YES + * + * Valid data in the MEGARequest object received in onRequestFinish when the error code + * is MEGAErrorTypeApiOk: + * - [MEGARequest password] - Returns the credentials in hexadecimal format + * + * @param user MEGAUser of the contact (@see [MEGASDK contactForEmail:]) to get the fingerprint + * @param delegate MEGARequestDelegate to track this request + */ +- (void)getUserCredentials:(MEGAUser *)user delegate:(id)delegate; + +/** + * @brief Checks if credentials are verified for the given user + * + * @param user MEGAUser of the contact whose credentiasl want to be checked + * @return YES if verified, NO otherwise + */ +- (BOOL)areCredentialsVerifiedOfUser:(MEGAUser *)user; + +/** + * @brief Verify credentials of a given user + * + * This function allow to tag credentials of a user as verified. It should be called when the + * logged in user compares the fingerprint of the user (provided by an independent and secure + * method) with the fingerprint shown by the app (@see [MEGASDK getUserCredentials:]). + * + * The associated request type with this request is MEGARequestTypeVerifyCredentials + * Valid data in the MEGARequest object received on callbacks: + * - [MEGARequest nodeHandle] - Returns userhandle + * + * @param user MEGAUser of the contact whose credentials want to be verified + * @param delegate MEGARequestDelegate to track this request + */ +- (void)verifyCredentialsOfUser:(MEGAUser *)user delegate:(id)delegate; + +/** + * @brief Reset credentials of a given user + * + * Call this function to forget the existing authentication of keys and signatures for a given + * user. A full reload of the account will start the authentication process again. + * + * The associated request type with this request is MEGARequestTypeVerifyCredentials + * Valid data in the MEGARequest object received on callbacks: + * - [MEGARequest nodeHandle] - Returns userhandle + * - [MEGARequest flag] - Returns YES + * + * @param user MEGAUser of the contact whose credentials want to be reset + * @param delegate MEGARequestDelegate to track this request + */ +- (void)resetCredentialsOfUser:(MEGAUser *)user delegate:(id)delegate; + +/** + * @brief Reset credentials of a given user + * + * Call this function to forget the existing authentication of keys and signatures for a given + * user. A full reload of the account will start the authentication process again. + * + * The associated request type with this request is MEGARequestTypeVerifyCredentials + * Valid data in the MEGARequest object received on callbacks: + * - [MEGARequest nodeHandle] - Returns userhandle + * - [MEGARequest flag] - Returns YES + * + * @param user MEGAUser of the contact whose credentials want to be reset + */ +- (void)resetCredentialsOfUser:(MEGAUser *)user; + #pragma mark - Create account and confirm account Requests /** @@ -1620,7 +1703,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { * @param masterKey Base64-encoded string containing the master key (optional). * @param delegate Delegate to track this request */ -- (void)confirmResetPasswordWithLink:(NSString *)link newPassword:(NSString *)newPassword masterKey:(NSString *)masterKey delegate:(id)delegate; +- (void)confirmResetPasswordWithLink:(NSString *)link newPassword:(NSString *)newPassword masterKey:(nullable NSString *)masterKey delegate:(id)delegate; /** * @brief Set a new password for the account pointed by the recovery link. @@ -1645,7 +1728,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { * @param newPassword The new password to be set. * @param masterKey Base64-encoded string containing the master key (optional). */ -- (void)confirmResetPasswordWithLink:(NSString *)link newPassword:(NSString *)newPassword masterKey:(NSString *)masterKey; +- (void)confirmResetPasswordWithLink:(NSString *)link newPassword:(NSString *)newPassword masterKey:(nullable NSString *)masterKey; /** * @brief Initialize the cancellation of an account. @@ -1756,6 +1839,40 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { */ - (void)confirmCancelAccountWithLink:(NSString *)link password:(NSString *)password; +/** +* @brief Allow to resend the verification email for Weak Account Protection +* +* The verification email will be resent to the same address as it was previously sent to. +* +* This function can be called if the the reason for being blocked is: +* 700: the account is supended for Weak Account Protection. +* +* If the logged in account is not suspended or is suspended for some other reason, +* onRequestFinish will be called with the error code MEGAErrorTypeApiEAccess. +* +* If the logged in account has not been sent the unlock email before, +* onRequestFinish will be called with the error code MEGAErrorTypeApiEArgs. +* +* @param delegate MEGARequestDelegate to track this request +*/ +- (void)resendVerificationEmailWithDelegate:(id)delegate; + +/** +* @brief Allow to resend the verification email for Weak Account Protection +* +* The verification email will be resent to the same address as it was previously sent to. +* +* This function can be called if the the reason for being blocked is: +* 700: the account is supended for Weak Account Protection. +* +* If the logged in account is not suspended or is suspended for some other reason, +* onRequestFinish will be called with the error code MEGAErrorTypeApiEAccess. +* +* If the logged in account has not been sent the unlock email before, +* onRequestFinish will be called with the error code MEGAErrorTypeApiEArgs. +*/ +- (void)resendVerificationEmail; + /** * @brief Initialize the change of the email address associated to the account. * @@ -2003,6 +2120,58 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { */ - (void)keepMeAliveWithType:(KeepMeAlive)type enable:(BOOL)enable; +/** + * @brief Check the reason of being blocked. + * + * The associated request type with this request is MEGARequestTypeWhyAmIBlocked. + * + * This request can be sent internally at anytime (whenever an account gets blocked), so + * a MEGAGlobalListener should process the result, show the reason and logout. + * + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MEGAErrorTypeApiOk: + * - MEGARequest.text - Returns the reason string (in English) + * - MEGARequest.number - Returns the reason code. Possible values: + * 0: The account is not blocked + * 200: suspension message for any type of suspension, but copyright suspension. + * 300: suspension only for multiple copyright violations. + * 400: the subuser account has been disabled. + * 401: the subuser account has been removed. + * 500: The account needs to be verified by an SMS code. + * 700: the account is supended for Weak Account Protection. + * + * If the error code in the MEGARequest object received in onRequestFinish + * is MEGAErrorTypeApiOk, the user is not blocked. + * + * @param delegate MEGARequestDelegate to track this request + */ +- (void)whyAmIBlockedWithDelegate:(id)delegate; + +/** + * @brief Check the reason of being blocked. + * + * The associated request type with this request is MEGARequestTypeWhyAmIBlocked. + * + * This request can be sent internally at anytime (whenever an account gets blocked), so + * a MEGAGlobalListener should process the result, show the reason and logout. + * + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MEGAErrorTypeApiOk: + * - MEGARequest.text - Returns the reason string (in English) + * - MEGARequest.number - Returns the reason code. Possible values: + * 0: The account is not blocked + * 200: suspension message for any type of suspension, but copyright suspension. + * 300: suspension only for multiple copyright violations. + * 400: the subuser account has been disabled. + * 401: the subuser account has been removed. + * 500: The account needs to be verified by an SMS code. + * 700: the account is supended for Weak Account Protection. + * + * If the error code in the MEGARequest object received in onRequestFinish + * is MEGAErrorTypeApiOk, the user is not blocked. +*/ +- (void)whyAmIBlocked; + /** * @brief Get the next PSA (Public Service Announcement) that should be shown to the user * @@ -2760,6 +2929,19 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { */ - (void)publicNodeForMegaFileLink:(NSString *)megaFileLink; +/** +* @brief Build the URL for a public link +* +* @note This function does not create the public link itself. It simply builds the URL +* from the provided data. +* +* @param publicHandle Public handle of the link, in B64url encoding. +* @param key Encryption key of the link. +* @param isFolder True for folder links, false for file links. +* @return The public link for the provided data +*/ +- (NSString *)buildPublicLinkForHandle:(NSString *)publicHandle key:(NSString *)key isFolder:(BOOL)isFolder; + /** * @brief Set the GPS coordinates of image files as a node attribute. * @@ -3621,6 +3803,74 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { */ - (void)setUserAttributeType:(MEGAUserAttribute)type value:(NSString *)value; +/** + * @brief Gets the alias for an user + * + * The associated request type with this request is MEGARequestTypeGetAttrUser + * Valid data in the MEGARequest object received on callbacks: + * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeAlias + * - [MEGARequest nodeHandle] - Returns the handle of the node as binary + * - [MEGARequest text] - Return the handle of the node as base 64 string. + * + * Valid data in the MEGARequest object received in onRequestFinish when the error code + * is MEGAErrorTypeApiOk: + * - [MEGARequest name] - Returns the user alias. + * + * If the user alias doesn't exists the request will fail with the error code MEGAErrorTypeApiENoent + * + * @param handle Handle of the contact + * @param delegate MEGARequestDelegate to track this request + */ +- (void)getUserAliasWithHandle:(uint64_t)handle delegate:(id)delegate; + +/** + * @brief Gets the alias for an user + * + * The associated request type with this request is MEGARequestTypeGetAttrUser + * Valid data in the MEGARequest object received on callbacks: + * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeAlias + * - [MEGARequest nodeHandle] - Returns the handle of the node as binary + * - [MEGARequest text] - Return the handle of the node as base64 string. + * + * Valid data in the MEGARequest object received in onRequestFinish when the error code + * is MEGAErrorTypeApiOk: + * - [MEGARequest name] - Returns the user alias. + * + * If the user alias doesn't exists the request will fail with the error code MEGAErrorTypeApiENoent + * + * @param handle Handle of the contact + */ +- (void)getUserAliasWithHandle:(uint64_t)handle; + +/** + * @brief Set or reset an alias for a user + * + * The associated request type with this request is MEGARequestTypeGetAttrUser + * Valid data in the MEGARequest object received on callbacks: + * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeAlias + * - [MEGARequest nodeHandle] - Returns the handle of the node as binary + * - [MEGARequest text] - Return the handle of the node as base 64 string. + * + * @param alias the user alias, or null to reset the existing + * @param handle Handle of the contact + * @param delegate MEGARequestDelegate to track this request + */ +- (void)setUserAlias:(nullable NSString *)alias forHandle:(uint64_t)handle delegate:(id)delegate; + +/** + * @brief Set or reset an alias for a user + * + * The associated request type with this request is MEGARequestTypeGetAttrUser + * Valid data in the MEGARequest object received on callbacks: + * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeAlias + * - [MEGARequest nodeHandle] - Returns the handle of the node as binary + * - [MEGARequest text] - Return the handle of the node as base 64 string. + * + * @param alias the user alias, or null to reset the existing + * @param handle Handle of the contact + */ +- (void)setUserAlias:(nullable NSString *)alias forHandle:(uint64_t)handle; + /** * @brief Set an attribute of the current user. * @@ -4912,7 +5162,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { * The data in this parameter can be accessed using [MEGATransfer appData] in delegates * @param delegate Delegate to track this transfer. */ -- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(NSString *)appData delegate:(id)delegate; +- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(nullable NSString *)appData delegate:(id)delegate; /** * @brief Upload a file with a custom name. @@ -4926,7 +5176,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { * @param appData Custom app data to save in the MEGATransfer object * The data in this parameter can be accessed using [MEGATransfer appData] in delegates */ -- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(NSString *)appData; +- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(nullable NSString *)appData; /** * @brief Upload a file or a folder, saving custom app data during the transfer @@ -4945,7 +5195,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { * Use this parameter with caution. Set it to YES only if you are sure about what are you doing. * @param delegate MEGATransferDelegate to track this transfer */ -- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary delegate:(id)delegate; +- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary delegate:(id)delegate; /** * @brief Upload a file or a folder, saving custom app data during the transfer @@ -4963,7 +5213,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { * This parameter is intended to automatically delete temporary files that are only created to be uploaded. * Use this parameter with caution. Set it to YES only if you are sure about what are you doing. */ -- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary; +- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary; /** * @brief Upload a file or a folder, putting the transfer on top of the upload queue @@ -4982,7 +5232,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { * Use this parameter with caution. Set it to YES only if you are sure about what are you doing. * @param delegate MEGATransferDelegate to track this transfer */ -- (void)startUploadTopPriorityWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary delegate:(id)delegate; +- (void)startUploadTopPriorityWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary delegate:(id)delegate; /** * @brief Upload a file or a folder, putting the transfer on top of the upload queue @@ -5001,7 +5251,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { * Use this parameter with caution. Set it to YES only if you are sure about what are you doing. */ -- (void)startUploadTopPriorityWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary; +- (void)startUploadTopPriorityWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary; /** * @brief Upload a file or a folder @@ -5030,7 +5280,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { */ - (void)startUploadForChatWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent - appData:(NSString *)appData + appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary delegate:(id)delegate; @@ -5084,7 +5334,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { * * @param delegate Delegate to track this transfer. */ -- (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath appData:(NSString *)appData delegate:(id)delegate; +- (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath appData:(nullable NSString *)appData delegate:(id)delegate; /** * @brief Download a file from MEGA. @@ -5103,7 +5353,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { * related to the transfer. * */ -- (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath appData:(NSString *)appData; +- (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath appData:(nullable NSString *)appData; /** * @brief Download a file or a folder from MEGA, putting the transfer on top of the download queue. @@ -5123,7 +5373,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { * * @param delegate Delegate to track this transfer. */ -- (void)startDownloadTopPriorityWithNode:(MEGANode *)node localPath:(NSString *)localPath appData:(NSString *)appData delegate:(id)delegate; +- (void)startDownloadTopPriorityWithNode:(MEGANode *)node localPath:(NSString *)localPath appData:(nullable NSString *)appData delegate:(id)delegate; /** * @brief Download a file or a folder from MEGA, putting the transfer on top of the download queue. @@ -5142,7 +5392,7 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { * related to the transfer. * */ -- (void)startDownloadTopPriorityWithNode:(MEGANode *)node localPath:(NSString *)localPath appData:(NSString *)appData; +- (void)startDownloadTopPriorityWithNode:(MEGANode *)node localPath:(NSString *)localPath appData:(nullable NSString *)appData; /** * @brief Start an streaming download for a file in MEGA @@ -5921,6 +6171,22 @@ typedef NS_ENUM(NSInteger, BusinessStatus) { */ - (nullable MEGAUser *)userFromInShareNode:(MEGANode *)node; +/** +* @brief Get the user relative to an incoming share +* +* This function will return nil if the node is not found. +* +* If recurse is true, it will return nil if the root corresponding to +* the node received as argument doesn't represent the root of an incoming share. +* Otherwise, it will return nil if the node doesn't represent +* the root of an incoming share. +* +* @param node Node to look for inshare user. +* @param recurse use root node corresponding to the node passed +* @return MegaUser relative to the incoming share +*/ +- (nullable MEGAUser *)userFromInShareNode:(MEGANode *)node recurse:(BOOL)recurse; + /** * @brief Check if a MEGANode is being shared. * diff --git a/bindings/ios/MEGASdk.mm b/bindings/ios/MEGASdk.mm index 381ea65068..9b8b6c75d9 100644 --- a/bindings/ios/MEGASdk.mm +++ b/bindings/ios/MEGASdk.mm @@ -543,6 +543,14 @@ - (NSInteger)isLoggedIn { return self.megaApi->isLoggedIn(); } +- (void)fetchNodesWithDelegate:(id)delegate { + self.megaApi->fetchNodes([self createDelegateMEGARequestListener:delegate singleListener:YES]); +} + +- (void)fetchNodes { + self.megaApi->fetchNodes(); +} + - (void)logoutWithDelegate:(id)delegate { self.megaApi->logout([self createDelegateMEGARequestListener:delegate singleListener:YES]); } @@ -571,12 +579,43 @@ - (BOOL)checkPassword:(NSString *)password { return self.megaApi->checkPassword(password ? [password UTF8String] : NULL); } -- (void)fetchNodesWithDelegate:(id)delegate { - self.megaApi->fetchNodes([self createDelegateMEGARequestListener:delegate singleListener:YES]); +- (NSString *)myCredentials { + const char *val = self.megaApi->getMyCredentials(); + if (val) { + NSString *ret = [NSString.alloc initWithUTF8String:val]; + delete [] val; + return ret; + } else { + return nil; + } } -- (void)fetchNodes { - self.megaApi->fetchNodes(); +- (void)getUserCredentials:(MEGAUser *)user delegate:(id)delegate { + self.megaApi->getUserCredentials(user ? user.getCPtr : NULL, [self createDelegateMEGARequestListener:delegate singleListener:YES]); +} + +- (void)getUserCredentials:(MEGAUser *)user { + self.megaApi->getUserCredentials(user ? user.getCPtr : NULL); +} + +- (BOOL)areCredentialsVerifiedOfUser:(MEGAUser *)user { + return self.megaApi->areCredentialsVerified(user ? user.getCPtr : NULL); +} + +- (void)verifyCredentialsOfUser:(MEGAUser *)user delegate:(id)delegate { + self.megaApi->verifyCredentials(user ? user.getCPtr : NULL, [self createDelegateMEGARequestListener:delegate singleListener:YES]); +} + +- (void)verifyCredentialsOfUser:(MEGAUser *)user { + self.megaApi->verifyCredentials(user ? user.getCPtr : NULL); +} + +- (void)resetCredentialsOfUser:(MEGAUser *)user delegate:(id)delegate { + self.megaApi->resetCredentials(user ? user.getCPtr : NULL, [self createDelegateMEGARequestListener:delegate singleListener:YES]); +} + +- (void)resetCredentialsOfUser:(MEGAUser *)user { + self.megaApi->resetCredentials(user ? user.getCPtr : NULL); } #pragma mark - Create account and confirm account Requests @@ -670,11 +709,11 @@ - (void)queryResetPasswordLink:(NSString *)link { self.megaApi->queryResetPasswordLink((link != nil) ? [link UTF8String] : NULL); } -- (void)confirmResetPasswordWithLink:(NSString *)link newPassword:(NSString *)newPassword masterKey:(NSString *)masterKey delegate:(id)delegate { +- (void)confirmResetPasswordWithLink:(NSString *)link newPassword:(NSString *)newPassword masterKey:(nullable NSString *)masterKey delegate:(id)delegate { self.megaApi->confirmResetPassword((link != nil) ? [link UTF8String] : NULL, (newPassword != nil) ? [newPassword UTF8String] : NULL, (masterKey != nil) ? [masterKey UTF8String] : NULL, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } -- (void)confirmResetPasswordWithLink:(NSString *)link newPassword:(NSString *)newPassword masterKey:(NSString *)masterKey { +- (void)confirmResetPasswordWithLink:(NSString *)link newPassword:(NSString *)newPassword masterKey:(nullable NSString *)masterKey { self.megaApi->confirmResetPassword((link != nil) ? [link UTF8String] : NULL, (newPassword != nil) ? [newPassword UTF8String] : NULL, (masterKey != nil) ? [masterKey UTF8String] : NULL); } @@ -702,6 +741,14 @@ - (void)confirmCancelAccountWithLink:(NSString *)link password:(NSString *)passw self.megaApi->confirmCancelAccount((link != nil) ? [link UTF8String] : NULL, (password != nil) ? [password UTF8String] : NULL); } +- (void)resendVerificationEmailWithDelegate:(id)delegate { + self.megaApi->resendVerificationEmail([self createDelegateMEGARequestListener:delegate singleListener:YES]); +} + +- (void)resendVerificationEmail { + self.megaApi->resendVerificationEmail(); +} + - (void)changeEmail:(NSString *)email delegate:(id)delegate { self.megaApi->changeEmail((email != nil) ? [email UTF8String] : NULL, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } @@ -758,6 +805,14 @@ - (void)keepMeAliveWithType:(KeepMeAlive)type enable:(BOOL)enable { self.megaApi->keepMeAlive((int) type, enable); } +- (void)whyAmIBlockedWithDelegate:(id)delegate { + self.megaApi->whyAmIBlocked([self createDelegateMEGARequestListener:delegate singleListener:YES]); +} + +- (void)whyAmIBlocked { + self.megaApi->whyAmIBlocked(); +} + - (void)getPSAWithDelegate:(id)delegate { self.megaApi->getPSA([self createDelegateMEGARequestListener:delegate singleListener:YES]); } @@ -915,6 +970,16 @@ - (void)publicNodeForMegaFileLink:(NSString *)megaFileLink { self.megaApi->getPublicNode((megaFileLink != nil) ? [megaFileLink UTF8String] : NULL); } +- (NSString *)buildPublicLinkForHandle:(NSString *)publicHandle key:(NSString *)key isFolder:(BOOL)isFolder { + const char *link = self.megaApi->buildPublicLink(publicHandle.UTF8String, key.UTF8String, isFolder); + + if (!link) return nil; + NSString *stringLink = [NSString.alloc initWithUTF8String:link]; + + delete [] link; + return stringLink; +} + - (void)setNodeCoordinates:(MEGANode *)node latitude:(NSNumber *)latitude longitude:(NSNumber *)longitude delegate:(id)delegate { self.megaApi->setNodeCoordinates(node ? [node getCPtr] : NULL, (latitude ? latitude.doubleValue : MegaNode::INVALID_COORDINATE), (longitude ? longitude.doubleValue : MegaNode::INVALID_COORDINATE), [self createDelegateMEGARequestListener:delegate singleListener:YES]); } @@ -1077,6 +1142,24 @@ - (void)setUserAttributeType:(MEGAUserAttribute)type value:(NSString *)value del self.megaApi->setUserAttribute((int)type, (value != nil) ? [value UTF8String] : NULL, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } +- (void)getUserAliasWithHandle:(uint64_t)handle delegate:(id)delegate { + self.megaApi->getUserAlias(handle, [self createDelegateMEGARequestListener:delegate singleListener:YES]); +} + +- (void)getUserAliasWithHandle:(uint64_t)handle { + self.megaApi->getUserAlias(handle); +} + +- (void)setUserAlias:(nullable NSString *)alias forHandle:(uint64_t)handle delegate:(id)delegate { + self.megaApi->setUserAlias(handle, + alias.UTF8String, + [self createDelegateMEGARequestListener:delegate singleListener:YES]); +} + +- (void)setUserAlias:(nullable NSString *)alias forHandle:(uint64_t)handle { + self.megaApi->setUserAlias(handle, alias.UTF8String); +} + #pragma mark - Account management Requests - (void)getAccountDetailsWithDelegate:(id)delegate { @@ -1395,33 +1478,33 @@ - (void)startUploadToFileWithLocalPath:(NSString *)localPath parent:(MEGANode *) self.megaApi->startUpload((localPath != nil) ? [localPath UTF8String] : NULL, (parent != nil) ? [parent getCPtr] : NULL, (filename != nil) ? [filename UTF8String] : NULL); } -- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(NSString *)appData delegate:(id)delegate { +- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(nullable NSString *)appData delegate:(id)delegate { self.megaApi->startUploadWithData((localPath != nil) ? [localPath UTF8String] : NULL, (parent != nil) ? [parent getCPtr] : NULL, (appData !=nil) ? [appData UTF8String] : NULL, [self createDelegateMEGATransferListener:delegate singleListener:YES]); } -- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(NSString *)appData { +- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(nullable NSString *)appData { self.megaApi->startUploadWithData((localPath != nil) ? [localPath UTF8String] : NULL, (parent != nil) ? [parent getCPtr] : NULL, (appData !=nil) ? [appData UTF8String] : NULL); } -- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary delegate:(id)delegate { +- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary delegate:(id)delegate { self.megaApi->startUploadWithData((localPath != nil) ? [localPath UTF8String] : NULL, (parent != nil) ? [parent getCPtr] : NULL, (appData !=nil) ? [appData UTF8String] : NULL, isSourceTemporary, [self createDelegateMEGATransferListener:delegate singleListener:YES]); } -- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary { +- (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary { self.megaApi->startUploadWithData((localPath != nil) ? [localPath UTF8String] : NULL, (parent != nil) ? [parent getCPtr] : NULL, (appData !=nil) ? [appData UTF8String] : NULL, isSourceTemporary); } -- (void)startUploadTopPriorityWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary delegate:(id)delegate { +- (void)startUploadTopPriorityWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary delegate:(id)delegate { self.megaApi->startUploadWithTopPriority((localPath != nil) ? [localPath UTF8String] : NULL, (parent != nil) ? [parent getCPtr] : NULL, (appData !=nil) ? [appData UTF8String] : NULL, isSourceTemporary, [self createDelegateMEGATransferListener:delegate singleListener:YES]); } -- (void)startUploadTopPriorityWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary { +- (void)startUploadTopPriorityWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary { self.megaApi->startUploadWithTopPriority((localPath != nil) ? [localPath UTF8String] : NULL, (parent != nil) ? [parent getCPtr] : NULL, (appData !=nil) ? [appData UTF8String] : NULL, isSourceTemporary); } - (void)startUploadForChatWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent - appData:(NSString *)appData + appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary delegate:(id)delegate { self.megaApi->startUploadForChat(localPath.UTF8String, @@ -1439,19 +1522,19 @@ - (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath { self.megaApi->startDownload((node != nil) ? [node getCPtr] : NULL, (localPath != nil) ? [localPath UTF8String] : NULL); } -- (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath appData:(NSString *)appData { +- (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath appData:(nullable NSString *)appData { self.megaApi->startDownloadWithData((node != nil) ? [node getCPtr] : NULL, (localPath != nil) ? [localPath UTF8String] : NULL, (appData != nil) ? [appData UTF8String] : NULL); } -- (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath appData:(NSString *)appData delegate:(id)delegate{ +- (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath appData:(nullable NSString *)appData delegate:(id)delegate{ self.megaApi->startDownloadWithData((node != nil) ? [node getCPtr] : NULL, (localPath != nil) ? [localPath UTF8String] : NULL, (appData != nil) ? [appData UTF8String] : NULL, [self createDelegateMEGATransferListener:delegate singleListener:YES]); } -- (void)startDownloadTopPriorityWithNode:(MEGANode *)node localPath:(NSString *)localPath appData:(NSString *)appData delegate:(id)delegate { +- (void)startDownloadTopPriorityWithNode:(MEGANode *)node localPath:(NSString *)localPath appData:(nullable NSString *)appData delegate:(id)delegate { self.megaApi->startDownloadWithTopPriority((node != nil) ? [node getCPtr] : NULL, (localPath != nil) ? [localPath UTF8String] : NULL, (appData != nil) ? [appData UTF8String] : NULL, [self createDelegateMEGATransferListener:delegate singleListener:YES]); } -- (void)startDownloadTopPriorityWithNode:(MEGANode *)node localPath:(NSString *)localPath appData:(NSString *)appData { +- (void)startDownloadTopPriorityWithNode:(MEGANode *)node localPath:(NSString *)localPath appData:(nullable NSString *)appData { self.megaApi->startDownloadWithTopPriority((node != nil) ? [node getCPtr] : NULL, (localPath != nil) ? [localPath UTF8String] : NULL, (appData != nil) ? [appData UTF8String] : NULL); } @@ -1693,6 +1776,10 @@ - (MEGAUser *)userFromInShareNode:(MEGANode *)node { return [[MEGAUser alloc] initWithMegaUser:self.megaApi->getUserFromInShare(node ? [node getCPtr] : NULL) cMemoryOwn:YES]; } +- (MEGAUser *)userFromInShareNode:(MEGANode *)node recurse:(BOOL)recurse { + return [MEGAUser.alloc initWithMegaUser:self.megaApi->getUserFromInShare(node ? [node getCPtr] : NULL, recurse) cMemoryOwn:YES]; +} + - (BOOL)isSharedNode:(MEGANode *)node { if (!node) return NO; diff --git a/bindings/ios/MEGAUser.h b/bindings/ios/MEGAUser.h index 69248055ed..4482155077 100644 --- a/bindings/ios/MEGAUser.h +++ b/bindings/ios/MEGAUser.h @@ -50,7 +50,7 @@ typedef NS_ENUM(NSInteger, MEGAUserChangeType) { MEGAUserChangeTypeRubbishTime = 0x40000, MEGAUserChangeTypeStorageState = 0x80000, MEGAUserChangeTypeGeolocation = 0x100000, - + MEGAUserChangeTypeUserAlias = 0x1000000 }; /** diff --git a/bindings/ios/Private/DelegateMEGATreeProcessorListener.mm b/bindings/ios/Private/DelegateMEGATreeProcessorListener.mm index f6dbc7ec4c..e225e3d238 100644 --- a/bindings/ios/Private/DelegateMEGATreeProcessorListener.mm +++ b/bindings/ios/Private/DelegateMEGATreeProcessorListener.mm @@ -33,5 +33,5 @@ return [listener processMEGANode:[[MEGANode alloc] initWithMegaNode:node cMemoryOwn:NO]]; } - return false;; + return false; } diff --git a/bindings/java/nz/mega/sdk/MegaApiJava.java b/bindings/java/nz/mega/sdk/MegaApiJava.java index 31d30c418b..0f38606bd6 100644 --- a/bindings/java/nz/mega/sdk/MegaApiJava.java +++ b/bindings/java/nz/mega/sdk/MegaApiJava.java @@ -96,6 +96,10 @@ void runCallback(Runnable runnable) { public final static int USER_ATTR_LAST_PSA = MegaApi.USER_ATTR_LAST_PSA; public final static int USER_ATTR_STORAGE_STATE = MegaApi.USER_ATTR_STORAGE_STATE; public final static int USER_ATTR_GEOLOCATION = MegaApi.USER_ATTR_GEOLOCATION; + public final static int USER_ATTR_CAMERA_UPLOADS_FOLDER = MegaApi.USER_ATTR_CAMERA_UPLOADS_FOLDER; + public final static int USER_ATTR_MY_CHAT_FILES_FOLDER = MegaApi.USER_ATTR_MY_CHAT_FILES_FOLDER; + public final static int USER_ATTR_PUSH_SETTINGS = MegaApi.USER_ATTR_PUSH_SETTINGS; + public final static int USER_ATTR_ALIAS = MegaApi.USER_ATTR_ALIAS; public final static int NODE_ATTR_DURATION = MegaApi.NODE_ATTR_DURATION; public final static int NODE_ATTR_COORDINATES = MegaApi.NODE_ATTR_COORDINATES; @@ -137,6 +141,7 @@ void runCallback(Runnable runnable) { public final static int KEEP_ALIVE_CAMERA_UPLOADS = MegaApi.KEEP_ALIVE_CAMERA_UPLOADS; + public final static int STORAGE_STATE_UNKNOWN = MegaApi.STORAGE_STATE_UNKNOWN; public final static int STORAGE_STATE_GREEN = MegaApi.STORAGE_STATE_GREEN; public final static int STORAGE_STATE_ORANGE = MegaApi.STORAGE_STATE_ORANGE; public final static int STORAGE_STATE_RED = MegaApi.STORAGE_STATE_RED; @@ -1572,6 +1577,44 @@ public void confirmCancelAccount(String link, String pwd, MegaRequestListenerInt megaApi.confirmCancelAccount(link, pwd, createDelegateRequestListener(listener)); } + /** + * Allow to resend the verification email for Weak Account Protection + * + * The verification email will be resent to the same address as it was previously sent to. + * + * This function can be called if the the reason for being blocked is: + * 700: the account is supended for Weak Account Protection. + * + * If the logged in account is not suspended or is suspended for some other reason, + * onRequestFinish will be called with the error code MegaError::API_EACCESS. + * + * If the logged in account has not been sent the unlock email before, + * onRequestFinish will be called with the error code MegaError::API_EARGS. + * + * @param listener MegaRequestListener to track this request + */ + public void resendVerificationEmail(MegaRequestListenerInterface listener) { + megaApi.resendVerificationEmail(createDelegateRequestListener(listener)); + } + + /** + * Allow to resend the verification email for Weak Account Protection + * + * The verification email will be resent to the same address as it was previously sent to. + * + * This function can be called if the the reason for being blocked is: + * 700: the account is supended for Weak Account Protection. + * + * If the logged in account is not suspended or is suspended for some other reason, + * onRequestFinish will be called with the error code MegaError::API_EACCESS. + * + * If the logged in account has not been sent the unlock email before, + * onRequestFinish will be called with the error code MegaError::API_EARGS. + */ + public void resendVerificationEmail() { + megaApi.resendVerificationEmail(); + } + /** * Initialize the change of the email address associated to the account. * @@ -1686,13 +1729,15 @@ public int isLoggedIn() { * 300: suspension only for multiple copyright violations. * 400: the subuser account has been disabled. * 401: the subuser account has been removed. + * 500: The account needs to be verified by an SMS code. + * 700: the account is supended for Weak Account Protection. * * If the error code in the MegaRequest object received in onRequestFinish * is MegaError::API_OK, the user is not blocked. * * @param listener MegaRequestListener to track this request */ - void whyAmIBlocked(MegaRequestListenerInterface listener) { + public void whyAmIBlocked(MegaRequestListenerInterface listener) { megaApi.whyAmIBlocked(createDelegateRequestListener(listener)); } @@ -1713,11 +1758,13 @@ void whyAmIBlocked(MegaRequestListenerInterface listener) { * 300: suspension only for multiple copyright violations. * 400: the subuser account has been disabled. * 401: the subuser account has been removed. + * 500: The account needs to be verified by an SMS code. + * 700: the account is supended for Weak Account Protection. * * If the error code in the MegaRequest object received in onRequestFinish * is MegaError::API_OK, the user is not blocked. */ - void whyAmIBlocked() { + public void whyAmIBlocked() { megaApi.whyAmIBlocked(); } @@ -3167,6 +3214,9 @@ public String getUserAvatarColor(String userhandle){ * Get the state of the storage (private non-encrypted) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Get whether the user has enabled send geolocation messages (private) + * MegaApi::USER_ATTR_PUSH_SETTINGS = 23 + * Get the settings for push notifications (private non-encrypted) + * * @param listener MegaRequestListener to track this request */ public void getUserAttribute(MegaUser user, int type, MegaRequestListenerInterface listener) { @@ -3226,13 +3276,15 @@ public void getUserAttribute(MegaUser user, int type, MegaRequestListenerInterfa * Get the state of the storage (private non-encrypted) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Get whether the user has enabled send geolocation messages (private) + * MegaApi::USER_ATTR_PUSH_SETTINGS = 23 + * Get the settings for push notifications (private non-encrypted) */ public void getUserAttribute(MegaUser user, int type) { megaApi.getUserAttribute(user, type); } /** - * @brief Get an attribute of any user in MEGA. + * Get an attribute of any user in MEGA. * * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. @@ -3283,6 +3335,9 @@ public void getUserAttribute(MegaUser user, int type) { * Get the state of the storage (private non-encrypted) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Get whether the user has enabled send geolocation messages (private) + * MegaApi::USER_ATTR_PUSH_SETTINGS = 23 + * Get the settings for push notifications (private non-encrypted) + * * @param listener MegaRequestListener to track this request */ public void getUserAttribute(String email_or_handle, int type, MegaRequestListenerInterface listener) { @@ -3290,7 +3345,7 @@ public void getUserAttribute(String email_or_handle, int type, MegaRequestListen } /** - * @brief Get an attribute of any user in MEGA. + * Get an attribute of any user in MEGA. * * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. @@ -3341,6 +3396,9 @@ public void getUserAttribute(String email_or_handle, int type, MegaRequestListen * Get the state of the storage (private non-encrypted) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Get whether the user has enabled send geolocation messages (private) + * MegaApi::USER_ATTR_PUSH_SETTINGS = 23 + * Get the settings for push notifications (private non-encrypted) + * */ public void getUserAttribute(String email_or_handle, int type) { megaApi.getUserAttribute(email_or_handle, type); @@ -3397,6 +3455,9 @@ public void getUserAttribute(String email_or_handle, int type) { * Get the state of the storage (private non-encrypted) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Get whether the user has enabled send geolocation messages (private) + * MegaApi::USER_ATTR_PUSH_SETTINGS = 23 + * Get the settings for push notifications (private non-encrypted) + * * @param listener MegaRequestListener to track this request */ public void getUserAttribute(int type, MegaRequestListenerInterface listener) { @@ -3404,7 +3465,7 @@ public void getUserAttribute(int type, MegaRequestListenerInterface listener) { } /** - * @brief Get an attribute of the current account. + * Get an attribute of the current account. * * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. @@ -3454,6 +3515,9 @@ public void getUserAttribute(int type, MegaRequestListenerInterface listener) { * Get the state of the storage (private non-encrypted) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Get whether the user has enabled send geolocation messages (private) + * MegaApi::USER_ATTR_PUSH_SETTINGS = 23 + * Get the settings for push notifications (private non-encrypted) + * */ public void getUserAttribute(int type) { megaApi.getUserAttribute(type); @@ -3906,100 +3970,167 @@ public void fetchNodes() { } /** - * Get details about the MEGA account. - *

- * The associated request type with this request is MegaRequest.TYPE_ACCOUNT_DETAILS. - *

- * Valid data in the MegaRequest object received in onRequestFinish() when the error code - * is MegaError.API_OK:
- * - MegaRequest.getMegaAccountDetails() - Details of the MEGA account. - * - * @param listener - * MegaRequestListener to track this request. + * Get details about the MEGA account + * + * Only basic data will be available. If you can get more data (sessions, transactions, purchases), + * use MegaApi::getExtendedAccountDetails. + * + * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS + * + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MegaError::API_OK: + * - MegaRequest::getMegaAccountDetails - Details of the MEGA account + * - MegaRequest::getNumDetails - Requested flags + * + * The available flags are: + * - storage quota: (numDetails & 0x01) + * - transfer quota: (numDetails & 0x02) + * - pro level: (numDetails & 0x04) + * + * @param listener MegaRequestListener to track this request */ public void getAccountDetails(MegaRequestListenerInterface listener) { megaApi.getAccountDetails(createDelegateRequestListener(listener)); } /** - * Get details about the MEGA account. + * Get details about the MEGA account + * + * Only basic data will be available. If you can get more data (sessions, transactions, purchases), + * use MegaApi::getExtendedAccountDetails. + * + * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS + * + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MegaError::API_OK: + * - MegaRequest::getMegaAccountDetails - Details of the MEGA account + * - MegaRequest::getNumDetails - Requested flags + * + * The available flags are: + * - storage quota: (numDetails & 0x01) + * - transfer quota: (numDetails & 0x02) + * - pro level: (numDetails & 0x04) */ public void getAccountDetails() { megaApi.getAccountDetails(); } /** - * Get details about the MEGA account. - *

- * This function allows to optionally get data about sessions, transactions and purchases related to the account. - *

- * The associated request type with this request is MegaRequest.TYPE_ACCOUNT_DETAILS. - *

- * Valid data in the MegaRequest object received in onRequestFinish() when the error code - * is MegaError.API_OK:
- * - MegaRequest.getMegaAccountDetails() - Details of the MEGA account. - * - * @param sessions - * Boolean. Get sessions history if true. Do not get sessions history if false. - * @param purchases - * Boolean. Get purchase history if true. Do not get purchase history if false. - * @param transactions - * Boolean. Get transactions history if true. Do not get transactions history if false. - * @param listener - * MegaRequestListener to track this request. - */ - public void getExtendedAccountDetails(boolean sessions, boolean purchases, boolean transactions, MegaRequestListenerInterface listener) { - megaApi.getExtendedAccountDetails(sessions, purchases, transactions, createDelegateRequestListener(listener)); - } - - /** - * Get details about the MEGA account. - * - * This function allows to optionally get data about sessions, transactions and purchases related to the account. + * Get details about the MEGA account * - * @param sessions - * Boolean. Get sessions history if true. Do not get sessions history if false. - * @param purchases - * Boolean. Get purchase history if true. Do not get purchase history if false. - * @param transactions - * Boolean. Get transactions history if true. Do not get transactions history if false. + * Only basic data will be available. If you need more data (sessions, transactions, purchases), + * use MegaApi::getExtendedAccountDetails. + * + * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS + * + * Use this version of the function to get just the details you need, to minimise server load + * and keep the system highly available for all. At least one flag must be set. + * + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MegaError::API_OK: + * - MegaRequest::getMegaAccountDetails - Details of the MEGA account + * - MegaRequest::getNumDetails - Requested flags + * + * The available flags are: + * - storage quota: (numDetails & 0x01) + * - transfer quota: (numDetails & 0x02) + * - pro level: (numDetails & 0x04) + * + * In case none of the flags are set, the associated request will fail with error MegaError::API_EARGS. + * + * @param storage If true, account storage details are requested + * @param transfer If true, account transfer details are requested + * @param pro If true, pro level of account is requested + * @param listener MegaRequestListener to track this request */ - public void getExtendedAccountDetails(boolean sessions, boolean purchases, boolean transactions) { - megaApi.getExtendedAccountDetails(sessions, purchases, transactions); + public void getSpecificAccountDetails(boolean storage, boolean transfer, boolean pro, MegaRequestListenerInterface listener) { + megaApi.getSpecificAccountDetails(storage, transfer, pro, -1, createDelegateRequestListener(listener)); } /** - * Get details about the MEGA account. - * - * This function allows to optionally get data about sessions and purchases related to the account. + * Get details about the MEGA account * - * @param sessions - * Boolean. Get sessions history if true. Do not get sessions history if false. - * @param purchases - * Boolean. Get purchase history if true. Do not get purchase history if false. + * Only basic data will be available. If you need more data (sessions, transactions, purchases), + * use MegaApi::getExtendedAccountDetails. + * + * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS + * + * Use this version of the function to get just the details you need, to minimise server load + * and keep the system highly available for all. At least one flag must be set. + * + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MegaError::API_OK: + * - MegaRequest::getMegaAccountDetails - Details of the MEGA account + * - MegaRequest::getNumDetails - Requested flags + * + * The available flags are: + * - storage quota: (numDetails & 0x01) + * - transfer quota: (numDetails & 0x02) + * - pro level: (numDetails & 0x04) + * + * In case none of the flags are set, the associated request will fail with error MegaError::API_EARGS. + * + * @param storage If true, account storage details are requested + * @param transfer If true, account transfer details are requested + * @param pro If true, pro level of account is requested */ - public void getExtendedAccountDetails(boolean sessions, boolean purchases) { - megaApi.getExtendedAccountDetails(sessions, purchases); + public void getSpecificAccountDetails(boolean storage, boolean transfer, boolean pro) { + megaApi.getSpecificAccountDetails(storage, transfer, pro, -1); } /** - * Get details about the MEGA account. - * - * This function allows to optionally get data about sessions related to the account. + * Get details about the MEGA account + * + * This function allows to optionally get data about sessions, transactions and purchases related to the account. + * + * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS * - * @param sessions - * Boolean. Get sessions history if true. Do not get sessions history if false. + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MegaError::API_OK: + * - MegaRequest::getMegaAccountDetails - Details of the MEGA account + * - MegaRequest::getNumDetails - Requested flags + * + * The available flags are: + * - transactions: (numDetails & 0x08) + * - purchases: (numDetails & 0x10) + * - sessions: (numDetails & 0x020) + * + * In case none of the flags are set, the associated request will fail with error MegaError::API_EARGS. + * + * @param sessions If true, sessions are requested + * @param purchases If true, purchases are requested + * @param transactions If true, transactions are requested + * @param listener MegaRequestListener to track this request */ - public void getExtendedAccountDetails(boolean sessions) { - megaApi.getExtendedAccountDetails(sessions); + public void getExtendedAccountDetails(boolean sessions, boolean purchases, boolean transactions, MegaRequestListenerInterface listener) { + megaApi.getExtendedAccountDetails(sessions, purchases, transactions, createDelegateRequestListener(listener)); } /** - * Get details about the MEGA account. - * + * Get details about the MEGA account + * + * This function allows to optionally get data about sessions, transactions and purchases related to the account. + * + * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS + * + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MegaError::API_OK: + * - MegaRequest::getMegaAccountDetails - Details of the MEGA account + * - MegaRequest::getNumDetails - Requested flags + * + * The available flags are: + * - transactions: (numDetails & 0x08) + * - purchases: (numDetails & 0x10) + * - sessions: (numDetails & 0x020) + * + * In case none of the flags are set, the associated request will fail with error MegaError::API_EARGS. + * + * @param sessions If true, sessions are requested + * @param purchases If true, purchases are requested + * @param transactions If true, transactions are requested */ - public void getExtendedAccountDetails() { - megaApi.getExtendedAccountDetails(); + public void getExtendedAccountDetails(boolean sessions, boolean purchases, boolean transactions) { + megaApi.getExtendedAccountDetails(sessions, purchases, transactions); } /** @@ -4671,6 +4802,112 @@ public void isGeolocationEnabled(MegaRequestListenerInterface listener){ megaApi.isGeolocationEnabled(createDelegateRequestListener(listener)); } + /** + * Set My Chat Files target folder. + * + * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER + * Valid data in the MegaRequest object received on callbacks: + * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER + * + * @param nodehandle MegaHandle of the node to be used as target folder + * @param listener MegaRequestListener to track this request + */ + public void setMyChatFilesFolder(long nodehandle, MegaRequestListenerInterface listener) { + megaApi.setMyChatFilesFolder(nodehandle, createDelegateRequestListener(listener)); + } + + /** + * Gets My chat files target folder. + * + * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER + * Valid data in the MegaRequest object received on callbacks: + * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER + * + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MegaError::API_OK: + * - MegaRequest::getNodehandle - Returns the handle of the node where My Chat Files are stored + * + * If the folder is not set, the request will fail with the error code MegaError::API_ENOENT. + * + * @param listener MegaRequestListener to track this request + */ + public void getMyChatFilesFolder(MegaRequestListenerInterface listener){ + megaApi.getMyChatFilesFolder(createDelegateRequestListener(listener)); + } + + /** + * Set Camera Uploads primary target folder. + * + * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER + * Valid data in the MegaRequest object received on callbacks: + * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER + * - MegaRequest::getFlag - Returns false + * - MegaRequest::getNodehandle - Returns the provided node handle + * + * @param nodehandle MegaHandle of the node to be used as primary target folder + * @param listener MegaRequestListener to track this request + */ + public void setCameraUploadsFolder(long nodehandle, MegaRequestListenerInterface listener){ + megaApi.setCameraUploadsFolder(nodehandle, createDelegateRequestListener(listener)); + } + + /** + * Set Camera Uploads secondary target folder. + * + * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER + * Valid data in the MegaRequest object received on callbacks: + * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER + * - MegaRequest::getFlag - Returns true + * - MegaRequest::getNodehandle - Returns the provided node handle + * + * @param nodehandle MegaHandle of the node to be used as secondary target folder + * @param listener MegaRequestListener to track this request + */ + + public void setCameraUploadsFolderSecondary(long nodehandle, MegaRequestListenerInterface listener){ + megaApi.setCameraUploadsFolderSecondary(nodehandle, createDelegateRequestListener(listener)); + } + + /** + * Gets Camera Uploads primary target folder. + * + * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER + * Valid data in the MegaRequest object received on callbacks: + * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER + * - MegaRequest::getFlag - Returns false + * + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MegaError::API_OK: + * - MegaRequest::getNodehandle - Returns the handle of the primary node where Camera Uploads files are stored + * + * If the folder is not set, the request will fail with the error code MegaError::API_ENOENT. + * + * @param listener MegaRequestListener to track this request + */ + void getCameraUploadsFolder(MegaRequestListenerInterface listener){ + megaApi.getCameraUploadsFolder(createDelegateRequestListener(listener)); + } + + /** + * Gets Camera Uploads secondary target folder. + * + * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER + * Valid data in the MegaRequest object received on callbacks: + * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER + * - MegaRequest::getFlag - Returns true + * + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MegaError::API_OK: + * - MegaRequest::getNodehandle - Returns the handle of the secondary node where Camera Uploads files are stored + * + * If the secondary folder is not set, the request will fail with the error code MegaError::API_ENOENT. + * + * @param listener MegaRequestListener to track this request + */ + void getCameraUploadsFolderSecondary(MegaRequestListenerInterface listener){ + megaApi.getCameraUploadsFolderSecondary(createDelegateRequestListener(listener)); + } + /** * Get the number of days for rubbish-bin cleaning scheduler * @@ -6606,6 +6843,45 @@ public ArrayList getInSharesList() { return shareListToArray(megaApi.getInSharesList()); } + /** + * Get the user relative to an incoming share + * + * This function will return NULL if the node is not found. + * + * If recurse is true, it will return NULL if the root corresponding to + * the node received as argument doesn't represent the root of an incoming share. + * Otherwise, it will return NULL if the node doesn't represent + * the root of an incoming share. + * + * You take the ownership of the returned value + * + * @param node Node to look for inshare user. + * @param recurse use root node corresponding to the node passed + * @return MegaUser relative to the incoming share + */ + public MegaUser getUserFromInShare(MegaNode node, boolean recurse) { + return megaApi.getUserFromInShare(node, recurse); + } + + /** + * Get the user relative to an incoming share + * + * This function will return NULL if the node is not found. + * + * If recurse is true, it will return NULL if the root corresponding to + * the node received as argument doesn't represent the root of an incoming share. + * Otherwise, it will return NULL if the node doesn't represent + * the root of an incoming share. + * + * You take the ownership of the returned value + * + * @param node Node to look for inshare user. + * @return MegaUser relative to the incoming share + */ + public MegaUser getUserFromInShare(MegaNode node) { + return megaApi.getUserFromInShare(node); + } + /** * Check if a MegaNode is being shared. *

diff --git a/bindings/java/nz/mega/sdk/MegaGlobalListenerInterface.java b/bindings/java/nz/mega/sdk/MegaGlobalListenerInterface.java index 4aa0d51fd7..df84df0f31 100644 --- a/bindings/java/nz/mega/sdk/MegaGlobalListenerInterface.java +++ b/bindings/java/nz/mega/sdk/MegaGlobalListenerInterface.java @@ -154,6 +154,8 @@ public interface MegaGlobalListenerInterface { * 300: suspension only for multiple copyright violations. * 400: the subuser account has been disabled. * 401: the subuser account has been removed. + * 500: The account needs to be verified by an SMS code. + * 700: the account is supended for Weak Account Protection. * * - MegaEvent::EVENT_STORAGE: when the status of the storage changes. * diff --git a/bindings/qt/sdk.pri b/bindings/qt/sdk.pri index d26725f411..aa68cb00d7 100644 --- a/bindings/qt/sdk.pri +++ b/bindings/qt/sdk.pri @@ -498,6 +498,8 @@ win32 { } LIBS += -lshlwapi -lws2_32 -luser32 -lsodium -lcryptopp -lzlibstat + + DEFINES += NOMINMAX } unix:!macx { diff --git a/configure.ac b/configure.ac index 1aa020d1c1..47d8aa7ce8 100644 --- a/configure.ac +++ b/configure.ac @@ -192,16 +192,17 @@ AC_MSG_CHECKING([platform options]) case $host in *-*-cygwin*) LIBS_EXTRA="-luser32 -lkernel32" + CXXFLAGS="$CXXFLAGS -DNOMINMAX" WIN32=yes ;; *-*-mingw*) LIBS_EXTRA="-lws2_32 -lcrypt32 -lole32 -lwinmm -lshlwapi -lshell32" - CXXFLAGS="$CXXFLAGS -DUNICODE -DWINVER=0x0501 -DHAVE_STRUCT_TIMESPEC" + CXXFLAGS="$CXXFLAGS -DUNICODE -DWINVER=0x0501 -DHAVE_STRUCT_TIMESPEC -DNOMINMAX" WIN32=yes ;; *-*-msys*) LIBS_EXTRA="-lws2_32 -lcrypt32 -lole32 -lwinmm -lshlwapi -lshell32" - CXXFLAGS="$CXXFLAGS -DUNICODE -DWINVER=0x0501 -DHAVE_STRUCT_TIMESPEC -Wl,-no-undefined" + CXXFLAGS="$CXXFLAGS -DUNICODE -DWINVER=0x0501 -DHAVE_STRUCT_TIMESPEC -DNOMINMAX -Wl,-no-undefined" LIBMEGA_EXTRALDFLAGS="-no-undefined" AC_SUBST(LIBMEGA_EXTRALDFLAGS) WIN32=yes diff --git a/contrib/QtCreator/MEGAtests/MEGAtest_integration/MEGAtest_integration.pro b/contrib/QtCreator/MEGAtests/MEGAtest_integration/MEGAtest_integration.pro index cd9f886d4f..286f566d27 100644 --- a/contrib/QtCreator/MEGAtests/MEGAtest_integration/MEGAtest_integration.pro +++ b/contrib/QtCreator/MEGAtests/MEGAtest_integration/MEGAtest_integration.pro @@ -16,6 +16,7 @@ CONFIG += USE_MEDIAINFO CONFIG += USE_LIBRAW CONFIG += USE_FFMPEG CONFIG -= qt +CONFIG += object_parallel_to_source LIBS += -lgtest @@ -27,10 +28,10 @@ CONFIG += c++17 QMAKE_CXXFLAGS+=-std=c++17 SOURCES += \ - ../../../../tests/integration/main.cpp \ - ../../../../tests/integration/SdkTest_test.cpp \ - ../../../../tests/integration/Sync_test.cpp +../../../../tests/integration/main.cpp \ +../../../../tests/integration/SdkTest_test.cpp \ +../../../../tests/integration/Sync_test.cpp HEADERS += \ - ../../../../tests/integration/test.h \ - ../../../../tests/integration/SdkTest_test.h +../../../../tests/integration/test.h \ +../../../../tests/integration/SdkTest_test.h diff --git a/contrib/QtCreator/MEGAtests/MEGAtest_unit/MEGAtest_unit.pro b/contrib/QtCreator/MEGAtests/MEGAtest_unit/MEGAtest_unit.pro index 98a9095410..7b7bbeee67 100644 --- a/contrib/QtCreator/MEGAtests/MEGAtest_unit/MEGAtest_unit.pro +++ b/contrib/QtCreator/MEGAtests/MEGAtest_unit/MEGAtest_unit.pro @@ -16,15 +16,31 @@ CONFIG += USE_MEDIAINFO CONFIG += USE_LIBRAW CONFIG += USE_FFMPEG CONFIG -= qt +CONFIG += object_parallel_to_source LIBS += -lgtest include(../../../../bindings/qt/sdk.pri) SOURCES += \ - ../../../../tests/unit/main.cpp \ - ../../../../tests/unit/Commands_test.cpp \ - ../../../../tests/unit/Crypto_test.cpp \ - ../../../../tests/unit/Serialization_test.cpp \ - ../../../../tests/unit/PayCrypter_test.cpp \ - ../../../../tests/unit/MegaApi_test.cpp +../../../../tests/unit/Commands_test.cpp \ +../../../../tests/unit/Crypto_test.cpp \ +../../../../tests/unit/FileFingerprint_test.cpp \ +../../../../tests/unit/FsNode.cpp \ +../../../../tests/unit/Logging_test.cpp \ +../../../../tests/unit/main.cpp \ +../../../../tests/unit/MegaApi_test.cpp \ +../../../../tests/unit/PayCrypter_test.cpp \ +../../../../tests/unit/Serialization_test.cpp \ +../../../../tests/unit/Sync_test.cpp \ +../../../../tests/unit/utils.cpp \ +../../../../tests/unit/utils_test.cpp + +HEADERS += \ +../../../../tests/unit/constants.h \ +../../../../tests/unit/DefaultedDirAccess.h \ +../../../../tests/unit/DefaultedFileAccess.h \ +../../../../tests/unit/DefaultedFileSystemAccess.h \ +../../../../tests/unit/FsNode.h \ +../../../../tests/unit/NotImplemented.h \ +../../../../tests/unit/utils.h diff --git a/contrib/QtCreator/MEGAtests/MEGAtool_purge_account/MEGAtool_purge_account.pro b/contrib/QtCreator/MEGAtests/MEGAtool_purge_account/MEGAtool_purge_account.pro index 2e9638afa8..2736e1ccc8 100644 --- a/contrib/QtCreator/MEGAtests/MEGAtool_purge_account/MEGAtool_purge_account.pro +++ b/contrib/QtCreator/MEGAtests/MEGAtool_purge_account/MEGAtool_purge_account.pro @@ -15,6 +15,7 @@ CONFIG += USE_MEGAAPI CONFIG += USE_MEDIAINFO CONFIG += USE_FFMPEG CONFIG -= qt +CONFIG += object_parallel_to_source LIBS += -lgtest diff --git a/contrib/cmake/CMakeLists.txt b/contrib/cmake/CMakeLists.txt index 6b4d8a95a1..c1f50ded03 100644 --- a/contrib/cmake/CMakeLists.txt +++ b/contrib/cmake/CMakeLists.txt @@ -167,14 +167,20 @@ if (WIN32) add_definitions( -DNOMINMAX ) IF (NOT USE_PREBUILT_3RDPARTY) - #Link against the static C/C++ libraries on windows + #Link against the static C/C++ libraries on windows. Though, if linking with prebuilt QT we need dynamic CRT foreach(flag_var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif(${flag_var} MATCHES "/MD") + if (MEGA_LINK_DYNAMIC_CRT) + if(${flag_var} MATCHES "/MT") + string(REGEX REPLACE "/MT" "/MD" ${flag_var} "${${flag_var}}") + endif() + else () + if(${flag_var} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endif() + endif () endforeach(flag_var) ENDIF() @@ -320,7 +326,7 @@ IF(WIN32) IF(USE_MEDIAINFO) ImportStaticLibrary(mediainfo "${Mega3rdPartyDir}/MediaInfoLib-mw/Source" - "${Mega3rdPartyDir}/MediaInfoLib-mw/Project/MSVC2017/Win32/Debug/MediaInfo-Static.lib" + "${Mega3rdPartyDir}/MediaInfoLib-mw/Project/MSVC2017/Win32/Debug/MediaInfo-Static.lib" "${Mega3rdPartyDir}/MediaInfoLib-mw/Project/MSVC2017/Win32/Release/MediaInfo-Static.lib" "${Mega3rdPartyDir}/MediaInfoLib-mw/Project/MSVC2017/x64/Debug/MediaInfo-Static.lib" "${Mega3rdPartyDir}/MediaInfoLib-mw/Project/MSVC2017/x64/Release/MediaInfo-Static.lib") @@ -334,20 +340,20 @@ IF(WIN32) IF(USE_FREEIMAGE) ImportVcpkgLibrary(freeimage "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/FreeImaged.lib" "${vcpkg_dir}/lib/FreeImage.lib") - ImportVcpkgLibrary(freeimage_Iex "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/Iex-2_2.lib" "${vcpkg_dir}/lib/Iex-2_2.lib") - ImportVcpkgLibrary(freeimage_IexMath "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/IexMath-2_2.lib" "${vcpkg_dir}/lib/IexMath-2_2.lib") - ImportVcpkgLibrary(freeimage_IlmImf "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/IlmImf-2_2.lib" "${vcpkg_dir}/lib/IlmImf-2_2.lib") - ImportVcpkgLibrary(freeimage_IlmImfUtil "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/IlmImfUtil-2_2.lib" "${vcpkg_dir}/lib/IlmImfUtil-2_2.lib") - ImportVcpkgLibrary(freeimage_IlmThread "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/IlmThread-2_2.lib" "${vcpkg_dir}/lib/IlmThread-2_2.lib") - ImportVcpkgLibrary(freeimage_jpeg "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/jpeg.lib" "${vcpkg_dir}/lib/jpeg.lib") - ImportVcpkgLibrary(freeimage_turbojpeg "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/turbojpeg.lib" "${vcpkg_dir}/lib/turbojpeg.lib") + ImportVcpkgLibrary(freeimage_Iex "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/Iex-2_3_d.lib" "${vcpkg_dir}/lib/Iex-2_2.lib") + ImportVcpkgLibrary(freeimage_IexMath "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/IexMath-2_3_d.lib" "${vcpkg_dir}/lib/IexMath-2_2.lib") + ImportVcpkgLibrary(freeimage_IlmImf "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/IlmImf-2_3_d.lib" "${vcpkg_dir}/lib/IlmImf-2_2.lib") + ImportVcpkgLibrary(freeimage_IlmImfUtil "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/IlmImfUtil-2_3_d.lib" "${vcpkg_dir}/lib/IlmImfUtil-2_2.lib") + ImportVcpkgLibrary(freeimage_IlmThread "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/IlmThread-2_3_s_d.lib" "${vcpkg_dir}/lib/IlmThread-2_2.lib") + ImportVcpkgLibrary(freeimage_jpeg "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/jpegd.lib" "${vcpkg_dir}/lib/jpeg.lib") + ImportVcpkgLibrary(freeimage_turbojpeg "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/turbojpegd.lib" "${vcpkg_dir}/lib/turbojpeg.lib") ImportVcpkgLibrary(freeimage_jpegxr "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/jpegxrd.lib" "${vcpkg_dir}/lib/jpegxr.lib") ImportVcpkgLibrary(freeimage_jxrglue "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/jxrglued.lib" "${vcpkg_dir}/lib/jxrglue.lib") ImportVcpkgLibrary(freeimage_openjp2 "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/openjp2.lib" "${vcpkg_dir}/lib/openjp2.lib") - ImportVcpkgLibrary(freeimage_half "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/half.lib" "${vcpkg_dir}/lib/half.lib") - ImportVcpkgLibrary(freeimage_jasper "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/jasper.lib" "${vcpkg_dir}/lib/jasper.lib") + ImportVcpkgLibrary(freeimage_half "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/half-2_3_s_d.lib" "${vcpkg_dir}/lib/half.lib") + ImportVcpkgLibrary(freeimage_jasper "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/jasperd.lib" "${vcpkg_dir}/lib/jasper.lib") ImportVcpkgLibrary(freeimage_libpng "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/libpng16d.lib" "${vcpkg_dir}/lib/libpng16.lib") - ImportVcpkgLibrary(freeimage_lzma "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/lzma.lib" "${vcpkg_dir}/lib/lzma.lib") + ImportVcpkgLibrary(freeimage_lzma "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/lzmad.lib" "${vcpkg_dir}/lib/lzma.lib") ImportVcpkgLibrary(freeimage_lcms2 "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/lcms2d.lib" "${vcpkg_dir}/lib/lcms2.lib") ImportVcpkgLibrary(freeimage_raw "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/rawd.lib" "${vcpkg_dir}/lib/raw.lib") ImportVcpkgLibrary(freeimage_tiff "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/tiffd.lib" "${vcpkg_dir}/lib/tiff.lib") @@ -611,19 +617,67 @@ if(WIN32) target_link_libraries(Mega PUBLIC crypt32.lib) endif(WIN32) +OPTION( ENABLE_CODECOVERAGE "Enable code coverage testing support" ) + +if ( ENABLE_CODECOVERAGE ) + + if ( NOT CMAKE_BUILD_TYPE STREQUAL "Debug" ) + message( WARNING "Code coverage results with an optimised (non-Debug) build may be misleading" ) + endif ( NOT CMAKE_BUILD_TYPE STREQUAL "Debug" ) + + if ( NOT DEFINED CODECOV_OUTPUTFILE ) + set( CODECOV_OUTPUTFILE cmake_coverage.output ) + endif ( NOT DEFINED CODECOV_OUTPUTFILE ) + + if ( NOT DEFINED CODECOV_HTMLOUTPUTDIR ) + set( CODECOV_HTMLOUTPUTDIR coverage_results ) + endif ( NOT DEFINED CODECOV_HTMLOUTPUTDIR ) + + if ( CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_GNUCXX ) + find_program( CODECOV_GCOV gcov ) + find_program( CODECOV_LCOV lcov ) + find_program( CODECOV_GENHTML genhtml ) + add_definitions( -fprofile-arcs -ftest-coverage ) + link_libraries( gcov ) + set( CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} --coverage ) + add_custom_target( coverage_init ALL ${CODECOV_LCOV} --base-directory . --directory ${CMAKE_BINARY_DIR} --output-file ${CODECOV_OUTPUTFILE} --capture --initial ) + add_custom_target( coverage ${CODECOV_LCOV} --base-directory . --directory ${CMAKE_BINARY_DIR} --output-file ${CODECOV_OUTPUTFILE} --capture COMMAND genhtml -o ${CODECOV_HTMLOUTPUTDIR} ${CODECOV_OUTPUTFILE} ) +endif ( CMAKE_COMPILER_IS_GNUCXX ) + +endif (ENABLE_CODECOVERAGE ) + #test apps -add_executable(test_unit ${MegaDir}/tests/unit/Commands_test.cpp - ${MegaDir}/tests/unit/Crypto_test.cpp - ${MegaDir}/tests/unit/Serialization_test.cpp - ${MegaDir}/tests/unit/main.cpp - ${MegaDir}/tests/unit/MegaApi_test.cpp - ${MegaDir}/tests/unit/PayCrypter_test.cpp) - -add_executable(test_integration ${MegaDir}/tests/integration/main.cpp - ${MegaDir}/tests/integration/SdkTest_test.cpp - ${MegaDir}/tests/integration/Sync_test.cpp) - -add_executable(tool_purge_account ${MegaDir}/tests/tool/purge_account.cpp) +add_executable(test_unit + ${MegaDir}/tests/unit/Commands_test.cpp + ${MegaDir}/tests/unit/constants.h + ${MegaDir}/tests/unit/Crypto_test.cpp + ${MegaDir}/tests/unit/DefaultedDirAccess.h + ${MegaDir}/tests/unit/DefaultedFileAccess.h + ${MegaDir}/tests/unit/DefaultedFileSystemAccess.h + ${MegaDir}/tests/unit/FileFingerprint_test.cpp + ${MegaDir}/tests/unit/FsNode.cpp + ${MegaDir}/tests/unit/FsNode.h + ${MegaDir}/tests/unit/Logging_test.cpp + ${MegaDir}/tests/unit/main.cpp + ${MegaDir}/tests/unit/MegaApi_test.cpp + ${MegaDir}/tests/unit/NotImplemented.h + ${MegaDir}/tests/unit/PayCrypter_test.cpp + ${MegaDir}/tests/unit/Serialization_test.cpp + ${MegaDir}/tests/unit/Sync_test.cpp + ${MegaDir}/tests/unit/utils.cpp + ${MegaDir}/tests/unit/utils.h + ${MegaDir}/tests/unit/utils_test.cpp +) + +add_executable(test_integration + ${MegaDir}/tests/integration/main.cpp + ${MegaDir}/tests/integration/SdkTest_test.cpp + ${MegaDir}/tests/integration/Sync_test.cpp +) + +add_executable(tool_purge_account + ${MegaDir}/tests/tool/purge_account.cpp +) target_compile_definitions(test_unit PRIVATE _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) target_compile_definitions(test_integration PRIVATE _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) diff --git a/examples/megacli.cpp b/examples/megacli.cpp index de6f82ec55..53aa318e6c 100644 --- a/examples/megacli.cpp +++ b/examples/megacli.cpp @@ -514,7 +514,7 @@ AppFileGet::AppFileGet(Node* n, handle ch, byte* cfilekey, m_off_t csize, m_time if (!cfingerprint->size() || !unserializefingerprint(cfingerprint)) { - memcpy(crc, filekey, sizeof crc); + memcpy(crc.data(), filekey, sizeof crc); } name = *cfilename; @@ -1872,7 +1872,7 @@ class TreeProcCopy_mcli : public TreeProc // copy key (if file) or generate new key (if folder) if (n->type == FILENODE) { - t->nodekey = n->nodekey; + t->nodekey = n->nodekey(); } else { @@ -2058,7 +2058,7 @@ string showMediaInfo(Node* n, MediaFileInfo& /*mediaInfo*/, bool oneline) { if (n->hasfileattribute(fa_media)) { - MediaProperties mp = MediaProperties::decodeMediaPropertiesAttributes(n->fileattrstring, (uint32_t*)(n->nodekey.data() + FILENODEKEYLENGTH / 2)); + MediaProperties mp = MediaProperties::decodeMediaPropertiesAttributes(n->fileattrstring, (uint32_t*)(n->nodekey().data() + FILENODEKEYLENGTH / 2)); return showMediaInfo(mp, client->mediaFileInfo, oneline); } return "The node has no mediainfo attribute"; @@ -2372,7 +2372,7 @@ void exec_sendDeferred(autocomplete::ACState& s) void exec_codeTimings(autocomplete::ACState& s) { bool reset = s.extractflag("-reset"); - cout << client->performanceStats.report(reset, client->httpio, client->waiter) << flush; + cout << client->performanceStats.report(reset, client->httpio, client->waiter, client->reqs) << flush; } #endif @@ -2543,6 +2543,55 @@ void printAuthringInformation(handle userhandle) } } +void exec_setmaxconnections(autocomplete::ACState& s) +{ + auto direction = s.words[1].s == "put" ? PUT : GET; + if (s.words.size() == 3) + { + client->setmaxconnections(direction, atoi(s.words[2].s.c_str())); + } + cout << "connections: " << (int)client->connections[direction] << endl; +} + + +class MegaCLILogger : public ::mega::Logger { +public: + ofstream mLogFile; + + void log(const char*, int loglevel, const char*, const char *message) override + { + if (mLogFile.is_open()) + { + mLogFile << Waiter::ds << " " << SimpleLogger::toStr(static_cast(loglevel)) << ": " << message << std::endl; + } + else + { +#ifdef _WIN32 + string s; + s.reserve(1024); + s += message; + s += "\r\n"; + OutputDebugStringA(s.c_str()); +#else + if (loglevel >= SimpleLogger::logCurrentLevel) + { + auto t = std::time(NULL); + char ts[50]; + if (!std::strftime(ts, sizeof(ts), "%H:%M:%S", std::localtime(&t))) + { + ts[0] = '\0'; + } + std::cout << "[" << ts << "] " << SimpleLogger::toStr(static_cast(loglevel)) << ": " << message << std::endl; + } +#endif + } + } +}; + +MegaCLILogger gLogger; + + + autocomplete::ACN autocompleteSyntax() { using namespace autocomplete; @@ -2627,14 +2676,14 @@ autocomplete::ACN autocompleteSyntax() p->Add(exec_locallogout, sequence(text("locallogout"))); p->Add(exec_symlink, sequence(text("symlink"))); p->Add(exec_version, sequence(text("version"))); - p->Add(exec_debug, sequence(text("debug"), opt(either(flag("-on"), flag("-off"))))); + p->Add(exec_debug, sequence(text("debug"), opt(either(flag("-on"), flag("-off"))), opt(localFSFile()))); p->Add(exec_verbose, sequence(text("verbose"), opt(either(flag("-on"), flag("-off"))))); #if defined(WIN32) && defined(NO_READLINE) p->Add(exec_clear, sequence(text("clear"))); p->Add(exec_codepage, sequence(text("codepage"), opt(sequence(wholenumber(65001), opt(wholenumber(65001)))))); p->Add(exec_log, sequence(text("log"), either(text("utf8"), text("utf16"), text("codepage")), localFSFile())); #endif - p->Add(exec_test, sequence(text("test"))); + p->Add(exec_test, sequence(text("test"), opt(param("data")))); #ifdef ENABLE_CHAT p->Add(exec_chats, sequence(text("chats"))); p->Add(exec_chatc, sequence(text("chatc"), param("group"), repeat(opt(sequence(contactEmail(client), either(text("ro"), text("sta"), text("mod"))))))); @@ -2692,6 +2741,8 @@ autocomplete::ACN autocompleteSyntax() p->Add(exec_showattributes, sequence(text("showattributes"), remoteFSPath(client, &cwd))); + p->Add(exec_setmaxconnections, sequence(text("setmaxconnections"), either(text("put"), text("get")), opt(wholenumber(4)))); + return autocompleteTemplate = std::move(p); } @@ -3256,7 +3307,7 @@ void exec_cp(autocomplete::ACState& s) unsigned nc; handle ovhandle = UNDEF; - if (!n->nodekey.size()) + if (!n->keyApplied()) { cout << "Cannot copy a node without key" << endl; return; @@ -3454,9 +3505,12 @@ void exec_get(autocomplete::ACState& s) } else { - if (client->openfilelink(s.words[1].s.c_str(), 0) == API_OK) + handle ph = UNDEF; + byte key[FILENODEKEYLENGTH]; + if (client->parsepubliclink(s.words[1].s.c_str(), ph, key, false) == API_OK) { cout << "Checking link..." << endl; + client->openfilelink(ph, key, 0); return; } @@ -3494,7 +3548,7 @@ void exec_get(autocomplete::ACState& s) f->pubauth = pubauth; f->hprivate = true; f->hforeign = true; - memcpy(f->filekey, n->nodekey.data(), FILENODEKEYLENGTH); + memcpy(f->filekey, n->nodekey().data(), FILENODEKEYLENGTH); } f->appxfer_it = appxferq[GET].insert(appxferq[GET].end(), f); @@ -3825,10 +3879,10 @@ void exec_sync(autocomplete::ACState& s) static const char* syncstatenames[] = { "Initial scan, please wait", "Active", "Failed" }; - if ((*it)->localroot.node) + if ((*it)->localroot->node) { - nodepath((*it)->localroot.node->nodehandle, &remotepath); - client->fsaccess->local2path(&(*it)->localroot.localname, &localpath); + nodepath((*it)->localroot->node->nodehandle, &remotepath); + client->fsaccess->local2path(&(*it)->localroot->localname, &localpath); cout << i++ << ": " << localpath << " to " << remotepath << " - " << syncstatenames[(*it)->state] << ", " << (*it)->localbytes @@ -4215,7 +4269,7 @@ void exec_getfa(autocomplete::ACState& s) { if (n->hasfileattribute(type)) { - client->getfa(n->nodehandle, &n->fileattrstring, &n->nodekey, type, cancel); + client->getfa(n->nodehandle, &n->fileattrstring, n->nodekey(), type, cancel); c++; } } @@ -4225,7 +4279,7 @@ void exec_getfa(autocomplete::ACState& s) { if ((*it)->type == FILENODE && (*it)->hasfileattribute(type)) { - client->getfa((*it)->nodehandle, &(*it)->fileattrstring, &(*it)->nodekey, type, cancel); + client->getfa((*it)->nodehandle, &(*it)->fileattrstring, (*it)->nodekey(), type, cancel); c++; } } @@ -4428,6 +4482,19 @@ void exec_debug(autocomplete::ACState& s) bool turnon = s.extractflag("-on"); bool turnoff = s.extractflag("-off"); + if (s.words.size() > 1) + { + gLogger.mLogFile.close(); + if (!s.words[1].s.empty()) + { + gLogger.mLogFile.open(s.words[1].s.c_str()); + if (!gLogger.mLogFile.is_open()) + { + cout << "Log file open failed: '" << s.words[1].s << "'" << endl; + } + } + } + bool state = client->debugstate(); if ((turnon && !state) || (turnoff && state) || (!turnon && !turnoff)) { @@ -5118,9 +5185,13 @@ void exec_export(autocomplete::ACState& s) void exec_import(autocomplete::ACState& s) { - if (client->openfilelink(s.words[1].s.c_str(), 1) == API_OK) + handle ph = UNDEF; + byte key[FILENODEKEYLENGTH]; + error e = client->parsepubliclink(s.words[1].s.c_str(), ph, key, false); + if (e == API_OK) { cout << "Opening link..." << endl; + client->openfilelink(ph, key, 1); } else { @@ -5134,7 +5205,7 @@ void exec_folderlinkinfo(autocomplete::ACState& s) handle ph = UNDEF; byte folderkey[SymmCipher::KEYLENGTH]; - if (client->parsefolderlink(publiclink.c_str(), ph, folderkey) == API_OK) + if (client->parsepubliclink(publiclink.c_str(), ph, folderkey, true) == API_OK) { cout << "Loading public folder link info..." << endl; client->getpubliclinkinfo(ph); @@ -5862,7 +5933,7 @@ void exec_locallogout(autocomplete::ACState& s) cout << "Logging off locally..." << endl; cwd = UNDEF; - client->locallogout(); + client->locallogout(false); } void exec_recentnodes(autocomplete::ACState& s) @@ -5954,7 +6025,7 @@ void DemoApp::request_error(error e) if ((e == API_ESID) || (e == API_ENOENT)) // Invalid session or Invalid folder handle { cout << "Invalid or expired session, logging out..." << endl; - client->locallogout(); + client->locallogout(false); return; } else if (e == API_EBLOCKED) @@ -6378,7 +6449,7 @@ void DemoApp::whyamiblocked_result(int code) if (code != 500) { cout << "Logging out..." << endl; - client->locallogout(); + client->locallogout(false); } } } @@ -6429,7 +6500,7 @@ void DemoApp::exportnode_result(handle h, handle ph) if (n->type == FILENODE) { - cout << MegaClient::getPublicLink(client->mNewLinkFormat, n->type, ph, Base64Str((const byte*)n->nodekey.data())) << endl; + cout << MegaClient::getPublicLink(client->mNewLinkFormat, n->type, ph, Base64Str((const byte*)n->nodekey().data())) << endl; } else { @@ -6581,8 +6652,8 @@ void DemoApp::folderlinkinfo_result(error e, handle owner, handle /*ph*/, string } handle ph; - byte folderkey[SymmCipher::KEYLENGTH]; - error eaux = client->parsefolderlink(publiclink.c_str(), ph, folderkey); + byte folderkey[FOLDERNODEKEYLENGTH]; + error eaux = client->parsepubliclink(publiclink.c_str(), ph, folderkey, true); assert(eaux == API_OK); // Decrypt nodekey with the key of the folder link @@ -6791,7 +6862,7 @@ void DemoApp::notify_confirmation(const char *email) } } -void DemoApp::enumeratequotaitems_result(handle, unsigned, unsigned, unsigned, unsigned, unsigned, const char*, const char*, const char*, const char*) +void DemoApp::enumeratequotaitems_result(unsigned, handle, unsigned, int, int, unsigned, unsigned, unsigned, const char*, const char*, const char*, const char*) { // FIXME: implement } @@ -7325,41 +7396,13 @@ void megacli() } } - -class MegaCLILogger : public ::mega::Logger { -public: - void log(const char*, int loglevel, const char*, const char *message) override - { -#ifdef _WIN32 - string s; - s.reserve(1024); - s += message; - s += "\r\n"; - OutputDebugStringA(s.c_str()); -#else - if (loglevel >= SimpleLogger::logCurrentLevel) - { - auto t = std::time(NULL); - char ts[50]; - if (!std::strftime(ts, sizeof(ts), "%H:%M:%S", std::localtime(&t))) - { - ts[0] = '\0'; - } - std::cout << "[" << ts << "] " << SimpleLogger::toStr(static_cast(loglevel)) << ": " << message << std::endl; - } -#endif - } -}; - -MegaCLILogger logger; - int main() { #ifdef _WIN32 SimpleLogger::setLogLevel(logMax); // warning and stronger to console; info and weaker to VS output window - SimpleLogger::setOutputClass(&logger); + SimpleLogger::setOutputClass(&gLogger); #else - SimpleLogger::setOutputClass(&logger); + SimpleLogger::setOutputClass(&gLogger); #endif console = new CONSOLE_CLASS; diff --git a/examples/megacli.h b/examples/megacli.h index ce55efe1f2..c3b3c392b5 100644 --- a/examples/megacli.h +++ b/examples/megacli.h @@ -239,7 +239,7 @@ struct DemoApp : public MegaApp void userattr_update(User*, int, const char*) override; - void enumeratequotaitems_result(handle, unsigned, unsigned, unsigned, unsigned, unsigned, const char*, const char*, const char*, const char*) override; + void enumeratequotaitems_result(unsigned, handle, unsigned, int, int, unsigned, unsigned, unsigned, const char*, const char*, const char*, const char*) override; void enumeratequotaitems_result(error) override; void additem_result(error) override; void checkout_result(const char*, error) override; diff --git a/include/mega/backofftimer.h b/include/mega/backofftimer.h index c16e80d450..fdec89c88a 100644 --- a/include/mega/backofftimer.h +++ b/include/mega/backofftimer.h @@ -53,7 +53,7 @@ class MEGA_API BackoffTimer bool arm(); // time left for event to become armed - dstime retryin(); + dstime retryin() const; // current backoff delta dstime backoffdelta(); @@ -67,6 +67,89 @@ class MEGA_API BackoffTimer BackoffTimer(PrnGen &rng); }; +class MEGA_API BackoffTimerTracked; + +// This class keeps track of a group of BackoffTimerTracked, which register and deregister themselves. +// Timers are in the multimap when they have non-0 non-NEVER timeouts set, giving us a much smaller group should we need to iterate it. +class MEGA_API BackoffTimerGroupTracker +{ + std::multimap timeouts; + +public: + typedef std::multimap::iterator Iter; + + inline Iter add(BackoffTimerTracked* bt); + inline void remove(Iter i) { timeouts.erase(i); } + + // Find out the soonest (non-0 and non-NEVER) timeout in the group. + // For transfers, it calls set(0) on any timed out timers, as the old code did. + void update(dstime* waituntil, bool transfers); +}; + + +// Just like a backoff timer, but is part of a group where we want to know the soonest (non-0) timeout in the group immediately +// Also, the enable() function can be used to exclude timers when they are not relevant, while keeping the timer settings. +class MEGA_API BackoffTimerTracked +{ + bool mIsEnabled; + BackoffTimer bt; + BackoffTimerGroupTracker& mTracker; + BackoffTimerGroupTracker::Iter mTrackerPos; + + void untrack(); + void track(); + +public: + BackoffTimerTracked(PrnGen &rng, BackoffTimerGroupTracker& tr); + ~BackoffTimerTracked(); + + inline bool arm() { untrack(); bool result = bt.arm(); track(); return result; } + inline void backoff() { untrack(); bt.backoff(); track(); } + inline void backoff(dstime t) { untrack(); bt.backoff(t); track(); } + inline void set(dstime t) { untrack(); bt.set(t); track(); } + inline void update(dstime* t) { untrack(); bt.update(t); track(); } + inline void reset() { untrack(); bt.reset(); track(); } + + inline bool armed() const { return bt.armed(); }; + inline dstime nextset() const { return bt.nextset(); }; + inline dstime retryin() const { return bt.retryin(); } + + inline void enable(bool b) { untrack(); mIsEnabled = b; track(); } + inline bool enabled() { return mIsEnabled; } +}; + +inline auto BackoffTimerGroupTracker::add(BackoffTimerTracked* bt) -> Iter +{ + return timeouts.emplace(bt->nextset() ? bt->nextset() : NEVER, bt); +} + +inline void BackoffTimerTracked::untrack() +{ + if (mIsEnabled && bt.nextset() != 0 && bt.nextset() != NEVER) + { + mTracker.remove(mTrackerPos); + mTrackerPos = BackoffTimerGroupTracker::Iter(); + } +} + +inline void BackoffTimerTracked::track() +{ + if (mIsEnabled && bt.nextset() != 0 && bt.nextset() != NEVER) + { + mTrackerPos = mTracker.add(this); + } +} + +inline BackoffTimerTracked::BackoffTimerTracked(PrnGen &rng, BackoffTimerGroupTracker& tr) : mIsEnabled(true), bt(rng), mTracker(tr) +{ + track(); +} + +inline BackoffTimerTracked::~BackoffTimerTracked() +{ + untrack(); +} + class MEGA_API TimerWithBackoff: public BackoffTimer { diff --git a/include/mega/command.h b/include/mega/command.h index 2b1660d145..247c4034eb 100644 --- a/include/mega/command.h +++ b/include/mega/command.h @@ -455,7 +455,7 @@ class MEGA_API CommandGetFile : public Command void cancel(); void procresult(); - CommandGetFile(MegaClient *client, TransferSlot*, byte*, handle, bool, const char* = NULL, const char* = NULL, const char *chatauth = NULL); + CommandGetFile(MegaClient *client, TransferSlot*, const byte*, handle, bool, const char* = NULL, const char* = NULL, const char *chatauth = NULL); }; class MEGA_API CommandPutFile : public Command @@ -758,6 +758,14 @@ class MEGA_API CommandSendEvent : public Command CommandSendEvent(MegaClient*, int, const char *); }; +class MEGA_API CommandSupportTicket : public Command +{ +public: + void procresult(); + + CommandSupportTicket(MegaClient*, const char *message, int type = 1); // by default, 1:technical_issue +}; + class MEGA_API CommandCleanRubbishBin : public Command { public: @@ -806,6 +814,14 @@ class MEGA_API CommandConfirmCancelLink : public Command CommandConfirmCancelLink(MegaClient *, const char *); }; +class MEGA_API CommandResendVerificationEmail : public Command +{ +public: + void procresult(); + + CommandResendVerificationEmail(MegaClient *); +}; + class MEGA_API CommandValidatePassword : public Command { public: diff --git a/include/mega/file.h b/include/mega/file.h index bb98c7772c..c90ba41777 100644 --- a/include/mega/file.h +++ b/include/mega/file.h @@ -87,14 +87,14 @@ struct MEGA_API File: public FileFingerprint char *chatauth; // if !hprivate, filekey and size must be valid - byte filekey[FILENODEKEYLENGTH]; + byte filekey[FILENODEKEYLENGTH]{}; // for remote file drops: uid or e-mail address of recipient string targetuser; // transfer linkage Transfer* transfer; - file_list::iterator file_it; + file_list::iterator file_it{}; File(); virtual ~File(); diff --git a/include/mega/filefingerprint.h b/include/mega/filefingerprint.h index 7e7d594f43..30c81029d3 100644 --- a/include/mega/filefingerprint.h +++ b/include/mega/filefingerprint.h @@ -21,6 +21,8 @@ #pragma once +#include + #include "types.h" #include "filesystem.h" @@ -31,14 +33,18 @@ struct MEGA_API FileFingerprint : public Cachable { m_off_t size = -1; m_time_t mtime = 0; - int32_t crc[4]{}; + std::array crc{}; // if true, represents actual file data // if false, is constructed from node ctime/key bool isvalid = false; + // Generates a fingerprint by iterating through`fa` bool genfingerprint(FileAccess* fa, bool ignoremtime = false); + + // Generates a fingerprint by iterating through `is` bool genfingerprint(InputStreamAccess* is, m_time_t cmtime, bool ignoremtime = false); + void serializefingerprint(string* d) const; int unserializefingerprint(string* d); @@ -59,4 +65,26 @@ struct MEGA_API FileFingerprintCmp bool operator==(const FileFingerprint& lhs, const FileFingerprint& rhs); +// A light-weight fingerprint only based on size and mtime +struct MEGA_API LightFileFingerprint +{ + m_off_t size = -1; + m_time_t mtime = 0; + + LightFileFingerprint() = default; + + MEGA_DEFAULT_COPY_MOVE(LightFileFingerprint) + + // Establishes a new fingerprint not involving I/O + bool genfingerprint(m_off_t filesize, m_time_t filemtime); +}; + +// Orders light file fingerprints by size and mtime in terms of "<" +struct MEGA_API LightFileFingerprintCmp +{ + bool operator()(const LightFileFingerprint* a, const LightFileFingerprint* b) const; +}; + +bool operator==(const LightFileFingerprint& lhs, const LightFileFingerprint& rhs); + } // mega diff --git a/include/mega/logging.h b/include/mega/logging.h index ea05f6cd4a..13a4b3cc06 100644 --- a/include/mega/logging.h +++ b/include/mega/logging.h @@ -1,4 +1,4 @@ -/** +/** * @file mega/logging.h * @brief Logging class * @@ -104,6 +104,8 @@ #include #endif +#include "mega/utils.h" + namespace mega { // available log levels @@ -150,14 +152,16 @@ class SimpleLogger #else std::array mBuffer; // will be stack-allocated since SimpleLogger is stack-allocated std::array::iterator mBufferIt; + using DiffType = std::array::difference_type; + using NumBuf = std::array; template - void copyToBuffer(const DataIterator dataIt, size_t currentSize) + void copyToBuffer(const DataIterator dataIt, DiffType currentSize) { - size_t start = 0; + DiffType start = 0; while (currentSize > 0) { - const auto size = std::min(currentSize, static_cast(std::distance(mBufferIt, mBuffer.end() - 1))); + const auto size = std::min(currentSize, std::distance(mBufferIt, mBuffer.end() - 1)); mBufferIt = std::copy(dataIt + start, dataIt + start + size, mBufferIt); if (mBufferIt == mBuffer.end() - 1) { @@ -170,30 +174,30 @@ class SimpleLogger void outputBuffer() { + *mBufferIt = '\0'; if (logger) { - *mBufferIt = 0; logger->log(nullptr, level, nullptr, mBuffer.data()); - mBufferIt = mBuffer.begin(); } + mBufferIt = mBuffer.begin(); } template typename std::enable_if::value>::type logValue(const T value) { - char str[20]; - const auto size = std::sprintf(str, "%d", static_cast(value)); - copyToBuffer(str, size); + NumBuf buf; + const auto size = snprintf(buf.data(), buf.size(), "%d", static_cast(value)); + copyToBuffer(buf.data(), std::min(size, static_cast(buf.size()) - 1)); } template typename std::enable_if::value && !std::is_same::value>::type logValue(const T value) { - char str[20]; - const auto size = std::sprintf(str, "%p", reinterpret_cast(value)); - copyToBuffer(str, size); + NumBuf buf; + const auto size = snprintf(buf.data(), buf.size(), "%p", reinterpret_cast(value)); + copyToBuffer(buf.data(), std::min(size, static_cast(buf.size()) - 1)); } template @@ -201,9 +205,9 @@ class SimpleLogger && !std::is_same::value && !std::is_same::value>::type logValue(const T value) { - char str[20]; - const auto size = std::sprintf(str, "%d", value); - copyToBuffer(str, size); + NumBuf buf; + const auto size = snprintf(buf.data(), buf.size(), "%d", value); + copyToBuffer(buf.data(), std::min(size, static_cast(buf.size()) - 1)); } template @@ -211,9 +215,9 @@ class SimpleLogger && std::is_same::value && !std::is_same::value>::type logValue(const T value) { - char str[20]; - const auto size = std::sprintf(str, "%ld", value); - copyToBuffer(str, size); + NumBuf buf; + const auto size = snprintf(buf.data(), buf.size(), "%ld", value); + copyToBuffer(buf.data(), std::min(size, static_cast(buf.size()) - 1)); } template @@ -221,9 +225,9 @@ class SimpleLogger && !std::is_same::value && std::is_same::value>::type logValue(const T value) { - char str[20]; - const auto size = std::sprintf(str, "%lld", value); - copyToBuffer(str, size); + NumBuf buf; + const auto size = snprintf(buf.data(), buf.size(), "%lld", value); + copyToBuffer(buf.data(), std::min(size, static_cast(buf.size()) - 1)); } template @@ -231,9 +235,9 @@ class SimpleLogger && !std::is_same::value && !std::is_same::value>::type logValue(const T value) { - char str[20]; - const auto size = std::sprintf(str, "%u", value); - copyToBuffer(str, size); + NumBuf buf; + const auto size = snprintf(buf.data(), buf.size(), "%u", value); + copyToBuffer(buf.data(), std::min(size, static_cast(buf.size()) - 1)); } template @@ -241,9 +245,9 @@ class SimpleLogger && std::is_same::value && !std::is_same::value>::type logValue(const T value) { - char str[20]; - const auto size = std::sprintf(str, "%lu", value); - copyToBuffer(str, size); + NumBuf buf; + const auto size = snprintf(buf.data(), buf.size(), "%lu", value); + copyToBuffer(buf.data(), std::min(size, static_cast(buf.size()) - 1)); } template @@ -251,28 +255,28 @@ class SimpleLogger && !std::is_same::value && std::is_same::value>::type logValue(const T value) { - char str[20]; - const auto size = std::sprintf(str, "%llu", value); - copyToBuffer(str, size); + NumBuf buf; + const auto size = snprintf(buf.data(), buf.size(), "%llu", value); + copyToBuffer(buf.data(), std::min(size, static_cast(buf.size()) - 1)); } template typename std::enable_if::value>::type logValue(const T value) { - char str[20]; - const auto size = std::sprintf(str, "%f", value); - copyToBuffer(str, size); + NumBuf buf; + const auto size = snprintf(buf.data(), buf.size(), "%g", value); + copyToBuffer(buf.data(), std::min(size, static_cast(buf.size()) - 1)); } void logValue(const char* value) { - copyToBuffer(value, std::strlen(value)); + copyToBuffer(value, static_cast(std::strlen(value))); } void logValue(const std::string& value) { - copyToBuffer(value.begin(), value.size()); + copyToBuffer(value.begin(), static_cast(value.size())); } #endif @@ -288,7 +292,15 @@ class SimpleLogger #endif { #ifdef ENABLE_LOG_PERFORMANCE - logValue(filename); + const char * actualFileName = strrchr(filename, '/'); //The project part of the path uses `/` for Windows too. + if (actualFileName) + { + logValue(actualFileName+1); + } + else + { + logValue(filename); + } copyToBuffer(":", 1); logValue(line); copyToBuffer(" ", 1); @@ -350,7 +362,7 @@ class SimpleLogger SimpleLogger& operator<<(T* obj) { #ifdef ENABLE_LOG_PERFORMANCE - if (obj != NULL) + if (obj) { logValue(obj); } @@ -359,7 +371,7 @@ class SimpleLogger copyToBuffer("(NULL)", 6); } #else - if (obj != NULL) + if (obj) { ostr << obj; } @@ -374,6 +386,7 @@ class SimpleLogger template ::value>::type> SimpleLogger& operator<<(const T obj) { + static_assert(!std::is_same::value, "T cannot be nullptr_t"); #ifdef ENABLE_LOG_PERFORMANCE logValue(obj); #else diff --git a/include/mega/megaapp.h b/include/mega/megaapp.h index 10149171e7..a9f954e47c 100644 --- a/include/mega/megaapp.h +++ b/include/mega/megaapp.h @@ -146,7 +146,7 @@ struct MEGA_API MegaApp virtual void putfa_result(handle, fatype, const char*) { } // purchase transactions - virtual void enumeratequotaitems_result(handle, unsigned, unsigned, unsigned, unsigned, unsigned, const char*, const char*, const char*, const char*) { } + virtual void enumeratequotaitems_result(unsigned, handle, unsigned, int, int, unsigned, unsigned, unsigned, const char*, const char*, const char*, const char*) { } virtual void enumeratequotaitems_result(error) { } virtual void additem_result(error) { } virtual void checkout_result(const char*, error) { } @@ -156,8 +156,11 @@ struct MEGA_API MegaApp virtual void creditcardcancelsubscriptions_result(error) {} virtual void getpaymentmethods_result(int, error) {} virtual void copysession_result(string*, error) { } + + // feedback from user/client virtual void userfeedbackstore_result(error) { } virtual void sendevent_result(error) { } + virtual void supportticket_result(error) { } // user invites/attributes virtual void removecontact_result(error) { } @@ -218,6 +221,9 @@ struct MEGA_API MegaApp // get change email link result virtual void getemaillink_result(error) {} + // resend verification email + virtual void resendverificationemail_result(error) {}; + // confirm change email link result virtual void confirmemaillink_result(error) {} diff --git a/include/mega/megaclient.h b/include/mega/megaclient.h index 79d0f687b0..a3f9a1a7cb 100644 --- a/include/mega/megaclient.h +++ b/include/mega/megaclient.h @@ -321,6 +321,9 @@ class MEGA_API MegaClient // create a copy of the current session void copysession(); + // resend the verification email to the same email address as it was previously sent to + void resendverificationemail(); + // get the data for a session transfer // the caller takes the ownership of the returned value // if the second parameter isn't NULL, it's used as session id instead of the current one @@ -330,14 +333,14 @@ class MEGA_API MegaClient void killsession(handle session); void killallsessions(); - // extract public handle and key from folder link - error parsefolderlink(const char* folderlink, handle &h, byte *key); + // extract public handle and key from a public file/folder link + error parsepubliclink(const char *link, handle &ph, byte *key, bool isFolderLink); // set folder link: node, key error folderaccess(const char*folderlink); - // open exported file link - error openfilelink(const char*, int); + // open exported file link (op=0 -> download, op=1 fetch data) + void openfilelink(handle ph, const byte *key, int op); // decrypt password-protected public link // the caller takes the ownership of the returned value in decryptedLink parameter @@ -462,7 +465,7 @@ class MEGA_API MegaClient void putfa(handle, fatype, SymmCipher*, string*, bool checkAccess = true); // queue file attribute retrieval - error getfa(handle h, string *fileattrstring, string *nodekey, fatype, int = 0); + error getfa(handle h, string *fileattrstring, const string &nodekey, fatype, int = 0); // notify delayed upload completion subsystem about new file attribute void checkfacompletion(handle, Transfer* = NULL); @@ -523,10 +526,7 @@ class MEGA_API MegaClient void logout(); // free all state information - void locallogout(); - - // remove caches - void removecaches(); + void locallogout(bool removecaches); // SDK version const char* version(); @@ -598,6 +598,9 @@ class MEGA_API MegaClient void sendevent(int, const char *); void sendevent(int, const char *, int tag); + // create support ticket + void supportticket(const char *message, int type); + // clean rubbish bin void cleanrubbishbin(); @@ -880,6 +883,9 @@ class MEGA_API MegaClient void init(); + // remove caches + void removeCaches(); + // add node to vector and return index unsigned addnode(node_vector*, Node*) const; @@ -1001,6 +1007,7 @@ class MEGA_API MegaClient // Server-MegaClient request JSON and processing state flag ("processing a element") JSON jsonsc; bool insca; + bool insca_notlast; // no two interrelated client instances should ever have the same sessionid char sessionid[10]; @@ -1051,6 +1058,7 @@ class MEGA_API MegaClient // transfer queues (PUT/GET) transfer_map transfers[2]; + BackoffTimerGroupTracker transferRetryBackoffs[2]; // transfer list to manage the priority of transfers TransferList transferlist; @@ -1073,6 +1081,9 @@ class MEGA_API MegaClient // transfer tslots transferslot_list tslots; + // keep track of next transfer slot timeout + BackoffTimerGroupTracker transferSlotsBackoff; + // next TransferSlot to doio() on transferslot_list::iterator slotit; @@ -1100,6 +1111,9 @@ class MEGA_API MegaClient // total number of Node objects long long totalNodes; + // tracks how many nodes have had a successful applykey() + long long mAppliedKeyNodeCount = 0; + // server-client request sequence number char scsn[12]; @@ -1302,7 +1316,7 @@ class MEGA_API MegaClient dstime disconnecttimestamp; // process object arrays by the API server - int readnodes(JSON*, int, putsource_t = PUTNODES_APP, NewNode* = NULL, int = 0, int = 0); + int readnodes(JSON*, int, putsource_t = PUTNODES_APP, NewNode* = NULL, int = 0, int = 0, bool applykeys = false); void readok(JSON*); void readokelement(JSON*); @@ -1404,7 +1418,10 @@ class MEGA_API MegaClient string clientname; // apply keys - int applykeys(); + void applykeys(); + + // send andy key rewrites prepared when keys were applied + void sendkeyrewrites(); // symmetric password challenge int checktsid(byte* sidbuf, unsigned len); @@ -1566,7 +1583,7 @@ class MEGA_API MegaClient uint64_t prepwaitImmediate = 0, prepwaitZero = 0, prepwaitHttpio = 0, prepwaitFsaccess = 0, nonzeroWait = 0; CodeCounter::DurationSum csRequestWaitTime; CodeCounter::DurationSum transfersActiveTime; - std::string report(bool reset, HttpIO* httpio, Waiter* waiter); + std::string report(bool reset, HttpIO* httpio, Waiter* waiter, const RequestDispatcher& reqs); } performanceStats; MegaClient(MegaApp*, Waiter*, HttpIO*, FileSystemAccess*, DbAccess*, GfxProc*, const char*, const char*); diff --git a/include/mega/node.h b/include/mega/node.h index 3e557caa12..80aa929c2c 100644 --- a/include/mega/node.h +++ b/include/mega/node.h @@ -1,3 +1,4 @@ + /** * @file mega/node.h * @brief Classes for accessing local and remote nodes @@ -41,13 +42,9 @@ struct MEGA_API NodeCore // node type nodetype_t type; - // full folder/file key, symmetrically or asymmetrically encrypted - // node crypto keys (raw or cooked - - // cooked if size() == FOLDERNODEKEYLENGTH or FILEFOLDERNODEKEYLENGTH) - string nodekey; - // node attributes string *attrstring; + }; // new node for putnodes() @@ -56,11 +53,13 @@ struct MEGA_API NewNode : public NodeCore static const int OLDUPLOADTOKENLEN = 27; static const int UPLOADTOKENLEN = 36; + string nodekey; + newnodesource_t source; handle ovhandle; handle uploadhandle; - byte uploadtoken[UPLOADTOKENLEN]; + byte uploadtoken[UPLOADTOKENLEN]{}; handle syncid; LocalNode* localnode; @@ -110,7 +109,16 @@ struct Fingerprints // filesystem node struct MEGA_API Node : public NodeCore, FileFingerprint { - MegaClient* client; + MegaClient* client = nullptr; + + // supplies the nodekey (which is private to ensure we track changes to it) + const string& nodekey() const; + + // Also returns the key but does not assert that the key has been applied. Only use it where we don't need the node to be readable. + const string& nodekeyUnchecked() const; + + // check if the key is present and is the correct size for this node + bool keyApplied() const; // change parent node association bool setparent(Node*); @@ -140,10 +148,10 @@ struct MEGA_API Node : public NodeCore, FileFingerprint AttrMap attrs; // owner - handle owner; + handle owner = mega::UNDEF; // actual time this node was created (cannot be set by user) - m_time_t ctime; + m_time_t ctime = 0; // file attributes string fileattrstring; @@ -159,21 +167,21 @@ struct MEGA_API Node : public NodeCore, FileFingerprint static void parseattr(byte*, AttrMap&, m_off_t, m_time_t&, string&, string&, FileFingerprint&); // inbound share - Share* inshare; + Share* inshare = nullptr; // outbound shares by user - share_map *outshares; + share_map* outshares = nullptr; // outbound pending shares - share_map *pendingshares; + share_map* pendingshares = nullptr; // incoming/outgoing share key - SymmCipher* sharekey; + SymmCipher* sharekey = nullptr; // app-private pointer - void* appdata; + void* appdata = nullptr; - bool foreignkey; + bool foreignkey = false; struct { @@ -192,6 +200,8 @@ struct MEGA_API Node : public NodeCore, FileFingerprint void setkey(const byte* = NULL); + void setkeyfromjson(const char*); + void setfingerprint(); void faspec(string*); @@ -199,7 +209,7 @@ struct MEGA_API Node : public NodeCore, FileFingerprint NodeCounter subnodeCounts() const; // parent - Node* parent; + Node* parent = nullptr; // children node_list children; @@ -212,13 +222,13 @@ struct MEGA_API Node : public NodeCore, FileFingerprint #ifdef ENABLE_SYNC // related synced item or NULL - LocalNode* localnode; + LocalNode* localnode = nullptr; // active sync get - struct SyncFileGet* syncget; + struct SyncFileGet* syncget = nullptr; // state of removal to //bin / SyncDebris - syncdel_t syncdeleted; + syncdel_t syncdeleted = SYNCDEL_NONE; // location in the todebris node_set node_set::iterator todebris_it; @@ -229,62 +239,88 @@ struct MEGA_API Node : public NodeCore, FileFingerprint #endif // source tag - int tag; + int tag = 0; // check if node is below this node bool isbelow(Node*) const; // handle of public link for the node - PublicLink *plink; + PublicLink* plink = nullptr; void setpubliclink(handle, m_time_t, m_time_t, bool); bool serialize(string*); - static Node* unserialize(MegaClient*, string*, node_vector*); + static Node* unserialize(MegaClient*, const string*, node_vector*); Node(MegaClient*, vector*, handle, handle, nodetype_t, m_off_t, handle, const char*, m_time_t); ~Node(); + +private: + // full folder/file key, symmetrically or asymmetrically encrypted + // node crypto keys (raw or cooked - + // cooked if size() == FOLDERNODEKEYLENGTH or FILEFOLDERNODEKEYLENGTH) + string nodekeydata; }; +inline const string& Node::nodekey() const +{ + assert(keyApplied() || type == ROOTNODE || type == INCOMINGNODE || type == RUBBISHNODE); + return nodekeydata; +} + +inline const string& Node::nodekeyUnchecked() const +{ + return nodekeydata; +} + +inline bool Node::keyApplied() const +{ + return nodekeydata.size() == size_t((type == FILENODE) ? FILENODEKEYLENGTH : FOLDERNODEKEYLENGTH); +} + + #ifdef ENABLE_SYNC struct MEGA_API LocalNode : public File { - class Sync* sync; + class Sync* sync = nullptr; // parent linkage - LocalNode* parent; + LocalNode* parent = nullptr; // stored to rebuild tree after serialization => this must not be a pointer to parent->dbid - int32_t parent_dbid; + int32_t parent_dbid = 0; + + // whether this node can be synced to the remote tree + bool mSyncable = true; // children by name localnode_map children; // for botched filesystems with legacy secondary ("short") names - string *slocalname; + string* slocalname = nullptr; localnode_map schildren; // local filesystem node ID (inode...) for rename/move detection - handle fsid; - handlelocalnode_map::iterator fsid_it; + handle fsid = mega::UNDEF; + handlelocalnode_map::iterator fsid_it{}; // related cloud node, if any - Node* node; + Node* node = nullptr; // related pending node creation or NULL - NewNode* newnode; + NewNode* newnode = nullptr; // FILENODE or FOLDERNODE - nodetype_t type; + nodetype_t type = TYPE_UNKNOWN; // detection of deleted filesystem records - int scanseqno; + int scanseqno = 0; // number of iterations since last seen - int notseen; + int notseen = 0; // global sync reference - handle syncid; + handle syncid = mega::UNDEF; struct { @@ -302,7 +338,8 @@ struct MEGA_API LocalNode : public File }; // current subtree sync state: current and displayed - treestate_t ts, dts; + treestate_t ts = TREESTATE_NONE; + treestate_t dts = TREESTATE_NONE; // update sync state all the way to the root node void treestate(treestate_t = TREESTATE_NONE); @@ -311,14 +348,14 @@ struct MEGA_API LocalNode : public File treestate_t checkstate(); // timer to delay upload start - dstime nagleds; + dstime nagleds = 0; void bumpnagleds(); // if delage > 0, own iterator inside MegaClient::localsyncnotseen - localnode_set::iterator notseen_it; + localnode_set::iterator notseen_it{}; // build full local path to this node - void getlocalpath(string*, bool sdisable = false) const; + void getlocalpath(string*, bool sdisable = false, const std::string* localseparator = nullptr) const; void getlocalsubpath(string*) const; string localnodedisplaypath(FileSystemAccess& fsa) const; @@ -327,7 +364,7 @@ struct MEGA_API LocalNode : public File #ifdef USE_INOTIFY // node-specific DirNotify tag - handle dirnotifytag; + handle dirnotifytag = mega::UNDEF; #endif void prepare(); @@ -337,7 +374,9 @@ struct MEGA_API LocalNode : public File void setnotseen(int); - void setfsid(handle); + // set fsid - assume that an existing assignment of the same fsid is no longer current and revoke. + // fsidnodes is a map from fsid to LocalNode, keeping track of all fs ids. + void setfsid(handle newfsid, handlelocalnode_map& fsidnodes); void setnameparent(LocalNode*, string*); @@ -345,7 +384,7 @@ struct MEGA_API LocalNode : public File void init(Sync*, nodetype_t, LocalNode*, string*); virtual bool serialize(string*); - static LocalNode* unserialize( Sync* sync, string* sData ); + static LocalNode* unserialize( Sync* sync, const string* sData ); ~LocalNode(); }; diff --git a/include/mega/posix/megawaiter.h b/include/mega/posix/megawaiter.h index e2084ec5ce..324860753b 100644 --- a/include/mega/posix/megawaiter.h +++ b/include/mega/posix/megawaiter.h @@ -23,6 +23,7 @@ #define WAIT_CLASS PosixWaiter #include "mega/waiter.h" +#include namespace mega { struct PosixWaiter : public Waiter diff --git a/include/mega/sharenodekeys.h b/include/mega/sharenodekeys.h index b82f830f49..464fb4fb5f 100644 --- a/include/mega/sharenodekeys.h +++ b/include/mega/sharenodekeys.h @@ -36,8 +36,13 @@ class MEGA_API ShareNodeKeys int addshare(Node*); public: + // a convenience function for calling the full add() below when working with Node* void add(Node*, Node*, int); - void add(NodeCore*, Node*, int, const byte* = NULL, int = 0); + + // Adds keys needed for sharing the node (specifed wtih nodekey/nodehandle) to the `keys` and `items` collections. + // Each node may be in multiple shares so the parent chain is traversed and if this node is in multiple shares, then multiple keys are added. + // The result is suitable for sending all the collected keys for each share, per Node, to the API. + void add(const string& nodekey, handle nodehandle, Node*, int, const byte* = NULL, int = 0); void get(Command*, bool skiphandles = false); }; diff --git a/include/mega/sync.h b/include/mega/sync.h index aa6c1414ef..645e6322a7 100644 --- a/include/mega/sync.h +++ b/include/mega/sync.h @@ -30,13 +30,11 @@ namespace mega { // Returns true for a path that can be synced (.debris is not one of those). bool isPathSyncable(const string& localpath, const string& localdebris, const string& localseparator); -// Invalidates the fs IDs of all local nodes below `l` and removes them from `fsidnodes`. -void invalidateFilesystemIds(handlelocalnode_map& fsidnodes, LocalNode& l, size_t& count); - // Searching from the back, this function compares path1 and path2 character by character and // returns the number of consecutive character matches (excluding separators) but only including whole node names. // It's assumed that the paths are normalized (e.g. not contain ..) and separated with the given `localseparator`. -int computeReversePathMatchScore(const string& path1, const string& path2, const string& localseparator); +// `accumulated` is a buffer that is used to avoid constant reallocations. +int computeReversePathMatchScore(string& accumulated, const string& path1, const string& path2, const string& localseparator); // Recursively iterates through the filesystem tree starting at the sync root and assigns // fs IDs to those local nodes that match the fingerprint retrieved from disk. @@ -46,15 +44,15 @@ bool assignFilesystemIds(Sync& sync, MegaApp& app, FileSystemAccess& fsaccess, h class MEGA_API Sync { public: - void *appData; + void* appData = nullptr; - MegaClient* client; + MegaClient* client = nullptr; // sync-wide directory notification provider std::unique_ptr dirnotify; - // root of local filesystem tree, holding the sync's root folder - LocalNode localroot; + // root of local filesystem tree, holding the sync's root folder. Never null except briefly in the destructor (to ensure efficient db usage) + unique_ptr localroot; // Path used to normalize sync locaroot name when using prefix /System/Volumes/Data needed by fsevents, due to notification paths // are served with such prefix from macOS catalina + @@ -62,13 +60,13 @@ class MEGA_API Sync string mFsEventsPath; #endif // current state - syncstate_t state; + syncstate_t state = SYNC_INITIALSCAN; // are we conducting a full tree scan? (during initialization and if event notification failed) - bool fullscan; + bool fullscan = true; // syncing to an inbound share? - bool inshare; + bool inshare = false; // deletion queue set deleteq; @@ -100,8 +98,8 @@ class MEGA_API Sync // scan specific path LocalNode* checkpath(LocalNode*, string*, string* = NULL, dstime* = NULL, bool wejustcreatedthisfolder = false); - m_off_t localbytes; - unsigned localnodes[2]; + m_off_t localbytes = 0; + unsigned localnodes[2]{}; // look up LocalNode relative to localroot LocalNode* localnodebypath(LocalNode*, string*, LocalNode** = NULL, string* = NULL); @@ -115,14 +113,14 @@ class MEGA_API Sync bool scan(string*, FileAccess*); // own position in session sync list - sync_list::iterator sync_it; + sync_list::iterator sync_it{}; // rescan sequence number (incremented when a full rescan or a new // notification batch starts) - int scanseqno; + int scanseqno = 0; // notified nodes originating from this sync bear this tag - int tag; + int tag = 0; // debris path component relative to the base path string debris, localdebris; @@ -131,31 +129,32 @@ class MEGA_API Sync std::unique_ptr tmpfa; // state cache table - DbTable* statecachetable; + DbTable* statecachetable = nullptr; // move file or folder to localdebris bool movetolocaldebris(string* localpath); // original filesystem fingerprint - fsfp_t fsfp; + fsfp_t fsfp = 0; // does the filesystem have stable IDs? (FAT does not) bool fsstableids = false; // Error that causes a cancellation - error errorcode; + error errorcode = API_OK; // true if the sync hasn't loaded cached LocalNodes yet - bool initializing; + bool initializing = true; // true if the local synced folder is a network folder - bool isnetwork; + bool isnetwork = false; // values related to possible files being updated - m_off_t updatedfilesize; - m_time_t updatedfilets; - m_time_t updatedfileinitialts; - + m_off_t updatedfilesize = ~0; + m_time_t updatedfilets = 0; + m_time_t updatedfileinitialts = 0; + + Sync() = default; Sync(MegaClient*, string*, const char*, string*, Node*, fsfp_t, bool, int, void*); ~Sync(); diff --git a/include/mega/testhooks.h b/include/mega/testhooks.h index 0cbd27f21b..1af85b74bd 100644 --- a/include/mega/testhooks.h +++ b/include/mega/testhooks.h @@ -33,7 +33,7 @@ namespace mega { // The preprocessor is used to ensure that code is not present for release builds, so it can't cause problems. // Additionally the hooks use std::function so a suitable compiler and library are needed to leverage those tests. -#if defined(_DEBUG) +#ifdef DEBUG #define MEGASDK_DEBUG_TEST_HOOKS_ENABLED diff --git a/include/mega/transfer.h b/include/mega/transfer.h index 3764c85fae..dbe8361d46 100644 --- a/include/mega/transfer.h +++ b/include/mega/transfer.h @@ -47,7 +47,7 @@ struct MEGA_API Transfer : public FileFingerprint // failures/backoff unsigned failcount; - BackoffTimer bt; + BackoffTimerTracked bt; // representative local filename for this transfer string localfilename; @@ -99,8 +99,8 @@ struct MEGA_API Transfer : public FileFingerprint // execute completion void completefiles(); - // next position to download/upload - m_off_t nextpos(); + // remove file from transfer including in cache + void removeTransferFile(error, File* f, DBTableTransactionCommitter* committer); // previous wrong fingerprint FileFingerprint badfp; diff --git a/include/mega/transferslot.h b/include/mega/transferslot.h index c402688227..e5298c5ab3 100644 --- a/include/mega/transferslot.h +++ b/include/mega/transferslot.h @@ -29,6 +29,21 @@ namespace mega { +// Helper class: Automatically manage backoff timer enablement - if the slot is in progress and has an fa, the transfer's backoff timer should not be considered +// (part of a performance upgrade, so we don't loop all the transfers, calling their bt.update() on every preparewait() ) +class TransferSlotFileAccess +{ + std::unique_ptr fa; + Transfer* transfer; +public: + TransferSlotFileAccess(std::unique_ptr&& p, Transfer* t); + ~TransferSlotFileAccess(); + void reset(std::unique_ptr&& p = nullptr); + inline operator bool() { return bool(fa); } + inline FileAccess* operator->() { return fa.get(); } + inline operator FileAccess* () { return fa.get(); } +}; + class DBTableTransactionCommitter; // active transfer @@ -38,7 +53,7 @@ struct MEGA_API TransferSlot struct Transfer* transfer; // associated source/destination file - std::unique_ptr fa; + TransferSlotFileAccess fa; // command in flight to obtain temporary URL Command* pendingcmd; @@ -116,7 +131,7 @@ struct MEGA_API TransferSlot // slot operation retry timer bool retrying; - BackoffTimer retrybt; + BackoffTimerTracked retrybt; // transfer failure flag. MegaClient will increment the transfer->errorcount when it sees this set. bool failure; @@ -124,10 +139,10 @@ struct MEGA_API TransferSlot TransferSlot(Transfer*); ~TransferSlot(); -protected: +private: void toggleport(HttpReqXfer* req); bool tryRaidRecoveryFromHttpGetError(unsigned i); - + bool checkTransferFinished(DBTableTransactionCommitter& committer, MegaClient* client); }; } // namespace diff --git a/include/mega/types.h b/include/mega/types.h index 3025a3176e..3e739860fb 100644 --- a/include/mega/types.h +++ b/include/mega/types.h @@ -188,10 +188,11 @@ typedef enum ErrorCodes API_EINTERNAL = -1, ///< Internal error. API_EARGS = -2, ///< Bad arguments. API_EAGAIN = -3, ///< Request failed, retry with exponential backoff. - API_ERATELIMIT = -4, ///< Too many requests, slow down. - API_EFAILED = -5, ///< Request failed permanently. + DAEMON_EFAILED = -4, ///< If returned from the daemon: EFAILED + API_ERATELIMIT = -4, ///< If returned from the API: Too many requests, slow down. + API_EFAILED = -5, ///< Request failed permanently. This one is only produced by the API, only per command (not batch level) API_ETOOMANY = -6, ///< Too many requests for this resource. - API_ERANGE = -7, ///< Resource access out of rage. + API_ERANGE = -7, ///< Resource access out of range. API_EEXPIRED = -8, ///< Resource expired. API_ENOENT = -9, ///< Resource does not exist. API_ECIRCULAR = -10, ///< Circular linkage. diff --git a/include/mega/version.h b/include/mega/version.h index 434cccc9ab..505828bb6c 100644 --- a/include/mega/version.h +++ b/include/mega/version.h @@ -5,5 +5,5 @@ #define MEGA_MINOR_VERSION 6 #endif #ifndef MEGA_MICRO_VERSION -#define MEGA_MICRO_VERSION 5 +#define MEGA_MICRO_VERSION 6 #endif diff --git a/include/megaapi.h b/include/megaapi.h index ba4e1276cd..49c88c2b47 100644 --- a/include/megaapi.h +++ b/include/megaapi.h @@ -2551,6 +2551,11 @@ class MegaUserAlertList * @return Number of MegaUserAlert objects in the list */ virtual int size() const; + + /** + * @brief Removes all MegaUserAlert objects from the list (does not delete them) + */ + virtual void clear(); }; @@ -2744,7 +2749,8 @@ class MegaRequest TYPE_GET_CLOUD_STORAGE_USED, TYPE_SEND_SMS_VERIFICATIONCODE, TYPE_CHECK_SMS_VERIFICATIONCODE, TYPE_GET_REGISTERED_CONTACTS, TYPE_GET_COUNTRY_CALLING_CODES, - TYPE_VERIFY_CREDENTIALS, TYPE_GET_MISC_FLAGS, + TYPE_VERIFY_CREDENTIALS, TYPE_GET_MISC_FLAGS, TYPE_RESEND_VERIFICATION_EMAIL, + TYPE_SUPPORT_TICKET, TOTAL_OF_REQUEST_TYPES }; @@ -3649,6 +3655,7 @@ class MegaTransfer * @brief Returns the parent path related to this request * * For uploads, this function returns the path to the folder containing the source file. + * except when uploading files for support: it will return the support account then. * For downloads, it returns that path to the folder containing the destination file. * * The SDK retains the ownership of the returned value. It will be valid until @@ -5183,7 +5190,7 @@ class MegaError API_ERATELIMIT = -4, ///< Too many requests, slow down. API_EFAILED = -5, ///< Request failed permanently. API_ETOOMANY = -6, ///< Too many requests for this resource. - API_ERANGE = -7, ///< Resource access out of rage. + API_ERANGE = -7, ///< Resource access out of range. API_EEXPIRED = -8, ///< Resource expired. API_ENOENT = -9, ///< Resource does not exist. API_ECIRCULAR = -10, ///< Circular linkage. @@ -5926,6 +5933,8 @@ class MegaGlobalListener * 300: suspension only for multiple copyright violations. * 400: the subuser account has been disabled. * 401: the subuser account has been removed. + * 500: The account needs to be verified by an SMS code. + * 700: the account is supended for Weak Account Protection. * * - MegaEvent::EVENT_STORAGE: when the status of the storage changes. * @@ -6416,6 +6425,8 @@ class MegaListener * 300: suspension only for multiple copyright violations. * 400: the subuser account has been disabled. * 401: the subuser account has been removed. + * 500: The account needs to be verified by an SMS code. + * 700: the account is supended for Weak Account Protection. * * - MegaEvent::EVENT_STORAGE: when the status of the storage changes. * @@ -7296,8 +7307,10 @@ class MegaApi /** * @brief Check if server-side Rubbish Bin autopurging is enabled for the current account * - * Before using this function, it's needed to: - * - If you are logged-in: call to MegaApi::login and MegaApi::fetchNodes. + * This function will NOT return a valid value until the callback onEvent with + * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of + * a fetchnodes to check this value, but only when it follows a login with user and password, + * not when an existing session is resumed. * * @return True if this feature is enabled. Otherwise false. */ @@ -7306,8 +7319,10 @@ class MegaApi /** * @brief Check if the account has VOIP push enabled * - * Before using this function, it's needed to: - * - If you are logged-in: call to MegaApi::login and MegaApi::fetchNodes. + * This function will NOT return a valid value until the callback onEvent with + * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of + * a fetchnodes to check this value, but only when it follows a login with user and password, + * not when an existing session is resumed. * * @return True if this feature is enabled. Otherwise false. */ @@ -7316,9 +7331,12 @@ class MegaApi /** * @brief Check if the new format for public links is enabled * - * Before using this function, it's needed to: - * - If you are logged-in: call to MegaApi::login and MegaApi::fetchNodes. - * - If you are not logged-in: call to MegaApi::getMiscFlags. + * This function will NOT return a valid value until the callback onEvent with + * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of + * a fetchnodes to check this value, but only when it follows a login with user and password, + * not when an existing session is resumed. + * + * For not logged-in mode, you need to call MegaApi::getMiscFlags first. * * @return True if this feature is enabled. Otherwise, false. */ @@ -7329,9 +7347,12 @@ class MegaApi * * The result indicated whether the MegaApi::sendSMSVerificationCode function can be used. * - * Before using this function, it's needed to: - * - If you are logged-in: call to MegaApi::login and MegaApi::fetchNodes. - * - If you are not logged-in: call to MegaApi::getMiscFlags. + * This function will NOT return a valid value until the callback onEvent with + * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of + * a fetchnodes to check this value, but only when it follows a login with user and password, + * not when an existing session is resumed. + * + * For not logged-in mode, you need to call MegaApi::getMiscFlags first. * * @return 2 = Opt-in and unblock SMS allowed. 1 = Only unblock SMS allowed. 0 = No SMS allowed */ @@ -7352,9 +7373,12 @@ class MegaApi /** * @brief Check if multi-factor authentication can be enabled for the current account. * - * Before using this function, it's needed to: - * - If you are logged-in: call to MegaApi::login and MegaApi::fetchNodes. - * - If you are not logged-in: call to MegaApi::getMiscFlags. + * This function will NOT return a valid value until the callback onEvent with + * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of + * a fetchnodes to check this value, but only when it follows a login with user and password, + * not when an existing session is resumed. + * + * For not logged-in mode, you need to call MegaApi::getMiscFlags first. * * @return True if multi-factor authentication can be enabled for the current account, otherwise false. */ @@ -7830,7 +7854,7 @@ class MegaApi * The associated request type with this request is MegaRequest::TYPE_SEND_SIGNUP_LINK. * * @param email Email for the account - * @param name Firstname of the user + * @param name Fullname of the user (firstname + lastname) * @param password Password for the account * @param listener MegaRequestListener to track this request */ @@ -7843,7 +7867,7 @@ class MegaApi * email address, in case the user mistyped the email at the registration form. * * @param email Email for the account - * @param name Firstname of the user + * @param name Fullname of the user (firstname + lastname) * @param base64pwkey Private key returned by MegaRequest::getPrivateKey in the onRequestFinish callback of createAccount * @param listener MegaRequestListener to track this request * @@ -8046,6 +8070,24 @@ class MegaApi */ void confirmCancelAccount(const char *link, const char *pwd, MegaRequestListener *listener = NULL); + /** + * @brief Allow to resend the verification email for Weak Account Protection + * + * The verification email will be resent to the same address as it was previously sent to. + * + * This function can be called if the the reason for being blocked is: + * 700: the account is supended for Weak Account Protection. + * + * If the logged in account is not suspended or is suspended for some other reason, + * onRequestFinish will be called with the error code MegaError::API_EACCESS. + * + * If the logged in account has not been sent the unlock email before, + * onRequestFinish will be called with the error code MegaError::API_EARGS. + * + * @param listener MegaRequestListener to track this request + */ + void resendVerificationEmail(MegaRequestListener *listener = NULL); + /** * @brief Initialize the change of the email address associated to the account. * @@ -8112,9 +8154,10 @@ class MegaApi * The SDK will start using the provided proxy settings as soon as this function returns. * * @param proxySettings Proxy settings + * @param listener MegaRequestListener to track this request * @see MegaProxy */ - void setProxySettings(MegaProxy *proxySettings); + void setProxySettings(MegaProxy *proxySettings, MegaRequestListener *listener = NULL); /** * @brief Try to detect the system's proxy settings @@ -8153,6 +8196,7 @@ class MegaApi * 400: the subuser account has been disabled. * 401: the subuser account has been removed. * 500: The account needs to be verified by an SMS code. + * 700: the account is supended for Weak Account Protection. * * If the error code in the MegaRequest object received in onRequestFinish * is MegaError::API_OK, the user is not blocked. @@ -8950,6 +8994,21 @@ class MegaApi */ void getPublicNode(const char* megaFileLink, MegaRequestListener *listener = NULL); + /** + * @brief Build the URL for a public link + * + * @note This function does not create the public link itself. It simply builds the URL + * from the provided data. + * + * You take the ownership of the returned value. + * + * @param publicHandle Public handle of the link, in B64url encoding. + * @param key Encryption key of the link. + * @param isFolder True for folder links, false for file links. + * @return The public link for the provided data + */ + const char *buildPublicLink(const char *publicHandle, const char *key, bool isFolder); + /** * @brief Get the thumbnail of a node * @@ -10650,6 +10709,28 @@ class MegaApi */ void sendEvent(int eventType, const char* message, MegaRequestListener *listener = NULL); + /** + * @brief Create a new ticket for support with attached description + * + * The associated request type with this request is MegaRequest::TYPE_SUPPORT_TICKET + * Valid data in the MegaRequest object received on callbacks: + * - MegaRequest::getParamType - Returns the type of the ticket + * - MegaRequest::getText - Returns the description of the issue + * + * @param message Description of the issue for support + * @param type Ticket type. These are the available types: + * 0 for General Enquiry + * 1 for Technical Issue + * 2 for Payment Issue + * 3 for Forgotten Password + * 4 for Transfer Issue + * 5 for Contact/Sharing Issue + * 6 for MEGAsync Issue + * 7 for Missing/Invisible Data + * @param listener MegaRequestListener to track this request + */ + void createSupportTicket(const char* message, int type = 1, MegaRequestListener *listener = NULL); + /** * @brief Send a debug report * @@ -10703,6 +10784,24 @@ class MegaApi /////////////////// TRANSFERS /////////////////// + /** + * @brief Upload a file to support + * + * If the status of the business account is expired, onTransferFinish will be called with the error + * code MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning message similar to + * "Your business account is overdue, please contact your administrator." + * + * For folders, onTransferFinish will be called with error MegaError:API_EARGS; + * + * @param localPath Local path of the file + * @param isSourceTemporary Pass the ownership of the file to the SDK, that will DELETE it when the upload finishes. + * This parameter is intended to automatically delete temporary files that are only created to be uploaded. + * Use this parameter with caution. Set it to true only if you are sure about what are you doing. + * @param listener MegaTransferListener to track this transfer + */ + void startUploadForSupport(const char* localPath, bool isSourceTemporary = false, MegaTransferListener *listener=NULL); + + /** * @brief Upload a file or a folder * @@ -11991,6 +12090,12 @@ class MegaApi */ bool isScanning(); + /** + * @brief Check if any synchronization is in state syncing or pending + * @return true if it is syncing, otherwise false + */ + bool isSyncing(); + /** * @brief Check if the MegaNode is synchronized with a local file * @param MegaNode to check @@ -12783,15 +12888,20 @@ class MegaApi /** * @brief Get the user relative to an incoming share * - * This function will return NULL if the node is not found or doesn't represent - * the root of an incoming share. + * This function will return NULL if the node is not found + * + * When recurse is true and the root of the specified node is not an incoming share, + * this function will return NULL. + * When recurse is false and the specified node doesn't represent the root of an + * incoming share, this function will return NULL. * * You take the ownership of the returned value * - * @param node Incoming share + * @param node Node to look for inshare user. + * @param recurse use root node corresponding to the node passed * @return MegaUser relative to the incoming share */ - MegaUser *getUserFromInShare(MegaNode *node); + MegaUser *getUserFromInShare(MegaNode *node, bool recurse = false); /** * @brief Check if a MegaNode is being shared by/with your own user @@ -16417,16 +16527,20 @@ class MegaPricing /** * @brief Get the number of GB of storage associated with the product * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) - * @return number of GB of storage + * @note business plans have unlimited storage + * @return number of GB of storage, zero if index is invalid, or -1 + * if pricing plan is a business plan */ - virtual unsigned int getGBStorage(int productIndex); + virtual int getGBStorage(int productIndex); /** * @brief Get the number of GB of bandwidth associated with the product * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) - * @return number of GB of bandwidth + * @note business plans have unlimited bandwidth + * @return number of GB of bandwidth, zero if index is invalid, or -1, + * if pricing plan is a business plan */ - virtual unsigned int getGBTransfer(int productIndex); + virtual int getGBTransfer(int productIndex); /** * @brief Get the duration of the product (in months) @@ -16436,7 +16550,7 @@ class MegaPricing virtual int getMonths(int productIndex); /** - * @brief getAmount Get the price of the product (in cents) + * @brief Get the price of the product (in cents) * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Price of the product (in cents) */ @@ -16471,7 +16585,8 @@ class MegaPricing * the MegaPricing object is deleted. * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) - * @return iOS ID of the product + * @return iOS ID of the product, NULL if index is invalid or an empty string + * if pricing plan is a business plan. */ virtual const char* getIosID(int productIndex); @@ -16482,10 +16597,25 @@ class MegaPricing * the MegaPricing object is deleted. * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) - * @return Android ID of the product + * @return Android ID of the product, NULL if index is invalid or an empty string + * if pricing plan is a business plan. */ virtual const char* getAndroidID(int productIndex); + /** + * @brief Returns if the pricing plan is a business plan + * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) + * @return true if the pricing plan is a business plan, otherwise return false + */ + virtual bool isBusinessType(int productIndex); + + /** + * @brief Get the monthly price of the product (in cents) + * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) + * @return Monthly price of the product (in cents) + */ + virtual int getAmountMonth(int productIndex); + /** * @brief Creates a copy of this MegaPricing object. * diff --git a/include/megaapi_impl.h b/include/megaapi_impl.h index 913da7d2ba..27c49c23e3 100644 --- a/include/megaapi_impl.h +++ b/include/megaapi_impl.h @@ -732,7 +732,7 @@ class MegaTransferPrivate : public MegaTransfer, public Cachable MegaHandle nodeHandle; MegaHandle parentHandle; const char* path; - const char* parentPath; + const char* parentPath; //used as targetUser for uploads const char* fileName; char *lastBytes; MegaNode *publicNode; @@ -1058,8 +1058,8 @@ class MegaRequestPrivate : public MegaRequest void setTotalBytes(long long totalBytes); void setTransferredBytes(long long transferredBytes); void setTag(int tag); - void addProduct(handle product, int proLevel, unsigned int gbStorage, unsigned int gbTransfer, - int months, int amount, const char *currency, const char *description, const char *iosid, const char *androidid); + void addProduct(unsigned int type, handle product, int proLevel, int gbStorage, int gbTransfer, + int months, int amount, int amountMonth, const char *currency, const char *description, const char *iosid, const char *androidid); void setProxy(Proxy *proxy); Proxy *getProxy(); void setTimeZoneDetails(MegaTimeZoneDetails *timeZoneDetails); @@ -1334,25 +1334,29 @@ class MegaPricingPrivate : public MegaPricing virtual int getNumProducts(); virtual MegaHandle getHandle(int productIndex); virtual int getProLevel(int productIndex); - virtual unsigned int getGBStorage(int productIndex); - virtual unsigned int getGBTransfer(int productIndex); + virtual int getGBStorage(int productIndex); + virtual int getGBTransfer(int productIndex); virtual int getMonths(int productIndex); virtual int getAmount(int productIndex); virtual const char* getCurrency(int productIndex); virtual const char* getDescription(int productIndex); virtual const char* getIosID(int productIndex); virtual const char* getAndroidID(int productIndex); + virtual bool isBusinessType(int productIndex); + virtual int getAmountMonth(int productIndex); virtual MegaPricing *copy(); - void addProduct(handle product, int proLevel, unsigned int gbStorage, unsigned int gbTransfer, - int months, int amount, const char *currency, const char *description, const char *iosid, const char *androidid); + void addProduct(unsigned int type, handle product, int proLevel, int gbStorage, int gbTransfer, + int months, int amount, int amountMonth, const char *currency, const char *description, const char *iosid, const char *androidid); private: + vector type; vector handles; vector proLevel; - vector gbStorage; - vector gbTransfer; + vector gbStorage; + vector gbTransfer; vector months; vector amount; + vector amountMonth; vector currency; vector description; vector iosId; @@ -1668,6 +1672,7 @@ class MegaUserAlertListPrivate : public MegaUserAlertList virtual MegaUserAlertList *copy() const; virtual MegaUserAlert* get(int i) const; virtual int size() const; + virtual void clear(); protected: MegaUserAlertListPrivate(MegaUserAlertListPrivate *userList); @@ -2040,9 +2045,10 @@ class MegaApiImpl : public MegaApp void confirmResetPasswordLink(const char *link, const char *newPwd, const char *masterKey = NULL, MegaRequestListener *listener = NULL); void cancelAccount(MegaRequestListener *listener = NULL); void confirmCancelAccount(const char *link, const char *pwd, MegaRequestListener *listener = NULL); + void resendVerificationEmail(MegaRequestListener *listener = NULL); void changeEmail(const char *email, MegaRequestListener *listener = NULL); void confirmChangeEmail(const char *link, const char *pwd, MegaRequestListener *listener = NULL); - void setProxySettings(MegaProxy *proxySettings); + void setProxySettings(MegaProxy *proxySettings, MegaRequestListener *listener = NULL); MegaProxy *getAutoProxySettings(); int isLoggedIn(); void whyAmIBlocked(bool logout, MegaRequestListener *listener = NULL); @@ -2088,6 +2094,7 @@ class MegaApiImpl : public MegaApp void decryptPasswordProtectedLink(const char* link, const char* password, MegaRequestListener *listener = NULL); void encryptLinkWithPassword(const char* link, const char* password, MegaRequestListener *listener = NULL); void getPublicNode(const char* megaFileLink, MegaRequestListener *listener = NULL); + const char *buildPublicLink(const char *publicHandle, const char *key, bool isFolder); void getThumbnail(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener = NULL); void cancelGetThumbnail(MegaNode* node, MegaRequestListener *listener = NULL); void setThumbnail(MegaNode* node, const char *srcFilePath, MegaRequestListener *listener = NULL); @@ -2150,6 +2157,7 @@ class MegaApiImpl : public MegaApp void submitFeedback(int rating, const char *comment, MegaRequestListener *listener = NULL); void reportEvent(const char *details = NULL, MegaRequestListener *listener = NULL); void sendEvent(int eventType, const char* message, MegaRequestListener *listener = NULL); + void createSupportTicket(const char* message, int type = 1, MegaRequestListener *listener = NULL); void useHttpsOnly(bool httpsOnly, MegaRequestListener *listener = NULL); bool usingHttpsOnly(); @@ -2168,6 +2176,8 @@ class MegaApiImpl : public MegaApp void startUpload(const char* localPath, MegaNode *parent, int64_t mtime, MegaTransferListener *listener=NULL); void startUpload(const char* localPath, MegaNode* parent, const char* fileName, MegaTransferListener *listener = NULL); void startUpload(bool startFirst, const char* localPath, MegaNode* parent, const char* fileName, int64_t mtime, int folderTransferTag, bool isBackup, const char *appData, bool isSourceFileTemporary, bool forceNewUpload, MegaTransferListener *listener); + void startUpload(bool startFirst, const char* localPath, MegaNode* parent, const char* fileName, const char* targetUser, int64_t mtime, int folderTransferTag, bool isBackup, const char *appData, bool isSourceFileTemporary, bool forceNewUpload, MegaTransferListener *listener); + void startUploadForSupport(const char *localPath, bool isSourceTemporary = false, MegaTransferListener *listener=NULL); void startDownload(MegaNode* node, const char* localPath, MegaTransferListener *listener = NULL); void startDownload(bool startFirst, MegaNode *node, const char* target, int folderTransferTag, const char *appData, MegaTransferListener *listener); void startStreaming(MegaNode* node, m_off_t startPos, m_off_t size, MegaTransferListener *listener); @@ -2234,6 +2244,8 @@ class MegaApiImpl : public MegaApp bool is_syncable(long long size); int isNodeSyncable(MegaNode *megaNode); bool isIndexing(); + bool isSyncing(); + MegaSync *getSyncByTag(int tag); MegaSync *getSyncByNode(MegaNode *node); MegaSync *getSyncByPath(const char * localPath); @@ -2288,7 +2300,7 @@ class MegaApiImpl : public MegaApp MegaNodeList *getInShares(MegaUser* user, int order); MegaNodeList *getInShares(int order); MegaShareList *getInSharesList(int order); - MegaUser *getUserFromInShare(MegaNode *node); + MegaUser *getUserFromInShare(MegaNode *node, bool recurse = false); bool isPendingShare(MegaNode *node); MegaShareList *getOutShares(int order); MegaShareList *getOutShares(MegaNode *node); @@ -2812,8 +2824,8 @@ class MegaApiImpl : public MegaApp void putfa_result(handle, fatype, const char*) override; // purchase transactions - void enumeratequotaitems_result(handle product, unsigned prolevel, unsigned gbstorage, unsigned gbtransfer, - unsigned months, unsigned amount, const char* currency, const char* description, const char* iosid, const char* androidid) override; + void enumeratequotaitems_result(unsigned type, handle product, unsigned prolevel, int gbstorage, int gbtransfer, + unsigned months, unsigned amount, unsigned amountMonth, const char* currency, const char* description, const char* iosid, const char* androidid) override; void enumeratequotaitems_result(error e) override; void additem_result(error) override; void checkout_result(const char*, error) override; @@ -2826,6 +2838,7 @@ class MegaApiImpl : public MegaApp void userfeedbackstore_result(error) override; void sendevent_result(error) override; + void supportticket_result(error) override; void checkfile_result(handle h, error e) override; void checkfile_result(handle h, error e, byte* filekey, m_off_t size, m_time_t ts, m_time_t tm, string* filename, string* fingerprint, string* fileattrstring) override; @@ -2878,6 +2891,7 @@ class MegaApiImpl : public MegaApp void confirmrecoverylink_result(error) override; void confirmcancellink_result(error) override; void getemaillink_result(error) override; + void resendverificationemail_result(error) override; void confirmemaillink_result(error) override; void getversion_result(int, const char*, error) override; void getlocalsslcertificate_result(m_time_t, string *certdata, error) override; diff --git a/src/backofftimer.cpp b/src/backofftimer.cpp index 4e05780b80..c45fe6b2af 100644 --- a/src/backofftimer.cpp +++ b/src/backofftimer.cpp @@ -86,7 +86,7 @@ void BackoffTimer::set(dstime newds) } } -dstime BackoffTimer::retryin() +dstime BackoffTimer::retryin() const { if (armed()) { @@ -136,4 +136,91 @@ TimerWithBackoff::TimerWithBackoff(PrnGen &rng, int tag) this->tag = tag; } + +void BackoffTimerGroupTracker::update(dstime* waituntil, bool transfers) +{ + // This function performs a similar action as calling BackoffTimer::update for all the timers in the group, + // which is to say, the `waituntil` parameter will be updated with the soonest time that we would need to + // wake up from any of the timers in this group, should any of them be in a back-off state. + // There are also some side-effects specfic to transfers which are preserved from the old system. + + vector v; + v.reserve(timeouts.size()); + + if (transfers) + { + // used to be: + //for (transfer_map::iterator it = transfers[d].begin(); it != transfers[d].end(); it++) + //{ + // if ((!it->second->slot || !it->second->slot->fa) + // && it->second->bt.nextset()) + // { + // it->second->bt.update(dsmin); + // if (it->second->bt.armed()) + // { + // // fire the timer only once but keeping it armed + // it->second->bt.set(0); + // LOG_debug << "Disabling armed transfer backoff"; + // } + // } + //} + + for (auto t : timeouts) + { + // put the ones to work on in a vector, as working on them changes their position in the map + if (t.first <= Waiter::ds) + { + v.push_back(t.second); + } + else + { + break; + } + } + + for (auto t : v) + { + t->update(waituntil); + if (t->armed()) + { + // fire the timer only once but keeping it armed + t->set(0); + LOG_debug << "Disabling armed transfer backoff"; + } + } + + } + else + { + // used to be: + //for (transferslot_list::iterator it = tslots.begin(); it != tslots.end(); it++) + //{ + // if (!(*it)->retrybt.armed()) + // { + // (*it)->retrybt.update(&nds); + // } + //} + + // put the ones to work on in a vector, as working on them changes their position in the map + for (auto t : timeouts) + { + if (t.second->armed()) + { + v.push_back(t.second); + } + if (t.first > Waiter::ds) + { + break; + } + } + + for (auto t : v) + { + // update may set next=1 so we can't just call the first one. + t->update(waituntil); + } + + } +} + } // namespace diff --git a/src/commands.cpp b/src/commands.cpp index 19dae50aaf..6597efa786 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -327,7 +327,7 @@ CommandPutFile::CommandPutFile(MegaClient* client, TransferSlot* ctslot, int ms) // send minimum set of different tree's roots for API to check overquota set targetRoots; - beginarray("t"); + bool begun = false; for (auto &file : tslot->transfer->files) { if (!ISUNDEF(file->h)) @@ -343,11 +343,32 @@ CommandPutFile::CommandPutFile(MegaClient* client, TransferSlot* ctslot, int ms) targetRoots.insert(rootnode); } + if (!begun) + { + beginarray("t"); + begun = true; + } element((byte*)&file->h, MegaClient::NODEHANDLE); } } - endarray(); + + if (begun) + { + endarray(); + } + else + { + // Target user goes alone, not inside an array. Note: we are skipping this if a)more than two b)the array had been created for node handles + for (auto &file : tslot->transfer->files) + { + if (ISUNDEF(file->h) && file->targetuser.size()) + { + arg("t", file->targetuser.c_str()); + break; + } + } + } } void CommandPutFile::cancel() @@ -614,7 +635,7 @@ void CommandDirectRead::procresult() } // request temporary source URL for full-file access (p == private node) -CommandGetFile::CommandGetFile(MegaClient *client, TransferSlot* ctslot, byte* key, handle h, bool p, const char *privateauth, const char *publicauth, const char *chatauth) +CommandGetFile::CommandGetFile(MegaClient *client, TransferSlot* ctslot, const byte* key, handle h, bool p, const char *privateauth, const char *publicauth, const char *chatauth) { cmd("g"); arg(p ? "n" : "p", (byte*)&h, MegaClient::NODEHANDLE); @@ -1121,11 +1142,11 @@ CommandPutNodes::CommandPutNodes(MegaClient* client, handle th, { case NEW_PUBLIC: case NEW_NODE: - snk.add((NodeCore*)(nn + i), tn, 0); + snk.add(nn[i].nodekey, nn[i].nodehandle, tn, 0); break; case NEW_UPLOAD: - snk.add((NodeCore*)(nn + i), tn, 0, nn[i].uploadtoken, (int)sizeof nn->uploadtoken); + snk.add(nn[i].nodekey, nn[i].nodehandle, tn, 0, nn[i].uploadtoken, (int)sizeof nn->uploadtoken); break; } } @@ -1147,6 +1168,7 @@ void CommandPutNodes::procresult() { if (client->tctable) { + client->mTctableRequestCommitter->beginOnce(); vector &ids = it->second; for (unsigned int i = 0; i < ids.size(); i++) { @@ -1226,7 +1248,7 @@ void CommandPutNodes::procresult() { case 'f': empty = !memcmp(client->json.pos, "[]", 2); - if (client->readnodes(&client->json, 1, source, nn, nnsize, tag)) + if (client->readnodes(&client->json, 1, source, nn, nnsize, tag, true)) // do apply keys to received nodes only as we go for command response, much much faster for many small responses { e = API_OK; } @@ -1239,7 +1261,7 @@ void CommandPutNodes::procresult() break; case MAKENAMEID2('f', '2'): - if (!client->readnodes(&client->json, 1)) + if (!client->readnodes(&client->json, 1, PUTNODES_APP, NULL, 0, 0, true)) // do apply keys to received nodes only as we go for command response, much much faster for many small responses { LOG_err << "Parse error (readversions)"; e = API_EINTERNAL; @@ -1263,7 +1285,7 @@ void CommandPutNodes::procresult() } } - client->applykeys(); + client->sendkeyrewrites(); #ifdef ENABLE_SYNC if (source == PUTNODES_SYNC) @@ -2369,8 +2391,8 @@ void CommandUpdatePendingContact::procresult() CommandEnumerateQuotaItems::CommandEnumerateQuotaItems(MegaClient* client) { cmd("utqa"); - arg("f", 1); - + arg("nf", 1); + arg("b", 1); tag = client->reqtag; } @@ -2381,62 +2403,126 @@ void CommandEnumerateQuotaItems::procresult() return client->app->enumeratequotaitems_result((error)client->json.getint()); } - handle product; - int prolevel, gbstorage, gbtransfer, months; - unsigned amount; - const char* a; - const char* c; - const char* d; - const char* ios; - const char* android; - string currency; - string description; - string ios_id; - string android_id; - - while (client->json.enterarray()) + while (client->json.enterobject()) { - if (ISUNDEF((product = client->json.gethandle(8))) - || ((prolevel = int(client->json.getint())) < 0) - || ((gbstorage = int(client->json.getint())) < 0) - || ((gbtransfer = int(client->json.getint())) < 0) - || ((months = int(client->json.getint())) < 0) - || !(a = client->json.getvalue()) - || !(c = client->json.getvalue()) - || !(d = client->json.getvalue()) - || !(ios = client->json.getvalue()) - || !(android = client->json.getvalue())) + handle product = UNDEF; + int prolevel = -1, gbstorage = -1, gbtransfer = -1, months = -1, type = -1; + unsigned amount = 0, amountMonth = 0; + const char* amountStr = nullptr; + const char* amountMonthStr = nullptr; + const char* curr = nullptr; + const char* desc = nullptr; + const char* ios = nullptr; + const char* android = nullptr; + string currency; + string description; + string ios_id; + string android_id; + bool finished = false; + while (!finished) { - return client->app->enumeratequotaitems_result(API_EINTERNAL); - } + switch (client->json.getnameid()) + { + case MAKENAMEID2('i', 't'): + type = static_cast(client->json.getint()); + break; + case MAKENAMEID2('i', 'd'): + product = client->json.gethandle(8); + break; + case MAKENAMEID2('a', 'l'): + prolevel = static_cast(client->json.getint()); + break; + case 's': + gbstorage = static_cast(client->json.getint()); + break; + case 't': + gbtransfer = static_cast(client->json.getint()); + break; + case 'm': + months = static_cast(client->json.getint()); + break; + case 'p': + amountStr = client->json.getvalue(); + break; + case 'c': + curr = client->json.getvalue(); + break; + case 'd': + desc = client->json.getvalue(); + break; + case MAKENAMEID3('i', 'o', 's'): + ios = client->json.getvalue(); + break; + case MAKENAMEID6('g', 'o', 'o', 'g', 'l', 'e'): + android = client->json.getvalue(); + break; + case MAKENAMEID3('m', 'b', 'p'): + amountMonthStr = client->json.getvalue(); + break; + case EOO: + if (type < 0 + || ISUNDEF(product) + || (prolevel < 0) + || (!type && gbstorage < 0) + || (!type && gbtransfer < 0) + || (months < 0) + || !amountStr + || !curr + || !desc + || !amountMonthStr + || (!type && !ios) + || (!type && !android)) + { + return client->app->enumeratequotaitems_result(API_EINTERNAL); + } + finished = true; + break; + default: + return client->app->enumeratequotaitems_result(API_EINTERNAL); + } + } - Node::copystring(¤cy, c); - Node::copystring(&description, d); + client->json.leaveobject(); + Node::copystring(¤cy, curr); + Node::copystring(&description, desc); Node::copystring(&ios_id, ios); Node::copystring(&android_id, android); + amount = atoi(amountStr) * 100; + if ((curr = strchr(amountStr, '.'))) + { + curr++; + if ((*curr >= '0') && (*curr <= '9')) + { + amount += (*curr - '0') * 10; + } + curr++; + if ((*curr >= '0') && (*curr <= '9')) + { + amount += *curr - '0'; + } + } - amount = atoi(a) * 100; - if ((c = strchr(a, '.'))) + amountMonth = atoi(amountMonthStr) * 100; + if ((curr = strchr(amountMonthStr, '.'))) { - c++; - if ((*c >= '0') && (*c <= '9')) + curr++; + if ((*curr >= '0') && (*curr <= '9')) { - amount += (*c - '0') * 10; + amountMonth += (*curr - '0') * 10; } - c++; - if ((*c >= '0') && (*c <= '9')) + curr++; + if ((*curr >= '0') && (*curr <= '9')) { - amount += *c - '0'; + amountMonth += *curr - '0'; } } - client->app->enumeratequotaitems_result(product, prolevel, gbstorage, - gbtransfer, months, amount, + client->app->enumeratequotaitems_result(type, product, prolevel, gbstorage, + gbtransfer, months, amount, amountMonth, currency.c_str(), description.c_str(), ios_id.c_str(), android_id.c_str()); - client->json.leavearray(); } client->app->enumeratequotaitems_result(API_OK); @@ -3376,10 +3462,10 @@ CommandNodeKeyUpdate::CommandNodeKeyUpdate(MegaClient* client, handle_vector* v) if ((n = client->nodebyhandle(h))) { - client->key.ecb_encrypt((byte*)n->nodekey.data(), nodekey, n->nodekey.size()); + client->key.ecb_encrypt((byte*)n->nodekey().data(), nodekey, n->nodekey().size()); element(h, MegaClient::NODEHANDLE); - element(nodekey, int(n->nodekey.size())); + element(nodekey, int(n->nodekey().size())); } } @@ -3487,6 +3573,10 @@ void CommandPubKeyRequest::procresult() if (!ISUNDEF(uh)) { client->mapuser(uh, u->email.c_str()); + if (u->isTemporary && u->uid == u->email) //update uid with the received USERHANDLE (will be used as target for putnodes) + { + u->uid = Base64Str(uh); + } } if (client->fetchingkeys && u->userhandle == client->me && len_pubk) @@ -5236,6 +5326,29 @@ void CommandSendEvent::procresult() } } +CommandSupportTicket::CommandSupportTicket(MegaClient *client, const char *message, int type) +{ + cmd("sse"); + arg("t", type); + arg("b", 1); // base64 encoding for `msg` + arg("m", (const byte*)message, int(strlen(message))); + + tag = client->reqtag; +} + +void CommandSupportTicket::procresult() +{ + if (client->json.isnumeric()) + { + client->app->supportticket_result((error)client->json.getint()); + } + else + { + client->json.storeobject(); + client->app->supportticket_result(API_EINTERNAL); + } +} + CommandCleanRubbishBin::CommandCleanRubbishBin(MegaClient *client) { cmd("dr"); @@ -5450,6 +5563,25 @@ void CommandConfirmCancelLink::procresult() } } +CommandResendVerificationEmail::CommandResendVerificationEmail(MegaClient *client) +{ + cmd("era"); + tag = client->reqtag; +} + +void CommandResendVerificationEmail::procresult() +{ + if (client->json.isnumeric()) + { + client->app->resendverificationemail_result((error)client->json.getint()); + } + else + { + client->json.storeobject(); + client->app->resendverificationemail_result((error)API_EINTERNAL); + } +} + CommandValidatePassword::CommandValidatePassword(MegaClient *client, const char *email, uint64_t emailhash) { cmd("us"); diff --git a/src/file.cpp b/src/file.cpp index 919f66fb8f..7c74e2531b 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -337,10 +337,10 @@ void File::completed(Transfer* t, LocalNode* l) { handle th = h; - // inaccessible target folder - use / instead + // inaccessible target folder - use //bin instead if (!t->client->nodebyhandle(th)) { - th = t->client->rootnodes[0]; + th = t->client->rootnodes[RUBBISHNODE - ROOTNODE]; } #ifdef ENABLE_SYNC if (l) @@ -516,7 +516,7 @@ void SyncFileGet::prepare() } else { - transfer->localfilename = sync->localroot.localname; + transfer->localfilename = sync->localroot->localname; } sync->client->fsaccess->tmpnamelocal(&tmpname); diff --git a/src/filefingerprint.cpp b/src/filefingerprint.cpp index 7615082675..81bd7f33f3 100644 --- a/src/filefingerprint.cpp +++ b/src/filefingerprint.cpp @@ -62,14 +62,14 @@ bool operator==(const FileFingerprint& lhs, const FileFingerprint& rhs) return true; } - return !memcmp(lhs.crc, rhs.crc, sizeof lhs.crc); + return !memcmp(lhs.crc.data(), rhs.crc.data(), sizeof lhs.crc); } bool FileFingerprint::serialize(string *d) { d->append((const char*)&size, sizeof(size)); d->append((const char*)&mtime, sizeof(mtime)); - d->append((const char*)crc, sizeof(crc)); + d->append((const char*)crc.data(), sizeof(crc)); d->append((const char*)&isvalid, sizeof(isvalid)); return true; @@ -94,7 +94,7 @@ FileFingerprint *FileFingerprint::unserialize(string *d) fp->mtime = MemAccess::get(ptr); ptr += sizeof(m_time_t); - memcpy(fp->crc, ptr, sizeof(fp->crc)); + memcpy(fp->crc.data(), ptr, sizeof(fp->crc)); ptr += sizeof(fp->crc); fp->isvalid = MemAccess::get(ptr); @@ -107,17 +107,16 @@ FileFingerprint *FileFingerprint::unserialize(string *d) FileFingerprint::FileFingerprint(const FileFingerprint& other) : size{other.size} , mtime{other.mtime} +, crc(other.crc) , isvalid{other.isvalid} -{ - memcpy(crc, other.crc, sizeof(crc)); -} +{} FileFingerprint& FileFingerprint::operator=(const FileFingerprint& other) { assert(this != &other); size = other.size; mtime = other.mtime; - memcpy(crc, other.crc, sizeof(crc)); + crc = other.crc; isvalid = other.isvalid; return *this; } @@ -125,7 +124,8 @@ FileFingerprint& FileFingerprint::operator=(const FileFingerprint& other) bool FileFingerprint::genfingerprint(FileAccess* fa, bool ignoremtime) { bool changed = false; - int32_t newcrc[sizeof crc / sizeof *crc], crcval; + decltype(crc) newcrc; + int32_t crcval; if (mtime != fa->mtime) { @@ -148,7 +148,7 @@ bool FileFingerprint::genfingerprint(FileAccess* fa, bool ignoremtime) if (size <= (m_off_t)sizeof crc) { // tiny file: read verbatim, NUL pad - if (!fa->frawread((byte*)newcrc, static_cast(size), 0, true)) + if (!fa->frawread((byte*)newcrc.data(), static_cast(size), 0, true)) { size = -1; fa->closef(); @@ -157,7 +157,7 @@ bool FileFingerprint::genfingerprint(FileAccess* fa, bool ignoremtime) if (size < (m_off_t)sizeof(crc)) { - memset((byte*)newcrc + size, 0, size_t(sizeof(crc) - size)); + memset((byte*)newcrc.data() + size, 0, size_t(sizeof(crc) - size)); } } else if (size <= MAXFULL) @@ -173,10 +173,10 @@ bool FileFingerprint::genfingerprint(FileAccess* fa, bool ignoremtime) return true; } - for (unsigned i = 0; i < sizeof crc / sizeof *crc; i++) + for (unsigned i = 0; i < crc.size(); i++) { - int begin = int(i * size / (sizeof crc / sizeof *crc)); - int end = int((i + 1) * size / (sizeof crc / sizeof *crc)); + int begin = int(i * size / crc.size()); + int end = int((i + 1) * size / crc.size()); crc32.add(buf + begin, end - begin); crc32.get((byte*)&crcval); @@ -189,16 +189,16 @@ bool FileFingerprint::genfingerprint(FileAccess* fa, bool ignoremtime) // large file: sparse coverage, four sparse CRC32s HashCRC32 crc32; byte block[4 * sizeof crc]; - const unsigned blocks = MAXFULL / (sizeof block * sizeof crc / sizeof *crc); + const unsigned blocks = MAXFULL / (sizeof block * crc.size()); - for (unsigned i = 0; i < sizeof crc / sizeof *crc; i++) + for (unsigned i = 0; i < crc.size(); i++) { for (unsigned j = 0; j < blocks; j++) { if (!fa->frawread(block, sizeof block, (size - sizeof block) * (i * blocks + j) - / (sizeof crc / sizeof *crc * blocks - 1), true)) + / (crc.size() * blocks - 1), true)) { size = -1; fa->closef(); @@ -213,9 +213,9 @@ bool FileFingerprint::genfingerprint(FileAccess* fa, bool ignoremtime) } } - if (memcmp(crc, newcrc, sizeof crc)) + if (crc != newcrc) { - memcpy(crc, newcrc, sizeof crc); + crc = newcrc; changed = true; } @@ -232,7 +232,8 @@ bool FileFingerprint::genfingerprint(FileAccess* fa, bool ignoremtime) bool FileFingerprint::genfingerprint(InputStreamAccess *is, m_time_t cmtime, bool ignoremtime) { bool changed = false; - int32_t newcrc[sizeof crc / sizeof *crc], crcval; + decltype(crc) newcrc; + int32_t crcval; if (mtime != cmtime) { @@ -255,7 +256,7 @@ bool FileFingerprint::genfingerprint(InputStreamAccess *is, m_time_t cmtime, boo if (size <= (m_off_t)sizeof crc) { // tiny file: read verbatim, NUL pad - if (!is->read((byte*)newcrc, (unsigned int)size)) + if (!is->read((byte*)newcrc.data(), (unsigned int)size)) { size = -1; return true; @@ -263,7 +264,7 @@ bool FileFingerprint::genfingerprint(InputStreamAccess *is, m_time_t cmtime, boo if (size < (m_off_t)sizeof(crc)) { - memset((byte*)newcrc + size, 0, size_t(sizeof(crc) - size)); + memset((byte*)newcrc.data() + size, 0, size_t(sizeof(crc) - size)); } } else if (size <= MAXFULL) @@ -278,10 +279,10 @@ bool FileFingerprint::genfingerprint(InputStreamAccess *is, m_time_t cmtime, boo return true; } - for (unsigned i = 0; i < sizeof crc / sizeof *crc; i++) + for (unsigned i = 0; i < crc.size(); i++) { - int begin = int(i * size / (sizeof crc / sizeof *crc)); - int end = int((i + 1) * size / (sizeof crc / sizeof *crc)); + int begin = int(i * size / crc.size()); + int end = int((i + 1) * size / crc.size()); crc32.add(buf + begin, end - begin); crc32.get((byte*)&crcval); @@ -294,16 +295,16 @@ bool FileFingerprint::genfingerprint(InputStreamAccess *is, m_time_t cmtime, boo // large file: sparse coverage, four sparse CRC32s HashCRC32 crc32; byte block[4 * sizeof crc]; - const unsigned blocks = MAXFULL / (sizeof block * sizeof crc / sizeof *crc); + const unsigned blocks = MAXFULL / (sizeof block * crc.size()); m_off_t current = 0; - for (unsigned i = 0; i < sizeof crc / sizeof *crc; i++) + for (unsigned i = 0; i < crc.size(); i++) { for (unsigned j = 0; j < blocks; j++) { m_off_t offset = (size - sizeof block) * (i * blocks + j) - / (sizeof crc / sizeof *crc * blocks - 1); + / (crc.size() * blocks - 1); //Seek for (m_off_t fullstep = offset - current; fullstep > 0; ) // 500G or more and the step doesn't fit in 32 bits @@ -334,9 +335,9 @@ bool FileFingerprint::genfingerprint(InputStreamAccess *is, m_time_t cmtime, boo } } - if (memcmp(crc, newcrc, sizeof crc)) + if (crc != newcrc) { - memcpy(crc, newcrc, sizeof crc); + crc = newcrc; changed = true; } @@ -355,7 +356,7 @@ void FileFingerprint::serializefingerprint(string* d) const byte buf[sizeof crc + 1 + sizeof mtime]; int l; - memcpy(buf, crc, sizeof crc); + memcpy(buf, crc.data(), sizeof crc); l = Serialize64::serialize(buf + sizeof crc, mtime); d->resize((sizeof crc + l) * 4 / 3 + 4); @@ -379,7 +380,7 @@ int FileFingerprint::unserializefingerprint(string* d) return 0; } - memcpy(crc, buf, sizeof crc); + memcpy(crc.data(), buf, sizeof crc); mtime = t; @@ -410,6 +411,38 @@ bool FileFingerprintCmp::operator()(const FileFingerprint* a, const FileFingerpr return false; } - return memcmp(a->crc, b->crc, sizeof a->crc) < 0; + return memcmp(a->crc.data(), b->crc.data(), sizeof a->crc) < 0; } -} // namespace + +bool LightFileFingerprint::genfingerprint(const m_off_t filesize, const m_time_t filemtime) +{ + bool changed = false; + + if (mtime != filemtime) + { + mtime = filemtime; + changed = true; + } + + if (size != filesize) + { + size = filesize; + changed = true; + } + + return changed; +} + +bool LightFileFingerprintCmp::operator()(const LightFileFingerprint* a, const LightFileFingerprint* b) const +{ + assert(a); + assert(b); + return std::tie(a->mtime, a->size) < std::tie(b->mtime, b->size); +} + +bool operator==(const LightFileFingerprint& lhs, const LightFileFingerprint& rhs) +{ + return std::tie(lhs.mtime, lhs.size) == std::tie(rhs.mtime, rhs.size); +} + +} // mega diff --git a/src/include.am b/src/include.am index 3a7d11d48d..9c36be6555 100644 --- a/src/include.am +++ b/src/include.am @@ -42,6 +42,7 @@ src_libmega_la_SOURCES += src/mediafileattribute.cpp src_libmega_la_SOURCES += src/node.cpp src_libmega_la_SOURCES += src/pubkeyaction.cpp src_libmega_la_SOURCES += src/raid.cpp +src_libmega_la_SOURCES += src/testhooks.cpp src_libmega_la_SOURCES += src/request.cpp src_libmega_la_SOURCES += src/serialize64.cpp src_libmega_la_SOURCES += src/share.cpp diff --git a/src/megaapi.cpp b/src/megaapi.cpp index f564975b0c..921f2ccd76 100644 --- a/src/megaapi.cpp +++ b/src/megaapi.cpp @@ -279,6 +279,7 @@ int MegaUserAlertList::size() const return 0; } +void MegaUserAlertList::clear() { } MegaRecentActionBucket::~MegaRecentActionBucket() @@ -2130,6 +2131,11 @@ void MegaApi::confirmCancelAccount(const char *link, const char *pwd, MegaReques pImpl->confirmCancelAccount(link, pwd, listener); } +void MegaApi::resendVerificationEmail(MegaRequestListener *listener) +{ + pImpl->resendVerificationEmail(listener); +} + void MegaApi::changeEmail(const char *email, MegaRequestListener *listener) { pImpl->changeEmail(email, listener); @@ -2145,9 +2151,9 @@ void MegaApi::confirmChangeEmail(const char *link, const char *pwd, MegaRequestL pImpl->confirmChangeEmail(link, pwd, listener); } -void MegaApi::setProxySettings(MegaProxy *proxySettings) +void MegaApi::setProxySettings(MegaProxy *proxySettings, MegaRequestListener *listener) { - pImpl->setProxySettings(proxySettings); + pImpl->setProxySettings(proxySettings, listener); } MegaProxy *MegaApi::getAutoProxySettings() @@ -2255,6 +2261,11 @@ void MegaApi::getPublicNode(const char* megaFileLink, MegaRequestListener *liste pImpl->getPublicNode(megaFileLink, listener); } +const char *MegaApi::buildPublicLink(const char *publicHandle, const char *key, bool isFolder) +{ + return pImpl->buildPublicLink(publicHandle, key, isFolder); +} + void MegaApi::getThumbnail(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener) { pImpl->getThumbnail(node, dstFilePath, listener); @@ -2629,7 +2640,7 @@ void MegaApi::getUserAlias(MegaHandle uh, MegaRequestListener *listener) void MegaApi::setUserAlias(MegaHandle uh, const char *alias, MegaRequestListener *listener) { - pImpl->setUserAlias(uh, alias); + pImpl->setUserAlias(uh, alias, listener); } void MegaApi::getRubbishBinAutopurgePeriod(MegaRequestListener *listener) @@ -2677,6 +2688,11 @@ void MegaApi::sendEvent(int eventType, const char *message, MegaRequestListener pImpl->sendEvent(eventType, message, listener); } +void MegaApi::createSupportTicket(const char *message, int type, MegaRequestListener *listener) +{ + pImpl->createSupportTicket(message, type, listener); +} + void MegaApi::reportDebugEvent(const char *text, MegaRequestListener *listener) { pImpl->reportEvent(text, listener); @@ -2868,6 +2884,10 @@ void MegaApi::startUpload(const char* localPath, MegaNode* parent, MegaTransferL pImpl->startUpload(localPath, parent, listener); } +void MegaApi::startUploadForSupport(const char* localPath, bool isSourceTemporary, MegaTransferListener *listener) +{ + pImpl->startUploadForSupport(localPath, isSourceTemporary, listener); +} MegaStringList *MegaApi::getBackupFolders(int backuptag) const { @@ -3134,6 +3154,11 @@ bool MegaApi::isScanning() return pImpl->isIndexing(); } +bool MegaApi::isSyncing() +{ + return pImpl->isSyncing(); +} + bool MegaApi::isSynced(MegaNode *n) { return pImpl->isSynced(n); @@ -3323,9 +3348,9 @@ MegaShareList* MegaApi::getInSharesList(int order) return pImpl->getInSharesList(order); } -MegaUser *MegaApi::getUserFromInShare(MegaNode *node) +MegaUser *MegaApi::getUserFromInShare(MegaNode *node, bool recurse) { - return pImpl->getUserFromInShare(node); + return pImpl->getUserFromInShare(node, recurse); } bool MegaApi::isShared(MegaNode *node) @@ -5262,12 +5287,12 @@ int MegaPricing::getProLevel(int) return 0; } -unsigned int MegaPricing::getGBStorage(int) +int MegaPricing::getGBStorage(int) { return 0; } -unsigned int MegaPricing::getGBTransfer(int) +int MegaPricing::getGBTransfer(int) { return 0; } @@ -5302,6 +5327,16 @@ const char *MegaPricing::getAndroidID(int) return NULL; } +bool MegaPricing::isBusinessType(int) +{ + return false; +} + +int MegaPricing::getAmountMonth(int) +{ + return 0; +} + MegaPricing *MegaPricing::copy() { return NULL; diff --git a/src/megaapi_impl.cpp b/src/megaapi_impl.cpp index 023d08e3e1..485e686198 100644 --- a/src/megaapi_impl.cpp +++ b/src/megaapi_impl.cpp @@ -387,7 +387,7 @@ MegaNodePrivate::MegaNodePrivate(Node *node) this->attrstring.assign(node->attrstring->data(), node->attrstring->size()); } this->fileattrstring = node->fileattrstring; - this->nodekey.assign(node->nodekey.data(),node->nodekey.size()); + this->nodekey = node->nodekeyUnchecked(); this->changed = 0; if(node->changed.attrs) @@ -1345,6 +1345,24 @@ bool MegaApiImpl::isIndexing() return indexing; } +bool MegaApiImpl::isSyncing() +{ + if (!client->syncs.size()) + { + return false; + } + SdkMutexGuard g(sdkMutex); + + for (auto & sync : client->syncs) + { + if (sync->localroot->ts == TREESTATE_SYNCING || sync->localroot->ts == TREESTATE_PENDING) + { + return true; + } + } + return false; +} + MegaSync *MegaApiImpl::getSyncByTag(int tag) { sdkMutex.lock(); @@ -3642,11 +3660,11 @@ void MegaRequestPrivate::setTag(int tag) this->tag = tag; } -void MegaRequestPrivate::addProduct(handle product, int proLevel, unsigned int gbStorage, unsigned int gbTransfer, int months, int amount, const char *currency, const char* description, const char* iosid, const char* androidid) +void MegaRequestPrivate::addProduct(unsigned int type, handle product, int proLevel, int gbStorage, int gbTransfer, int months, int amount, int amountMonth, const char *currency, const char* description, const char* iosid, const char* androidid) { if (megaPricing) { - megaPricing->addProduct(product, proLevel, gbStorage, gbTransfer, months, amount, currency, description, iosid, androidid); + megaPricing->addProduct(type, product, proLevel, gbStorage, gbTransfer, months, amount, amountMonth, currency, description, iosid, androidid); } } @@ -3817,6 +3835,8 @@ const char *MegaRequestPrivate::getRequestString() const case TYPE_GET_COUNTRY_CALLING_CODES: return "GET_COUNTRY_CALLING_CODES"; case TYPE_VERIFY_CREDENTIALS: return "VERIFY_CREDENTIALS"; case TYPE_GET_MISC_FLAGS: return "GET_MISC_FLAGS"; + case TYPE_RESEND_VERIFICATION_EMAIL: return "RESEND_VERIFICATION_EMAIL"; + case TYPE_SUPPORT_TICKET: return "SUPPORT_TICKET"; } return "UNKNOWN"; } @@ -4313,6 +4333,13 @@ int MegaUserAlertListPrivate::size() const return s; } +void MegaUserAlertListPrivate::clear() +{ + delete[] list; + s = 0; + list = nullptr; +} + MegaRecentActionBucketPrivate::MegaRecentActionBucketPrivate(recentaction& ra, MegaClient* mc) { User* u = mc->finduser(ra.user); @@ -4684,8 +4711,8 @@ MegaFileGet::MegaFileGet(MegaClient *client, Node *n, string dstPath) : MegaFile size = n->size; mtime = n->mtime; - if(n->nodekey.size()>=sizeof(filekey)) - memcpy(filekey,n->nodekey.data(),sizeof filekey); + if(n->nodekey().size()>=sizeof(filekey)) + memcpy(filekey,n->nodekey().data(),sizeof filekey); client->fsaccess->path2local(&finalPath, &localname); hprivate = true; @@ -5127,6 +5154,7 @@ MegaApiImpl::~MegaApiImpl() delete it->second; } + delete client; // only delete client now that transfers that might refer to it are finally removed. delete gfxAccess; delete fsAccess; delete waiter; @@ -6008,6 +6036,14 @@ void MegaApiImpl::confirmCancelAccount(const char *link, const char *pwd, MegaRe waiter->notify(); } +void MegaApiImpl::resendVerificationEmail(MegaRequestListener *listener) +{ + MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_RESEND_VERIFICATION_EMAIL); + request->setListener(listener); + requestQueue.push(request); + waiter->notify(); +} + void MegaApiImpl::changeEmail(const char *email, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK, listener); @@ -6025,7 +6061,7 @@ void MegaApiImpl::confirmChangeEmail(const char *link, const char *pwd, MegaRequ waiter->notify(); } -void MegaApiImpl::setProxySettings(MegaProxy *proxySettings) +void MegaApiImpl::setProxySettings(MegaProxy *proxySettings, MegaRequestListener *listener) { Proxy *localProxySettings = new Proxy(); localProxySettings->setProxyType(proxySettings->getProxyType()); @@ -6073,7 +6109,7 @@ void MegaApiImpl::setProxySettings(MegaProxy *proxySettings) localProxySettings->setCredentials(&localusername, &localpassword); } - MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_PROXY); + MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_PROXY, listener); request->setProxy(localProxySettings); requestQueue.push(request); waiter->notify(); @@ -6188,18 +6224,19 @@ void MegaApiImpl::loop() } sendPendingRequests(); sendPendingScRequest(); - if(threadExit) - break; + if (threadExit) + { + // The ~MegaApiImpl() destructor is responsible for deleting the client after join()ing the thread + // But first call locallogout() which may call back to the app (on this thread as usual) to close some requestMap items etc + client->locallogout(false); + return; + } sdkMutex.lock(); client->exec(); sdkMutex.unlock(); } } - - sdkMutex.lock(); - delete client; - sdkMutex.unlock(); } @@ -6393,6 +6430,13 @@ void MegaApiImpl::getPublicNode(const char* megaFileLink, MegaRequestListener *l waiter->notify(); } +const char *MegaApiImpl::buildPublicLink(const char *publicHandle, const char *key, bool isFolder) +{ + handle ph = MegaApi::base64ToHandle(publicHandle); + string link = client->getPublicLink(client->mNewLinkFormat, isFolder ? FOLDERNODE : FILENODE, ph, key); + return MegaApi::strdup(link.c_str()); +} + void MegaApiImpl::getThumbnail(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener) { getNodeAttribute(node, GfxProc::THUMBNAIL, dstFilePath, listener); @@ -6955,6 +6999,15 @@ void MegaApiImpl::sendEvent(int eventType, const char *message, MegaRequestListe waiter->notify(); } +void MegaApiImpl::createSupportTicket(const char *message, int type, MegaRequestListener *listener) +{ + MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SUPPORT_TICKET, listener); + request->setParamType(type); + request->setText(message); + requestQueue.push(request); + waiter->notify(); +} + void MegaApiImpl::useHttpsOnly(bool usehttps, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_USE_HTTPS_ONLY, listener); @@ -7907,7 +7960,7 @@ void MegaApiImpl::startTimer( int64_t period, MegaRequestListener *listener) waiter->notify(); } -void MegaApiImpl::startUpload(bool startFirst, const char *localPath, MegaNode *parent, const char *fileName, int64_t mtime, int folderTransferTag, bool isBackup, const char *appData, bool isSourceFileTemporary, bool forceNewUpload, MegaTransferListener *listener) +void MegaApiImpl::startUpload(bool startFirst, const char *localPath, MegaNode *parent, const char *fileName, const char *targetUser, int64_t mtime, int folderTransferTag, bool isBackup, const char *appData, bool isSourceFileTemporary, bool forceNewUpload, MegaTransferListener *listener) { MegaTransferPrivate* transfer = new MegaTransferPrivate(MegaTransfer::TYPE_UPLOAD, listener); if(localPath) @@ -7925,6 +7978,11 @@ void MegaApiImpl::startUpload(bool startFirst, const char *localPath, MegaNode * transfer->setParentHandle(parent->getHandle()); } + if (targetUser) + { + transfer->setParentPath(targetUser); + } + transfer->setMaxRetries(maxRetries); transfer->setAppData(appData); transfer->setSourceFileTemporary(isSourceFileTemporary); @@ -7950,6 +8008,9 @@ void MegaApiImpl::startUpload(bool startFirst, const char *localPath, MegaNode * waiter->notify(); } +void MegaApiImpl::startUpload(bool startFirst, const char *localPath, MegaNode *parent, const char *fileName, int64_t mtime, int folderTransferTag, bool isBackup, const char *appData, bool isSourceFileTemporary, bool forceNewUpload, MegaTransferListener *listener) +{ return startUpload(startFirst, localPath, parent, fileName, nullptr, mtime, folderTransferTag, isBackup, appData, isSourceFileTemporary, forceNewUpload, listener); } + void MegaApiImpl::startUpload(const char* localPath, MegaNode* parent, MegaTransferListener *listener) { return startUpload(false, localPath, parent, (const char *)NULL, -1, 0, false, NULL, false, false, listener); } @@ -7959,6 +8020,11 @@ void MegaApiImpl::startUpload(const char *localPath, MegaNode *parent, int64_t m void MegaApiImpl::startUpload(const char* localPath, MegaNode* parent, const char* fileName, MegaTransferListener *listener) { return startUpload(false, localPath, parent, fileName, -1, 0, false, NULL, false, false, listener); } +void MegaApiImpl::startUploadForSupport(const char *localPath, bool isSourceTemporary, MegaTransferListener *listener) +{ + return startUpload(true, localPath, nullptr, nullptr, "pGTOqu7_Fek", -1, 0, false, nullptr, isSourceTemporary, false, listener); +} + void MegaApiImpl::startDownload(bool startFirst, MegaNode *node, const char* localPath, int folderTransferTag, const char *appData, MegaTransferListener *listener) { MegaTransferPrivate* transfer = new MegaTransferPrivate(MegaTransfer::TYPE_DOWNLOAD, listener); @@ -8108,7 +8174,7 @@ bool MegaApiImpl::moveToLocalDebris(const char *path) Sync *sync = NULL; for (sync_list::iterator it = client->syncs.begin(); it != client->syncs.end(); it++) { - string *localroot = &((*it)->localroot.localname); + string *localroot = &((*it)->localroot->localname); if(((localroot->size()+fsAccess->localseparator.size())data(), localpath.data(), localroot->size()) && !memcmp(fsAccess->localseparator.data(), localpath.data()+localroot->size(), fsAccess->localseparator.size())) @@ -8166,8 +8232,8 @@ int MegaApiImpl::syncPathState(string* path) for (sync_list::iterator it = client->syncs.begin(); it != client->syncs.end(); it++) { Sync *sync = (*it); - size_t ssize = sync->localroot.localname.size(); - if (path->size() < ssize || memcmp(path->data(), sync->localroot.localname.data(), ssize)) + size_t ssize = sync->localroot->localname.size(); + if (path->size() < ssize || memcmp(path->data(), sync->localroot->localname.data(), ssize)) { continue; } @@ -8185,7 +8251,7 @@ int MegaApiImpl::syncPathState(string* path) if (path->size() == ssize) { - state = sync->localroot.ts; + state = sync->localroot->ts; break; } else if (!memcmp(path->data()+ssize, client->fsaccess->localseparator.data(), client->fsaccess->localseparator.size())) @@ -8231,10 +8297,10 @@ MegaNode *MegaApiImpl::getSyncedNode(string *path) for (sync_list::iterator it = client->syncs.begin(); (it != client->syncs.end()) && (node == NULL); it++) { Sync *sync = (*it); - if (path->size() == sync->localroot.localname.size() && - !memcmp(path->data(), sync->localroot.localname.data(), path->size())) + if (path->size() == sync->localroot->localname.size() && + !memcmp(path->data(), sync->localroot->localname.data(), path->size())) { - node = MegaNodePrivate::fromNode(sync->localroot.node); + node = MegaNodePrivate::fromNode(sync->localroot->node); break; } @@ -9953,10 +10019,17 @@ void MegaApiImpl::getCameraUploadsFolder(bool secondary, MegaRequestListener *li void MegaApiImpl::setCameraUploadsFolder(MegaHandle nodehandle, bool secondary, MegaRequestListener *listener) { + MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); + MegaStringMapPrivate stringMap; const char *key = secondary ? "sh" : "h"; stringMap.set(key, Base64Str(nodehandle)); - setUserAttribute(MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER, &stringMap, listener); + request->setMegaStringMap(&stringMap); + request->setParamType(MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER); + request->setFlag(secondary); + request->setNodeHandle(nodehandle); + requestQueue.push(request); + waiter->notify(); } void MegaApiImpl::getMyChatFilesFolder(MegaRequestListener *listener) @@ -10255,7 +10328,7 @@ MegaShareList* MegaApiImpl::getInSharesList(int order) return shareList; } -MegaUser *MegaApiImpl::getUserFromInShare(MegaNode *megaNode) +MegaUser *MegaApiImpl::getUserFromInShare(MegaNode *megaNode, bool recurse) { if (!megaNode) { @@ -10267,6 +10340,11 @@ MegaUser *MegaApiImpl::getUserFromInShare(MegaNode *megaNode) sdkMutex.lock(); Node *node = client->nodebyhandle(megaNode->getHandle()); + if (recurse && node) + { + node = client->getrootnode(node); + } + if (node && node->inshare && node->inshare->user) { user = MegaUserPrivate::fromUser(node->inshare->user); @@ -11411,7 +11489,7 @@ char *MegaApiImpl::getCRC(const char *filePath) string result; result.resize((sizeof fp.crc) * 4 / 3 + 4); - result.resize(Base64::btoa((const byte *)fp.crc, sizeof fp.crc, (char*)result.c_str())); + result.resize(Base64::btoa((const byte *)fp.crc.data(), sizeof fp.crc, (char*)result.c_str())); return MegaApi::strdup(result.c_str()); } @@ -11425,7 +11503,7 @@ char *MegaApiImpl::getCRCFromFingerprint(const char *fingerprint) string result; result.resize((sizeof fp->crc) * 4 / 3 + 4); - result.resize(Base64::btoa((const byte *)fp->crc, sizeof fp->crc,(char*)result.c_str())); + result.resize(Base64::btoa((const byte *)fp->crc.data(), sizeof fp->crc,(char*)result.c_str())); delete fp; return MegaApi::strdup(result.c_str()); @@ -11445,7 +11523,7 @@ char *MegaApiImpl::getCRC(MegaNode *n) string result; result.resize((sizeof node->crc) * 4 / 3 + 4); - result.resize(Base64::btoa((const byte *)node->crc, sizeof node->crc, (char*)result.c_str())); + result.resize(Base64::btoa((const byte *)node->crc.data(), sizeof node->crc.data(), (char*)result.c_str())); sdkMutex.unlock(); return MegaApi::strdup(result.c_str()); @@ -11469,7 +11547,7 @@ MegaNode *MegaApiImpl::getNodeByCRC(const char *crc, MegaNode *parent) for (node_list::iterator it = node->children.begin(); it != node->children.end(); it++) { Node *child = (*it); - if(!memcmp(child->crc, binarycrc, sizeof(node->crc))) + if(!memcmp(child->crc.data(), binarycrc, sizeof(node->crc))) { MegaNode *result = MegaNodePrivate::fromNode(child); sdkMutex.unlock(); @@ -12021,6 +12099,16 @@ void MegaApiImpl::getemaillink_result(error e) fireOnRequestFinish(request, MegaError(e)); } +void MegaApiImpl::resendverificationemail_result(error e) +{ + auto it = requestMap.find(client->restag); + if (it == requestMap.end()) return; + MegaRequestPrivate *request = it->second; + if (!request || ((request->getType() != MegaRequest::TYPE_RESEND_VERIFICATION_EMAIL))) return; + + fireOnRequestFinish(request, MegaError(e)); +} + void MegaApiImpl::confirmemaillink_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; @@ -12482,16 +12570,16 @@ void MegaApiImpl::syncupdate_state(Sync *sync, syncstate_t newstate) if(syncMap.find(sync->tag) == syncMap.end()) return; MegaSyncPrivate* megaSync = syncMap.at(sync->tag); megaSync->setState(newstate); - LOG_debug << "Sync state change: " << newstate << " Path: " << sync->localroot.name; + LOG_debug << "Sync state change: " << newstate << " Path: " << sync->localroot->name; client->abortbackoff(false); if (newstate == SYNC_FAILED) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_ADD_SYNC); - if(sync->localroot.node) + if(sync->localroot->node) { - request->setNodeHandle(sync->localroot.node->nodehandle); + request->setNodeHandle(sync->localroot->node->nodehandle); } int nextTag = client->nextreqtag(); @@ -13001,11 +13089,13 @@ void MegaApiImpl::fetchnodes_result(error e) // set names silently... int creqtag = client->reqtag; client->reqtag = 0; - if (request->getName()) + string firstname = request->getName() ? request->getName() : ""; + if (!firstname.empty()) { client->putua(ATTR_FIRSTNAME, (const byte*) request->getName(), int(strlen(request->getName()))); } - if (request->getText()) + string lastname = request->getText() ? request->getText() : ""; + if (!lastname.empty()) { client->putua(ATTR_LASTNAME, (const byte*) request->getText(), int(strlen(request->getText()))); } @@ -13017,7 +13107,8 @@ void MegaApiImpl::fetchnodes_result(error e) { if (client->nsr_enabled) { - string derivedKey = client->sendsignuplink2(request->getEmail(), request->getPassword(), request->getName()); + string fullname = firstname + lastname; + string derivedKey = client->sendsignuplink2(request->getEmail(), request->getPassword(), fullname.c_str()); string b64derivedKey; Base64::btoa(derivedKey, b64derivedKey); request->setPrivateKey(b64derivedKey.c_str()); @@ -13361,7 +13452,7 @@ void MegaApiImpl::putfa_result(handle h, fatype, const char *) fireOnRequestFinish(request, megaError); } -void MegaApiImpl::enumeratequotaitems_result(handle product, unsigned prolevel, unsigned gbstorage, unsigned gbtransfer, unsigned months, unsigned amount, const char* currency, const char* description, const char* iosid, const char* androidid) +void MegaApiImpl::enumeratequotaitems_result(unsigned type, handle product, unsigned prolevel, int gbstorage, int gbtransfer, unsigned months, unsigned amount, unsigned amountMonth, const char* currency, const char* description, const char* iosid, const char* androidid) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); @@ -13372,7 +13463,7 @@ void MegaApiImpl::enumeratequotaitems_result(handle product, unsigned prolevel, return; } - request->addProduct(product, prolevel, gbstorage, gbtransfer, months, amount, currency, description, iosid, androidid); + request->addProduct(type, product, prolevel, gbstorage, gbtransfer, months, amount, amountMonth, currency, description, iosid, androidid); } void MegaApiImpl::enumeratequotaitems_result(error e) @@ -13526,6 +13617,15 @@ void MegaApiImpl::sendevent_result(error e) fireOnRequestFinish(request, MegaError(e)); } +void MegaApiImpl::supportticket_result(error e) +{ + if(requestMap.find(client->restag) == requestMap.end()) return; + MegaRequestPrivate* request = requestMap.at(client->restag); + if(!request || (request->getType() != MegaRequest::TYPE_SUPPORT_TICKET)) return; + + fireOnRequestFinish(request, MegaError(e)); +} + void MegaApiImpl::creditcardstore_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; @@ -13735,8 +13835,7 @@ void MegaApiImpl::request_error(error e) if (e == API_ESID) { - client->removecaches(); - client->locallogout(); + client->locallogout(true); } requestQueue.push(request); waiter->notify(); @@ -14049,9 +14148,9 @@ void MegaApiImpl::exportnode_result(handle h, handle ph) // the key if (n->type == FILENODE) { - if(n->nodekey.size() >= FILENODEKEYLENGTH) + if(n->nodekey().size() >= FILENODEKEYLENGTH) { - Base64::btoa((const byte*)n->nodekey.data(), FILENODEKEYLENGTH, key); + Base64::btoa((const byte*)n->nodekey().data(), FILENODEKEYLENGTH, key); } else { @@ -14855,10 +14954,11 @@ void MegaApiImpl::whyamiblocked_result(int code) return; } - if (request->getFlag() && code != 500) // don't log out if we can be unblocked via sms verification + if (request->getFlag() + && code != 500 // don't log out if we can be unblocked via sms verification + && code != 700) // don't log out if we can be unblocked via verification email (weak account protection) { - client->removecaches(); - client->locallogout(); + client->locallogout(true); MegaRequestPrivate *logoutRequest = new MegaRequestPrivate(MegaRequest::TYPE_LOGOUT); logoutRequest->setFlag(false); @@ -14896,6 +14996,10 @@ void MegaApiImpl::whyamiblocked_result(int code) { reason = "Your account has been blocked pending verification via SMS."; } + else if (code == 700) + { + reason = "Your account has been temporarily suspended for your safety. Please verify your email and follow its steps to unlock your account."; + } //else if (code == 300) --> default reason request->setNumber(code); @@ -16308,7 +16412,7 @@ int naturalsorting_compare (const char *i, const char *j) while ( (char_i = *i) && (char_j = *j) ) { bool char_i_isDigit = isDigit(i); - bool char_j_isDigit = isDigit(j);; + bool char_j_isDigit = isDigit(j); if (char_i_isDigit && char_j_isDigit) { @@ -17512,7 +17616,11 @@ unsigned MegaApiImpl::sendPendingTransfers() Node *parent = client->nodebyhandle(transfer->getParentHandle()); bool startFirst = transfer->shouldStartFirst(); - if (!localPath || !parent || parent->type == FILENODE || !fileName || !(*fileName)) + bool uploadToInbox = ISUNDEF(transfer->getParentHandle()) && transfer->getParentPath() && (strchr(transfer->getParentPath(), '@') || (strlen(transfer->getParentPath()) == 11)); + const char *inboxTarget = uploadToInbox ? transfer->getParentPath() : nullptr; + + if (!localPath || !fileName || !(*fileName) + || (!uploadToInbox && (!parent || parent->type == FILENODE) ) ) { e = API_EARGS; break; @@ -17530,6 +17638,14 @@ unsigned MegaApiImpl::sendPendingTransfers() } nodetype_t type = fa->type; + if (type == FOLDERNODE && uploadToInbox) + { + //Folder upload is not possible when sending to Inbox: + //API won't return handle for folder creation, and even if that was the case + //doing a put nodes with t = userhandle & the corresponding handle as parent p, API returns EACCESS + e = API_EREAD; + break; + } m_off_t size = fa->size; FileFingerprint fp; if (type == FILENODE) @@ -17573,7 +17689,7 @@ unsigned MegaApiImpl::sendPendingTransfers() if (!forceToUpload) { Node *samenode = client->nodebyfingerprint(&fp); - if (samenode && samenode->nodekey.size() && !hasToForceUpload(*samenode, *transfer)) + if (samenode && samenode->nodekey().size() && !hasToForceUpload(*samenode, *transfer)) { pendingUploads++; transfer->setState(MegaTransfer::STATE_QUEUED); @@ -17606,7 +17722,15 @@ unsigned MegaApiImpl::sendPendingTransfers() { tc.nn->ovhandle = client->getovhandle(parent, &sname); } - client->putnodes(parent->nodehandle, tc.nn, nc); + + if (uploadToInbox) + { + client->putnodes(inboxTarget, tc.nn, nc); + } + else + { + client->putnodes(parent->nodehandle, tc.nn, nc); + } transfer->setDeltaSize(size); transfer->setSpeed(0); @@ -17620,7 +17744,8 @@ unsigned MegaApiImpl::sendPendingTransfers() currentTransfer = transfer; string wFileName = fileName; - MegaFilePut *f = new MegaFilePut(client, &wLocalPath, &wFileName, transfer->getParentHandle(), "", mtime, isSourceTemporary); + MegaFilePut *f = new MegaFilePut(client, &wLocalPath, &wFileName, transfer->getParentHandle(), uploadToInbox ? inboxTarget : "", mtime, isSourceTemporary); + *static_cast(f) = fp; // deliberate slicing - startxfer would re-fingerprint if we don't supply this info f->setTransfer(transfer); bool started = client->startxfer(PUT, f, committer, true, startFirst, transfer->isBackupTransfer()); if (!started) @@ -18184,7 +18309,7 @@ void MegaApiImpl::sendPendingRequests() requestMap[request->getTag()]=request; - client->locallogout(); + client->locallogout(false); if (sessionKey) { byte session[MAX_SESSION_LENGTH]; @@ -18511,7 +18636,7 @@ void MegaApiImpl::sendPendingRequests() unsigned nc; TreeProcCopy tc; - if (!node->nodekey.size()) + if (!node->nodekey().size()) { e = API_EKEY; break; @@ -18634,11 +18759,11 @@ void MegaApiImpl::sendPendingRequests() newnode->nodehandle = version->nodehandle; newnode->parenthandle = UNDEF; newnode->ovhandle = current->nodehandle; - newnode->nodekey = version->nodekey; + newnode->nodekey = version->nodekey(); newnode->attrstring = new string(); if (newnode->nodekey.size()) { - key.setkey((const byte*)version->nodekey.data(), version->type); + key.setkey((const byte*)version->nodekey().data(), version->type); version->attrs.getjson(&attrstring); client->makeattr(&key, newnode->attrstring, attrstring.c_str()); } @@ -18747,7 +18872,18 @@ void MegaApiImpl::sendPendingRequests() break; } - e = client->openfilelink(megaFileLink, 1); + handle ph = UNDEF; + byte key[FILENODEKEYLENGTH]; + e = client->parsepubliclink(megaFileLink, ph, key, false); + if (e == API_OK) + { + client->openfilelink(ph, key, 1); + } + else if (e == API_EINCOMPLETE) // no key provided, check only the existence of the node + { + client->openfilelink(ph, nullptr, 1); + e = API_OK; + } break; } case MegaRequest::TYPE_PASSWORD_LINK: @@ -18881,7 +19017,7 @@ void MegaApiImpl::sendPendingRequests() } else { - client->locallogout(); + client->locallogout(false); client->restag = nextTag; logout_result(API_OK); } @@ -18908,7 +19044,7 @@ void MegaApiImpl::sendPendingRequests() if (!fa) { fileattrstring = node->fileattrstring; - key = node->nodekey; + key = node->nodekey(); } else { @@ -18922,7 +19058,7 @@ void MegaApiImpl::sendPendingRequests() } key.assign((const char *)nodekey, sizeof nodekey); } - e = client->getfa(h, &fileattrstring, &key, (fatype) type); + e = client->getfa(h, &fileattrstring, key, (fatype) type); if(e == API_EEXIST) { e = API_OK; @@ -19600,7 +19736,7 @@ void MegaApiImpl::sendPendingRequests() requestMap[reqtag] = request; - client->locallogout(); + client->locallogout(false); if (resumeProcess) { @@ -20419,11 +20555,11 @@ void MegaApiImpl::sendPendingRequests() it++; int tag = sync->tag; - if (!sync->localroot.node || sync->localroot.node->nodehandle == nodehandle) + if (!sync->localroot->node || sync->localroot->node->nodehandle == nodehandle) { string path; - fsAccess->local2path(&sync->localroot.localname, &path); - if (!request->getFile() || sync->localroot.node) + fsAccess->local2path(&sync->localroot->localname, &path); + if (!request->getFile() || sync->localroot->node) { request->setFile(path.c_str()); } @@ -20599,6 +20735,20 @@ void MegaApiImpl::sendPendingRequests() client->sendevent(number, text); break; } + case MegaRequest::TYPE_SUPPORT_TICKET: + { + int type = request->getParamType(); + const char *message = request->getText(); + + if ((type < 0 || type > 7) || !message) + { + e = API_EARGS; + break; + } + + client->supportticket(message, type); + break; + } case MegaRequest::TYPE_GET_USER_DATA: { const char *email = request->getEmail(); @@ -20637,6 +20787,11 @@ void MegaApiImpl::sendPendingRequests() client->copysession(); break; } + case MegaRequest::TYPE_RESEND_VERIFICATION_EMAIL: + { + client->resendverificationemail(); + break; + } case MegaRequest::TYPE_CLEAN_RUBBISH_BIN: { client->cleanrubbishbin(); @@ -21275,12 +21430,12 @@ void MegaApiImpl::sendPendingRequests() } handle h = UNDEF; - byte folderkey[SymmCipher::KEYLENGTH]; - e = client->parsefolderlink(link, h, folderkey); + byte folderkey[FOLDERNODEKEYLENGTH]; + e = client->parsepubliclink(link, h, folderkey, true); if (e == API_OK) { request->setNodeHandle(h); - Base64Str folderkeyB64(folderkey); + Base64Str folderkeyB64(folderkey); request->setPrivateKey(folderkeyB64.chars); client->getpubliclinkinfo(h); } @@ -21616,7 +21771,7 @@ void TreeProcCopy::proc(MegaClient* client, Node* n) t->parenthandle = n->parent ? n->parent->nodehandle : UNDEF; // copy key (if file) or generate new key (if folder) - if (n->type == FILENODE) t->nodekey = n->nodekey; + if (n->type == FILENODE) t->nodekey = n->nodekey(); else { byte buf[FOLDERNODEKEYLENGTH]; @@ -22321,18 +22476,22 @@ int MegaPricingPrivate::getProLevel(int productIndex) return 0; } -unsigned int MegaPricingPrivate::getGBStorage(int productIndex) +int MegaPricingPrivate::getGBStorage(int productIndex) { - if((unsigned)productIndex < gbStorage.size()) - return gbStorage[productIndex]; + if (static_cast(productIndex) < gbStorage.size()) + { + return gbStorage[static_cast(productIndex)]; + } return 0; } -unsigned int MegaPricingPrivate::getGBTransfer(int productIndex) +int MegaPricingPrivate::getGBTransfer(int productIndex) { - if((unsigned)productIndex < gbTransfer.size()) - return gbTransfer[productIndex]; + if (static_cast(productIndex) < gbTransfer.size()) + { + return gbTransfer[static_cast(productIndex)]; + } return 0; } @@ -22385,27 +22544,45 @@ const char *MegaPricingPrivate::getAndroidID(int productIndex) return NULL; } +bool MegaPricingPrivate::isBusinessType(int productIndex) +{ + if((unsigned)productIndex < type.size()) + return type[productIndex]; + + return false; +} + +int MegaPricingPrivate::getAmountMonth(int productIndex) +{ + if((unsigned)productIndex < amountMonth.size()) + return amountMonth[productIndex]; + + return 0; +} + MegaPricing *MegaPricingPrivate::copy() { MegaPricingPrivate *megaPricing = new MegaPricingPrivate(); for(unsigned i=0; iaddProduct(handles[i], proLevel[i], gbStorage[i], gbTransfer[i], - months[i], amount[i], currency[i], description[i], iosId[i], androidId[i]); + megaPricing->addProduct(type[i], handles[i], proLevel[i], gbStorage[i], gbTransfer[i], + months[i], amount[i], amountMonth[i], currency[i], description[i], iosId[i], androidId[i]); } return megaPricing; } -void MegaPricingPrivate::addProduct(handle product, int proLevel, unsigned int gbStorage, unsigned int gbTransfer, int months, int amount, const char *currency, - const char* description, const char* iosid, const char* androidid) +void MegaPricingPrivate::addProduct(unsigned int type, handle product, int proLevel, int gbStorage, int gbTransfer, int months, int amount, int amountMonth, + const char *currency, const char* description, const char* iosid, const char* androidid) { + this->type.push_back(type); this->handles.push_back(product); this->proLevel.push_back(proLevel); this->gbStorage.push_back(gbStorage); this->gbTransfer.push_back(gbTransfer); this->months.push_back(months); this->amount.push_back(amount); + this->amountMonth.push_back(amountMonth); this->currency.push_back(MegaApi::strdup(currency)); this->description.push_back(MegaApi::strdup(description)); this->iosId.push_back(MegaApi::strdup(iosid)); @@ -23232,7 +23409,8 @@ void MegaFolderUploadController::onFolderAvailable(MegaHandle handle) { size_t t = localPath.size(); - while (da->dnext(&localPath, &localname, client->followsymlinks)) + nodetype_t dirEntryType; + while (da->dnext(&localPath, &localname, client->followsymlinks, &dirEntryType)) { if (t) { @@ -23241,35 +23419,30 @@ void MegaFolderUploadController::onFolderAvailable(MegaHandle handle) localPath.append(localname); - auto fa = client->fsaccess->newfileaccess(); - if (fa->fopen(&localPath, true, false)) + string name = localname; + client->fsaccess->local2name(&name); + if (dirEntryType == FILENODE) { - string name = localname; - client->fsaccess->local2name(&name); - if (fa->type == FILENODE) - { - pendingTransfers++; - string utf8path; - client->fsaccess->local2path(&localPath, &utf8path); + pendingTransfers++; + string utf8path; + client->fsaccess->local2path(&localPath, &utf8path); megaApi->startUpload(false, utf8path.c_str(), parent, (const char *)NULL, -1, tag, false, NULL, false, false, this); + } + else if (dirEntryType == FOLDERNODE) + { + MegaNode *child = megaApi->getChildNode(parent, name.c_str()); + if(!child || !child->isFolder()) + { + pendingFolders.push_back(localPath); + megaApi->createFolder(name.c_str(), parent, this); } else { - MegaNode *child = megaApi->getChildNode(parent, name.c_str()); - if(!child || !child->isFolder()) - { - pendingFolders.push_back(localPath); - megaApi->createFolder(name.c_str(), parent, this); - } - else - { - pendingFolders.push_front(localPath); - onFolderAvailable(child->getHandle()); - } - delete child; + pendingFolders.push_front(localPath); + onFolderAvailable(child->getHandle()); } + delete child; } - localPath.resize(t); } } @@ -23415,7 +23588,7 @@ MegaBackupController::MegaBackupController(MegaApiImpl *megaApi, int tag, int fo } else { - this->state = MegaBackup::BACKUP_FAILED;; + this->state = MegaBackup::BACKUP_FAILED; } } @@ -29071,6 +29244,13 @@ void MegaFTPServer::processReceivedData(MegaTCPContext *tcpctx, ssize_t nread, c { MegaNodeList *children = ftpctx->megaApi->getChildren(node); assert(!ftpctx->ftpDataServer->resultmsj.size()); + + if (ftpctx->command == FTP_CMD_LIST) + { + ftpctx->ftpDataServer->resultmsj.append(getListingLineFromNode(node,".")); + ftpctx->ftpDataServer->resultmsj.append(crlfout); + } + for (int i = 0; i < children->size(); i++) { MegaNode *child = children->get(i); diff --git a/src/megaclient.cpp b/src/megaclient.cpp index 898f8c180f..bbde867e6f 100644 --- a/src/megaclient.cpp +++ b/src/megaclient.cpp @@ -459,7 +459,7 @@ void MegaClient::mergenewshare(NewShare *s, bool notify) // b) have we just lost full access to the subtree a sync is in? for (sync_list::iterator it = syncs.begin(); it != syncs.end(); it++) { - if ((*it)->inshare && ((*it)->state == SYNC_ACTIVE || (*it)->state == SYNC_INITIALSCAN) && !checkaccess((*it)->localroot.node, FULL)) + if ((*it)->inshare && ((*it)->state == SYNC_ACTIVE || (*it)->state == SYNC_INITIALSCAN) && !checkaccess((*it)->localroot->node, FULL)) { LOG_warn << "Existing inbound share sync lost full access"; (*it)->errorcode = API_EACCESS; @@ -1026,6 +1026,7 @@ void MegaClient::init() chunkfailed = false; statecurrent = false; totalNodes = 0; + mAppliedKeyNodeCount = 0; faretrying = false; #ifdef ENABLE_SYNC @@ -1068,6 +1069,7 @@ void MegaClient::init() jsonsc.pos = NULL; insca = false; + insca_notlast = false; scnotifyurl.clear(); *scsn = 0; @@ -1226,7 +1228,7 @@ MegaClient::MegaClient(MegaApp* a, Waiter* w, HttpIO* h, FileSystemAccess* f, Db MegaClient::~MegaClient() { - locallogout(); + locallogout(false); delete pendingcs; delete pendingsc; @@ -1742,8 +1744,7 @@ void MegaClient::exec() if (loggedout) { - removecaches(); - locallogout(); + locallogout(true); app->logout_result(API_OK); } } @@ -1903,7 +1904,7 @@ void MegaClient::exec() && pendingsc->in.size() && pendingsc->in[0] == '0') { - LOG_debug << "Waitd keep-alive received"; + LOG_debug << "SC keep-alive received"; delete pendingsc; pendingsc = NULL; btsc.reset(); @@ -1912,6 +1913,8 @@ void MegaClient::exec() if (*pendingsc->in.c_str() == '{') { + insca = false; + insca_notlast = false; jsonsc.begin(pendingsc->in.c_str()); jsonsc.enterobject(); break; @@ -2087,7 +2090,6 @@ void MegaClient::exec() } pendingsc->posturl.append(auth); pendingsc->type = REQ_JSON; - LOG_debug << "Sending keep-alive to waitd"; pendingsc->post(this); jsonsc.pos = NULL; } @@ -2367,7 +2369,7 @@ void MegaClient::exec() if (!syncadding) { LOG_debug << "Running syncup to create missing folders"; - syncup(&sync->localroot, &nds); + syncup(sync->localroot.get(), &nds); sync->cachenodes(); } @@ -2384,7 +2386,7 @@ void MegaClient::exec() // scan for items that were deleted while the sync was stopped // FIXME: defer this until RETRY queue is processed sync->scanseqno++; - sync->deletemissing(&sync->localroot); + sync->deletemissing(sync->localroot.get()); } } } @@ -2473,7 +2475,7 @@ void MegaClient::exec() for (it = syncs.begin(); it != syncs.end(); it++) { // make sure that the remote synced folder still exists - if (!(*it)->localroot.node) + if (!(*it)->localroot->node) { LOG_err << "The remote root node doesn't exist"; (*it)->errorcode = API_ENOENT; @@ -2533,7 +2535,7 @@ void MegaClient::exec() && !syncadding && syncuprequired && !syncnagleretry) { LOG_debug << "Running syncup on demand"; - repeatsyncup |= !syncup(&(*it)->localroot, &nds); + repeatsyncup |= !syncup((*it)->localroot.get(), &nds); syncupdone = true; (*it)->cachenodes(); } @@ -2583,7 +2585,7 @@ void MegaClient::exec() if (sync->fullscan) { // recursively delete all LocalNodes that were deleted (not moved or renamed!) - sync->deletemissing(&sync->localroot); + sync->deletemissing(sync->localroot.get()); sync->cachenodes(); } @@ -2607,7 +2609,7 @@ void MegaClient::exec() } scanfailed = true; - sync->scan(&sync->localroot.localname, NULL); + sync->scan(&sync->localroot->localname, NULL); sync->dirnotify->error = 0; sync->fullscan = true; sync->scanseqno++; @@ -2660,7 +2662,7 @@ void MegaClient::exec() for (it = syncs.begin(); it != syncs.end(); it++) { // make sure that the remote synced folder still exists - if (!(*it)->localroot.node) + if (!(*it)->localroot->node) { LOG_err << "The remote root node doesn't exist"; (*it)->errorcode = API_ENOENT; @@ -2668,11 +2670,11 @@ void MegaClient::exec() } else { - string localpath = (*it)->localroot.localname; + string localpath = (*it)->localroot->localname; if ((*it)->state == SYNC_ACTIVE || (*it)->state == SYNC_INITIALSCAN) { LOG_debug << "Running syncdown on demand"; - if (!syncdown(&(*it)->localroot, &localpath, true)) + if (!syncdown((*it)->localroot.get(), &localpath, true)) { // a local filesystem item was locked - schedule periodic retry // and force a full rescan afterwards as the local item may @@ -2790,7 +2792,7 @@ void MegaClient::exec() if (Waiter::ds > lasttime + 1200) { lasttime = Waiter::ds; - LOG_info << performanceStats.report(false, httpio, waiter); + LOG_info << performanceStats.report(false, httpio, waiter, reqs); } #endif } @@ -2841,13 +2843,7 @@ int MegaClient::preparewait() nexttransferretry(GET, &nds); // retry transferslots - for (transferslot_list::iterator it = tslots.begin(); it != tslots.end(); it++) - { - if (!(*it)->retrybt.armed()) - { - (*it)->retrybt.update(&nds); - } - } + transferSlotsBackoff.update(&nds, false); for (pendinghttp_map::iterator it = pendinghttp.begin(); it != pendinghttp.end(); it++) { @@ -3260,7 +3256,7 @@ bool MegaClient::dispatch(direction_t d) // MegaClient::moretransfers() if ((n = nodebyhandle((*it)->h)) && n->type == FILENODE) { - k = (const byte*)n->nodekey.data(); + k = (const byte*)n->nodekey().data(); nexttransfer->size = n->size; } } @@ -3416,7 +3412,7 @@ bool MegaClient::dispatch(direction_t d) if (!gfxdisabled && gfx && gfx->isgfx(&nexttransfer->localfilename)) { // we want all imagery to be safely tucked away before completing the upload, so we bump minfa - nexttransfer->minfa += gfx->gendimensionsputfa(ts->fa.get(), &nexttransfer->localfilename, nexttransfer->uploadhandle, nexttransfer->transfercipher(), -1, false); + nexttransfer->minfa += gfx->gendimensionsputfa(ts->fa, &nexttransfer->localfilename, nexttransfer->uploadhandle, nexttransfer->transfercipher(), -1, false); } } } @@ -3582,23 +3578,11 @@ void MegaClient::freeq(direction_t d) } // determine next scheduled transfer retry -// FIXME: make this an ordered set and only check the first element instead of -// scanning the full map! void MegaClient::nexttransferretry(direction_t d, dstime* dsmin) { - for (transfer_map::iterator it = transfers[d].begin(); it != transfers[d].end(); it++) + if (!xferpaused[d]) // avoid setting the timer's next=1 if it won't be processed { - if ((!it->second->slot || !it->second->slot->fa) - && it->second->bt.nextset()) - { - it->second->bt.update(dsmin); - if (it->second->bt.armed()) - { - // fire the timer only once but keeping it armed - it->second->bt.set(0); - LOG_debug << "Disabling armed transfer backoff"; - } - } + transferRetryBackoffs[d].update(dsmin, true); } } @@ -3687,8 +3671,7 @@ void MegaClient::logout() { if (loggedin() != FULLACCOUNT) { - removecaches(); - locallogout(); + locallogout(true); restag = reqtag; app->logout_result(API_OK); @@ -3699,8 +3682,13 @@ void MegaClient::logout() reqs.add(new CommandLogout(this)); } -void MegaClient::locallogout() +void MegaClient::locallogout(bool removecaches) { + if (removecaches) + { + removeCaches(); + } + delete sctable; sctable = NULL; pendingsccommit = false; @@ -3830,7 +3818,7 @@ void MegaClient::locallogout() fetchingkeys = false; } -void MegaClient::removecaches() +void MegaClient::removeCaches() { if (sctable) { @@ -3988,6 +3976,11 @@ bool MegaClient::procsc() jsonsc.storeobject(&scnotifyurl); break; + case MAKENAMEID2('i', 'r'): + // when spoonfeeding is in action, there may still be more actionpackets to be delivered. + insca_notlast = jsonsc.getint() == 1; + break; + case MAKENAMEID2('s', 'n'): // the sn element is guaranteed to be the last in sequence setscsn(&jsonsc); @@ -4010,11 +4003,11 @@ bool MegaClient::procsc() break; case EOO: - LOG_debug << "Processing of action packets finished"; + LOG_debug << "Processing of action packets finished. More to follow: " << insca_notlast; mergenewshares(1); applykeys(); - if (!statecurrent) + if (!statecurrent && !insca_notlast) // with actionpacket spoonfeeding, just finishing a batch does not mean we are up to date yet - keep going while "ir":1 { if (fetchingnodes) { @@ -4086,7 +4079,7 @@ bool MegaClient::procsc() string report; fnstats.toJsonArray(&report); - sendevent(99426, report.c_str(), 0); + sendevent(99426, report.c_str(), 0); // Treeproc performance log // NULL vector: "notify all elements" app->nodes_updated(NULL, int(nodes.size())); @@ -4108,7 +4101,11 @@ bool MegaClient::procsc() useralerts.begincatchup = true; } } - app->catchup_result(); + + if (!insca_notlast) + { + app->catchup_result(); + } return true; case 'a': @@ -4594,7 +4591,7 @@ void MegaClient::finalizesc(bool complete) } // queue node file attribute for retrieval or cancel retrieval -error MegaClient::getfa(handle h, string *fileattrstring, string *nodekey, fatype t, int cancel) +error MegaClient::getfa(handle h, string *fileattrstring, const string &nodekey, fatype t, int cancel) { // locate this file attribute type in the nodes's attribute string handle fah; @@ -4673,7 +4670,7 @@ error MegaClient::getfa(handle h, string *fileattrstring, string *nodekey, fatyp if (!*fafp) { - *fafp = new FileAttributeFetch(h, *nodekey, t, reqtag); + *fafp = new FileAttributeFetch(h, nodekey, t, reqtag); } else { @@ -6456,7 +6453,7 @@ void MegaClient::notifypurge(void) for (sync_list::iterator it = syncs.begin(); it != syncs.end(); it++) { if (((*it)->state == SYNC_ACTIVE || (*it)->state == SYNC_INITIALSCAN) - && (*it)->localroot.node->changed.removed) + && (*it)->localroot->node->changed.removed) { delsync(*it); } @@ -6475,7 +6472,7 @@ void MegaClient::notifypurge(void) Node* n = nodenotify[i]; if (n->attrstring) { - LOG_err << "NO_KEY node: " << n->type << " " << n->size << " " << n->nodehandle << " " << n->nodekey.size(); + LOG_err << "NO_KEY node: " << n->type << " " << n->size << " " << n->nodehandle << " " << n->nodekeyUnchecked().size(); #ifdef ENABLE_SYNC if (n->localnode) { @@ -7119,7 +7116,7 @@ uint64_t MegaClient::stringhash64(string* s, SymmCipher* c) } // read and add/verify node array -int MegaClient::readnodes(JSON* j, int notify, putsource_t source, NewNode* nn, int nnsize, int tag) +int MegaClient::readnodes(JSON* j, int notify, putsource_t source, NewNode* nn, int nnsize, int tag, bool applykeys) { if (!j->enterarray()) { @@ -7301,7 +7298,7 @@ int MegaClient::readnodes(JSON* j, int notify, putsource_t source, NewNode* nn, { LOG_warn << "Updating the key of a NO_KEY node"; Node::copystring(n->attrstring, a); - Node::copystring(&n->nodekey, k); + n->setkeyfromjson(k); } } else @@ -7359,7 +7356,7 @@ int MegaClient::readnodes(JSON* j, int notify, putsource_t source, NewNode* nn, n->attrstring = new string; Node::copystring(n->attrstring, a); - Node::copystring(&n->nodekey, k); + n->setkeyfromjson(k); if (!ISUNDEF(su)) { @@ -7413,6 +7410,11 @@ int MegaClient::readnodes(JSON* j, int notify, putsource_t source, NewNode* nn, { notifynode(n); } + + if (applykeys) + { + n->applykey(); + } } } @@ -7869,20 +7871,25 @@ void MegaClient::procph(JSON *j) } } -int MegaClient::applykeys() +void MegaClient::applykeys() { - int t = 0; + CodeCounter::ScopeTimer ccst(performanceStats.applyKeys); - // FIXME: rather than iterating through the whole node set, maintain subset - // with missing keys - for (node_map::iterator it = nodes.begin(); it != nodes.end(); it++) + int noKeyExpected = (rootnodes[0] != UNDEF) + (rootnodes[1] != UNDEF) + (rootnodes[2] != UNDEF); + + if (nodes.size() > size_t(mAppliedKeyNodeCount + noKeyExpected)) { - if (it->second->applykey()) + for (auto& it : nodes) { - t++; + it.second->applykey(); } } + sendkeyrewrites(); +} + +void MegaClient::sendkeyrewrites() +{ if (sharekeyrewrite.size()) { reqs.add(new CommandShareKeyUpdate(this, &sharekeyrewrite)); @@ -7894,8 +7901,6 @@ int MegaClient::applykeys() reqs.add(new CommandNodeKeyUpdate(this, &nodekeyrewrite)); nodekeyrewrite.clear(); } - - return t; } // user/contact list @@ -7991,64 +7996,90 @@ bool MegaClient::readusers(JSON* j, bool actionpackets) return j->leavearray(); } -error MegaClient::parsefolderlink(const char *folderlink, handle &h, byte *key) +// Supported formats: +// - file links: #![!] +// [!] +// /file/[][#] +// +// - folder links: #F![!] +// /folder/[][#] +error MegaClient::parsepubliclink(const char* link, handle& ph, byte* key, bool isFolderLink) { - // structure of public folder links: https://mega.nz/#F!! or https://mega.nz/folder/# - - const char* ptr; - if (!((ptr = strstr(folderlink, "#F!")) && (strlen(ptr) >= 11)) && - !((ptr = strstr(folderlink, "folder/")) && (strlen(ptr) >= 15))) + bool isFolder; + const char* ptr = nullptr; + if ((ptr = strstr(link, "#F!"))) { - return API_EARGS; + ptr += 3; + isFolder = true; } - - const char *nodeHandleBegining = nullptr; - if (*ptr == 'f') + else if ((ptr = strstr(link, "folder/"))) { - nodeHandleBegining = ptr + 7; - ptr += 15; + ptr += 7; + isFolder = true; } - else + else if ((ptr = strstr(link, "#!"))) { - nodeHandleBegining = ptr + 3; - ptr += 11; + ptr += 2; + isFolder = false; } - - if (*ptr == '\0') // no key provided, link is incomplete + else if ((ptr = strstr(link, "file/"))) { - return API_EINCOMPLETE; + ptr += 5; + isFolder = false; } - else if (*ptr != '!' && *ptr != '#') + else // legacy file link format without '#' { - return API_EARGS; + ptr = link; + isFolder = false; } - // Node handle size is 6 Bytes, so we init with zeros to avoid comparison problems - handle auxh = 0; - if (Base64::atob(nodeHandleBegining, (byte*)&auxh, NODEHANDLE) != NODEHANDLE) + if (isFolder != isFolderLink) { - return API_EARGS; + return API_EARGS; // type of link mismatch } - byte auxkey[SymmCipher::KEYLENGTH]; - const char *k = ptr + 1; - if (Base64::atob(k, auxkey, sizeof auxkey) != sizeof auxkey) + if (strlen(ptr) < 8) // no public handle in the link { return API_EARGS; } - h = auxh; - memcpy(key, auxkey, sizeof auxkey); - return API_OK; + ph = 0; //otherwise atob will give an unexpected result + if (Base64::atob(ptr, (byte*)&ph, NODEHANDLE) == NODEHANDLE) + { + ptr += 8; + + // skip any tracking parameter introduced by third-party websites + while(*ptr && *ptr != '!' && *ptr != '#') + { + ptr++; + } + + if (!*ptr || ((*ptr == '#' || *ptr == '!') && *(ptr + 1) == '\0')) // no key provided + { + return API_EINCOMPLETE; + } + + if (*ptr == '!' || *ptr == '#') + { + const char *k = ptr + 1; // skip '!' or '#' separator + int keylen = isFolderLink ? FOLDERNODEKEYLENGTH : FILENODEKEYLENGTH; + if (Base64::atob(k, key, keylen) == keylen) + { + return API_OK; + } + } + } + + return API_EARGS; } error MegaClient::folderaccess(const char *folderlink) { handle h = UNDEF; - byte folderkey[SymmCipher::KEYLENGTH]; + byte folderkey[FOLDERNODEKEYLENGTH]; error e; - if ((e = parsefolderlink(folderlink, h, folderkey)) == API_OK) + if ((e = parsepubliclink(folderlink, h, folderkey, true)) == API_OK) { setrootnode(h); key.setkey(folderkey); @@ -8236,6 +8267,11 @@ int MegaClient::dumpsession(byte* session, size_t size) return int(size); } +void MegaClient::resendverificationemail() +{ + reqs.add(new CommandResendVerificationEmail(this)); +} + void MegaClient::copysession() { reqs.add(new CommandCopySession(this)); @@ -9008,7 +9044,7 @@ void MegaClient::putua(userattr_map *attrs, int ctag) for (userattr_map::iterator it = attrs->begin(); it != attrs->end(); it++) { - attr_t type = it->first;; + attr_t type = it->first; if (User::needversioning(type) != 1) { @@ -9100,8 +9136,8 @@ void MegaClient::notifynode(Node* n) { // report a "NO_KEY" event - char* buf = new char[n->nodekey.size() * 4 / 3 + 4]; - Base64::btoa((byte *)n->nodekey.data(), int(n->nodekey.size()), buf); + char* buf = new char[n->nodekey().size() * 4 / 3 + 4]; + Base64::btoa((byte *)n->nodekey().data(), int(n->nodekey().size()), buf); int changed = 0; changed |= (int)n->changed.removed; @@ -9391,8 +9427,8 @@ void MegaClient::procsnk(JSON* j) if (n && n->isbelow(sn)) { byte keybuf[FILENODEKEYLENGTH]; - size_t keysize = n->nodekey.size(); - sn->sharekey->ecb_encrypt((byte*)n->nodekey.data(), keybuf, keysize); + size_t keysize = n->nodekey().size(); + sn->sharekey->ecb_encrypt((byte*)n->nodekey().data(), keybuf, keysize); reqs.add(new CommandSingleKeyCR(sh, nh, keybuf, keysize)); } } @@ -9847,9 +9883,9 @@ void MegaClient::cr_response(node_vector* shares, node_vector* nodes, JSON* sele { if (setkey >= 0) { - if (setkey == (int)n->nodekey.size()) + if (setkey == (int)n->nodekey().size()) { - sn->sharekey->ecb_decrypt(keybuf, n->nodekey.size()); + sn->sharekey->ecb_decrypt(keybuf, n->nodekey().size()); n->setkey(keybuf); setkey = -1; } @@ -9857,7 +9893,7 @@ void MegaClient::cr_response(node_vector* shares, node_vector* nodes, JSON* sele else { n->applykey(); - int keysize = int(n->nodekey.size()); + int keysize = int(n->nodekey().size()); if (sn->sharekey && keysize == (n->type == FILENODE ? FILENODEKEYLENGTH : FOLDERNODEKEYLENGTH)) { unsigned nsi, nni; @@ -9868,7 +9904,7 @@ void MegaClient::cr_response(node_vector* shares, node_vector* nodes, JSON* sele sprintf(buf, "\",%u,%u,\"", nsi, nni); // generate & queue share nodekey - sn->sharekey->ecb_encrypt((byte*)n->nodekey.data(), keybuf, size_t(keysize)); + sn->sharekey->ecb_encrypt((byte*)n->nodekey().data(), keybuf, size_t(keysize)); Base64::btoa(keybuf, keysize, strchr(buf + 7, 0)); crkeys.append(buf); } @@ -9975,58 +10011,18 @@ void MegaClient::getpubliclink(Node* n, int del, m_time_t ets) } // open exported file link -// formats supported: ...#!publichandle!key, publichandle!key or file/publichandle#key -error MegaClient::openfilelink(const char* link, int op) +// formats supported: ...#!publichandle!key, publichandle!key or file/[][#] +void MegaClient::openfilelink(handle ph, const byte *key, int op) { - const char* ptr = NULL; - handle ph = 0; - byte key[FILENODEKEYLENGTH]; - - if ((ptr = strstr(link, "#!"))) - { - ptr += 2; - } - else if ((ptr = strstr(link, "file/"))) + if (op) { - ptr += 5; + reqs.add(new CommandGetPH(this, ph, key, op)); } - else // legacy format without '#' + else { - ptr = link; + assert(key); + reqs.add(new CommandGetFile(this, NULL, key, ph, false)); } - - if (Base64::atob(ptr, (byte*)&ph, NODEHANDLE) == NODEHANDLE) - { - ptr += 8; - if (*ptr == '!' || (*ptr == '#' && *(ptr + 1) != '\0')) - { - ptr++; - - if (Base64::atob(ptr, key, sizeof key) == sizeof key) - { - if (op) - { - reqs.add(new CommandGetPH(this, ph, key, op)); - } - else - { - reqs.add(new CommandGetFile(this, NULL, key, ph, false)); - } - - return API_OK; - } - } - else if (*ptr == '\0' || *ptr == '#') // no key provided, check only the existence of the node - { - if (op) - { - reqs.add(new CommandGetPH(this, ph, NULL, op)); - return API_OK; - } - } - } - - return API_EARGS; } /* Format of password-protected links @@ -10156,70 +10152,18 @@ error MegaClient::decryptlink(const char *link, const char *pwd, string* decrypt error MegaClient::encryptlink(const char *link, const char *pwd, string *encryptedLink) { - if (!pwd || !link) + if (!pwd || !link || !encryptedLink) { LOG_err << "Empty link or empty password to encrypt link"; return API_EARGS; } - const char* ptr = NULL; - const char* end = link + strlen(link); - - if (!(ptr = strstr(link, "#")) || ptr >= end) - { - LOG_err << "Invalid format of public link or incomplete"; - return API_EARGS; - } - ptr++; // skip '#' - - int isFolder; - if (*ptr == 'F') - { - isFolder = true; - ptr++; // skip 'F' - } - else if (*ptr == '!') - { - isFolder = false; - } - else - { - LOG_err << "Invalid format of public link"; - return API_EARGS; - } - ptr++; // skip '!' separator - - if (ptr + 8 >= end) - { - LOG_err << "Incomplete public link"; - return API_EINCOMPLETE; - } - + bool isFolder = (strstr(link, "#F!") || strstr(link, "folder/")); handle ph; - if (Base64::atob(ptr, (byte*)&ph, NODEHANDLE) != NODEHANDLE) - { - LOG_err << "Invalid format of public link"; - return API_EARGS; - } - ptr += 8; // skip public handle - - if (ptr + 1 >= end || *ptr != '!') - { - LOG_err << "Invalid format of public link"; - return API_EARGS; - } - ptr++; // skip '!' separator - size_t linkKeySize = isFolder ? FOLDERNODEKEYLENGTH : FILENODEKEYLENGTH; - string linkKey; - linkKey.resize(linkKeySize); - if ((size_t) Base64::atob(ptr, (byte *)linkKey.data(), int(linkKey.size())) != linkKeySize) - { - LOG_err << "Invalid encryption key in the public link"; - return API_EKEY; - } - - if (encryptedLink) + std::unique_ptr linkKey(new byte[linkKeySize]); + error e = parsepubliclink(link, ph, linkKey.get(), isFolder); + if (e == API_OK) { // Derive MAC key with salt+pwd byte derivedKey[64]; @@ -10288,7 +10232,7 @@ error MegaClient::encryptlink(const char *link, const char *pwd, string *encrypt encryptedLink->append(encLink); } - return API_OK; + return e; } bool MegaClient::loggedinfolderlink() @@ -10907,6 +10851,7 @@ void MegaClient::fetchnodes(bool nocache) jsonsc.pos = NULL; scnotifyurl.clear(); insca = false; + insca_notlast = false; btsc.reset(); // don't allow to start new sc requests yet @@ -11746,7 +11691,7 @@ void MegaClient::purgenodesusersabortsc() // request direct read by node pointer void MegaClient::pread(Node* n, m_off_t count, m_off_t offset, void* appdata) { - queueread(n->nodehandle, true, n->nodecipher(), MemAccess::get((const char*)n->nodekey.data() + SymmCipher::KEYLENGTH), count, offset, appdata); + queueread(n->nodehandle, true, n->nodecipher(), MemAccess::get((const char*)n->nodekey().data() + SymmCipher::KEYLENGTH), count, offset, appdata); } // request direct read by exported handle / key @@ -11923,7 +11868,7 @@ error MegaClient::isnodesyncable(Node *remotenode, bool *isinshare) { if ((*it)->state == SYNC_ACTIVE || (*it)->state == SYNC_INITIALSCAN) { - n = (*it)->localroot.node; + n = (*it)->localroot->node; do { if (n == remotenode) @@ -11942,7 +11887,7 @@ error MegaClient::isnodesyncable(Node *remotenode, bool *isinshare) for (sync_list::iterator it = syncs.begin(); it != syncs.end(); it++) { if (((*it)->state == SYNC_ACTIVE || (*it)->state == SYNC_INITIALSCAN) - && n == (*it)->localroot.node) + && n == (*it)->localroot->node) { return API_EEXIST; } @@ -12139,7 +12084,7 @@ void MegaClient::addchild(remotenode_map* nchildren, string* name, Node* n, list if (!*npp || n->mtime > (*npp)->mtime || (n->mtime == (*npp)->mtime && n->size > (*npp)->size) - || (n->mtime == (*npp)->mtime && n->size == (*npp)->size && memcmp(n->crc, (*npp)->crc, sizeof n->crc) > 0)) + || (n->mtime == (*npp)->mtime && n->size == (*npp)->size && memcmp(n->crc.data(), (*npp)->crc.data(), sizeof n->crc) > 0)) { *npp = n; } @@ -12256,7 +12201,7 @@ bool MegaClient::syncdown(LocalNode* l, string* localpath, bool rubbish) } else if (ll->mtime == rit->second->mtime && (ll->size > rit->second->size - || (ll->size == rit->second->size && memcmp(ll->crc, rit->second->crc, sizeof ll->crc) > 0))) + || (ll->size == rit->second->size && memcmp(ll->crc.data(), rit->second->crc.data(), sizeof ll->crc) > 0))) { if (ll->size < rit->second->size) @@ -12540,8 +12485,8 @@ bool MegaClient::syncup(LocalNode* l, dstime* nds) { if (!l->reported) { - char* buf = new char[(*it)->nodekey.size() * 4 / 3 + 4]; - Base64::btoa((byte *)(*it)->nodekey.data(), int((*it)->nodekey.size()), buf); + char* buf = new char[(*it)->nodekey().size() * 4 / 3 + 4]; + Base64::btoa((byte *)(*it)->nodekey().data(), int((*it)->nodekey().size()), buf); LOG_warn << "Sync: Undecryptable child node. " << buf; @@ -12725,7 +12670,7 @@ bool MegaClient::syncup(LocalNode* l, dstime* nds) continue; } - if (ll->size == rit->second->size && memcmp(ll->crc, rit->second->crc, sizeof ll->crc) < 0) + if (ll->size == rit->second->size && memcmp(ll->crc.data(), rit->second->crc.data(), sizeof ll->crc) < 0) { LOG_warn << "Syncup. Same mtime and size, but lower CRC: " << ll->name << " mtime: " << ll->mtime << " size: " << ll->size << " Nhandle: " << LOG_NODEHANDLE(rit->second->nodehandle); @@ -12738,7 +12683,7 @@ bool MegaClient::syncup(LocalNode* l, dstime* nds) << " NSize: " << rit->second->size << " Nmtime: " << rit->second->mtime << " Nhandle: " << LOG_NODEHANDLE(rit->second->nodehandle); #ifdef WIN32 - if(ll->size == ll->node->size && !memcmp(ll->crc, ll->node->crc, sizeof(ll->crc))) + if(ll->size == ll->node->size && !memcmp(ll->crc.data(), ll->node->crc.data(), sizeof(ll->crc))) { LOG_debug << "Modification time changed only"; auto f = fsaccess->newfileaccess(); @@ -13065,6 +13010,11 @@ void MegaClient::syncupdate() n = NULL; l = synccreate[i]; + if (l->type == FILENODE && l->parent->node) + { + l->h = l->parent->node->nodehandle; + } + if (l->type == FOLDERNODE || (n = nodebyfingerprint(l))) { // create remote folder or copy file if it already exists @@ -13094,7 +13044,7 @@ void MegaClient::syncupdate() // this is a file - copy, use original key & attributes // FIXME: move instead of creating a copy if it is in // rubbish to reduce node creation load - nnp->nodekey = n->nodekey; + nnp->nodekey = n->nodekey(); tattrs.map = n->attrs.map; nameid rrname = AttrMap::string2nameid("rr"); @@ -13148,14 +13098,17 @@ void MegaClient::syncupdate() else { // add nodes unless parent node has been deleted - if (synccreate[start]->parent->node) + LocalNode *localNode = synccreate[start]; + if (localNode->parent->node) { syncadding++; + assert(localNode->type == FOLDERNODE + || localNode->h == localNode->parent->node->nodehandle); // if it's a file, it should match reqs.add(new CommandPutNodes(this, - synccreate[start]->parent->node->nodehandle, + localNode->parent->node->nodehandle, NULL, nn, int(nnp - nn), - synccreate[start]->sync->tag, + localNode->sync->tag, PUTNODES_SYNC)); syncactivity = true; @@ -13523,16 +13476,16 @@ bool MegaClient::startxfer(direction_t d, File* f, DBTableTransactionCommitter& return false; } - #ifdef USE_MEDIAINFO +#ifdef USE_MEDIAINFO mediaFileInfo.requestCodecMappingsOneTime(this, &f->localname); - #endif +#endif } else { if (!f->isvalid) { // no valid fingerprint: use filekey as its replacement - memcpy(f->crc, f->filekey, sizeof f->crc); + memcpy(f->crc.data(), f->filekey, sizeof f->crc); } } @@ -13691,6 +13644,9 @@ bool MegaClient::startxfer(direction_t d, File* f, DBTableTransactionCommitter& t->failed(API_EOVERQUOTA, committer); } } + + assert( (ISUNDEF(f->h) && f->targetuser.size() && (f->targetuser.size() == 11 || f->targetuser.find("@")!=string::npos) ) // <- uploading to inbox + || (!ISUNDEF(f->h) && (nodebyhandle(f->h) || d == GET) )); // target handle for the upload should be known at this time (except for inbox uploads) } return true; @@ -13704,11 +13660,7 @@ void MegaClient::stopxfer(File* f, DBTableTransactionCommitter* committer) LOG_debug << "Stopping transfer: " << f->name; Transfer *transfer = f->transfer; - transfer->files.erase(f->file_it); - filecachedel(f, committer); - app->file_removed(f, API_EINCOMPLETE); - f->transfer = NULL; - f->terminated(); + transfer->removeTransferFile(API_EINCOMPLETE, f, committer); // last file for this transfer removed? shut down transfer. if (!transfer->files.size()) @@ -13869,12 +13821,12 @@ namespace action_bucket_compare bool nodeIsVideo(const Node* n, char ext[12], const MegaClient& mc) { - if (n->hasfileattribute(fa_media) && n->nodekey.size() == FILENODEKEYLENGTH) + if (n->hasfileattribute(fa_media) && n->nodekey().size() == FILENODEKEYLENGTH) { #ifdef USE_MEDIAINFO if (mc.mediaFileInfo.mediaCodecsReceived) { - MediaProperties mp = MediaProperties::decodeMediaPropertiesAttributes(n->fileattrstring, (uint32_t*)(n->nodekey.data() + FILENODEKEYLENGTH / 2)); + MediaProperties mp = MediaProperties::decodeMediaPropertiesAttributes(n->fileattrstring, (uint32_t*)(n->nodekey().data() + FILENODEKEYLENGTH / 2)); unsigned videocodec = mp.videocodecid; if (!videocodec && mp.shortformat) { @@ -14164,6 +14116,11 @@ void MegaClient::sendevent(int event, const char *message, int tag) reqtag = creqtag; } +void MegaClient::supportticket(const char *message, int type) +{ + reqs.add(new CommandSupportTicket(this, message, type)); +} + void MegaClient::cleanrubbishbin() { reqs.add(new CommandCleanRubbishBin(this)); @@ -14329,7 +14286,7 @@ void MegaClient::getwelcomepdf() } #ifdef MEGA_MEASURE_CODE -std::string MegaClient::PerformanceStats::report(bool reset, HttpIO* httpio, Waiter* waiter) +std::string MegaClient::PerformanceStats::report(bool reset, HttpIO* httpio, Waiter* waiter, const RequestDispatcher& reqs) { std::ostringstream s; s << prepareWait.report(reset) << "\n" @@ -14344,6 +14301,7 @@ std::string MegaClient::PerformanceStats::report(bool reset, HttpIO* httpio, Wai << scProcessingTime.report(reset) << "\n" << csResponseProcessingTime.report(reset) << "\n" << " cs Request waiting time: " << csRequestWaitTime.report(reset) << "\n" + << " cs requests sent/received: " << reqs.csRequestsSent << "/" << reqs.csRequestsCompleted << " batches: " << reqs.csBatchesSent << "/" << reqs.csBatchesReceived << "\n" << " transfers active time: " << transfersActiveTime.report(reset) << "\n" << " transfer starts/finishes: " << transferStarts << " " << transferFinishes << "\n" << " transfer temperror/fails: " << transferTempErrors << " " << transferFails << "\n" diff --git a/src/node.cpp b/src/node.cpp index 21ba555d48..0d395be256 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -90,41 +90,44 @@ Node::Node(MegaClient* cclient, node_vector* dp, handle h, handle ph, memset(&changed,-1,sizeof changed); changed.removed = false; - if (client) - { - Node* p; - - client->nodes[h] = this; + Node* p; - // folder link access: first returned record defines root node and - // identity - if (ISUNDEF(*client->rootnodes)) - { - *client->rootnodes = h; - } + client->nodes[h] = this; - if (t >= ROOTNODE && t <= RUBBISHNODE) - { - client->rootnodes[t - ROOTNODE] = h; - } + // folder link access: first returned record defines root node and + // identity + if (ISUNDEF(*client->rootnodes)) + { + *client->rootnodes = h; + } - // set parent linkage or queue for delayed parent linkage in case of - // out-of-order delivery - if ((p = client->nodebyhandle(ph))) - { - setparent(p); - } - else - { - dp->push_back(this); - } + if (t >= ROOTNODE && t <= RUBBISHNODE) + { + client->rootnodes[t - ROOTNODE] = h; + } - client->mFingerprints.newnode(this); + // set parent linkage or queue for delayed parent linkage in case of + // out-of-order delivery + if ((p = client->nodebyhandle(ph))) + { + setparent(p); } + else + { + dp->push_back(this); + } + + client->mFingerprints.newnode(this); } Node::~Node() { + if (keyApplied()) + { + client->mAppliedKeyNodeCount--; + assert(client->mAppliedKeyNodeCount >= 0); + } + // abort pending direct reads client->preadabort(this); @@ -208,12 +211,23 @@ Node::~Node() #endif } +void Node::setkeyfromjson(const char* k) +{ + if (keyApplied()) --client->mAppliedKeyNodeCount; + Node::copystring(&nodekeydata, k); + if (keyApplied()) ++client->mAppliedKeyNodeCount; + assert(client->mAppliedKeyNodeCount >= 0); +} + // update node key and decrypt attributes void Node::setkey(const byte* newkey) { if (newkey) { - nodekey.assign((char*)newkey, (type == FILENODE) ? FILENODEKEYLENGTH + 0 : FOLDERNODEKEYLENGTH + 0); + if (keyApplied()) --client->mAppliedKeyNodeCount; + nodekeydata.assign(reinterpret_cast(newkey), (type == FILENODE) ? FILENODEKEYLENGTH : FOLDERNODEKEYLENGTH); + if (keyApplied()) ++client->mAppliedKeyNodeCount; + assert(client->mAppliedKeyNodeCount >= 0); } setattr(); @@ -221,7 +235,7 @@ void Node::setkey(const byte* newkey) // parse serialized node and return Node object - updates nodes hash and parent // mismatch vector -Node* Node::unserialize(MegaClient* client, string* d, node_vector* dp) +Node* Node::unserialize(MegaClient* client, const string* d, node_vector* dp) { handle h, ph; nodetype_t t; @@ -281,7 +295,7 @@ Node* Node::unserialize(MegaClient* client, string* d, node_vector* dp) if ((t == FILENODE) || (t == FOLDERNODE)) { - int keylen = ((t == FILENODE) ? FILENODEKEYLENGTH + 0 : FOLDERNODEKEYLENGTH + 0); + int keylen = ((t == FILENODE) ? FILENODEKEYLENGTH : FOLDERNODEKEYLENGTH); if (ptr + keylen + 8 + sizeof(short) > end) { @@ -449,21 +463,21 @@ bool Node::serialize(string* d) switch (type) { case FILENODE: - if ((int)nodekey.size() != FILENODEKEYLENGTH) + if ((int)nodekeydata.size() != FILENODEKEYLENGTH) { return false; } break; case FOLDERNODE: - if ((int)nodekey.size() != FOLDERNODEKEYLENGTH) + if ((int)nodekeydata.size() != FOLDERNODEKEYLENGTH) { return false; } break; default: - if (nodekey.size()) + if (nodekeydata.size()) { return false; } @@ -497,11 +511,11 @@ bool Node::serialize(string* d) ts = (time_t)ctime; d->append((char*)&ts, sizeof(ts)); - d->append(nodekey); + d->append(nodekeydata); if (type == FILENODE) { - ll = (short)fileattrstring.size() + 1; + ll = static_cast(fileattrstring.size() + 1); d->append((char*)&ll, sizeof ll); d->append(fileattrstring.c_str(), ll); } @@ -512,7 +526,7 @@ bool Node::serialize(string* d) char hasLinkCreationTs = plink ? 1 : 0; d->append((char*)&hasLinkCreationTs, 1); - d->append("\0\0\0\0\0", 6); + d->append("\0\0\0\0\0", 6); // Use these bytes for extensions if (inshare) { @@ -672,7 +686,7 @@ void Node::parseattr(byte *bufattr, AttrMap &attrs, m_off_t size, m_time_t &mtim // return temporary SymmCipher for this nodekey SymmCipher* Node::nodecipher() { - if (client->tmpnodecipher.setkey(&nodekey)) + if (client->tmpnodecipher.setkey(&nodekeydata)) { return &client->tmpnodecipher; } @@ -718,7 +732,7 @@ void Node::setattr() // otherwise, the file's fingerprint is derived from the file's mtime/size/key void Node::setfingerprint() { - if (type == FILENODE && nodekey.size() >= sizeof crc) + if (type == FILENODE && nodekeydata.size() >= sizeof crc) { client->mFingerprints.remove(this); @@ -736,7 +750,7 @@ void Node::setfingerprint() // size and client timestamp instead if (!isvalid) { - memcpy(crc, nodekey.data(), sizeof crc); + memcpy(crc.data(), nodekeydata.data(), sizeof crc); mtime = ctime; } @@ -858,10 +872,6 @@ int Node::hasfileattribute(const string *fileattrstring, fatype t) // attempt to apply node key - sets nodekey to a raw key if successful bool Node::applykey() { - unsigned int keylength = (type == FILENODE) - ? FILENODEKEYLENGTH + 0 - : FOLDERNODEKEYLENGTH + 0; - if (type > FOLDERNODE) { //Root nodes contain an empty attrstring @@ -869,7 +879,7 @@ bool Node::applykey() attrstring = NULL; } - if (nodekey.size() == keylength || !nodekey.size()) + if (keyApplied() || !nodekeydata.size()) { return false; } @@ -881,12 +891,12 @@ bool Node::applykey() SymmCipher* sc = &client->key; handle me = client->loggedin() ? client->me : *client->rootnodes; - while ((t = nodekey.find_first_of(':', t)) != string::npos) + while ((t = nodekeydata.find_first_of(':', t)) != string::npos) { // compound key: locate suitable subkey (always symmetric) h = 0; - l = Base64::atob(nodekey.c_str() + (nodekey.find_last_of('/', t) + 1), (byte*)&h, sizeof h); + l = Base64::atob(nodekeydata.c_str() + (nodekeydata.find_last_of('/', t) + 1), (byte*)&h, sizeof h); t++; if (l == MegaClient::USERHANDLE) @@ -918,7 +928,7 @@ bool Node::applykey() } } - k = nodekey.c_str() + t; + k = nodekeydata.c_str() + t; break; } @@ -928,7 +938,7 @@ bool Node::applykey() { if (l < 0) { - k = nodekey.c_str(); + k = nodekeydata.c_str(); } else { @@ -937,13 +947,16 @@ bool Node::applykey() } byte key[FILENODEKEYLENGTH]; + unsigned keylength = (type == FILENODE) ? FILENODEKEYLENGTH : FOLDERNODEKEYLENGTH; if (client->decryptkey(k, key, keylength, sc, 0, nodehandle)) { - nodekey.assign((const char*)key, keylength); + client->mAppliedKeyNodeCount++; + nodekeydata.assign((const char*)key, keylength); setattr(); } + assert(keyApplied()); return true; } @@ -1332,7 +1345,9 @@ void LocalNode::bumpnagleds() } LocalNode::LocalNode() -: sync{nullptr} +: deleted{false} +, created{false} +, reported{false} , checked{false} {} @@ -1498,7 +1513,7 @@ void LocalNode::setnotseen(int newnotseen) } // set fsid - assume that an existing assignment of the same fsid is no longer current and revoke -void LocalNode::setfsid(handle newfsid) +void LocalNode::setfsid(handle newfsid, handlelocalnode_map& fsidnodes) { if (!sync) { @@ -1507,26 +1522,26 @@ void LocalNode::setfsid(handle newfsid) return; } - if (fsid_it != sync->client->fsidnode.end()) + if (fsid_it != fsidnodes.end()) { if (newfsid == fsid) { return; } - sync->client->fsidnode.erase(fsid_it); + fsidnodes.erase(fsid_it); } fsid = newfsid; - pair r = sync->client->fsidnode.insert(pair(fsid, this)); + pair r = fsidnodes.insert(std::make_pair(fsid, this)); fsid_it = r.first; if (!r.second) { // remove previous fsid assignment (the node is likely about to be deleted) - fsid_it->second->fsid_it = sync->client->fsidnode.end(); + fsid_it->second->fsid_it = fsidnodes.end(); fsid_it->second = this; } } @@ -1540,6 +1555,11 @@ LocalNode::~LocalNode() return; } + if (!sync->client) + { + return; + } + if (sync->state == SYNC_ACTIVE || sync->state == SYNC_INITIALSCAN) { sync->statecachedel(this); @@ -1630,7 +1650,7 @@ LocalNode::~LocalNode() } } -void LocalNode::getlocalpath(string* path, bool sdisable) const +void LocalNode::getlocalpath(string* path, bool sdisable, const std::string* localseparator) const { if (!sync) { @@ -1658,7 +1678,7 @@ void LocalNode::getlocalpath(string* path, bool sdisable) const if ((l = l->parent)) { - path->insert(0, sync->client->fsaccess->localseparator); + path->insert(0, localseparator ? *localseparator : sync->client->fsaccess->localseparator); } if (sdisable) @@ -1779,17 +1799,22 @@ bool LocalNode::serialize(string* d) if (type == FILENODE) { - d->append((const char*)crc, sizeof crc); + d->append((const char*)crc.data(), sizeof crc); byte buf[sizeof mtime+1]; d->append((const char*)buf, Serialize64::serialize(buf, mtime)); } + const char syncable = mSyncable ? 1 : 0; + d->append(&syncable, sizeof(syncable)); + + d->append("\0\0\0\0\0\0\0", 8); // Use these bytes for extensions + return true; } -LocalNode* LocalNode::unserialize(Sync* sync, string* d) +LocalNode* LocalNode::unserialize(Sync* sync, const string* d) { if (d->size() < sizeof(m_off_t) // type/size combo + sizeof(handle) // fsid @@ -1855,13 +1880,42 @@ LocalNode* LocalNode::unserialize(Sync* sync, string* d) memcpy(crc, ptr, sizeof crc); ptr += sizeof crc; - if (Serialize64::unserialize((byte*)ptr, static_cast(end - ptr), &mtime) < 0) + int mtimeSize; + if ((mtimeSize = Serialize64::unserialize((byte*)ptr, static_cast(end - ptr), &mtime)) < 0) { LOG_err << "LocalNode unserialization failed - malformed fingerprint mtime"; return NULL; } + else + { + ptr += mtimeSize; + } } + char syncable = 1; + if (ptr < end) + { + if (ptr + sizeof(syncable) + 8 > end) + { + LOG_err << "LocalNode unserialization failed - syncable flag"; + return NULL; + } + + syncable = MemAccess::get(ptr); + ptr += sizeof(syncable); + + // skip extension bytes + for (int i = 8; i--;) + { + if (ptr + (unsigned char)*ptr < end) + { + ptr += (unsigned char)*ptr + 1; + } + } + } + + assert(ptr == end); + LocalNode* l = new LocalNode(); l->type = type; @@ -1872,22 +1926,23 @@ LocalNode* LocalNode::unserialize(Sync* sync, string* d) l->fsid = fsid; l->localname.assign(localname, localnamelen); - l->slocalname = NULL; + l->slocalname = nullptr; l->name.assign(localname, localnamelen); sync->client->fsaccess->local2name(&l->name); - memcpy(l->crc, crc, sizeof crc); + memcpy(l->crc.data(), crc, sizeof crc); l->mtime = mtime; - l->isvalid = 1; + l->isvalid = true; l->node = sync->client->nodebyhandle(h); - l->parent = NULL; + l->parent = nullptr; l->sync = sync; + l->mSyncable = syncable == 1; // FIXME: serialize/unserialize l->created = false; l->reported = false; - l->checked = h != UNDEF; + l->checked = h != UNDEF; // TODO: Is this a bug? h will never be UNDEF return l; } diff --git a/src/posix/fs.cpp b/src/posix/fs.cpp index 5bf2d90e5f..e9b1c99e04 100644 --- a/src/posix/fs.cpp +++ b/src/posix/fs.cpp @@ -902,11 +902,11 @@ int PosixFileSystemAccess::checkevents(Waiter* w) for (it = client->syncs.begin(); it != client->syncs.end(); it++) { - int rsize = (*it)->mFsEventsPath.size() ? (*it)->mFsEventsPath.size() : (*it)->localroot.localname.size(); + int rsize = (*it)->mFsEventsPath.size() ? (*it)->mFsEventsPath.size() : (*it)->localroot->localname.size(); int isize = (*it)->dirnotify->ignore.size(); if (psize >= rsize - && !memcmp((*it)->mFsEventsPath.size() ? (*it)->mFsEventsPath.c_str() : (*it)->localroot.localname.c_str(), path, rsize) // prefix match + && !memcmp((*it)->mFsEventsPath.size() ? (*it)->mFsEventsPath.c_str() : (*it)->localroot->localname.c_str(), path, rsize) // prefix match && (!path[rsize] || path[rsize] == '/') // at end: end of path or path separator && (psize <= (rsize + isize) // not ignored || (path[rsize + isize + 1] && path[rsize + isize + 1] != '/') @@ -944,9 +944,9 @@ int PosixFileSystemAccess::checkevents(Waiter* w) { if (paths[i]) { - LOG_debug << "Filesystem notification. Root: " << pathsync[i]->localroot.name << " Path: " << paths[i]; + LOG_debug << "Filesystem notification. Root: " << pathsync[i]->localroot->name << " Path: " << paths[i]; pathsync[i]->dirnotify->notify(DirNotify::DIREVENTS, - &pathsync[i]->localroot, + pathsync[i]->localroot.get(), paths[i], strlen(paths[i])); diff --git a/src/serialize64.cpp b/src/serialize64.cpp index 7e3a683ba8..f21113aa8c 100644 --- a/src/serialize64.cpp +++ b/src/serialize64.cpp @@ -22,35 +22,36 @@ #include "mega/serialize64.h" namespace mega { -int Serialize64::serialize(byte* b, uint64_t v) +int Serialize64::serialize(byte* bytes, uint64_t value) { - byte p = 0; + byte byteCount = 0; - while (v) + while (value) { - b[++p] = (byte)v; - v >>= 8; + bytes[++byteCount] = (byte)value; + value >>= 8; } + bytes[0] = byteCount; - return (*b = p) + 1; + return byteCount + 1; } -int Serialize64::unserialize(byte* b, int blen, uint64_t* v) +int Serialize64::unserialize(byte* bytes, int blen, uint64_t* value) { - byte p = *b; + byte byteCount = bytes[0]; - if ((p > sizeof(*v)) || (p >= blen)) + if ((byteCount > sizeof(*value)) || (byteCount >= blen)) { return -1; } - *v = 0; + *value = 0; - while (p) + while (byteCount) { - *v = (*v << 8) + b[(int)p--]; + *value = (*value << 8) + bytes[(int)byteCount--]; } - return *b + 1; + return bytes[0] + 1; } } // namespace diff --git a/src/sharenodekeys.cpp b/src/sharenodekeys.cpp index 1bde8ce0fc..8c638f892a 100644 --- a/src/sharenodekeys.cpp +++ b/src/sharenodekeys.cpp @@ -49,11 +49,11 @@ void ShareNodeKeys::add(Node* n, Node* sn, int specific) sn = n; } - add((NodeCore*)n, sn, specific); + add(n->nodekey(), n->nodehandle, sn, specific); } // add a nodecore (!sn: all relevant shares, otherwise starting from sn, fixed: only sn) -void ShareNodeKeys::add(NodeCore* n, Node* sn, int specific, const byte* item, int itemlen) +void ShareNodeKeys::add(const string& nodekey, handle nodehandle, Node* sn, int specific, const byte* item, int itemlen) { char buf[96]; char* ptr; @@ -67,10 +67,10 @@ void ShareNodeKeys::add(NodeCore* n, Node* sn, int specific, const byte* item, i { sprintf(buf, ",%d,%d,\"", addshare(sn), (int)items.size()); - sn->sharekey->ecb_encrypt((byte*)n->nodekey.data(), key, n->nodekey.size()); + sn->sharekey->ecb_encrypt((byte*)nodekey.data(), key, nodekey.size()); ptr = strchr(buf + 5, 0); - ptr += Base64::btoa(key, int(n->nodekey.size()), ptr); + ptr += Base64::btoa(key, int(nodekey.size()), ptr); *ptr++ = '"'; keys.append(buf, ptr - buf); @@ -88,7 +88,7 @@ void ShareNodeKeys::add(NodeCore* n, Node* sn, int specific, const byte* item, i } else { - items[items.size() - 1].assign((const char*)&n->nodehandle, MegaClient::NODEHANDLE); + items[items.size() - 1].assign((const char*)&nodehandle, MegaClient::NODEHANDLE); } } } diff --git a/src/sync.cpp b/src/sync.cpp index f84e88d72c..ad213b5ce9 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -19,6 +19,9 @@ * program. */ +#include +#include + #include "mega.h" #ifdef ENABLE_SYNC @@ -38,6 +41,49 @@ const dstime Sync::RECENT_VERSION_INTERVAL_SECS = 10800; namespace { +// Need this to store `LightFileFingerprint` by-value in `FingerprintSet` +struct LightFileFingerprintComparator +{ + bool operator()(const LightFileFingerprint& lhs, const LightFileFingerprint& rhs) const + { + return LightFileFingerprintCmp{}(&lhs, &rhs); + } +}; + +// Represents a file/folder for use in assigning fs IDs +struct FsFile +{ + handle fsid; + string path; +}; + +// Caches fingerprints +class FingerprintCache +{ +public: + using FingerprintSet = std::set; + + // Adds a new fingerprint + template::type>::value>::type> + const LightFileFingerprint* add(T&& ffp) + { + const auto insertPair = mFingerprints.insert(std::forward(ffp)); + return &*insertPair.first; + } + + // Returns the set of all fingerprints + const FingerprintSet& all() const + { + return mFingerprints; + } + +private: + FingerprintSet mFingerprints; +}; + +using FingerprintLocalNodeMap = std::multimap; +using FingerprintFileMap = std::multimap; + // Collects all syncable filesystem paths in the given folder under `localpath` set collectAllPathsInFolder(Sync& sync, MegaApp& app, FileSystemAccess& fsaccess, string localpath, const string& localdebris, const string& localseparator) @@ -62,7 +108,7 @@ set collectAllPathsInFolder(Sync& sync, MegaApp& app, FileSystemAccess& return {}; } - set paths; + set paths; // has to be a std::set to enforce same sorting as `children` of `LocalNode` const size_t localpathSize = localpath.size(); @@ -96,49 +142,41 @@ set collectAllPathsInFolder(Sync& sync, MegaApp& app, FileSystemAccess& } // Combines another fingerprint into `ffp` -void hashCombineFingerprint(FileFingerprint& ffp, const FileFingerprint& other) +void hashCombineFingerprint(LightFileFingerprint& ffp, const LightFileFingerprint& other) { - assert(other.isvalid); hashCombine(ffp.size, other.size); hashCombine(ffp.mtime, other.mtime); - for (size_t i = 0; i < sizeof(other.crc) / sizeof(*other.crc); ++i) - { - hashCombine(ffp.crc[i], other.crc[i]); - } - ffp.isvalid = true; } // Combines the fingerprints of all file nodes in the given map -void combinedFingerprint(FileFingerprint& ffp, const localnode_map& nodeMap) +bool combinedFingerprint(LightFileFingerprint& ffp, const localnode_map& nodeMap) { - ffp.isvalid = false; + bool success = false; for (const auto& nodePair : nodeMap) { const LocalNode& l = *nodePair.second; if (l.type == FILENODE) { - if (!l.isvalid) - { - LOG_err << "Invalid fingerprint: " << l.localname; - ffp.isvalid = false; - break; - } - hashCombineFingerprint(ffp, l); + LightFileFingerprint lFfp; + lFfp.genfingerprint(l.size, l.mtime); + hashCombineFingerprint(ffp, lFfp); + success = true; } } + return success; } // Combines the fingerprints of all files in the given paths -void combinedFingerprint(FileFingerprint& ffp, FileSystemAccess& fsaccess, const set& paths) +bool combinedFingerprint(LightFileFingerprint& ffp, FileSystemAccess& fsaccess, const set& paths) { - ffp.isvalid = false; + bool success = false; for (const auto& path : paths) { auto fa = fsaccess.newfileaccess(false); if (!fa->fopen(const_cast(&path), true, false)) { LOG_err << "Unable to open path: " << path; - ffp.isvalid = false; + success = false; break; } if (fa->mIsSymLink) @@ -148,82 +186,74 @@ void combinedFingerprint(FileFingerprint& ffp, FileSystemAccess& fsaccess, const } if (fa->type == FILENODE) { - FileFingerprint faFfp; - faFfp.genfingerprint(fa.get()); - if (!faFfp.isvalid) - { - LOG_err << "Invalid fingerprint: " << path; - ffp.isvalid = false; - break; - } + LightFileFingerprint faFfp; + faFfp.genfingerprint(fa->size, fa->mtime); hashCombineFingerprint(ffp, faFfp); + success = true; } } + return success; } // Computes the fingerprint of the given `l` (file or folder) and stores it in `ffp` -void computeFingerprint(FileFingerprint& ffp, const LocalNode& l) +bool computeFingerprint(LightFileFingerprint& ffp, const LocalNode& l) { if (l.type == FILENODE) { - if (!l.isvalid) - { - LOG_err << "Invalid fingerprint: " << l.localname; - return; - } - ffp = l; + ffp.genfingerprint(l.size, l.mtime); + return true; } else if (l.type == FOLDERNODE) { - combinedFingerprint(ffp, l.children); + return combinedFingerprint(ffp, l.children); } else { assert(false && "Invalid node type"); + return false; } } // Computes the fingerprint of the given `fa` (file or folder) and stores it in `ffp` -void computeFingerprint(FileFingerprint& ffp, FileSystemAccess& fsaccess, FileAccess& fa, const set& paths) +bool computeFingerprint(LightFileFingerprint& ffp, FileSystemAccess& fsaccess, + FileAccess& fa, const std::string& path, const set& paths) { if (fa.type == FILENODE) { assert(paths.empty()); - ffp.genfingerprint(&fa); - if (!ffp.isvalid) - { - LOG_err << "Invalid fingerprint"; - return; - } + ffp.genfingerprint(fa.size, fa.mtime); + return true; } else if (fa.type == FOLDERNODE) { - combinedFingerprint(ffp, fsaccess, paths); + return combinedFingerprint(ffp, fsaccess, paths); } else { assert(false && "Invalid node type"); + return false; } } -struct FileFingerprintComparator +// Collects all `LocalNode`s by storing them in `localnodes`, keyed by LightFileFingerprint. +// Invalidates the fs IDs of all local nodes. +// Stores all fingerprints in `fingerprints` for later reference. +void collectAllLocalNodes(FingerprintCache& fingerprints, FingerprintLocalNodeMap& localnodes, + LocalNode& l, handlelocalnode_map& fsidnodes, const string& localseparator) { - bool operator()(const FileFingerprint& lhs, const FileFingerprint& rhs) const + // invalidate fsid of `l` + l.fsid = mega::UNDEF; + if (l.fsid_it != fsidnodes.end()) { - return FileFingerprintCmp{}(&lhs, &rhs); + fsidnodes.erase(l.fsid_it); + l.fsid_it = fsidnodes.end(); } -}; - -using FingerprintMap = std::multimap; - -// Collects all LocalNodes by storing them in `fingerprints`, keyed by FileFingerprint -void collectAllFingerprints(FingerprintMap& fingerprints, LocalNode& l) -{ - FileFingerprint ffp; - computeFingerprint(ffp, l); - if (ffp.isvalid) + // collect fingerprint + LightFileFingerprint ffp; + if (computeFingerprint(ffp, l)) { - fingerprints.insert(std::make_pair(ffp, &l)); + const auto ffpPtr = fingerprints.add(std::move(ffp)); + localnodes.insert(std::make_pair(ffpPtr, &l)); } if (l.type == FILENODE) { @@ -231,75 +261,32 @@ void collectAllFingerprints(FingerprintMap& fingerprints, LocalNode& l) } for (auto& childPair : l.children) { - collectAllFingerprints(fingerprints, *childPair.second); + collectAllLocalNodes(fingerprints, localnodes, *childPair.second, fsidnodes, localseparator); } } -// Assigns `fa`'s fs ID to the local node from `fingerprints` that matches the fingerprint. -// If there are multiple matches the node that's best matching the given preferred path is used. -void assignFilesystemId(FileSystemAccess& fsaccess, FileAccess& fa, handlelocalnode_map& fsidnodes, - FingerprintMap& fingerprints, string preferredNodePath, - const string& localseparator, const set& paths = {}) +// Collects all `File`s by storing them in `files`, keyed by FileFingerprint. +// Stores all fingerprints in `fingerprints` for later reference. +void collectAllFiles(bool& success, FingerprintCache& fingerprints, FingerprintFileMap& files, + Sync& sync, MegaApp& app, FileSystemAccess& fsaccess, const string& localpath, + const string& localdebris, const string& localseparator) { - if (!fa.fsidvalid) - { - LOG_err << "Invalid fs id"; - return; - } - - const auto fsId = fa.fsid; - FileFingerprint ffp; - computeFingerprint(ffp, fsaccess, fa, paths); - if (!ffp.isvalid) - { - return; - } - - const auto nodeRange = fingerprints.equal_range(ffp); - const auto nodeCount = std::distance(nodeRange.first, nodeRange.second); - if (nodeCount <= 0) - { - return; - } - else if (nodeCount == 1) + auto insertFingerprint = [&files, &fingerprints](FileSystemAccess& fsaccess, FileAccess& fa, + const std::string& path, const set& paths) { - nodeRange.first->second->setfsid(fsId); - fingerprints.erase(nodeRange.first); - } - else - { - // We're assigning `fa.fsid` to the node that is the best match to `preferredNodePath`. - auto bestNodeIt = nodeRange.first; - int bestScore = -1; - - for (auto nodeIt = nodeRange.first; nodeIt != nodeRange.second; ++nodeIt) + LightFileFingerprint ffp; + if (computeFingerprint(ffp, fsaccess, fa, path, paths)) { - string nodePath; - nodeIt->second->getlocalpath(&nodePath, false); - - const auto score = computeReversePathMatchScore(preferredNodePath, nodePath, localseparator); - - if (score > bestScore) - { - bestScore = score; - bestNodeIt = nodeIt; - } + const auto ffpPtr = fingerprints.add(std::move(ffp)); + files.insert(std::make_pair(ffpPtr, FsFile{fa.fsid, path})); } + }; - bestNodeIt->second->setfsid(fsId); - fingerprints.erase(bestNodeIt); - } -} - -// Recursively assigns fs IDs -void assignFilesystemIdsImpl(bool& success, Sync& sync, MegaApp& app, handlelocalnode_map& fsidnodes, - FileSystemAccess& fsaccess, string localpath, const string& localdebris, - const string& localseparator, FingerprintMap& fingerprints) -{ auto fa = fsaccess.newfileaccess(false); - if (!(success = fa->fopen(&localpath, true, false))) + if (!fa->fopen(const_cast(&localpath), true, false)) { LOG_err << "Unable to open path: " << localpath; + success = false; return; } if (fa->mIsSymLink) @@ -307,20 +294,25 @@ void assignFilesystemIdsImpl(bool& success, Sync& sync, MegaApp& app, handleloca LOG_debug << "Ignoring symlink: " << localpath; return; } + if (!fa->fsidvalid) + { + LOG_err << "Invalid fs id for: " << localpath; + success = false; + return; + } if (fa->type == FILENODE) { - assignFilesystemId(fsaccess, *fa, fsidnodes, fingerprints, localpath, localseparator); + insertFingerprint(fsaccess, *fa, localpath, {}); } else if (fa->type == FOLDERNODE) { const auto paths = collectAllPathsInFolder(sync, app, fsaccess, localpath, localdebris, localseparator); - assignFilesystemId(fsaccess, *fa, fsidnodes, fingerprints, localpath, localseparator, paths); + insertFingerprint(fsaccess, *fa, localpath, paths); fa.reset(); for (const auto& path : paths) { - assignFilesystemIdsImpl(success, sync, app, fsidnodes, fsaccess, path, - localdebris, localseparator, fingerprints); + collectAllFiles(success, fingerprints, files, sync, app, fsaccess, path, localdebris, localseparator); } } else @@ -331,6 +323,85 @@ void assignFilesystemIdsImpl(bool& success, Sync& sync, MegaApp& app, handleloca } } +// Assigns fs IDs from `files` to those `localnodes` that match the fingerprints found in `files`. +// If there are multiple matches we apply a best-path heuristic. +size_t assignFilesystemIdsImpl(const FingerprintCache& fingerprints, FingerprintLocalNodeMap& localnodes, + FingerprintFileMap& files, handlelocalnode_map& fsidnodes, const string& localseparator) +{ + string nodePath; + string accumulated; + size_t assignmentCount = 0; + for (const auto& fp : fingerprints.all()) + { + const auto nodeRange = localnodes.equal_range(&fp); + const auto nodeCount = std::distance(nodeRange.first, nodeRange.second); + if (nodeCount <= 0) + { + continue; + } + + const auto fileRange = files.equal_range(&fp); + const auto fileCount = std::distance(fileRange.first, fileRange.second); + if (fileCount <= 0) + { + // without files we cannot assign fs IDs to these localnodes, so no need to keep them + localnodes.erase(nodeRange.first, nodeRange.second); + continue; + } + + struct Element + { + int score; + handle fsid; + LocalNode* l; + }; + std::vector elements; + elements.reserve(nodeCount * fileCount); + + for (auto nodeIt = nodeRange.first; nodeIt != nodeRange.second; ++nodeIt) + { + auto l = nodeIt->second; + if (l != l->sync->localroot.get()) // never assign fs ID to the root localnode + { + nodePath.clear(); + l->getlocalpath(&nodePath, false, &localseparator); + for (auto fileIt = fileRange.first; fileIt != fileRange.second; ++fileIt) + { + const auto& filePath = fileIt->second.path; + const auto score = computeReversePathMatchScore(accumulated, nodePath, filePath, localseparator); + if (score > 0) // leaf name must match + { + elements.push_back({score, fileIt->second.fsid, l}); + } + } + } + } + + // Sort in descending order by score. Elements with highest score come first + std::sort(elements.begin(), elements.end(), [](const Element& e1, const Element& e2) + { + return e1.score > e2.score; + }); + + std::unordered_set usedFsIds; + for (const auto& e : elements) + { + if (e.l->fsid == mega::UNDEF // node not assigned + && usedFsIds.find(e.fsid) == usedFsIds.end()) // fsid not used + { + e.l->setfsid(e.fsid, fsidnodes); + usedFsIds.insert(e.fsid); + ++assignmentCount; + } + } + + // the fingerprint that these files and localnodes correspond to has now finished processing + files.erase(fileRange.first, fileRange.second); + localnodes.erase(nodeRange.first, nodeRange.second); + } + return assignmentCount; +} + } // anonymous bool isPathSyncable(const string& localpath, const string& localdebris, const string& localseparator) @@ -343,38 +414,20 @@ bool isPathSyncable(const string& localpath, const string& localdebris, const st localseparator.size())); } -void invalidateFilesystemIds(handlelocalnode_map& fsidnodes, LocalNode& l, size_t& count) -{ - l.fsid = mega::UNDEF; - ++count; - if (l.fsid_it != fsidnodes.end()) - { - fsidnodes.erase(l.fsid_it); - l.fsid_it = fsidnodes.end(); - } - if (l.type == FILENODE) - { - return; - } - for (auto& childPair : l.children) - { - invalidateFilesystemIds(fsidnodes, *childPair.second, count); - } -} - -int computeReversePathMatchScore(const string& path1, const string& path2, const string& localseparator) +int computeReversePathMatchScore(string& accumulated, const string& path1, const string& path2, const string& localseparator) { if (path1.empty() || path2.empty()) { return 0; } + accumulated.clear(); + const auto path1End = path1.size() - 1; const auto path2End = path2.size() - 1; size_t index = 0; size_t separatorBias = 0; - string accumulated; while (index <= path1End && index <= path2End) { const auto value1 = path1[path1End - index]; @@ -411,10 +464,11 @@ int computeReversePathMatchScore(const string& path1, const string& path2, const bool assignFilesystemIds(Sync& sync, MegaApp& app, FileSystemAccess& fsaccess, handlelocalnode_map& fsidnodes, const string& localdebris, const string& localseparator) { - auto rootpath = sync.localroot.localname; + const auto& rootpath = sync.localroot->localname; + LOG_info << "Assigning fs IDs at rootpath: " << rootpath; auto fa = fsaccess.newfileaccess(false); - if (!fa->fopen(&rootpath, true, false)) + if (!fa->fopen(const_cast(&rootpath), true, false)) { LOG_err << "Unable to open rootpath"; return false; @@ -433,20 +487,26 @@ bool assignFilesystemIds(Sync& sync, MegaApp& app, FileSystemAccess& fsaccess, h } fa.reset(); - // Ensures that unmatched nodes (local nodes that don't have a fingerprint that's - // the same as a file on disk) have invalid IDs. - size_t invalidatedCount = 0; - invalidateFilesystemIds(fsidnodes, sync.localroot, invalidatedCount); - LOG_info << "Number of invalidated fs IDs: " << invalidatedCount; + bool success = true; + + FingerprintCache fingerprints; - FingerprintMap fingerprints; - collectAllFingerprints(fingerprints, sync.localroot); - LOG_info << "Number of fingerprints before assignment: " << fingerprints.size(); + FingerprintLocalNodeMap localnodes; + collectAllLocalNodes(fingerprints, localnodes, *sync.localroot, fsidnodes, localseparator); + LOG_info << "Number of localnodes: " << localnodes.size(); - bool success = true; - assignFilesystemIdsImpl(success, sync, app, fsidnodes, fsaccess, rootpath, - localdebris, localseparator, fingerprints); - LOG_info << "Number of fingerprints after assignment: " << fingerprints.size(); + if (localnodes.empty()) + { + return success; + } + + FingerprintFileMap files; + collectAllFiles(success, fingerprints, files, sync, app, fsaccess, rootpath, localdebris, localseparator); + LOG_info << "Number of files: " << files.size(); + + LOG_info << "Number of fingerprints: " << fingerprints.all().size(); + const auto assignmentCount = assignFilesystemIdsImpl(fingerprints, localnodes, files, fsidnodes, localseparator); + LOG_info << "Number of fsid assignments: " << assignmentCount; return success; } @@ -455,6 +515,7 @@ bool assignFilesystemIds(Sync& sync, MegaApp& app, FileSystemAccess& fsaccess, h // and a full read of the subtree is initiated Sync::Sync(MegaClient* cclient, string* crootpath, const char* cdebris, string* clocaldebris, Node* remotenode, fsfp_t cfsfp, bool cinshare, int ctag, void *cappdata) + : localroot(new LocalNode) { isnetwork = false; client = cclient; @@ -509,10 +570,9 @@ Sync::Sync(MegaClient* cclient, string* crootpath, const char* cdebris, fsstableids = dirnotify->fsstableids(); LOG_info << "Filesystem IDs are stable: " << fsstableids; - fsstableids = true; // TODO: Remove this once the fs ID assignment is working properly - localroot.init(this, FOLDERNODE, NULL, crootpath); - localroot.setnode(remotenode); + localroot->init(this, FOLDERNODE, NULL, crootpath); + localroot->setnode(remotenode); #ifdef __APPLE__ if (macOSmajorVersion() >= 19) //macOS catalina+ @@ -579,16 +639,26 @@ Sync::~Sync() tmpfa.reset(); // stop all active and pending downloads - if (localroot.node) + if (localroot->node) { TreeProcDelSyncGet tdsg; - client->proctree(localroot.node, &tdsg); + // Create a committer to ensure we update the transfer database in an efficient single commit, + // if there are transactions in progress. + DBTableTransactionCommitter committer(client->tctable); + client->proctree(localroot->node, &tdsg); } delete statecachetable; client->syncs.erase(sync_it); client->syncactivity = true; + + { + // Create a committer and recursively delete all the associated LocalNodes, and their associated transfer and file objects. + // If any have transactions in progress, the committer will ensure we update the transfer database in an efficient single commit. + DBTableTransactionCommitter committer(client->tctable); + localroot.reset(); + } } void Sync::addstatecachechildren(uint32_t parent_dbid, idlocalnode_map* tmap, string* path, LocalNode *p, int maxdepth) @@ -620,7 +690,7 @@ void Sync::addstatecachechildren(uint32_t parent_dbid, idlocalnode_map* tmap, st l->parent_dbid = parent_dbid; l->size = size; - l->setfsid(fsid); + l->setfsid(fsid, client->fsidnode); l->setnode(node); if (maxdepth) @@ -654,7 +724,7 @@ bool Sync::readstatecache() } // recursively build LocalNode tree, set scanseqnos to sync's current scanseqno - addstatecachechildren(0, &tmap, &localroot.localname, &localroot, 100); + addstatecachechildren(0, &tmap, &localroot->localname, localroot.get(), 100); // trigger a single-pass full scan to identify deleted nodes fullscan = true; @@ -721,7 +791,7 @@ void Sync::cachenodes() for (set::iterator it = insertq.begin(); it != insertq.end(); ) { - if ((*it)->parent->dbid || (*it)->parent == &localroot) + if ((*it)->parent->dbid || (*it)->parent == localroot.get()) { statecachetable->put(MegaClient::CACHEDLOCALNODE, *it, &client->key); insertq.erase(it++); @@ -777,8 +847,8 @@ LocalNode* Sync::localnodebypath(LocalNode* l, string* localpath, LocalNode** pa { // verify matching localroot prefix - this should always succeed for // internal use - if (memcmp(ptr, localroot.localname.data(), localroot.localname.size()) - || memcmp(ptr + localroot.localname.size(), + if (memcmp(ptr, localroot->localname.data(), localroot->localname.size()) + || memcmp(ptr + localroot->localname.size(), client->fsaccess->localseparator.data(), separatorlen)) { @@ -790,7 +860,7 @@ LocalNode* Sync::localnodebypath(LocalNode* l, string* localpath, LocalNode** pa return NULL; } - l = &localroot; + l = localroot.get(); ptr += l->localname.size() + client->fsaccess->localseparator.size(); } @@ -1028,7 +1098,7 @@ LocalNode* Sync::checkpath(LocalNode* l, string* localpath, string* localname, d return NULL; } - isroot = l == &localroot && !newname.size(); + isroot = l == localroot.get() && !newname.size(); } LOG_verbose << "Scanning: " << path; @@ -1052,7 +1122,7 @@ LocalNode* Sync::checkpath(LocalNode* l, string* localpath, string* localname, d lastpart, (localname ? *localpath : tmppath).size() - lastpart); - LocalNode* cl = (parent ? parent : &localroot)->childbyname(&fname); + LocalNode* cl = (parent ? parent : localroot.get())->childbyname(&fname); if (initializing && cl) { // the file seems to be still in the folder @@ -1151,10 +1221,10 @@ LocalNode* Sync::checkpath(LocalNode* l, string* localpath, string* localname, d #ifdef _WIN32 // only consider fsid matches between different syncs for local drives with the // same drive letter, to prevent problems with cloned Volume IDs - && (colon = strstr(parent->sync->localroot.name.c_str(), ":")) - && !memcmp(parent->sync->localroot.name.c_str(), - it->second->sync->localroot.name.c_str(), - colon - parent->sync->localroot.name.c_str()) + && (colon = strstr(parent->sync->localroot->name.c_str(), ":")) + && !memcmp(parent->sync->localroot->name.c_str(), + it->second->sync->localroot->name.c_str(), + colon - parent->sync->localroot->name.c_str()) #endif ) ) @@ -1203,7 +1273,7 @@ LocalNode* Sync::checkpath(LocalNode* l, string* localpath, string* localname, d { if (fa->fsidvalid && l->fsid != fa->fsid) { - l->setfsid(fa->fsid); + l->setfsid(fa->fsid, client->fsidnode); } m_off_t dsize = l->size > 0 ? l->size : 0; @@ -1242,7 +1312,7 @@ LocalNode* Sync::checkpath(LocalNode* l, string* localpath, string* localname, d // content scan anyway) if (fa->fsidvalid && fa->fsid != l->fsid) { - l->setfsid(fa->fsid); + l->setfsid(fa->fsid, client->fsidnode); newnode = true; } } @@ -1275,10 +1345,10 @@ LocalNode* Sync::checkpath(LocalNode* l, string* localpath, string* localname, d #ifdef _WIN32 // allow moves between different syncs only for local drives with the // same drive letter, to prevent problems with cloned Volume IDs - && (colon = strstr(parent->sync->localroot.name.c_str(), ":")) - && !memcmp(parent->sync->localroot.name.c_str(), - it->second->sync->localroot.name.c_str(), - colon - parent->sync->localroot.name.c_str()) + && (colon = strstr(parent->sync->localroot->name.c_str(), ":")) + && !memcmp(parent->sync->localroot->name.c_str(), + it->second->sync->localroot->name.c_str(), + colon - parent->sync->localroot->name.c_str()) #endif ) ) @@ -1429,7 +1499,7 @@ LocalNode* Sync::checkpath(LocalNode* l, string* localpath, string* localname, d if (fa->fsidvalid) { - l->setfsid(fa->fsid); + l->setfsid(fa->fsid, client->fsidnode); } newnode = true; @@ -1470,7 +1540,7 @@ LocalNode* Sync::checkpath(LocalNode* l, string* localpath, string* localname, d { if (fa->fsidvalid && l->fsid != fa->fsid) { - l->setfsid(fa->fsid); + l->setfsid(fa->fsid, client->fsidnode); } if (l->size > 0) diff --git a/src/testhooks.cpp b/src/testhooks.cpp index fc42ab7265..a7d05a2b68 100644 --- a/src/testhooks.cpp +++ b/src/testhooks.cpp @@ -33,4 +33,4 @@ namespace mega { } #endif -}; +} diff --git a/src/transfer.cpp b/src/transfer.cpp index 951b619d2c..855d41d9c3 100644 --- a/src/transfer.cpp +++ b/src/transfer.cpp @@ -33,7 +33,7 @@ namespace mega { Transfer::Transfer(MegaClient* cclient, direction_t ctype) - : bt(cclient->rng) + : bt(cclient->rng, cclient->transferRetryBackoffs[ctype]) { type = ctype; client = cclient; @@ -338,6 +338,16 @@ SymmCipher *Transfer::transfercipher() return &client->tmptransfercipher; } +void Transfer::removeTransferFile(error e, File* f, DBTableTransactionCommitter* committer) +{ + Transfer *transfer = f->transfer; + client->filecachedel(f, committer); + transfer->files.erase(f->file_it); + client->app->file_removed(f, e); + f->transfer = NULL; + f->terminated(); +} + // transfer attempt failed, notify all related files, collect request on // whether to abort the transfer, kill transfer if unanimous void Transfer::failed(error e, DBTableTransactionCommitter& committer, dstime timeleft) @@ -354,70 +364,83 @@ void Transfer::failed(error e, DBTableTransactionCommitter& committer, dstime ti client->reqtag = creqtag; } - if (e != API_EBUSINESSPASTDUE) + if (e == API_EOVERQUOTA) { - if (e == API_EOVERQUOTA) + if (!slot) { - if (!slot) - { - client->activateoverquota(timeleft); - bt.backoff(timeleft ? timeleft : NEVER); - client->app->transfer_failed(this, e, timeleft); - ++client->performanceStats.transferTempErrors; - } - else + bt.backoff(timeleft ? timeleft : NEVER); + client->activateoverquota(timeleft); + client->app->transfer_failed(this, e, timeleft); + ++client->performanceStats.transferTempErrors; + } + else + { + bool allForeignTargets = true; + for (auto &file : files) { - bool allForeignTargets = true; - for (auto &file : files) + if (client->isPrivateNode(file->h)) { - if (client->isPrivateNode(file->h)) - { - allForeignTargets = false; - break; - } + allForeignTargets = false; + break; } + } - /* If all targets are foreign and there's not a bandwidth overquota, transfer must fail. - * Otherwise we need to activate overquota. - */ - if (!timeleft && allForeignTargets) - { - client->app->transfer_failed(this, e, NEVER); - } - else - { - bt.backoff(timeleft ? timeleft : NEVER); - client->activateoverquota(timeleft); - } + /* If all targets are foreign and there's not a bandwidth overquota, transfer must fail. + * Otherwise we need to activate overquota. + */ + if (!timeleft && allForeignTargets) + { + client->app->transfer_failed(this, e); + } + else + { + bt.backoff(timeleft ? timeleft : NEVER); + client->activateoverquota(timeleft); } } - else - { - bt.backoff(); - state = TRANSFERSTATE_RETRYING; - client->app->transfer_failed(this, e, timeleft); - client->looprequested = true; - ++client->performanceStats.transferTempErrors; - } + } + else if (e == API_EARGS) + { + client->app->transfer_failed(this, e); + } + else if (e != API_EBUSINESSPASTDUE) + { + bt.backoff(); + state = TRANSFERSTATE_RETRYING; + client->app->transfer_failed(this, e, timeleft); + client->looprequested = true; + ++client->performanceStats.transferTempErrors; } for (file_list::iterator it = files.begin(); it != files.end();) { // Remove files with foreign targets, if transfer failed with a (foreign) storage overquota - if (e == API_EOVERQUOTA && !timeleft) + if (e == API_EOVERQUOTA + && !timeleft + && client->isForeignNode((*it)->h)) { - if (client->isForeignNode((*it)->h)) - { -#ifdef ENABLE_SYNC - if((*it)->syncxfer && e != API_EBUSINESSPASTDUE) - { - client->syncdownrequired = true; - } -#endif - client->app->file_removed(*it, e); - files.erase(it++); - continue; - } + File *f = (*it++); + removeTransferFile(API_EOVERQUOTA, f, &committer); + continue; + } + + /* + * If the transfer failed with API_EARGS, the target handle is invalid. For a sync-transfer, + * the actionpacket will eventually remove the target and the sync-engine will force to + * disable the synchronization of the folder. For non-sync-transfers, remove the file directly. + */ + if (e == API_EARGS) + { + File *f = (*it++); + if (f->syncxfer) + { + defer = true; + } + else + { + removeTransferFile(API_EARGS, f, &committer); + } + continue; } if (((*it)->failed(e) && (e != API_EBUSINESSPASTDUE)) @@ -467,7 +490,9 @@ void Transfer::failed(error e, DBTableTransactionCommitter& committer, dstime ti for (file_list::iterator it = files.begin(); it != files.end(); it++) { #ifdef ENABLE_SYNC - if((*it)->syncxfer && e != API_EBUSINESSPASTDUE) + if((*it)->syncxfer + && e != API_EBUSINESSPASTDUE + && e != API_EOVERQUOTA) { client->syncdownrequired = true; } @@ -494,13 +519,13 @@ void Transfer::addAnyMissingMediaFileAttributes(Node* node, /*const*/ std::strin #ifdef USE_MEDIAINFO char ext[8]; - if (((type == PUT && size >= 16) || (node && node->nodekey.size() == FILENODEKEYLENGTH && node->size >= 16)) && + if (((type == PUT && size >= 16) || (node && node->nodekey().size() == FILENODEKEYLENGTH && node->size >= 16)) && client->fsaccess->getextension(&localpath, ext, sizeof(ext)) && MediaProperties::isMediaFilenameExt(ext) && !client->mediaFileInfo.mediaCodecsFailed) { // for upload, the key is in the transfer. for download, the key is in the node. - uint32_t* attrKey = fileAttributeKeyPtr((type == PUT) ? filekey : (byte*)node->nodekey.data()); + uint32_t* attrKey = fileAttributeKeyPtr((type == PUT) ? filekey : (byte*)node->nodekey().data()); if (type == PUT || !node->hasfileattribute(fa_media) || client->mediaFileInfo.timeToRetryMediaPropertyExtraction(node->fileattrstring, attrKey)) { @@ -831,10 +856,10 @@ void Transfer::complete(DBTableTransactionCommitter& committer) if ((*it)->hprivate && !(*it)->hforeign && (n = client->nodebyhandle((*it)->h))) { if (!client->gfxdisabled && client->gfx && client->gfx->isgfx(&localname) && - keys.find(n->nodekey) == keys.end() && // this file hasn't been processed yet + keys.find(n->nodekey()) == keys.end() && // this file hasn't been processed yet client->checkaccess(n, OWNER)) { - keys.insert(n->nodekey); + keys.insert(n->nodekey()); // check if restoration of missing attributes failed in the past (no access) if (n->attrs.map.find('f') == n->attrs.map.end() || n->attrs.map['f'] != me64) @@ -995,11 +1020,7 @@ void Transfer::complete(DBTableTransactionCommitter& committer) client->syncdownrequired = true; } #endif - client->filecachedel(f, &committer); - files.erase(it++); - client->app->file_removed(f, API_EREAD); - f->transfer = NULL; - f->terminated(); + removeTransferFile(API_EREAD, f, &committer); } else { @@ -1052,24 +1073,6 @@ void Transfer::completefiles() ids.push_back(dbid); } -m_off_t Transfer::nextpos() -{ - while (chunkmacs.find(ChunkedHash::chunkfloor(pos)) != chunkmacs.end() && pos < size) - { - if (chunkmacs[ChunkedHash::chunkfloor(pos)].finished) - { - pos = ChunkedHash::chunkceil(pos, size); - } - else - { - pos += chunkmacs[ChunkedHash::chunkfloor(pos)].offset; - break; - } - } - - return pos; -} - DirectReadNode::DirectReadNode(MegaClient* cclient, handle ch, bool cp, SymmCipher* csymmcipher, int64_t cctriv, const char *privauth, const char *pubauth, const char *cauth) { client = cclient; diff --git a/src/transferslot.cpp b/src/transferslot.cpp index 06eea3973a..b49b1a0362 100644 --- a/src/transferslot.cpp +++ b/src/transferslot.cpp @@ -32,6 +32,26 @@ namespace mega { +TransferSlotFileAccess::TransferSlotFileAccess(std::unique_ptr&& p, Transfer* t) + : transfer(t) +{ + reset(std::move(p)); +} + +TransferSlotFileAccess::~TransferSlotFileAccess() +{ + reset(); +} + +void TransferSlotFileAccess::reset(std::unique_ptr&& p) +{ + fa = std::move(p); + + // transfer has no slot or slot has no fa: timer is enabled + transfer->bt.enable(!!p); +} + + // transfer attempts are considered failed after XFERTIMEOUT deciseconds // without data flow const dstime TransferSlot::XFERTIMEOUT = 600; @@ -51,8 +71,8 @@ const dstime TransferSlot::PROGRESSTIMEOUT = 10; const m_off_t TransferSlot::MAX_UPLOAD_GAP = 62914560; // 60 MB (up to 63 chunks) TransferSlot::TransferSlot(Transfer* ctransfer) - : retrybt(ctransfer->client->rng) - , fa(ctransfer->client->fsaccess->newfileaccess()) + : fa(ctransfer->client->fsaccess->newfileaccess(), ctransfer) + , retrybt(ctransfer->client->rng, ctransfer->client->transferSlotsBackoff) { starttime = 0; lastprogressreport = 0; @@ -174,7 +194,7 @@ TransferSlot::~TransferSlot() } // Open the file in synchonous mode - fa = transfer->client->fsaccess->newfileaccess(); + fa.reset(transfer->client->fsaccess->newfileaccess()); if (!fa->fopen(&transfer->localfilename, false, true)) { fa.reset(); @@ -324,6 +344,42 @@ int64_t TransferSlot::macsmac(chunkmac_map* m) return m->macsmac(transfer->transfercipher()); } +bool TransferSlot::checkTransferFinished(DBTableTransactionCommitter& committer, MegaClient* client) +{ + if (transfer->progresscompleted == transfer->size) + { + if (transfer->progresscompleted) + { + transfer->currentmetamac = macsmac(&transfer->chunkmacs); + transfer->hascurrentmetamac = true; + } + + // verify meta MAC + if (!transfer->progresscompleted + || (transfer->currentmetamac == transfer->metamac)) + { + client->transfercacheadd(transfer, &committer); + if (transfer->progresscompleted != progressreported) + { + progressreported = transfer->progresscompleted; + lastdata = Waiter::ds; + + progress(); + } + + transfer->complete(committer); + } + else + { + client->sendevent(99431, "MAC verification failed", 0); + transfer->chunkmacs.clear(); + transfer->failed(API_EKEY, committer); + } + return true; + } + return false; +} + // file transfer state machine void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committer) { @@ -347,10 +403,7 @@ void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committ } else { - int creqtag = client->reqtag; - client->reqtag = 0; - client->sendevent(99432, "MAC verification failed for cached download"); - client->reqtag = creqtag; + client->sendevent(99432, "MAC verification failed for cached download", 0); transfer->chunkmacs.clear(); return transfer->failed(API_EKEY, committer); @@ -365,16 +418,14 @@ void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committ } else { - int creqtag = client->reqtag; - client->reqtag = 0; - client->sendevent(99410, "No upload token available"); - client->reqtag = creqtag; + client->sendevent(99410, "No upload token available", 0); return transfer->failed(API_EINTERNAL, committer); } } retrying = false; + retrybt.reset(); // in case we don't delete the slot, and in case retrybt.next=1 transfer->state = TRANSFERSTATE_ACTIVE; if (!createconnectionsonce()) // don't use connections, reqs, or asyncIO before this point. @@ -534,10 +585,7 @@ void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committ error e = (error)atoi(reqs[i]->in.c_str()); if (e == API_EKEY) { - int creqtag = client->reqtag; - client->reqtag = 0; - client->sendevent(99429, "Integrity check failed in upload"); - client->reqtag = creqtag; + client->sendevent(99429, "Integrity check failed in upload", 0); lasterror = e; errorcount++; @@ -545,24 +593,22 @@ void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committ break; } - if (e == API_ERATELIMIT || (reqs[i]->contenttype.find("text/html") != string::npos + if (e == DAEMON_EFAILED || (reqs[i]->contenttype.find("text/html") != string::npos && !memcmp(reqs[i]->posturl.c_str(), "http:", 5))) { client->usehttps = true; client->app->notify_change_to_https(); - int creqtag = client->reqtag; - client->reqtag = 0; - if (e == API_ERATELIMIT) + if (e == DAEMON_EFAILED) { - client->sendevent(99440, "Retry requested by storage server"); + // megad returning -4 should result in restarting the transfer + client->sendevent(99440, "Retry requested by storage server", 0); } else { LOG_warn << "Invalid Content-Type detected during upload: " << reqs[i]->contenttype; } - client->sendevent(99436, "Automatic change to HTTPS"); - client->reqtag = creqtag; + client->sendevent(99436, "Automatic change to HTTPS", 0); return transfer->failed(API_EAGAIN, committer); } @@ -585,10 +631,7 @@ void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committ if (transfer->progresscompleted == transfer->size) { - int creqtag = client->reqtag; - client->reqtag = 0; - client->sendevent(99409, "No upload token received"); - client->reqtag = creqtag; + client->sendevent(99409, "No upload token received", 0); return transfer->failed(API_EINTERNAL, committer); } @@ -652,40 +695,11 @@ void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committ break; } - if (transfer->progresscompleted == transfer->size) + if (checkTransferFinished(committer, client)) { - if (transfer->progresscompleted) - { - transfer->currentmetamac = macsmac(&transfer->chunkmacs); - transfer->hascurrentmetamac = true; - } - - // verify meta MAC - if (!transfer->progresscompleted - || (transfer->currentmetamac == transfer->metamac)) - { - client->transfercacheadd(transfer, &committer); - if (transfer->progresscompleted != progressreported) - { - progressreported = transfer->progresscompleted; - lastdata = Waiter::ds; - - progress(); - } - - return transfer->complete(committer); - } - else - { - int creqtag = client->reqtag; - client->reqtag = 0; - client->sendevent(99431, "MAC verification failed"); - client->reqtag = creqtag; - - transfer->chunkmacs.clear(); - return transfer->failed(API_EKEY, committer); - } + return; } + client->transfercacheadd(transfer, &committer); reqs[i]->status = REQ_READY; } @@ -708,18 +722,12 @@ void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committ client->usehttps = true; client->app->notify_change_to_https(); - int creqtag = client->reqtag; - client->reqtag = 0; - client->sendevent(99436, "Automatic change to HTTPS"); - client->reqtag = creqtag; + client->sendevent(99436, "Automatic change to HTTPS", 0); return transfer->failed(API_EAGAIN, committer); } - int creqtag = client->reqtag; - client->reqtag = 0; - client->sendevent(99430, "Invalid chunk size"); - client->reqtag = creqtag; + client->sendevent(99430, "Invalid chunk size", 0); LOG_warn << "Invalid chunk size: " << reqs[i]->size << " - " << reqs[i]->bufpos; lasterror = API_EREAD; @@ -766,39 +774,9 @@ void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committ updatecontiguousprogress(); - if (transfer->progresscompleted == transfer->size) + if (checkTransferFinished(committer, client)) { - if (transfer->progresscompleted) - { - transfer->currentmetamac = macsmac(&transfer->chunkmacs); - transfer->hascurrentmetamac = true; - } - - // verify meta MAC - if (!transfer->progresscompleted - || (transfer->currentmetamac == transfer->metamac)) - { - client->transfercacheadd(transfer, &committer); - if (transfer->progresscompleted != progressreported) - { - progressreported = transfer->progresscompleted; - lastdata = Waiter::ds; - - progress(); - } - - return transfer->complete(committer); - } - else - { - int creqtag = client->reqtag; - client->reqtag = 0; - client->sendevent(99431, "MAC verification failed"); - client->reqtag = creqtag; - - transfer->chunkmacs.clear(); - return transfer->failed(API_EKEY, committer); - } + return; } client->transfercacheadd(transfer, &committer); @@ -856,10 +834,7 @@ void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committ client->usehttps = true; client->app->notify_change_to_https(); - int creqtag = client->reqtag; - client->reqtag = 0; - client->sendevent(99436, "Automatic change to HTTPS"); - client->reqtag = creqtag; + client->sendevent(99436, "Automatic change to HTTPS", 0); return transfer->failed(API_EAGAIN, committer); } @@ -868,10 +843,7 @@ void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committ { if (reqs[i]->timeleft < 0) { - int creqtag = client->reqtag; - client->reqtag = 0; - client->sendevent(99408, "Overquota without timeleft"); - client->reqtag = creqtag; + client->sendevent(99408, "Overquota without timeleft", 0); } LOG_warn << "Bandwidth overquota from storage server"; @@ -1034,10 +1006,7 @@ void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committ unsigned size = (unsigned)(posrange.second - posrange.first); if (size > 16777216) { - int creqtag = client->reqtag; - client->reqtag = 0; - client->sendevent(99434, "Invalid request size"); - client->reqtag = creqtag; + client->sendevent(99434, "Invalid request size", 0); transfer->chunkmacs.clear(); return transfer->failed(API_EINTERNAL, committer); @@ -1159,15 +1128,10 @@ void TransferSlot::doio(MegaClient* client, DBTableTransactionCommitter& committ } } - if (!failure) + if (!failure && backoff > 0) { - if (!backoff && (Waiter::ds - lastdata) < XFERTIMEOUT) - { - // no other backoff: check again at XFERMAXFAIL - backoff = XFERTIMEOUT - (Waiter::ds - lastdata); - } - retrybt.backoff(backoff); + retrying = true; // we don't bother checking the `retrybt` before calling `doio` unless `retrying` is set. } } diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000000..8a850fc566 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,24 @@ +`tests` is the root level directory for SDK testing. + +Unit and integration tests are contained within the `unit` and `integration` +directories, respectively. Note that unit tests do NOT do I/O and are very fast +whereas integration tests typically require I/O, are slower, and harder to maintain. + +Unit and integration tests require the google test framework: +https://github.com/google/googletest + +Building gtest and the tests itself depends very much on your OS and your build +system of choice. We currently support autotools, QMake, and CMake. + +You can run all tests or just a subset of the tests by supplying a filter to the +test executable, e.g., `./test_unit --gtest_filter=Crypto*` + +Unit and integration tests are organized in such a way that the filename +should match the name of the contained test suite. A single test file should +only contain a single test suite. E.g., `Crypto_test.cpp` should only contain +tests like `TEST(Crypto, blahblah)`. This makes test discovery more efficient. +Any testing framework code should live inside the `mt` namespace (= mega testing). + +The `tool` directory contains standalone test applications that must be run manually. + +The `python` directory contains work-in-progress system tests written in python. diff --git a/tests/include.am b/tests/include.am index 7c1a7b2f22..7919da7407 100644 --- a/tests/include.am +++ b/tests/include.am @@ -12,10 +12,16 @@ $(TESTS): $(top_builddir)/src/libmega.la tests_test_unit_SOURCES = \ tests/unit/Commands_test.cpp \ tests/unit/Crypto_test.cpp \ - tests/unit/Serialization_test.cpp \ + tests/unit/FileFingerprint_test.cpp \ + tests/unit/FsNode.cpp \ + tests/unit/Logging_test.cpp \ tests/unit/main.cpp \ tests/unit/MegaApi_test.cpp \ - tests/unit/PayCrypter_test.cpp + tests/unit/PayCrypter_test.cpp \ + tests/unit/Serialization_test.cpp \ + tests/unit/Sync_test.cpp \ + tests/unit/utils.cpp \ + tests/unit/utils_test.cpp tests_test_integration_SOURCES = \ tests/integration/main.cpp \ diff --git a/tests/integration/SdkTest_test.cpp b/tests/integration/SdkTest_test.cpp index 636470a0fe..0b8b102904 100644 --- a/tests/integration/SdkTest_test.cpp +++ b/tests/integration/SdkTest_test.cpp @@ -99,10 +99,7 @@ std::string megaApiCacheFolder(int index) #else p += "/"; #endif - if (index == 0) - p += "sdk_test_mega_cache_0"; - else - p += "sdk_test_mega_cache_1"; + p += "sdk_test_mega_cache_" + to_string(index); if (!fileexists(p)) { @@ -129,6 +126,19 @@ void WaitMillisec(unsigned n) #endif } +bool WaitFor(std::function&& f, unsigned millisec) +{ + unsigned waited = 0; + for (;;) + { + if (f()) return true; + if (waited >= millisec) return false; + WaitMillisec(100); + waited += 100; + } +} + + enum { USERALERT_ARRIVAL_MILLISEC = 1000 }; #ifdef _WIN32 @@ -181,23 +191,27 @@ namespace void SdkTest::SetUp() { // do some initialization - megaApi[0] = megaApi[1] = NULL; - + if (megaApi.size() < 2) + { + megaApi.resize(2); + mApi.resize(2); + } char *buf = getenv("MEGA_EMAIL"); if (buf) - email[0].assign(buf); - ASSERT_LT((size_t)0, email[0].length()) << "Set your username at the environment variable $MEGA_EMAIL"; + mApi[0].email.assign(buf); + ASSERT_LT((size_t)0, mApi[0].email.length()) << "Set your username at the environment variable $MEGA_EMAIL"; buf = getenv("MEGA_PWD"); if (buf) - pwd[0].assign(buf); - ASSERT_LT((size_t)0, pwd[0].length()) << "Set your password at the environment variable $MEGA_PWD"; + mApi[0].pwd.assign(buf); + ASSERT_LT((size_t)0, mApi[0].pwd.length()) << "Set your password at the environment variable $MEGA_PWD"; gTestingInvalidArgs = false; - if (megaApi[0] == NULL) + if (megaApi[0].get() == NULL) { - megaApi[0] = new MegaApi(APP_KEY.c_str(), megaApiCacheFolder(0).c_str(), USER_AGENT.c_str()); + megaApi[0].reset(new MegaApi(APP_KEY.c_str(), megaApiCacheFolder(0).c_str(), USER_AGENT.c_str())); + mApi[0].megaApi = megaApi[0].get(); megaApi[0]->setLoggingName("0"); megaApi[0]->addListener(this); @@ -250,40 +264,38 @@ void SdkTest::TearDown() } } -void SdkTest::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) +int SdkTest::getApiIndex(MegaApi* api) { - unsigned int apiIndex; - if (api == megaApi[0]) - { - apiIndex = 0; - } - else if (api == megaApi[1]) - { - apiIndex = 1; - } - else + int apiIndex = -1; + for (int i = int(megaApi.size()); i--; ) if (megaApi[i].get() == api) apiIndex = i; + if (apiIndex == -1) { LOG_err << "Instance of MegaApi not recognized"; - return; } + return apiIndex; +} - requestFlags[apiIndex][request->getType()] = true; - lastError[apiIndex] = e->getErrorCode(); +void SdkTest::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) +{ + int apiIndex = getApiIndex(api); + if (apiIndex < 0) return; + mApi[apiIndex].requestFlags[request->getType()] = true; + mApi[apiIndex].lastError = e->getErrorCode(); switch(request->getType()) { case MegaRequest::TYPE_CREATE_FOLDER: - h = request->getNodeHandle(); + mApi[apiIndex].h = request->getNodeHandle(); break; case MegaRequest::TYPE_COPY: - h = request->getNodeHandle(); + mApi[apiIndex].h = request->getNodeHandle(); break; case MegaRequest::TYPE_EXPORT: - if (lastError[apiIndex] == API_OK) + if (mApi[apiIndex].lastError == API_OK) { - h = request->getNodeHandle(); + mApi[apiIndex].h = request->getNodeHandle(); if (request->getAccess()) { link.assign(request->getLink()); @@ -292,30 +304,30 @@ void SdkTest::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) break; case MegaRequest::TYPE_GET_PUBLIC_NODE: - if (lastError[apiIndex] == API_OK) + if (mApi[apiIndex].lastError == API_OK) { publicNode = request->getPublicMegaNode(); } break; case MegaRequest::TYPE_IMPORT_LINK: - h = request->getNodeHandle(); + mApi[apiIndex].h = request->getNodeHandle(); break; case MegaRequest::TYPE_GET_ATTR_USER: - if ( (lastError[apiIndex] == API_OK) && (request->getParamType() != MegaApi::USER_ATTR_AVATAR) ) + if ( (mApi[apiIndex].lastError == API_OK) && (request->getParamType() != MegaApi::USER_ATTR_AVATAR) ) { attributeValue = request->getText(); } if (request->getParamType() == MegaApi::USER_ATTR_AVATAR) { - if (lastError[apiIndex] == API_OK) + if (mApi[apiIndex].lastError == API_OK) { attributeValue = "Avatar changed"; } - if (lastError[apiIndex] == API_ENOENT) + if (mApi[apiIndex].lastError == API_ENOENT) { attributeValue = "Avatar not found"; } @@ -325,26 +337,26 @@ void SdkTest::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) #ifdef ENABLE_CHAT case MegaRequest::TYPE_CHAT_CREATE: - if (lastError[apiIndex] == API_OK) + if (mApi[apiIndex].lastError == API_OK) { MegaTextChat *chat = request->getMegaTextChatList()->get(0)->copy(); - chatid = chat->getHandle(); - if (chats.find(chatid) != chats.end()) + mApi[apiIndex].chatid = chat->getHandle(); + if (mApi[apiIndex].chats.find(mApi[apiIndex].chatid) != mApi[apiIndex].chats.end()) { - delete chats[chatid]; + delete mApi[apiIndex].chats[mApi[apiIndex].chatid]; } - chats[chatid] = chat; + mApi[apiIndex].chats[mApi[apiIndex].chatid] = chat; } break; case MegaRequest::TYPE_CHAT_INVITE: - if (lastError[apiIndex] == API_OK) + if (mApi[apiIndex].lastError == API_OK) { - chatid = request->getNodeHandle(); - if (chats.find(chatid) != chats.end()) + mApi[apiIndex].chatid = request->getNodeHandle(); + if (mApi[apiIndex].chats.find(mApi[apiIndex].chatid) != mApi[apiIndex].chats.end()) { - MegaTextChat *chat = chats[chatid]; + MegaTextChat *chat = mApi[apiIndex].chats[mApi[apiIndex].chatid]; MegaHandle uh = request->getParentHandle(); int priv = request->getAccess(); userpriv_vector *privsbuf = new userpriv_vector; @@ -372,12 +384,12 @@ void SdkTest::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) break; case MegaRequest::TYPE_CHAT_REMOVE: - if (lastError[apiIndex] == API_OK) + if (mApi[apiIndex].lastError == API_OK) { - chatid = request->getNodeHandle(); - if (chats.find(chatid) != chats.end()) + mApi[apiIndex].chatid = request->getNodeHandle(); + if (mApi[apiIndex].chats.find(mApi[apiIndex].chatid) != mApi[apiIndex].chats.end()) { - MegaTextChat *chat = chats[chatid]; + MegaTextChat *chat = mApi[apiIndex].chats[mApi[apiIndex].chatid]; MegaHandle uh = request->getParentHandle(); userpriv_vector *privsbuf = new userpriv_vector; @@ -403,7 +415,7 @@ void SdkTest::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) break; case MegaRequest::TYPE_CHAT_URL: - if (lastError[apiIndex] == API_OK) + if (mApi[apiIndex].lastError == API_OK) { link.assign(request->getLink()); } @@ -411,7 +423,7 @@ void SdkTest::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) #endif case MegaRequest::TYPE_CREATE_ACCOUNT: - if (lastError[apiIndex] == API_OK) + if (mApi[apiIndex].lastError == API_OK) { sid = request->getSessionKey(); } @@ -425,14 +437,14 @@ void SdkTest::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) break; case MegaRequest::TYPE_GET_REGISTERED_CONTACTS: - if (lastError[apiIndex] == API_OK) + if (mApi[apiIndex].lastError == API_OK) { stringTable.reset(request->getMegaStringTable()->copy()); } break; case MegaRequest::TYPE_GET_COUNTRY_CALLING_CODES: - if (lastError[apiIndex] == API_OK) + if (mApi[apiIndex].lastError == API_OK) { stringListMap.reset(request->getMegaStringListMap()->copy()); } @@ -443,26 +455,14 @@ void SdkTest::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) void SdkTest::onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError* e) { - unsigned int apiIndex; - if (api == megaApi[0]) - { - apiIndex = 0; - } - else if (api == megaApi[1]) - { - apiIndex = 1; - } - else - { - LOG_err << "Instance of MegaApi not recognized"; - return; - } + int apiIndex = getApiIndex(api); + if (apiIndex < 0) return; - transferFlags[apiIndex][transfer->getType()] = true; - lastError[apiIndex] = e->getErrorCode(); + mApi[apiIndex].transferFlags[transfer->getType()] = true; + mApi[apiIndex].lastError = e->getErrorCode(); - if (lastError[apiIndex] == MegaError::API_OK) - h = transfer->getNodeHandle(); + if (mApi[apiIndex].lastError == MegaError::API_OK) + mApi[apiIndex].h = transfer->getNodeHandle(); } void SdkTest::onTransferUpdate(MegaApi *api, MegaTransfer *transfer) @@ -473,40 +473,16 @@ void SdkTest::onTransferUpdate(MegaApi *api, MegaTransfer *transfer) void SdkTest::onAccountUpdate(MegaApi* api) { - unsigned int apiIndex; - if (api == megaApi[0]) - { - apiIndex = 0; - } - else if (api == megaApi[1]) - { - apiIndex = 1; - } - else - { - LOG_err << "Instance of MegaApi not recognized"; - return; - } + int apiIndex = getApiIndex(api); + if (apiIndex < 0) return; - accountUpdated[apiIndex] = true; + mApi[apiIndex].accountUpdated = true; } void SdkTest::onUsersUpdate(MegaApi* api, MegaUserList *users) { - unsigned int apiIndex; - if (api == megaApi[0]) - { - apiIndex = 0; - } - else if (api == megaApi[1]) - { - apiIndex = 1; - } - else - { - LOG_err << "Instance of MegaApi not recognized"; - return; - } + int apiIndex = getApiIndex(api); + if (apiIndex < 0) return; if (!users) return; @@ -519,161 +495,126 @@ void SdkTest::onUsersUpdate(MegaApi* api, MegaUserList *users) || u->hasChanged(MegaUser::CHANGE_TYPE_FIRSTNAME) || u->hasChanged(MegaUser::CHANGE_TYPE_LASTNAME)) { - userUpdated[apiIndex] = true; + mApi[apiIndex].userUpdated = true; } else { // Contact is removed from main account - requestFlags[apiIndex][MegaRequest::TYPE_REMOVE_CONTACT] = true; - userUpdated[apiIndex] = true; + mApi[apiIndex].requestFlags[MegaRequest::TYPE_REMOVE_CONTACT] = true; + mApi[apiIndex].userUpdated = true; } } } void SdkTest::onNodesUpdate(MegaApi* api, MegaNodeList *nodes) { - unsigned int apiIndex; - if (api == megaApi[0]) - { - apiIndex = 0; - } - else if (api == megaApi[1]) - { - apiIndex = 1; - } - else - { - LOG_err << "Instance of MegaApi not recognized"; - return; - } + int apiIndex = getApiIndex(api); + if (apiIndex < 0) return; - nodeUpdated[apiIndex] = true; + mApi[apiIndex].nodeUpdated = true; } void SdkTest::onContactRequestsUpdate(MegaApi* api, MegaContactRequestList* requests) { - unsigned int apiIndex; - if (api == megaApi[0]) - { - apiIndex = 0; - } - else if (api == megaApi[1]) - { - apiIndex = 1; - } - else - { - LOG_err << "Instance of MegaApi not recognized"; - return; - } + int apiIndex = getApiIndex(api); + if (apiIndex < 0) return; - contactRequestUpdated[apiIndex] = true; + mApi[apiIndex].contactRequestUpdated = true; } #ifdef ENABLE_CHAT void SdkTest::onChatsUpdate(MegaApi *api, MegaTextChatList *chats) { - unsigned int apiIndex; - if (api == megaApi[0]) - { - apiIndex = 0; + int apiIndex = getApiIndex(api); + if (apiIndex < 0) return; - MegaTextChatList *list = NULL; - if (chats) + MegaTextChatList *list = NULL; + if (chats) + { + list = chats->copy(); + } + else + { + list = megaApi[apiIndex]->getChatList(); + } + for (int i = 0; i < list->size(); i++) + { + handle chatid = list->get(i)->getHandle(); + if (mApi[apiIndex].chats.find(chatid) != mApi[apiIndex].chats.end()) { - list = chats->copy(); + delete mApi[apiIndex].chats[chatid]; + mApi[apiIndex].chats[chatid] = list->get(i)->copy(); } else { - list = megaApi[0]->getChatList(); - } - for (int i = 0; i < list->size(); i++) - { - handle chatid = list->get(i)->getHandle(); - if (this->chats.find(chatid) != this->chats.end()) - { - delete this->chats[chatid]; - this->chats[chatid] = list->get(i)->copy(); - } - else - { - this->chats[chatid] = list->get(i)->copy(); - } + mApi[apiIndex].chats[chatid] = list->get(i)->copy(); } - delete list; - } - else if (api == megaApi[1]) - { - apiIndex = 1; - } - else - { - LOG_err << "Instance of MegaApi not recognized"; - return; } + delete list; - chatUpdated[apiIndex] = true; + mApi[apiIndex].chatUpdated = true; } void SdkTest::createChat(bool group, MegaTextChatPeerList *peers, int timeout) { - requestFlags[0][MegaRequest::TYPE_CHAT_CREATE] = false; + int apiIndex = 0; + mApi[apiIndex].requestFlags[MegaRequest::TYPE_CHAT_CREATE] = false; megaApi[0]->createChat(group, peers); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_CHAT_CREATE], timeout); + waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_CHAT_CREATE], timeout); if (timeout) { - ASSERT_TRUE(requestFlags[0][MegaRequest::TYPE_CHAT_CREATE]) << "Chat creation not finished after " << timeout << " seconds"; + ASSERT_TRUE(mApi[apiIndex].requestFlags[MegaRequest::TYPE_CHAT_CREATE]) << "Chat creation not finished after " << timeout << " seconds"; } - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Chat creation failed (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Chat creation failed (error: " << mApi[apiIndex].lastError << ")"; } #endif void SdkTest::login(unsigned int apiIndex, int timeout) { - requestFlags[apiIndex][MegaRequest::TYPE_LOGIN] = false; - megaApi[apiIndex]->login(email[apiIndex].data(), pwd[apiIndex].data()); + mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGIN] = false; + mApi[apiIndex].megaApi->login(mApi[apiIndex].email.data(), mApi[apiIndex].pwd.data()); - ASSERT_TRUE(waitForResponse(&requestFlags[apiIndex][MegaRequest::TYPE_LOGIN], timeout)) + ASSERT_TRUE(waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGIN], timeout)) << "Logging failed after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[apiIndex]) << "Logging failed (error: " << lastError[apiIndex] << ")"; - ASSERT_TRUE(megaApi[apiIndex]->isLoggedIn()) << "Not logged it"; + ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Logging failed (error: " << mApi[apiIndex].lastError << ")"; + ASSERT_TRUE(mApi[apiIndex].megaApi->isLoggedIn()) << "Not logged it"; } void SdkTest::loginBySessionId(unsigned int apiIndex, const std::string& sessionId, int timeout) { - requestFlags[apiIndex][MegaRequest::TYPE_LOGIN] = false; - megaApi[apiIndex]->login(email[apiIndex].data(), pwd[apiIndex].data()); + mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGIN] = false; + mApi[apiIndex].megaApi->login(mApi[apiIndex].email.data(), mApi[apiIndex].pwd.data()); - ASSERT_TRUE(waitForResponse(&requestFlags[apiIndex][MegaRequest::TYPE_LOGIN], timeout)) + ASSERT_TRUE(waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGIN], timeout)) << "Logging failed after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[apiIndex]) << "Logging failed (error: " << lastError[apiIndex] << ")"; - ASSERT_TRUE(megaApi[apiIndex]->isLoggedIn()) << "Not logged it"; + ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Logging failed (error: " << mApi[apiIndex].lastError << ")"; + ASSERT_TRUE(mApi[apiIndex].megaApi->isLoggedIn()) << "Not logged it"; } void SdkTest::fetchnodes(unsigned int apiIndex, int timeout) { - requestFlags[apiIndex][MegaRequest::TYPE_FETCH_NODES] = false; - megaApi[apiIndex]->fetchNodes(); + mApi[apiIndex].requestFlags[MegaRequest::TYPE_FETCH_NODES] = false; + mApi[apiIndex].megaApi->fetchNodes(); - ASSERT_TRUE( waitForResponse(&requestFlags[apiIndex][MegaRequest::TYPE_FETCH_NODES], timeout) ) + ASSERT_TRUE( waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_FETCH_NODES], timeout) ) << "Fetchnodes failed after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[apiIndex]) << "Fetchnodes failed (error: " << lastError[apiIndex] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Fetchnodes failed (error: " << mApi[apiIndex].lastError << ")"; } void SdkTest::logout(unsigned int apiIndex, int timeout) { - requestFlags[apiIndex][MegaRequest::TYPE_LOGOUT] = false; - megaApi[apiIndex]->logout(this); + mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGOUT] = false; + mApi[apiIndex].megaApi->logout(this); - EXPECT_TRUE( waitForResponse(&requestFlags[apiIndex][MegaRequest::TYPE_LOGOUT], timeout) ) + EXPECT_TRUE( waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGOUT], timeout) ) << "Logout failed after " << timeout << " seconds"; // if the connection was closed before the response of the request was received, the result is ESID - if (lastError[apiIndex] == MegaError::API_ESID) lastError[apiIndex] = MegaError::API_OK; + if (mApi[apiIndex].lastError == MegaError::API_ESID) mApi[apiIndex].lastError = MegaError::API_OK; - EXPECT_EQ(MegaError::API_OK, lastError[apiIndex]) << "Logout failed (error: " << lastError[apiIndex] << ")"; + EXPECT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Logout failed (error: " << mApi[apiIndex].lastError << ")"; } char* SdkTest::dumpSession() @@ -683,43 +624,36 @@ char* SdkTest::dumpSession() void SdkTest::locallogout(int timeout) { - requestFlags[0][MegaRequest::TYPE_LOGOUT] = false; - megaApi[0]->localLogout(this); + int apiIndex = 0; + mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGOUT] = false; + megaApi[apiIndex]->localLogout(this); - EXPECT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_LOGOUT], timeout) ) + EXPECT_TRUE( waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGOUT], timeout) ) << "Local logout failed after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Local logout failed (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Local logout failed (error: " << mApi[apiIndex].lastError << ")"; } void SdkTest::resumeSession(const char *session, int timeout) { - requestFlags[0][MegaRequest::TYPE_LOGIN] = false; - megaApi[0]->fastLogin(session, this); - - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_LOGIN], timeout) ) - << "Resume session failed after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Resume session failed (error: " << lastError[0] << ")"; + int apiIndex = 0; + ASSERT_EQ(MegaError::API_OK, synchronousFastLogin(apiIndex, session, this)) << "Resume session failed (error: " << mApi[apiIndex].lastError << ")"; } void SdkTest::purgeTree(MegaNode *p) { + int apiIndex = 0; MegaNodeList *children; children = megaApi[0]->getChildren(p); for (int i = 0; i < children->size(); i++) { MegaNode *n = children->get(i); + + // removing the folder removes the children anyway if (n->isFolder()) purgeTree(n); - - requestFlags[0][MegaRequest::TYPE_REMOVE] = false; - - megaApi[0]->remove(n); - - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_REMOVE]) ) - << "Remove node operation failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Remove node operation failed (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousRemove(apiIndex, n)) << "Remove node operation failed (error: " << mApi[apiIndex].lastError << ")"; } } @@ -755,11 +689,26 @@ bool SdkTest::waitForResponse(bool *responseReceived, unsigned int timeout) return true; // response is received } -bool SdkTest::synchronousCall(bool &responseFlag, std::function f, unsigned int timeout) +bool SdkTest::synchronousTransfer(int apiIndex, int type, std::function f, unsigned int timeout) { - responseFlag = false; + auto& flag = mApi[apiIndex].transferFlags[type]; + flag = false; f(); - return waitForResponse(&responseFlag, timeout); + auto result = waitForResponse(&flag, timeout); + EXPECT_TRUE(result) << "Transfer (type " << type << ") failed after " << timeout << " seconds"; + if (!result) mApi[apiIndex].lastError = -999; // local timeout + return result; +} + +bool SdkTest::synchronousRequest(int apiIndex, int type, std::function f, unsigned int timeout) +{ + auto& flag = mApi[apiIndex].requestFlags[type]; + flag = false; + f(); + auto result = waitForResponse(&flag, timeout); + EXPECT_TRUE(result) << "Request (type " << type << ") failed after " << timeout << " seconds"; + if (!result) mApi[apiIndex].lastError = -999; + return result; } void SdkTest::createFile(string filename, bool largeFile) @@ -784,12 +733,12 @@ void SdkTest::createFile(string filename, bool largeFile) } } -size_t SdkTest::getFilesize(string filename) +int64_t SdkTest::getFilesize(string filename) { struct stat stat_buf; int rc = stat(filename.c_str(), &stat_buf); - return rc == 0 ? stat_buf.st_size : -1; + return rc == 0 ? int64_t(stat_buf.st_size) : int64_t(-1); } void SdkTest::deleteFile(string filename) @@ -798,145 +747,137 @@ void SdkTest::deleteFile(string filename) } -void SdkTest::getMegaApiAux() +void SdkTest::getMegaApiAux(unsigned index) { - if (megaApi[1] == NULL) + if (index >= megaApi.size()) + { + megaApi.resize(index + 1); + mApi.resize(index + 1); + } + if (megaApi[index].get() == NULL) { - char *buf; - buf = getenv("MEGA_EMAIL_AUX"); - if (buf) - email[1].assign(buf); - ASSERT_LT((size_t) 0, email[1].length()) << "Set auxiliar username at the environment variable $MEGA_EMAIL_AUX"; + string strIndex = index > 1 ? to_string(index) : ""; + if (const char *buf = getenv(("MEGA_EMAIL_AUX" + strIndex).c_str())) + { + mApi[index].email.assign(buf); + } + ASSERT_LT((size_t) 0, mApi[index].email.length()) << "Set auxiliar username at the environment variable $MEGA_EMAIL_AUX" << strIndex; - buf = getenv("MEGA_PWD_AUX"); - if (buf) - pwd[1].assign(buf); - ASSERT_LT((size_t) 0, pwd[1].length()) << "Set the auxiliar password at the environment variable $MEGA_PWD_AUX"; + if (const char* buf = getenv(("MEGA_PWD_AUX" + strIndex).c_str())) + { + mApi[index].pwd.assign(buf); + } + ASSERT_LT((size_t) 0, mApi[index].pwd.length()) << "Set the auxiliar password at the environment variable $MEGA_PWD_AUX" << strIndex; - megaApi[1] = new MegaApi(APP_KEY.c_str(), megaApiCacheFolder(1).c_str(), USER_AGENT.c_str()); + megaApi[index].reset(new MegaApi(APP_KEY.c_str(), megaApiCacheFolder(index).c_str(), USER_AGENT.c_str())); + mApi[index].megaApi = megaApi[index].get(); - megaApi[1]->setLoggingName("1"); - megaApi[1]->setLogLevel(MegaApi::LOG_LEVEL_DEBUG); - megaApi[1]->addListener(this); + megaApi[index]->setLoggingName(to_string(index).c_str()); + megaApi[index]->setLogLevel(MegaApi::LOG_LEVEL_DEBUG); + megaApi[index]->addListener(this); // TODO: really should be per api - ASSERT_NO_FATAL_FAILURE( login(1) ); - ASSERT_NO_FATAL_FAILURE( fetchnodes(1) ); + ASSERT_NO_FATAL_FAILURE( login(index) ); + ASSERT_NO_FATAL_FAILURE( fetchnodes(index) ); } } void SdkTest::releaseMegaApi(unsigned int apiIndex) { - if (megaApi[apiIndex]) + assert(megaApi[apiIndex].get() == mApi[apiIndex].megaApi); + if (mApi[apiIndex].megaApi) { - if (megaApi[apiIndex]->isLoggedIn()) + if (mApi[apiIndex].megaApi->isLoggedIn()) { ASSERT_NO_FATAL_FAILURE( logout(apiIndex) ); } - delete megaApi[apiIndex]; - megaApi[apiIndex] = NULL; + megaApi[apiIndex].reset(); + mApi[apiIndex].megaApi = NULL; } } -void SdkTest::inviteContact(string email, string message, int action, int timeout) +void SdkTest::inviteContact(string email, string message, int action) { - requestFlags[0][MegaRequest::TYPE_INVITE_CONTACT] = false; - megaApi[0]->inviteContact(email.data(), message.data(), action); - - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_INVITE_CONTACT], timeout) ) - << "Contact invitation not finished after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Contact invitation failed (error: " << lastError[0] << ")"; + int apiIndex = 0; + ASSERT_EQ(MegaError::API_OK, synchronousInviteContact(apiIndex, email.data(), message.data(), action)) << "Contact invitation failed"; } -void SdkTest::replyContact(MegaContactRequest *cr, int action, int timeout) +void SdkTest::replyContact(MegaContactRequest *cr, int action) { - requestFlags[1][MegaRequest::TYPE_REPLY_CONTACT_REQUEST] = false; - megaApi[1]->replyContactRequest(cr, action); - - ASSERT_TRUE( waitForResponse(&requestFlags[1][MegaRequest::TYPE_REPLY_CONTACT_REQUEST], timeout) ) - << "Contact reply not finished after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[1]) << "Contact reply failed (error: " << lastError[1] << ")"; + int apiIndex = 1; + ASSERT_EQ(MegaError::API_OK, synchronousReplyContactRequest(apiIndex, cr, action)) << "Contact reply failed"; } void SdkTest::removeContact(string email, int timeout) { - MegaUser *u = megaApi[0]->getContact(email.data()); + int apiIndex = 0; + MegaUser *u = megaApi[apiIndex]->getContact(email.data()); bool null_pointer = (u == NULL); ASSERT_FALSE(null_pointer) << "Cannot find the specified contact (" << email << ")"; if (u->getVisibility() != MegaUser::VISIBILITY_VISIBLE) { - userUpdated[0] = true; // nothing to do + mApi[apiIndex].userUpdated = true; // nothing to do delete u; return; } - requestFlags[0][MegaRequest::TYPE_REMOVE_CONTACT] = false; - megaApi[0]->removeContact(u); - - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_REMOVE_CONTACT], timeout) ) - << "Contact deletion not finished after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Contact deletion failed (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousRemoveContact(apiIndex, u)) << "Contact deletion failed"; delete u; } void SdkTest::shareFolder(MegaNode *n, const char *email, int action, int timeout) { - requestFlags[0][MegaRequest::TYPE_SHARE] = false; - megaApi[0]->share(n, email, action); - - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_SHARE], timeout) ) - << "Folder sharing not finished after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Folder sharing failed (error: " << lastError[0] << ")" << endl << "User: " << email << " Action: " << action; + int apiIndex = 0; + ASSERT_EQ(MegaError::API_OK, synchronousShare(apiIndex, n, email, action)) << "Folder sharing failed" << endl << "User: " << email << " Action: " << action; } void SdkTest::createPublicLink(unsigned apiIndex, MegaNode *n, m_time_t expireDate, int timeout) { - requestFlags[apiIndex][MegaRequest::TYPE_EXPORT] = false; - megaApi[apiIndex]->exportNode(n, expireDate); + mApi[apiIndex].requestFlags[MegaRequest::TYPE_EXPORT] = false; + + auto err = synchronousExportNode(apiIndex, n, expireDate); - ASSERT_TRUE( waitForResponse(&requestFlags[apiIndex][MegaRequest::TYPE_EXPORT], timeout) ) - << "Public link creation not finished after " << timeout << " seconds"; if (!expireDate) { - ASSERT_EQ(MegaError::API_OK, lastError[apiIndex]) << "Public link creation failed (error: " << lastError[apiIndex] << ")"; + ASSERT_EQ(MegaError::API_OK, err) << "Public link creation failed (error: " << mApi[apiIndex].lastError << ")"; } else { - bool res = MegaError::API_OK != lastError[apiIndex]; - ASSERT_TRUE(res) << "Public link creation with expire time on free account (" << email[apiIndex] << ") succeed, and it mustn't"; + bool res = MegaError::API_OK != err && err != -999; + ASSERT_TRUE(res) << "Public link creation with expire time on free account (" << mApi[apiIndex].email << ") succeed, and it mustn't"; } } void SdkTest::importPublicLink(unsigned apiIndex, string link, MegaNode *parent, int timeout) { - requestFlags[apiIndex][MegaRequest::TYPE_IMPORT_LINK] = false; - megaApi[apiIndex]->importFileLink(link.data(), parent); + mApi[apiIndex].requestFlags[MegaRequest::TYPE_IMPORT_LINK] = false; + mApi[apiIndex].megaApi->importFileLink(link.data(), parent); - ASSERT_TRUE(waitForResponse(&requestFlags[apiIndex][MegaRequest::TYPE_IMPORT_LINK], timeout) ) + ASSERT_TRUE(waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_IMPORT_LINK], timeout) ) << "Public link import not finished after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[apiIndex]) << "Public link import failed (error: " << lastError[apiIndex] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Public link import failed (error: " << mApi[apiIndex].lastError << ")"; } void SdkTest::getPublicNode(unsigned apiIndex, string link, int timeout) { - requestFlags[apiIndex][MegaRequest::TYPE_GET_PUBLIC_NODE] = false; - megaApi[apiIndex]->getPublicNode(link.data()); + mApi[apiIndex].requestFlags[MegaRequest::TYPE_GET_PUBLIC_NODE] = false; + mApi[apiIndex].megaApi->getPublicNode(link.data()); - ASSERT_TRUE(waitForResponse(&requestFlags[apiIndex][MegaRequest::TYPE_GET_PUBLIC_NODE], timeout) ) + ASSERT_TRUE(waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_GET_PUBLIC_NODE], timeout) ) << "Public link retrieval not finished after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[apiIndex]) << "Public link retrieval failed (error: " << lastError[apiIndex] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Public link retrieval failed (error: " << mApi[apiIndex].lastError << ")"; } void SdkTest::removePublicLink(unsigned apiIndex, MegaNode *n, int timeout) { - requestFlags[apiIndex][MegaRequest::TYPE_EXPORT] = false; - megaApi[apiIndex]->disableExport(n); + mApi[apiIndex].requestFlags[MegaRequest::TYPE_EXPORT] = false; + mApi[apiIndex].megaApi->disableExport(n); - ASSERT_TRUE( waitForResponse(&requestFlags[apiIndex][MegaRequest::TYPE_EXPORT], timeout) ) + ASSERT_TRUE( waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_EXPORT], timeout) ) << "Public link removal not finished after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[apiIndex]) << "Public link removal failed (error: " << lastError[apiIndex] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Public link removal failed (error: " << mApi[apiIndex].lastError << ")"; } void SdkTest::getContactRequest(unsigned int apiIndex, bool outgoing, int expectedSize) @@ -945,94 +886,85 @@ void SdkTest::getContactRequest(unsigned int apiIndex, bool outgoing, int expect if (outgoing) { - crl = megaApi[apiIndex]->getOutgoingContactRequests(); - ASSERT_EQ(expectedSize, crl->size()) << "Too many outgoing contact requests in main account"; + crl = mApi[apiIndex].megaApi->getOutgoingContactRequests(); + ASSERT_EQ(expectedSize, crl->size()) << "Too many outgoing contact requests in account " << apiIndex; if (expectedSize) - cr[apiIndex] = crl->get(0)->copy(); + mApi[apiIndex].cr.reset(crl->get(0)->copy()); } else { - crl = megaApi[apiIndex]->getIncomingContactRequests(); - ASSERT_EQ(expectedSize, crl->size()) << "Too many incoming contact requests in auxiliar account"; + crl = mApi[apiIndex].megaApi->getIncomingContactRequests(); + ASSERT_EQ(expectedSize, crl->size()) << "Too many incoming contact requests in account " << apiIndex; if (expectedSize) - cr[apiIndex] = crl->get(0)->copy(); + mApi[apiIndex].cr.reset(crl->get(0)->copy()); } delete crl; } -void SdkTest::createFolder(unsigned int apiIndex, char *name, MegaNode *n, int timeout) +void SdkTest::createFolder(unsigned int apiIndex, const char *name, MegaNode *n, int timeout) { - requestFlags[apiIndex][MegaRequest::TYPE_CREATE_FOLDER] = false; - megaApi[apiIndex]->createFolder(name, n); + mApi[apiIndex].requestFlags[MegaRequest::TYPE_CREATE_FOLDER] = false; + mApi[apiIndex].megaApi->createFolder(name, n); - ASSERT_TRUE( waitForResponse(&requestFlags[apiIndex][MegaRequest::TYPE_CREATE_FOLDER], timeout) ) + ASSERT_TRUE( waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_CREATE_FOLDER], timeout) ) << "Folder creation failed after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[apiIndex]) << "Cannot create a folder (error: " << lastError[apiIndex] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Cannot create a folder (error: " << mApi[apiIndex].lastError << ")"; } -void SdkTest::getRegisteredContacts(const std::map& contacts, const int timeout) +void SdkTest::getRegisteredContacts(const std::map& contacts) { + int apiIndex = 0; + auto contactsStringMap = std::unique_ptr{MegaStringMap::createInstance()}; for (const auto& pair : contacts) { contactsStringMap->set(pair.first.c_str(), pair.second.c_str()); } - requestFlags[0][MegaRequest::TYPE_GET_REGISTERED_CONTACTS] = false; - megaApi[0]->getRegisteredContacts(contactsStringMap.get(), this); - - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_GET_REGISTERED_CONTACTS], timeout) ) - << "Get registered contacts not finished after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Get registered contacts failed (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousGetRegisteredContacts(apiIndex, contactsStringMap.get(), this)) << "Get registered contacts failed"; } void SdkTest::getCountryCallingCodes(const int timeout) { - requestFlags[0][MegaRequest::TYPE_GET_COUNTRY_CALLING_CODES] = false; - megaApi[0]->getCountryCallingCodes(this); - - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_GET_COUNTRY_CALLING_CODES], timeout) ) - << "Get country calling codes not finished after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Get country calling codes failed (error: " << lastError[0] << ")"; + int apiIndex = 0; + ASSERT_EQ(MegaError::API_OK, synchronousGetCountryCallingCodes(apiIndex, this)) << "Get country calling codes failed"; } void SdkTest::setUserAttribute(int type, string value, int timeout) { - requestFlags[0][MegaRequest::TYPE_SET_ATTR_USER] = false; + int apiIndex = 0; + mApi[apiIndex].requestFlags[MegaRequest::TYPE_SET_ATTR_USER] = false; if (type == MegaApi::USER_ATTR_AVATAR) { - megaApi[0]->setAvatar(value.empty() ? NULL : value.c_str()); + megaApi[apiIndex]->setAvatar(value.empty() ? NULL : value.c_str()); } else { - megaApi[0]->setUserAttribute(type, value.c_str()); + megaApi[apiIndex]->setUserAttribute(type, value.c_str()); } - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_USER], timeout) ) + ASSERT_TRUE( waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_SET_ATTR_USER], timeout) ) << "User attribute setup not finished after " << timeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "User attribute setup failed (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "User attribute setup failed (error: " << mApi[apiIndex].lastError << ")"; } -void SdkTest::getUserAttribute(MegaUser *u, int type, int timeout, int accountIndex) +void SdkTest::getUserAttribute(MegaUser *u, int type, int timeout, int apiIndex) { - requestFlags[accountIndex][MegaRequest::TYPE_GET_ATTR_USER] = false; + mApi[apiIndex].requestFlags[MegaRequest::TYPE_GET_ATTR_USER] = false; + int err; if (type == MegaApi::USER_ATTR_AVATAR) { - megaApi[accountIndex]->getUserAvatar(u, AVATARDST.data()); + err = synchronousGetUserAvatar(apiIndex, u, AVATARDST.data()); } else { - megaApi[accountIndex]->getUserAttribute(u, type); + err = synchronousGetUserAttribute(apiIndex, u, type); } - - ASSERT_TRUE( waitForResponse(&requestFlags[accountIndex][MegaRequest::TYPE_GET_ATTR_USER], timeout) ) - << "User attribute retrieval not finished after " << timeout << " seconds"; - - bool result = (lastError[accountIndex] == MegaError::API_OK) || (lastError[accountIndex] == MegaError::API_ENOENT); - ASSERT_TRUE(result) << "User attribute retrieval failed (error: " << lastError[accountIndex] << ")"; + bool result = (err == MegaError::API_OK) || (err == MegaError::API_ENOENT); + ASSERT_TRUE(result) << "User attribute retrieval failed (error: " << err << ")"; } ///////////////////////////__ Tests using SdkTest __////////////////////////////////// @@ -1055,31 +987,25 @@ TEST_F(SdkTest, DISABLED_SdkTestCreateAccount) LOG_info << "___TEST Create account___"; // Create an ephemeral session internally and send a confirmation link to email - requestFlags[0][MegaRequest::TYPE_CREATE_ACCOUNT] = false; - megaApi[0]->createAccount(email1.c_str(), pwd.c_str(), "MyFirstname", "MyLastname"); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_CREATE_ACCOUNT]) ) + ASSERT_TRUE(synchronousCreateAccount(0, email1.c_str(), pwd.c_str(), "MyFirstname", "MyLastname")) << "Account creation has failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Account creation failed (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Account creation failed (error: " << mApi[0].lastError << ")"; // Logout from ephemeral session and resume session ASSERT_NO_FATAL_FAILURE( locallogout() ); - requestFlags[0][MegaRequest::TYPE_CREATE_ACCOUNT] = false; - megaApi[0]->resumeCreateAccount(sid.c_str()); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_CREATE_ACCOUNT]) ) + ASSERT_TRUE(synchronousResumeCreateAccount(0, sid.c_str())) << "Account creation has failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Account creation failed (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Account creation failed (error: " << mApi[0].lastError << ")"; // Send the confirmation link to a different email address - requestFlags[0][MegaRequest::TYPE_SEND_SIGNUP_LINK] = false; - megaApi[0]->sendSignupLink(email2.c_str(), "MyFirstname", pwd.c_str()); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_SEND_SIGNUP_LINK]) ) + ASSERT_TRUE(synchronousSendSignupLink(0, email2.c_str(), "MyFirstname", pwd.c_str())) << "Send confirmation link to another email failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Send confirmation link to another email address failed (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Send confirmation link to another email address failed (error: " << mApi[0].lastError << ")"; // Now, confirm the account by using a different client... // ...and wait for the AP notifying the confirmation - bool *flag = &accountUpdated[0]; *flag = false; + bool *flag = &mApi[0].accountUpdated; *flag = false; ASSERT_TRUE( waitForResponse(flag) ) << "Account confirmation not received after " << maxTimeout << " seconds"; } @@ -1109,50 +1035,38 @@ TEST_F(SdkTest, SdkTestNodeAttributes) string filename1 = UPFILE; createFile(filename1, false); - transferFlags[0][MegaTransfer::TYPE_UPLOAD] = false; - megaApi[0]->startUpload(filename1.data(), rootnode); - waitForResponse(&transferFlags[0][MegaTransfer::TYPE_UPLOAD]); - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot upload a test file (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousStartUpload(0, filename1.data(), rootnode)) << "Cannot upload a test file"; - MegaNode *n1 = megaApi[0]->getNodeByHandle(h); + MegaNode *n1 = megaApi[0]->getNodeByHandle(mApi[0].h); bool null_pointer = (n1 == NULL); - ASSERT_FALSE(null_pointer) << "Cannot initialize test scenario (error: " << lastError[0] << ")"; + ASSERT_FALSE(null_pointer) << "Cannot initialize test scenario (error: " << mApi[0].lastError << ")"; // ___ Set invalid duration of a node ___ gTestingInvalidArgs = true; - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; - megaApi[0]->setNodeDuration(n1, -14); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); - ASSERT_EQ(MegaError::API_EARGS, lastError[0]) << "Unexpected error setting invalid node duration (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_EARGS, synchronousSetNodeDuration(0, n1, -14)) << "Unexpected error setting invalid node duration"; gTestingInvalidArgs = false; // ___ Set duration of a node ___ - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; - megaApi[0]->setNodeDuration(n1, 929734); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot set node duration (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousSetNodeDuration(0, n1, 929734)) << "Cannot set node duration"; delete n1; - n1 = megaApi[0]->getNodeByHandle(h); + n1 = megaApi[0]->getNodeByHandle(mApi[0].h); ASSERT_EQ(929734, n1->getDuration()) << "Duration value does not match"; // ___ Reset duration of a node ___ - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; - megaApi[0]->setNodeDuration(n1, -1); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot reset node duration (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousSetNodeDuration(0, n1, -1)) << "Cannot reset node duration"; delete n1; - n1 = megaApi[0]->getNodeByHandle(h); + n1 = megaApi[0]->getNodeByHandle(mApi[0].h); ASSERT_EQ(-1, n1->getDuration()) << "Duration value does not match"; @@ -1160,42 +1074,29 @@ TEST_F(SdkTest, SdkTestNodeAttributes) gTestingInvalidArgs = true; - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; - megaApi[0]->setNodeCoordinates(n1, -1523421.8719987255814, +6349.54); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); - ASSERT_EQ(MegaError::API_EARGS, lastError[0]) << "Unexpected error setting invalid node coordinates (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_EARGS, synchronousSetNodeCoordinates(0, n1, -1523421.8719987255814, +6349.54)) << "Unexpected error setting invalid node coordinates"; // ___ Set invalid coordinates of a node (out of range) ___ - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; - megaApi[0]->setNodeCoordinates(n1, -160.8719987255814, +49.54); // latitude must be [-90, 90] - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); - ASSERT_EQ(MegaError::API_EARGS, lastError[0]) << "Unexpected error setting invalid node coordinates (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_EARGS, synchronousSetNodeCoordinates(0, n1, -160.8719987255814, +49.54)) << "Unexpected error setting invalid node coordinates"; // ___ Set invalid coordinates of a node (out of range) ___ - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; - megaApi[0]->setNodeCoordinates(n1, MegaNode::INVALID_COORDINATE, +69.54); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); - ASSERT_EQ(MegaError::API_EARGS, lastError[0]) << "Unexpected error trying to reset only one coordinate (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_EARGS, synchronousSetNodeCoordinates(0, n1, MegaNode::INVALID_COORDINATE, +69.54)) << "Unexpected error trying to reset only one coordinate"; gTestingInvalidArgs = false; - // ___ Set coordinates of a node ___ double lat = -51.8719987255814; double lon = +179.54; - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; - megaApi[0]->setNodeCoordinates(n1, lat, lon); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot set node coordinates (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousSetNodeCoordinates(0, n1, lat, lon)) << "Cannot set node coordinates"; delete n1; - n1 = megaApi[0]->getNodeByHandle(h); + n1 = megaApi[0]->getNodeByHandle(mApi[0].h); // do same conversions to lose the same precision int buf = int(((lat + 90) / 180) * 0xFFFFFF); @@ -1214,13 +1115,10 @@ TEST_F(SdkTest, SdkTestNodeAttributes) lon = 0; lat = 0; - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; - megaApi[0]->setNodeCoordinates(n1, 0, 0); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot set node coordinates (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousSetNodeCoordinates(0, n1, 0, 0)) << "Cannot set node coordinates"; delete n1; - n1 = megaApi[0]->getNodeByHandle(h); + n1 = megaApi[0]->getNodeByHandle(mApi[0].h); // do same conversions to lose the same precision buf = int(((lat + 90) / 180) * 0xFFFFFF); @@ -1235,13 +1133,10 @@ TEST_F(SdkTest, SdkTestNodeAttributes) lat = 90; lon = 180; - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; - megaApi[0]->setNodeCoordinates(n1, lat, lon); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot set node coordinates (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousSetNodeCoordinates(0, n1, lat, lon)) << "Cannot set node coordinates"; delete n1; - n1 = megaApi[0]->getNodeByHandle(h); + n1 = megaApi[0]->getNodeByHandle(mApi[0].h); ASSERT_EQ(lat, n1->getLatitude()) << "Latitude value does not match"; bool value_ok = ((n1->getLongitude() == lon) || (n1->getLongitude() == -lon)); @@ -1253,13 +1148,10 @@ TEST_F(SdkTest, SdkTestNodeAttributes) lat = -90; lon = -180; - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; - megaApi[0]->setNodeCoordinates(n1, lat, lon); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot set node coordinates (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousSetNodeCoordinates(0, n1, lat, lon)) << "Cannot set node coordinates"; delete n1; - n1 = megaApi[0]->getNodeByHandle(h); + n1 = megaApi[0]->getNodeByHandle(mApi[0].h); ASSERT_EQ(lat, n1->getLatitude()) << "Latitude value does not match"; value_ok = ((n1->getLongitude() == lon) || (n1->getLongitude() == -lon)); @@ -1270,12 +1162,10 @@ TEST_F(SdkTest, SdkTestNodeAttributes) lat = lon = MegaNode::INVALID_COORDINATE; - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; - megaApi[0]->setNodeCoordinates(n1, lat, lon); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); + synchronousSetNodeCoordinates(0, n1, lat, lon); delete n1; - n1 = megaApi[0]->getNodeByHandle(h); + n1 = megaApi[0]->getNodeByHandle(mApi[0].h); ASSERT_EQ(lat, n1->getLatitude()) << "Latitude value does not match"; ASSERT_EQ(lon, n1->getLongitude()) << "Longitude value does not match"; @@ -1285,10 +1175,7 @@ TEST_F(SdkTest, SdkTestNodeAttributes) // ___ set the coords (shareable) lat = -51.8719987255814; lon = +179.54; - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; - megaApi[0]->setNodeCoordinates(n1, lat, lon); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot set node coordinates (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousSetNodeCoordinates(0, n1, lat, lon)) << "Cannot set node coordinates"; // ___ get a link to the file node ASSERT_NO_FATAL_FAILURE(createPublicLink(0, n1)); @@ -1300,36 +1187,33 @@ TEST_F(SdkTest, SdkTestNodeAttributes) // ___ import the link ASSERT_NO_FATAL_FAILURE(importPublicLink(1, nodelink, megaApi[1]->getRootNode())); - MegaNode *nimported = megaApi[1]->getNodeByHandle(h); + MegaNode *nimported = megaApi[1]->getNodeByHandle(mApi[1].h); ASSERT_TRUE(veryclose(lat, nimported->getLatitude())) << "Latitude " << n1->getLatitude() << " value does not match " << lat; ASSERT_TRUE(veryclose(lon, nimported->getLongitude())) << "Longitude " << n1->getLongitude() << " value does not match " << lon; // ___ remove the imported node, for a clean next test - requestFlags[1][MegaRequest::TYPE_REMOVE] = false; + mApi[1].requestFlags[MegaRequest::TYPE_REMOVE] = false; megaApi[1]->remove(nimported); - ASSERT_TRUE(waitForResponse(&requestFlags[1][MegaRequest::TYPE_REMOVE])) + ASSERT_TRUE(waitForResponse(&mApi[1].requestFlags[MegaRequest::TYPE_REMOVE])) << "Remove operation failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[1]) << "Cannot remove a node (error: " << lastError[1] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[1].lastError) << "Cannot remove a node (error: " << mApi[1].lastError << ")"; // ___ again but unshareable this time - totally separate new node - set the coords (unshareable) string filename2 = "a"+UPFILE; createFile(filename2, false); - transferFlags[0][MegaTransfer::TYPE_UPLOAD] = false; - megaApi[0]->startUpload(filename2.data(), rootnode); - waitForResponse(&transferFlags[0][MegaTransfer::TYPE_UPLOAD]); - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot upload a test file (error: " << lastError[0] << ")"; - MegaNode *n2 = megaApi[0]->getNodeByHandle(h); - ASSERT_NE(n2, ((void*)NULL)) << "Cannot initialize second node for scenario (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousStartUpload(0, filename2.data(), rootnode)) << "Cannot upload a test file"; + MegaNode *n2 = megaApi[0]->getNodeByHandle(mApi[0].h); + ASSERT_NE(n2, ((void*)NULL)) << "Cannot initialize second node for scenario (error: " << mApi[0].lastError << ")"; lat = -5 + -51.8719987255814; lon = -5 + +179.54; - requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE] = false; + mApi[0].requestFlags[MegaRequest::TYPE_SET_ATTR_NODE] = false; megaApi[0]->setUnshareableNodeCoordinates(n2, lat, lon); - waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_NODE]); - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot set unshareable node coordinates (error: " << lastError[0] << ")"; + waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_SET_ATTR_NODE]); + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot set unshareable node coordinates (error: " << mApi[0].lastError << ")"; // ___ confirm this user can read them MegaNode* selfread = megaApi[0]->getNodeByHandle(n2->getHandle()); @@ -1344,7 +1228,7 @@ TEST_F(SdkTest, SdkTestNodeAttributes) // ___ import the link ASSERT_NO_FATAL_FAILURE(importPublicLink(1, nodelink2, megaApi[1]->getRootNode())); - nimported = megaApi[1]->getNodeByHandle(h); + nimported = megaApi[1]->getNodeByHandle(mApi[1].h); // ___ confirm other user cannot read them lat = nimported->getLatitude(); @@ -1402,14 +1286,14 @@ TEST_F(SdkTest, SdkTestNodeOperations) // --- Rename a node --- - MegaNode *n1 = megaApi[0]->getNodeByHandle(h); + MegaNode *n1 = megaApi[0]->getNodeByHandle(mApi[0].h); strcpy(name1, "Folder renamed"); - requestFlags[0][MegaRequest::TYPE_RENAME] = false; + mApi[0].requestFlags[MegaRequest::TYPE_RENAME] = false; megaApi[0]->renameNode(n1, name1); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_RENAME]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_RENAME]) ) << "Rename operation failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot rename a node (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot rename a node (error: " << mApi[0].lastError << ")"; // --- Copy a node --- @@ -1417,12 +1301,12 @@ TEST_F(SdkTest, SdkTestNodeOperations) MegaNode *n2; char name2[64] = "Folder copy"; - requestFlags[0][MegaRequest::TYPE_COPY] = false; + mApi[0].requestFlags[MegaRequest::TYPE_COPY] = false; megaApi[0]->copyNode(n1, rootnode, name2); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_COPY]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_COPY]) ) << "Copy operation failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot create a copy of a node (error: " << lastError[0] << ")"; - n2 = megaApi[0]->getNodeByHandle(h); + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot create a copy of a node (error: " << mApi[0].lastError << ")"; + n2 = megaApi[0]->getNodeByHandle(mApi[0].h); // --- Get child nodes --- @@ -1470,11 +1354,11 @@ TEST_F(SdkTest, SdkTestNodeOperations) // --- Move a node --- - requestFlags[0][MegaRequest::TYPE_MOVE] = false; + mApi[0].requestFlags[MegaRequest::TYPE_MOVE] = false; megaApi[0]->moveNode(n1, n2); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_MOVE]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_MOVE]) ) << "Move operation failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot move node (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot move node (error: " << mApi[0].lastError << ")"; // --- Get parent node --- @@ -1487,20 +1371,20 @@ TEST_F(SdkTest, SdkTestNodeOperations) // --- Send to Rubbish bin --- - requestFlags[0][MegaRequest::TYPE_MOVE] = false; + mApi[0].requestFlags[MegaRequest::TYPE_MOVE] = false; megaApi[0]->moveNode(n2, megaApi[0]->getRubbishNode()); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_MOVE]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_MOVE]) ) << "Move operation failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot move node to Rubbish bin (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot move node to Rubbish bin (error: " << mApi[0].lastError << ")"; // --- Remove a node --- - requestFlags[0][MegaRequest::TYPE_REMOVE] = false; + mApi[0].requestFlags[MegaRequest::TYPE_REMOVE] = false; megaApi[0]->remove(n2); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_REMOVE]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_REMOVE]) ) << "Remove operation failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot remove a node (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot remove a node (error: " << mApi[0].lastError << ")"; delete rootnode; delete n1; @@ -1535,53 +1419,53 @@ TEST_F(SdkTest, SdkTestTransfers) // --- Cancel a transfer --- - requestFlags[0][MegaRequest::TYPE_CANCEL_TRANSFERS] = false; + mApi[0].requestFlags[MegaRequest::TYPE_CANCEL_TRANSFERS] = false; megaApi[0]->startUpload(filename1.data(), rootnode); megaApi[0]->cancelTransfers(MegaTransfer::TYPE_UPLOAD); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_CANCEL_TRANSFERS]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CANCEL_TRANSFERS]) ) << "Cancellation of transfers failed after " << maxTimeout << " seconds"; - EXPECT_EQ(MegaError::API_OK, lastError[0]) << "Transfer cancellation failed (error: " << lastError[0] << ")"; + EXPECT_EQ(MegaError::API_OK, mApi[0].lastError) << "Transfer cancellation failed (error: " << mApi[0].lastError << ")"; // --- Upload a file (part 1) --- - transferFlags[0][MegaTransfer::TYPE_UPLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD] = false; megaApi[0]->startUpload(filename1.data(), rootnode); // do not wait yet for completion // --- Pause a transfer --- - requestFlags[0][MegaRequest::TYPE_PAUSE_TRANSFERS] = false; + mApi[0].requestFlags[MegaRequest::TYPE_PAUSE_TRANSFERS] = false; megaApi[0]->pauseTransfers(true, MegaTransfer::TYPE_UPLOAD); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_PAUSE_TRANSFERS]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_PAUSE_TRANSFERS]) ) << "Pause of transfers failed after " << maxTimeout << " seconds"; - EXPECT_EQ(MegaError::API_OK, lastError[0]) << "Cannot pause transfer (error: " << lastError[0] << ")"; + EXPECT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot pause transfer (error: " << mApi[0].lastError << ")"; EXPECT_TRUE(megaApi[0]->areTransfersPaused(MegaTransfer::TYPE_UPLOAD)) << "Upload transfer not paused"; // --- Resume a transfer --- - requestFlags[0][MegaRequest::TYPE_PAUSE_TRANSFERS] = false; + mApi[0].requestFlags[MegaRequest::TYPE_PAUSE_TRANSFERS] = false; megaApi[0]->pauseTransfers(false, MegaTransfer::TYPE_UPLOAD); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_PAUSE_TRANSFERS]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_PAUSE_TRANSFERS]) ) << "Resumption of transfers after pause has failed after " << maxTimeout << " seconds"; - EXPECT_EQ(MegaError::API_OK, lastError[0]) << "Cannot resume transfer (error: " << lastError[0] << ")"; + EXPECT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot resume transfer (error: " << mApi[0].lastError << ")"; EXPECT_FALSE(megaApi[0]->areTransfersPaused(MegaTransfer::TYPE_UPLOAD)) << "Upload transfer not resumed"; // --- Upload a file (part 2) --- - ASSERT_TRUE( waitForResponse(&transferFlags[0][MegaTransfer::TYPE_UPLOAD], 600) ) + ASSERT_TRUE( waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD], 600) ) << "Upload transfer failed after " << 600 << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot upload file (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot upload file (error: " << mApi[0].lastError << ")"; - MegaNode *n1 = megaApi[0]->getNodeByHandle(h); + MegaNode *n1 = megaApi[0]->getNodeByHandle(mApi[0].h); bool null_pointer = (n1 == NULL); - ASSERT_FALSE(null_pointer) << "Cannot upload file (error: " << lastError[0] << ")"; - ASSERT_STREQ(filename1.data(), n1->getName()) << "Uploaded file with wrong name (error: " << lastError[0] << ")"; + ASSERT_FALSE(null_pointer) << "Cannot upload file (error: " << mApi[0].lastError << ")"; + ASSERT_STREQ(filename1.data(), n1->getName()) << "Uploaded file with wrong name (error: " << mApi[0].lastError << ")"; // --- Get node by fingerprint (needs to be a file, not a folder) --- @@ -1598,8 +1482,8 @@ TEST_F(SdkTest, SdkTestTransfers) // --- Get the size of a file --- - size_t filesize = getFilesize(filename1); - long long nodesize = megaApi[0]->getSize(n2); + int64_t filesize = getFilesize(filename1); + int64_t nodesize = megaApi[0]->getSize(n2); EXPECT_EQ(filesize, nodesize) << "Wrong size of uploaded file"; @@ -1607,17 +1491,17 @@ TEST_F(SdkTest, SdkTestTransfers) string filename2 = "./" + DOWNFILE; - transferFlags[0][MegaTransfer::TYPE_DOWNLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(n2, filename2.c_str()); - ASSERT_TRUE( waitForResponse(&transferFlags[0][MegaTransfer::TYPE_DOWNLOAD], 600) ) + ASSERT_TRUE( waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600) ) << "Download transfer failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot download the file (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the file (error: " << mApi[0].lastError << ")"; - MegaNode *n3 = megaApi[0]->getNodeByHandle(h); + MegaNode *n3 = megaApi[0]->getNodeByHandle(mApi[0].h); null_pointer = (n3 == NULL); ASSERT_FALSE(null_pointer) << "Cannot download node"; - ASSERT_EQ(n2->getHandle(), n3->getHandle()) << "Cannot download node (error: " << lastError[0] << ")"; + ASSERT_EQ(n2->getHandle(), n3->getHandle()) << "Cannot download node (error: " << mApi[0].lastError << ")"; // --- Upload a 0-bytes file --- @@ -1626,34 +1510,29 @@ TEST_F(SdkTest, SdkTestTransfers) FILE *fp = fopen(filename3.c_str(), "w"); fclose(fp); - transferFlags[0][MegaTransfer::TYPE_UPLOAD] = false; - megaApi[0]->startUpload(filename3.c_str(), rootnode); - - ASSERT_TRUE( waitForResponse(&transferFlags[0][MegaTransfer::TYPE_UPLOAD], 600) ) - << "Upload 0-byte file failed after " << 600 << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot upload file (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, synchronousStartUpload(0, filename3.c_str(), rootnode)) << "Cannot upload a test file"; - MegaNode *n4 = megaApi[0]->getNodeByHandle(h); + MegaNode *n4 = megaApi[0]->getNodeByHandle(mApi[0].h); null_pointer = (n4 == NULL); - ASSERT_FALSE(null_pointer) << "Cannot upload file (error: " << lastError[0] << ")"; - ASSERT_STREQ(filename3.data(), n4->getName()) << "Uploaded file with wrong name (error: " << lastError[0] << ")"; + ASSERT_FALSE(null_pointer) << "Cannot upload file (error: " << mApi[0].lastError << ")"; + ASSERT_STREQ(filename3.data(), n4->getName()) << "Uploaded file with wrong name (error: " << mApi[0].lastError << ")"; // --- Download a 0-byte file --- filename3 = "./" + EMPTYFILE; - transferFlags[0][MegaTransfer::TYPE_DOWNLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(n4, filename3.c_str()); - ASSERT_TRUE( waitForResponse(&transferFlags[0][MegaTransfer::TYPE_DOWNLOAD], 600) ) + ASSERT_TRUE( waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600) ) << "Download 0-byte file failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot download the file (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the file (error: " << mApi[0].lastError << ")"; - MegaNode *n5 = megaApi[0]->getNodeByHandle(h); + MegaNode *n5 = megaApi[0]->getNodeByHandle(mApi[0].h); null_pointer = (n5 == NULL); ASSERT_FALSE(null_pointer) << "Cannot download node"; - ASSERT_EQ(n4->getHandle(), n5->getHandle()) << "Cannot download node (error: " << lastError[0] << ")"; + ASSERT_EQ(n4->getHandle(), n5->getHandle()) << "Cannot download node (error: " << mApi[0].lastError << ")"; delete rootnode; @@ -1703,100 +1582,100 @@ TEST_F(SdkTest, SdkTestContacts) // --- Check my email and the email of the contact --- - EXPECT_STREQ(email[0].data(), megaApi[0]->getMyEmail()); - EXPECT_STREQ(email[1].data(), megaApi[1]->getMyEmail()); + EXPECT_STREQ(mApi[0].email.data(), megaApi[0]->getMyEmail()); + EXPECT_STREQ(mApi[1].email.data(), megaApi[1]->getMyEmail()); // --- Send a new contact request --- string message = "Hi contact. This is a testing message"; - contactRequestUpdated[0] = contactRequestUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( inviteContact(email[1], message, MegaContactRequest::INVITE_ACTION_ADD) ); + mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; + ASSERT_NO_FATAL_FAILURE( inviteContact(mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) ); // if there were too many invitations within a short period of time, the invitation can be rejected by // the API with `API_EOVERQUOTA = -17` as counter spamming meassure (+500 invites in the last 50 days) // --- Check the sent contact request --- - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[0]) ) // at the source side (main account) + ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the source side (main account) << "Contact request update not received after " << maxTimeout << " seconds"; ASSERT_NO_FATAL_FAILURE( getContactRequest(0, true) ); - ASSERT_STREQ(message.data(), cr[0]->getSourceMessage()) << "Message sent is corrupted"; - ASSERT_STREQ(email[0].data(), cr[0]->getSourceEmail()) << "Wrong source email"; - ASSERT_STREQ(email[1].data(), cr[0]->getTargetEmail()) << "Wrong target email"; - ASSERT_EQ(MegaContactRequest::STATUS_UNRESOLVED, cr[0]->getStatus()) << "Wrong contact request status"; - ASSERT_TRUE(cr[0]->isOutgoing()) << "Wrong direction of the contact request"; + ASSERT_STREQ(message.data(), mApi[0].cr->getSourceMessage()) << "Message sent is corrupted"; + ASSERT_STREQ(mApi[0].email.data(), mApi[0].cr->getSourceEmail()) << "Wrong source email"; + ASSERT_STREQ(mApi[1].email.data(), mApi[0].cr->getTargetEmail()) << "Wrong target email"; + ASSERT_EQ(MegaContactRequest::STATUS_UNRESOLVED, mApi[0].cr->getStatus()) << "Wrong contact request status"; + ASSERT_TRUE(mApi[0].cr->isOutgoing()) << "Wrong direction of the contact request"; - delete cr[0]; cr[0] = NULL; + mApi[0].cr.reset(); // --- Check received contact request --- - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[1]) ) // at the target side (auxiliar account) + ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request update not received after " << maxTimeout << " seconds"; ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) ); // There isn't message when a user invites the same user too many times, to avoid spamming - if (cr[1]->getSourceMessage()) + if (mApi[1].cr->getSourceMessage()) { - ASSERT_STREQ(message.data(), cr[1]->getSourceMessage()) << "Message received is corrupted"; + ASSERT_STREQ(message.data(), mApi[1].cr->getSourceMessage()) << "Message received is corrupted"; } - ASSERT_STREQ(email[0].data(), cr[1]->getSourceEmail()) << "Wrong source email"; - ASSERT_STREQ(NULL, cr[1]->getTargetEmail()) << "Wrong target email"; // NULL according to MegaApi documentation - ASSERT_EQ(MegaContactRequest::STATUS_UNRESOLVED, cr[1]->getStatus()) << "Wrong contact request status"; - ASSERT_FALSE(cr[1]->isOutgoing()) << "Wrong direction of the contact request"; + ASSERT_STREQ(mApi[0].email.data(), mApi[1].cr->getSourceEmail()) << "Wrong source email"; + ASSERT_STREQ(NULL, mApi[1].cr->getTargetEmail()) << "Wrong target email"; // NULL according to MegaApi documentation + ASSERT_EQ(MegaContactRequest::STATUS_UNRESOLVED, mApi[1].cr->getStatus()) << "Wrong contact request status"; + ASSERT_FALSE(mApi[1].cr->isOutgoing()) << "Wrong direction of the contact request"; - delete cr[1]; cr[1] = NULL; + mApi[1].cr.reset(); // --- Ignore received contact request --- ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) ); - contactRequestUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( replyContact(cr[1], MegaContactRequest::REPLY_ACTION_IGNORE) ); - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[1]) ) // at the target side (auxiliar account) + mApi[1].contactRequestUpdated = false; + ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_IGNORE) ); + ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request update not received after " << maxTimeout << " seconds"; // Ignoring a PCR does not generate actionpackets for the account sending the invitation - delete cr[1]; cr[1] = NULL; + mApi[1].cr.reset(); ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false, 0) ); - delete cr[1]; cr[1] = NULL; + mApi[1].cr.reset(); // --- Cancel the invitation --- message = "I don't wanna be your contact anymore"; - contactRequestUpdated[0] = false; - ASSERT_NO_FATAL_FAILURE( inviteContact(email[1], message, MegaContactRequest::INVITE_ACTION_DELETE) ); - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[0]) ) // at the target side (auxiliar account), where the deletion is checked + mApi[0].contactRequestUpdated = false; + ASSERT_NO_FATAL_FAILURE( inviteContact(mApi[1].email, message, MegaContactRequest::INVITE_ACTION_DELETE) ); + ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the target side (auxiliar account), where the deletion is checked << "Contact request update not received after " << maxTimeout << " seconds"; ASSERT_NO_FATAL_FAILURE( getContactRequest(0, true, 0) ); - delete cr[0]; cr[0] = NULL; + mApi[0].cr.reset(); // --- Remind a contact invitation (cannot until 2 weeks after invitation/last reminder) --- -// contactRequestUpdated[1] = false; -// megaApi->inviteContact(email[1].data(), message.data(), MegaContactRequest::INVITE_ACTION_REMIND); -// waitForResponse(&contactRequestUpdated[1], 0); // only at auxiliar account, where the deletion is checked +// mApi[1].contactRequestUpdated = false; +// megaApi->inviteContact(mApi[1].email.data(), message.data(), MegaContactRequest::INVITE_ACTION_REMIND); +// waitForResponse(&mApi[1].contactRequestUpdated, 0); // only at auxiliar account, where the deletion is checked -// ASSERT_TRUE(contactRequestUpdated[1]) << "Contact invitation reminder not received after " << timeout << " seconds"; +// ASSERT_TRUE(mApi[1].contactRequestUpdated) << "Contact invitation reminder not received after " << timeout << " seconds"; // --- Invite a new contact (again) --- - contactRequestUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( inviteContact(email[1], message, MegaContactRequest::INVITE_ACTION_ADD) ); - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[1]) ) // at the target side (auxiliar account) + mApi[1].contactRequestUpdated = false; + ASSERT_NO_FATAL_FAILURE( inviteContact(mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) ); + ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; @@ -1804,27 +1683,27 @@ TEST_F(SdkTest, SdkTestContacts) ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) ); - contactRequestUpdated[0] = contactRequestUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( replyContact(cr[1], MegaContactRequest::REPLY_ACTION_DENY) ); - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[1]) ) // at the target side (auxiliar account) + mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; + ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_DENY) ); + ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[0]) ) // at the source side (main account) + ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the source side (main account) << "Contact request creation not received after " << maxTimeout << " seconds"; - delete cr[1]; cr[1] = NULL; + mApi[1].cr.reset(); ASSERT_NO_FATAL_FAILURE( getContactRequest(0, true, 0) ); - delete cr[0]; cr[0] = NULL; + mApi[0].cr.reset(); ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false, 0) ); - delete cr[1]; cr[1] = NULL; + mApi[1].cr.reset(); // --- Invite a new contact (again) --- - contactRequestUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( inviteContact(email[1], message, MegaContactRequest::INVITE_ACTION_ADD) ); - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[1]) ) // at the target side (auxiliar account) + mApi[1].contactRequestUpdated = false; + ASSERT_NO_FATAL_FAILURE( inviteContact(mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) ); + ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; @@ -1832,29 +1711,29 @@ TEST_F(SdkTest, SdkTestContacts) ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) ); - contactRequestUpdated[0] = contactRequestUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( replyContact(cr[1], MegaContactRequest::REPLY_ACTION_ACCEPT) ); - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[0]) ) // at the target side (main account) + mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; + ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT) ); + ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the target side (main account) << "Contact request creation not received after " << maxTimeout << " seconds"; - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[1]) ) // at the target side (auxiliar account) + ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; - delete cr[1]; cr[1] = NULL; + mApi[1].cr.reset(); ASSERT_NO_FATAL_FAILURE( getContactRequest(0, true, 0) ); - delete cr[0]; cr[0] = NULL; + mApi[0].cr.reset(); ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false, 0) ); - delete cr[1]; cr[1] = NULL; + mApi[1].cr.reset(); // --- Modify firstname --- string firstname = "My firstname"; - userUpdated[1] = false; + mApi[1].userUpdated = false; ASSERT_NO_FATAL_FAILURE( setUserAttribute(MegaApi::USER_ATTR_FIRSTNAME, firstname)); - ASSERT_TRUE( waitForResponse(&userUpdated[1]) ) // at the target side (auxiliar account) + ASSERT_TRUE( waitForResponse(&mApi[1].userUpdated) ) // at the target side (auxiliar account) << "User attribute update not received after " << maxTimeout << " seconds"; @@ -1863,7 +1742,7 @@ TEST_F(SdkTest, SdkTestContacts) MegaUser *u = megaApi[0]->getMyUser(); bool null_pointer = (u == NULL); - ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << email[0]; + ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << mApi[0].email; ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_FIRSTNAME)); ASSERT_EQ( firstname, attributeValue) << "Firstname is wrong"; @@ -1875,9 +1754,9 @@ TEST_F(SdkTest, SdkTestContacts) u = megaApi[0]->getMyUser(); - requestFlags[0][MegaRequest::TYPE_SET_ATTR_USER] = false; + mApi[0].requestFlags[MegaRequest::TYPE_SET_ATTR_USER] = false; megaApi[0]->masterKeyExported(); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_SET_ATTR_USER]) ); + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_SET_ATTR_USER]) ); ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_PWD_REMINDER, maxTimeout, 0)); string pwdReminder = attributeValue; @@ -1905,9 +1784,9 @@ TEST_F(SdkTest, SdkTestContacts) ASSERT_TRUE(fileexists(AVATARSRC)) << "File " +AVATARSRC+ " is needed in folder " << cwd(); - userUpdated[1] = false; + mApi[1].userUpdated = false; ASSERT_NO_FATAL_FAILURE( setUserAttribute(MegaApi::USER_ATTR_AVATAR, AVATARSRC)); - ASSERT_TRUE( waitForResponse(&userUpdated[1]) ) // at the target side (auxiliar account) + ASSERT_TRUE( waitForResponse(&mApi[1].userUpdated) ) // at the target side (auxiliar account) << "User attribute update not received after " << maxTimeout << " seconds"; @@ -1916,15 +1795,15 @@ TEST_F(SdkTest, SdkTestContacts) u = megaApi[0]->getMyUser(); null_pointer = (u == NULL); - ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << email[0]; + ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << mApi[0].email; attributeValue = ""; ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_AVATAR)); ASSERT_STREQ( "Avatar changed", attributeValue.data()) << "Failed to change avatar"; - size_t filesizeSrc = getFilesize(AVATARSRC); - size_t filesizeDst = getFilesize(AVATARDST); + int64_t filesizeSrc = getFilesize(AVATARSRC); + int64_t filesizeDst = getFilesize(AVATARDST); ASSERT_EQ(filesizeDst, filesizeSrc) << "Received avatar differs from uploaded avatar"; delete u; @@ -1932,9 +1811,9 @@ TEST_F(SdkTest, SdkTestContacts) // --- Delete avatar --- - userUpdated[1] = false; + mApi[1].userUpdated = false; ASSERT_NO_FATAL_FAILURE( setUserAttribute(MegaApi::USER_ATTR_AVATAR, "")); - ASSERT_TRUE( waitForResponse(&userUpdated[1]) ) // at the target side (auxiliar account) + ASSERT_TRUE( waitForResponse(&mApi[1].userUpdated) ) // at the target side (auxiliar account) << "User attribute update not received after " << maxTimeout << " seconds"; @@ -1943,7 +1822,7 @@ TEST_F(SdkTest, SdkTestContacts) u = megaApi[0]->getMyUser(); null_pointer = (u == NULL); - ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << email[0]; + ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << mApi[0].email; attributeValue = ""; @@ -1955,15 +1834,15 @@ TEST_F(SdkTest, SdkTestContacts) // --- Delete an existing contact --- - userUpdated[0] = false; - ASSERT_NO_FATAL_FAILURE( removeContact(email[1]) ); - ASSERT_TRUE( waitForResponse(&userUpdated[0]) ) // at the target side (main account) + mApi[0].userUpdated = false; + ASSERT_NO_FATAL_FAILURE( removeContact(mApi[1].email) ); + ASSERT_TRUE( waitForResponse(&mApi[0].userUpdated) ) // at the target side (main account) << "User attribute update not received after " << maxTimeout << " seconds"; - u = megaApi[0]->getContact(email[1].data()); + u = megaApi[0]->getContact(mApi[1].email.data()); null_pointer = (u == NULL); - ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << email[1]; + ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << mApi[1].email; ASSERT_EQ(MegaUser::VISIBILITY_HIDDEN, u->getVisibility()) << "New contact is still visible"; delete u; @@ -1999,7 +1878,7 @@ bool SdkTest::checkAlert(int apiIndex, const string& title, const string& path) for (int i = 0; !ok && i < 10; ++i) { - MegaUserAlertList* list = megaApi[apiIndex]->getUserAlerts(); + MegaUserAlertList* list = mApi[apiIndex].megaApi->getUserAlerts(); if (list->size() > 0) { MegaUserAlert* a = list->get(list->size() - 1); @@ -2080,7 +1959,7 @@ TEST_F(SdkTest, SdkTestShares) ASSERT_NO_FATAL_FAILURE( createFolder(0, foldername1, rootnode) ); - hfolder1 = h; // 'h' is set in 'onRequestFinish()' + hfolder1 = mApi[0].h; // 'h' is set in 'onRequestFinish()' n1 = megaApi[0]->getNodeByHandle(hfolder1); char foldername2[64] = "subfolder"; @@ -2088,38 +1967,35 @@ TEST_F(SdkTest, SdkTestShares) ASSERT_NO_FATAL_FAILURE( createFolder(0, foldername2, megaApi[0]->getNodeByHandle(hfolder1)) ); - hfolder2 = h; + hfolder2 = mApi[0].h; MegaHandle hfile1; createFile(PUBLICFILE.data(), false); // not a large file since don't need to test transfers here - transferFlags[0][MegaTransfer::TYPE_UPLOAD] = false; - megaApi[0]->startUpload(PUBLICFILE.data(), megaApi[0]->getNodeByHandle(hfolder1)); - waitForResponse(&transferFlags[0][MegaTransfer::TYPE_UPLOAD], 0); // wait forever + ASSERT_EQ(MegaError::API_OK, synchronousStartUpload(0, PUBLICFILE.data(), megaApi[0]->getNodeByHandle(hfolder1))) << "Cannot upload a test file"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot upload file (error: " << lastError[0] << ")"; - hfile1 = h; + hfile1 = mApi[0].h; // --- Download authorized node from another account --- MegaNode *nNoAuth = megaApi[0]->getNodeByHandle(hfile1); - transferFlags[1][MegaTransfer::TYPE_DOWNLOAD] = false; + mApi[1].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[1]->startDownload(nNoAuth, "unauthorized_node"); - ASSERT_TRUE( waitForResponse(&transferFlags[1][MegaTransfer::TYPE_DOWNLOAD], 600) ) + ASSERT_TRUE( waitForResponse(&mApi[1].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600) ) << "Download transfer not finished after " << maxTimeout << " seconds"; - bool hasFailed = (lastError[1] != API_OK); + bool hasFailed = (mApi[1].lastError != API_OK); ASSERT_TRUE(hasFailed) << "Download of node without authorization successful! (it should fail)"; MegaNode *nAuth = megaApi[0]->authorizeNode(nNoAuth); - transferFlags[1][MegaTransfer::TYPE_DOWNLOAD] = false; + mApi[1].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[1]->startDownload(nAuth, "authorized_node"); - ASSERT_TRUE( waitForResponse(&transferFlags[1][MegaTransfer::TYPE_DOWNLOAD], 600) ) + ASSERT_TRUE( waitForResponse(&mApi[1].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600) ) << "Download transfer not finished after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[1]) << "Cannot download authorized node (error: " << lastError[1] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[1].lastError) << "Cannot download authorized node (error: " << mApi[1].lastError << ")"; delete nNoAuth; delete nAuth; @@ -2128,31 +2004,31 @@ TEST_F(SdkTest, SdkTestShares) string message = "Hi contact. Let's share some stuff"; - contactRequestUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( inviteContact(email[1], message, MegaContactRequest::INVITE_ACTION_ADD) ); - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[1]) ) // at the target side (auxiliar account) + mApi[1].contactRequestUpdated = false; + ASSERT_NO_FATAL_FAILURE( inviteContact(mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) ); + ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) ); - contactRequestUpdated[0] = contactRequestUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( replyContact(cr[1], MegaContactRequest::REPLY_ACTION_ACCEPT) ); - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[1]) ) // at the target side (auxiliar account) + mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; + ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT) ); + ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[0]) ) // at the source side (main account) + ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the source side (main account) << "Contact request creation not received after " << maxTimeout << " seconds"; - delete cr[1]; cr[1] = NULL; + mApi[1].cr.reset(); // --- Create a new outgoing share --- - nodeUpdated[0] = nodeUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( shareFolder(n1, email[1].data(), MegaShare::ACCESS_READ) ); - ASSERT_TRUE( waitForResponse(&nodeUpdated[0]) ) // at the target side (main account) + mApi[0].nodeUpdated = mApi[1].nodeUpdated = false; + ASSERT_NO_FATAL_FAILURE( shareFolder(n1, mApi[1].email.data(), MegaShare::ACCESS_READ) ); + ASSERT_TRUE( waitForResponse(&mApi[0].nodeUpdated) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; - ASSERT_TRUE( waitForResponse(&nodeUpdated[1]) ) // at the target side (auxiliar account) + ASSERT_TRUE( waitForResponse(&mApi[1].nodeUpdated) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; @@ -2166,7 +2042,7 @@ TEST_F(SdkTest, SdkTestShares) ASSERT_EQ(MegaShare::ACCESS_READ, s->getAccess()) << "Wrong access level of outgoing share"; ASSERT_EQ(hfolder1, s->getNodeHandle()) << "Wrong node handle of outgoing share"; - ASSERT_STREQ(email[1].data(), s->getUser()) << "Wrong email address of outgoing share"; + ASSERT_STREQ(mApi[1].email.data(), s->getUser()) << "Wrong email address of outgoing share"; ASSERT_TRUE(n1->isShared()) << "Wrong sharing information at outgoing share"; ASSERT_TRUE(n1->isOutShare()) << "Wrong sharing information at outgoing share"; @@ -2178,7 +2054,7 @@ TEST_F(SdkTest, SdkTestShares) sl = megaApi[1]->getInSharesList(); ASSERT_EQ(1, sl->size()) << "Incoming share not received in auxiliar account"; - nl = megaApi[1]->getInShares(megaApi[1]->getContact(email[0].data())); + nl = megaApi[1]->getInShares(megaApi[1]->getContact(mApi[0].email.data())); ASSERT_EQ(1, nl->size()) << "Incoming share not received in auxiliar account"; n = nl->get(0); @@ -2191,7 +2067,7 @@ TEST_F(SdkTest, SdkTestShares) delete nl; // check the corresponding user alert - ASSERT_TRUE(checkAlert(1, "New shared folder from " + email[0], email[0] + ":Shared-folder")); + ASSERT_TRUE(checkAlert(1, "New shared folder from " + mApi[0].email, mApi[0].email + ":Shared-folder")); // add a folder under the share char foldernameA[64] = "dummyname1"; @@ -2200,18 +2076,18 @@ TEST_F(SdkTest, SdkTestShares) ASSERT_NO_FATAL_FAILURE(createFolder(0, foldernameB, megaApi[0]->getNodeByHandle(hfolder2))); // check the corresponding user alert - ASSERT_TRUE(checkAlert(1, email[0] + " added 2 folders", megaApi[0]->getNodeByHandle(hfolder2)->getHandle(), 2)); + ASSERT_TRUE(checkAlert(1, mApi[0].email + " added 2 folders", megaApi[0]->getNodeByHandle(hfolder2)->getHandle(), 2)); // --- Modify the access level of an outgoing share --- - nodeUpdated[0] = nodeUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( shareFolder(megaApi[0]->getNodeByHandle(hfolder1), email[1].data(), MegaShare::ACCESS_READWRITE) ); - ASSERT_TRUE( waitForResponse(&nodeUpdated[0]) ) // at the target side (main account) + mApi[0].nodeUpdated = mApi[1].nodeUpdated = false; + ASSERT_NO_FATAL_FAILURE( shareFolder(megaApi[0]->getNodeByHandle(hfolder1), mApi[1].email.data(), MegaShare::ACCESS_READWRITE) ); + ASSERT_TRUE( waitForResponse(&mApi[0].nodeUpdated) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; - ASSERT_TRUE( waitForResponse(&nodeUpdated[1]) ) // at the target side (auxiliar account) + ASSERT_TRUE( waitForResponse(&mApi[1].nodeUpdated) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; - nl = megaApi[1]->getInShares(megaApi[1]->getContact(email[0].data())); + nl = megaApi[1]->getInShares(megaApi[1]->getContact(mApi[0].email.data())); ASSERT_EQ(1, nl->size()) << "Incoming share not received in auxiliar account"; n = nl->get(0); @@ -2222,18 +2098,18 @@ TEST_F(SdkTest, SdkTestShares) // --- Revoke access to an outgoing share --- - nodeUpdated[0] = nodeUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( shareFolder(n1, email[1].data(), MegaShare::ACCESS_UNKNOWN) ); - ASSERT_TRUE( waitForResponse(&nodeUpdated[0]) ) // at the target side (main account) + mApi[0].nodeUpdated = mApi[1].nodeUpdated = false; + ASSERT_NO_FATAL_FAILURE( shareFolder(n1, mApi[1].email.data(), MegaShare::ACCESS_UNKNOWN) ); + ASSERT_TRUE( waitForResponse(&mApi[0].nodeUpdated) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; - ASSERT_TRUE( waitForResponse(&nodeUpdated[1]) ) // at the target side (auxiliar account) + ASSERT_TRUE( waitForResponse(&mApi[1].nodeUpdated) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; sl = megaApi[0]->getOutShares(); ASSERT_EQ(0, sl->size()) << "Outgoing share revocation failed"; delete sl; - nl = megaApi[1]->getInShares(megaApi[1]->getContact(email[0].data())); + nl = megaApi[1]->getInShares(megaApi[1]->getContact(mApi[0].email.data())); ASSERT_EQ(0, nl->size()) << "Incoming share revocation failed"; delete nl; @@ -2242,8 +2118,8 @@ TEST_F(SdkTest, SdkTestShares) MegaUserAlertList* list = megaApi[1]->getUserAlerts(); ASSERT_TRUE(list->size() > 0); MegaUserAlert* a = list->get(list->size() - 1); - ASSERT_STREQ(a->getTitle(), ("Access to folders shared by " + email[0] + " was removed").c_str()); - ASSERT_STREQ(a->getPath(), (email[0] + ":Shared-folder").c_str()); + ASSERT_STREQ(a->getTitle(), ("Access to folders shared by " + mApi[0].email + " was removed").c_str()); + ASSERT_STREQ(a->getPath(), (mApi[0].email + ":Shared-folder").c_str()); ASSERT_NE(a->getNodeHandle(), UNDEF); delete list; } @@ -2257,12 +2133,12 @@ TEST_F(SdkTest, SdkTestShares) n = megaApi[0]->getNodeByHandle(hfolder2); - contactRequestUpdated[0] = false; - nodeUpdated[0] = false; + mApi[0].contactRequestUpdated = false; + mApi[0].nodeUpdated = false; ASSERT_NO_FATAL_FAILURE( shareFolder(n, emailfake, MegaShare::ACCESS_FULL) ); - ASSERT_TRUE( waitForResponse(&nodeUpdated[0]) ) // at the target side (main account) + ASSERT_TRUE( waitForResponse(&mApi[0].nodeUpdated) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[0]) ) // at the target side (main account) + ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the target side (main account) << "Contact request update not received after " << maxTimeout << " seconds"; sl = megaApi[0]->getPendingOutShares(n); delete n; @@ -2310,7 +2186,7 @@ TEST_F(SdkTest, SdkTestShares) ASSERT_NO_FATAL_FAILURE( importPublicLink(0, link, rootnode) ); - MegaNode *nimported = megaApi[0]->getNodeByHandle(h); + MegaNode *nimported = megaApi[0]->getNodeByHandle(mApi[0].h); ASSERT_STREQ(nfile1->getName(), nimported->getName()) << "Imported file with wrong name"; ASSERT_EQ(rootnode->getHandle(), nimported->getParentHandle()) << "Imported file in wrong path"; @@ -2328,7 +2204,7 @@ TEST_F(SdkTest, SdkTestShares) ASSERT_NO_FATAL_FAILURE( removePublicLink(0, nfile1) ); delete nfile1; - nfile1 = megaApi[0]->getNodeByHandle(h); + nfile1 = megaApi[0]->getNodeByHandle(mApi[0].h); ASSERT_FALSE(nfile1->isPublic()) << "Public link removal failed (still public)"; delete nimported; @@ -2363,6 +2239,87 @@ TEST_F(SdkTest, SdkTestShares) } + +TEST_F(SdkTest, SdkTestShareKeys) +{ + LOG_info << "___TEST ShareKeys___"; + + // Three user scenario, with nested shares and new nodes created that need keys to be shared to the other users. + // User A creates folder and shares it with user B + // User A creates folders / subfolder and shares it with user C + // When user C adds files to subfolder, does B receive the keys ? + + ASSERT_NO_FATAL_FAILURE(getMegaApiAux()); // login + fetchnodes + ASSERT_NO_FATAL_FAILURE(getMegaApiAux(2)); // login + fetchnodes + + unique_ptr rootnodeA(megaApi[0]->getRootNode()); + unique_ptr rootnodeB(megaApi[1]->getRootNode()); + unique_ptr rootnodeC(megaApi[2]->getRootNode()); + + ASSERT_TRUE(rootnodeA &&rootnodeB &&rootnodeC); + + ASSERT_NO_FATAL_FAILURE(createFolder(0, "share-folder-A", rootnodeA.get())); + unique_ptr shareFolderA(megaApi[0]->getNodeByHandle(mApi[0].h)); + ASSERT_TRUE(!!shareFolderA); + + ASSERT_NO_FATAL_FAILURE(createFolder(0, "sub-folder-A", shareFolderA.get())); + unique_ptr subFolderA(megaApi[0]->getNodeByHandle(mApi[0].h)); + ASSERT_TRUE(!!subFolderA); + + // Initialize a test scenario: create a new contact to share to + + ASSERT_EQ(MegaError::API_OK, synchronousInviteContact(0, mApi[1].email.c_str(), "SdkTestShareKeys contact request A to B", MegaContactRequest::INVITE_ACTION_ADD)); + ASSERT_EQ(MegaError::API_OK, synchronousInviteContact(0, mApi[2].email.c_str(), "SdkTestShareKeys contact request A to C", MegaContactRequest::INVITE_ACTION_ADD)); + + ASSERT_TRUE(WaitFor([this]() {return unique_ptr(megaApi[1]->getIncomingContactRequests())->size() == 1 + && unique_ptr(megaApi[2]->getIncomingContactRequests())->size() == 1;}, 3000)); + ASSERT_NO_FATAL_FAILURE(getContactRequest(1, false)); + ASSERT_NO_FATAL_FAILURE(getContactRequest(2, false)); + + + ASSERT_EQ(MegaError::API_OK, synchronousReplyContactRequest(1, mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); + ASSERT_EQ(MegaError::API_OK, synchronousReplyContactRequest(2, mApi[2].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); + + WaitMillisec(3000); + + ASSERT_EQ(MegaError::API_OK, synchronousShare(0, shareFolderA.get(), mApi[1].email.c_str(), MegaShare::ACCESS_READ)); + ASSERT_EQ(MegaError::API_OK, synchronousShare(0, subFolderA.get(), mApi[2].email.c_str(), MegaShare::ACCESS_FULL)); + + WaitFor([this]() { return unique_ptr(megaApi[1]->getInSharesList())->size() == 1 + && unique_ptr(megaApi[2]->getInSharesList())->size() == 1; }, 3000); + + unique_ptr nl1(megaApi[1]->getInShares(megaApi[1]->getContact(mApi[0].email.c_str()))); + unique_ptr nl2(megaApi[2]->getInShares(megaApi[2]->getContact(mApi[0].email.c_str()))); + + ASSERT_EQ(1, nl1->size()); + ASSERT_EQ(1, nl2->size()); + + MegaNode* receivedShareNodeB = nl1->get(0); + MegaNode* receivedShareNodeC = nl2->get(0); + + ASSERT_NO_FATAL_FAILURE(createFolder(2, "folderByC1", receivedShareNodeC)); + ASSERT_NO_FATAL_FAILURE(createFolder(2, "folderByC2", receivedShareNodeC)); + + WaitMillisec(10000); // make it shorter once we do actually get the keys (seems to need a bug fix) + + // can A see the added folders? + + unique_ptr aView(megaApi[0]->getChildren(subFolderA.get())); + ASSERT_EQ(2, aView->size()); + ASSERT_STREQ(aView->get(0)->getName(), "folderByC1"); + ASSERT_STREQ(aView->get(1)->getName(), "folderByC2"); + + // Can B see the added folders? + unique_ptr bView(megaApi[1]->getChildren(receivedShareNodeB)); + ASSERT_EQ(1, bView->size()); + ASSERT_STREQ(bView->get(0)->getName(), "sub-folder-A"); + unique_ptr bView2(megaApi[1]->getChildren(bView->get(0))); + ASSERT_EQ(2, bView2->size()); + ASSERT_STREQ(bView2->get(0)->getName(), "NO_KEY"); // TODO: This is technically not correct but a current side effect of avoiding going back to the servers frequently - to be fixed soon. For now choose the value that matches production + ASSERT_STREQ(bView2->get(1)->getName(), "NO_KEY"); +} + + /** * @brief TEST_F SdkTestConsoleAutocomplete * @@ -2463,7 +2420,7 @@ TEST_F(SdkTest, SdkTestConsoleAutocomplete) ::mega::handle megaCurDir = UNDEF; - MegaApiImpl* impl = *((MegaApiImpl**)(((char*)megaApi[0]) + sizeof(*megaApi[0])) - 1); //megaApi[0]->pImpl; + MegaApiImpl* impl = *((MegaApiImpl**)(((char*)megaApi[0].get()) + sizeof(*megaApi[0].get())) - 1); //megaApi[0]->pImpl; MegaClient* client = impl->getMegaClient(); @@ -2750,24 +2707,24 @@ TEST_F(SdkTest, SdkTestConsoleAutocomplete) MegaNode *rootnode = megaApi[0]->getRootNode(); ASSERT_NO_FATAL_FAILURE(createFolder(0, "test_autocomplete_megafs", rootnode)); - MegaNode *n0 = megaApi[0]->getNodeByHandle(h); + MegaNode *n0 = megaApi[0]->getNodeByHandle(mApi[0].h); - megaCurDir = h; + megaCurDir = mApi[0].h; ASSERT_NO_FATAL_FAILURE(createFolder(0, "dir1", n0)); - MegaNode *n1 = megaApi[0]->getNodeByHandle(h); + MegaNode *n1 = megaApi[0]->getNodeByHandle(mApi[0].h); ASSERT_NO_FATAL_FAILURE(createFolder(0, "sub11", n1)); ASSERT_NO_FATAL_FAILURE(createFolder(0, "sub12", n1)); ASSERT_NO_FATAL_FAILURE(createFolder(0, "dir2", n0)); - MegaNode *n2 = megaApi[0]->getNodeByHandle(h); + MegaNode *n2 = megaApi[0]->getNodeByHandle(mApi[0].h); ASSERT_NO_FATAL_FAILURE(createFolder(0, "sub21", n2)); ASSERT_NO_FATAL_FAILURE(createFolder(0, "sub22", n2)); ASSERT_NO_FATAL_FAILURE(createFolder(0, "dir2a", n0)); - MegaNode *n3 = megaApi[0]->getNodeByHandle(h); + MegaNode *n3 = megaApi[0]->getNodeByHandle(mApi[0].h); ASSERT_NO_FATAL_FAILURE(createFolder(0, "dir space", n3)); - MegaNode *n31 = megaApi[0]->getNodeByHandle(h); + MegaNode *n31 = megaApi[0]->getNodeByHandle(mApi[0].h); ASSERT_NO_FATAL_FAILURE(createFolder(0, "dir space2", n3)); ASSERT_NO_FATAL_FAILURE(createFolder(0, "nospace", n3)); ASSERT_NO_FATAL_FAILURE(createFolder(0, "next", n31)); @@ -3038,9 +2995,9 @@ TEST_F(SdkTest, SdkTestChat) string message = "Hi contact. This is a testing message"; - contactRequestUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( inviteContact(email[1], message, MegaContactRequest::INVITE_ACTION_ADD) ); - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[1]) ) // at the target side (auxiliar account) + mApi[1].contactRequestUpdated = false; + ASSERT_NO_FATAL_FAILURE( inviteContact(mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) ); + ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request update not received after " << maxTimeout << " seconds"; // if there were too many invitations within a short period of time, the invitation can be rejected by // the API with `API_EOVERQUOTA = -17` as counter spamming meassure (+500 invites in the last 50 days) @@ -3049,19 +3006,19 @@ TEST_F(SdkTest, SdkTestChat) ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) ); - contactRequestUpdated[0] = contactRequestUpdated[1] = false; - ASSERT_NO_FATAL_FAILURE( replyContact(cr[1], MegaContactRequest::REPLY_ACTION_ACCEPT) ); - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[1]) ) // at the target side (auxiliar account) + mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; + ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT) ); + ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request update not received after " << maxTimeout << " seconds"; - ASSERT_TRUE( waitForResponse(&contactRequestUpdated[0]) ) // at the target side (main account) + ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the target side (main account) << "Contact request update not received after " << maxTimeout << " seconds"; - delete cr[1]; cr[1] = NULL; + mApi[1].cr.reset(); // --- Check list of available chats --- (fetch is done at SetUp()) - size_t numChats = chats.size(); // permanent chats cannot be deleted, so they're kept forever + size_t numChats = mApi[0].chats.size(); // permanent chats cannot be deleted, so they're kept forever // --- Create a group chat --- @@ -3075,70 +3032,70 @@ TEST_F(SdkTest, SdkTestChat) peers->addPeer(h, PRIV_STANDARD); group = true; - chatUpdated[1] = false; - requestFlags[0][MegaRequest::TYPE_CHAT_CREATE] = false; + mApi[1].chatUpdated = false; + mApi[0].requestFlags[MegaRequest::TYPE_CHAT_CREATE] = false; ASSERT_NO_FATAL_FAILURE( createChat(group, peers) ); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_CHAT_CREATE]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_CREATE]) ) << "Cannot create a new chat"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Chat creation failed (error: " << lastError[0] << ")"; - ASSERT_TRUE( waitForResponse(&chatUpdated[1]) ) // at the target side (auxiliar account) + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Chat creation failed (error: " << mApi[0].lastError << ")"; + ASSERT_TRUE( waitForResponse(&mApi[1].chatUpdated )) // at the target side (auxiliar account) << "Chat update not received after " << maxTimeout << " seconds"; - MegaHandle chatid = this->chatid; // set at onRequestFinish() of chat creation request + MegaHandle chatid = mApi[0].chatid; // set at onRequestFinish() of chat creation request delete peers; // check the new chat information - ASSERT_EQ(chats.size(), ++numChats) << "Unexpected received number of chats"; - ASSERT_TRUE(chatUpdated[1]) << "The peer didn't receive notification of the chat creation"; + ASSERT_EQ(mApi[0].chats.size(), ++numChats) << "Unexpected received number of chats"; + ASSERT_TRUE(mApi[1].chatUpdated) << "The peer didn't receive notification of the chat creation"; // --- Remove a peer from the chat --- - chatUpdated[1] = false; - requestFlags[0][MegaRequest::TYPE_CHAT_REMOVE] = false; + mApi[1].chatUpdated = false; + mApi[0].requestFlags[MegaRequest::TYPE_CHAT_REMOVE] = false; megaApi[0]->removeFromChat(chatid, h); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_CHAT_REMOVE]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_REMOVE]) ) << "Chat remove failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Removal of chat peer failed (error: " << lastError[0] << ")"; - int numpeers = chats[chatid]->getPeerList() ? chats[chatid]->getPeerList()->size() : 0; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Removal of chat peer failed (error: " << mApi[0].lastError << ")"; + int numpeers = mApi[0].chats[chatid]->getPeerList() ? mApi[0].chats[chatid]->getPeerList()->size() : 0; ASSERT_EQ(numpeers, 0) << "Wrong number of peers in the list of peers"; - ASSERT_TRUE( waitForResponse(&chatUpdated[1]) ) // at the target side (auxiliar account) + ASSERT_TRUE( waitForResponse(&mApi[1].chatUpdated) ) // at the target side (auxiliar account) << "Didn't receive notification of the peer removal after " << maxTimeout << " seconds"; // --- Invite a contact to a chat --- - chatUpdated[1] = false; - requestFlags[0][MegaRequest::TYPE_CHAT_INVITE] = false; + mApi[1].chatUpdated = false; + mApi[0].requestFlags[MegaRequest::TYPE_CHAT_INVITE] = false; megaApi[0]->inviteToChat(chatid, h, PRIV_STANDARD); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_CHAT_INVITE]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_INVITE]) ) << "Chat invitation failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Invitation of chat peer failed (error: " << lastError[0] << ")"; - numpeers = chats[chatid]->getPeerList() ? chats[chatid]->getPeerList()->size() : 0; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Invitation of chat peer failed (error: " << mApi[0].lastError << ")"; + numpeers = mApi[0].chats[chatid]->getPeerList() ? mApi[0].chats[chatid]->getPeerList()->size() : 0; ASSERT_EQ(numpeers, 1) << "Wrong number of peers in the list of peers"; - ASSERT_TRUE( waitForResponse(&chatUpdated[1]) ) // at the target side (auxiliar account) + ASSERT_TRUE( waitForResponse(&mApi[1].chatUpdated) ) // at the target side (auxiliar account) << "The peer didn't receive notification of the invitation after " << maxTimeout << " seconds"; // --- Get the user-specific URL for the chat --- - requestFlags[0][MegaRequest::TYPE_CHAT_URL] = false; + mApi[0].requestFlags[MegaRequest::TYPE_CHAT_URL] = false; megaApi[0]->getUrlChat(chatid); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_CHAT_URL]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_URL]) ) << "Retrieval of chat URL failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Retrieval of chat URL failed (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Retrieval of chat URL failed (error: " << mApi[0].lastError << ")"; // --- Update Permissions of an existing peer in the chat - chatUpdated[1] = false; - requestFlags[0][MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS] = false; + mApi[1].chatUpdated = false; + mApi[0].requestFlags[MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS] = false; megaApi[0]->updateChatPermissions(chatid, h, PRIV_RO); - ASSERT_TRUE( waitForResponse(&requestFlags[0][MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS]) ) + ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS]) ) << "Update chat permissions failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Update of chat permissions failed (error: " << lastError[0] << ")"; - ASSERT_TRUE( waitForResponse(&chatUpdated[1]) ) // at the target side (auxiliar account) + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Update of chat permissions failed (error: " << mApi[0].lastError << ")"; + ASSERT_TRUE( waitForResponse(&mApi[1].chatUpdated) ) // at the target side (auxiliar account) << "The peer didn't receive notification of the invitation after " << maxTimeout << " seconds"; } @@ -3387,7 +3344,7 @@ TEST_F(SdkTest, SdkTestCloudraidTransfers) MegaNode *rootnode = megaApi[0]->getRootNode(); ASSERT_NO_FATAL_FAILURE(importPublicLink(0, "https://mega.nz/#!zAJnUTYD!8YE5dXrnIEJ47NdDfFEvqtOefhuDMphyae0KY5zrhns", rootnode)); - MegaHandle imported_file_handle = h; + MegaHandle imported_file_handle = mApi[0].h; MegaNode *nimported = megaApi[0]->getNodeByHandle(imported_file_handle); @@ -3396,11 +3353,11 @@ TEST_F(SdkTest, SdkTestCloudraidTransfers) deleteFile(filename.c_str()); // plain cloudraid download - transferFlags[0][MegaTransfer::TYPE_DOWNLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported, filename.c_str()); - ASSERT_TRUE(waitForResponse(&transferFlags[0][MegaTransfer::TYPE_DOWNLOAD], 600)) + ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600)) << "Download cloudraid transfer failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot download the cloudraid file (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")"; // cloudraid download with periodic pause and resume @@ -3417,7 +3374,7 @@ TEST_F(SdkTest, SdkTestCloudraidTransfers) { onTransferUpdate_progress = 0; onTransferUpdate_filesize = 0; - transferFlags[0][MegaTransfer::TYPE_DOWNLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported, filename.c_str()); m_off_t lastprogress = 0, pausecount = 0; @@ -3438,8 +3395,8 @@ TEST_F(SdkTest, SdkTestCloudraidTransfers) ASSERT_GE(onTransferUpdate_filesize, 0u); ASSERT_TRUE(onTransferUpdate_progress == onTransferUpdate_filesize); ASSERT_GE(pausecount, 3); - ASSERT_TRUE(waitForResponse(&transferFlags[0][MegaTransfer::TYPE_DOWNLOAD], 1))<< "Download cloudraid transfer with pauses failed"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot download the cloudraid file (error: " << lastError[0] << ")"; + ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 1))<< "Download cloudraid transfer with pauses failed"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")"; } @@ -3449,7 +3406,7 @@ TEST_F(SdkTest, SdkTestCloudraidTransfers) // cloudraid download with periodic full exit and resume from session ID // plain cloudraid download { - transferFlags[0][MegaTransfer::TYPE_DOWNLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported, filename.c_str()); std::string sessionId = megaApi[0]->dumpSession(); @@ -3463,12 +3420,12 @@ TEST_F(SdkTest, SdkTestCloudraidTransfers) { if (onTransferUpdate_progress > lastprogress + onTransferUpdate_filesize/6) { - delete megaApi[0]; - megaApi[0] = NULL; + megaApi[0].reset(); exitresumecount += 1; WaitMillisec(100); - megaApi[0] = new MegaApi(APP_KEY.c_str(), megaApiCacheFolder(0).c_str(), USER_AGENT.c_str()); + megaApi[0].reset(new MegaApi(APP_KEY.c_str(), megaApiCacheFolder(0).c_str(), USER_AGENT.c_str())); + mApi[0].megaApi = megaApi[0].get(); megaApi[0]->setLogLevel(MegaApi::LOG_LEVEL_DEBUG); megaApi[0]->addListener(this); @@ -3480,8 +3437,8 @@ TEST_F(SdkTest, SdkTestCloudraidTransfers) } ASSERT_TRUE(onTransferUpdate_progress == onTransferUpdate_filesize); ASSERT_GE(exitresumecount, 3u); - ASSERT_TRUE(waitForResponse(&transferFlags[0][MegaTransfer::TYPE_DOWNLOAD], 1)) << "Download cloudraid transfer with pauses failed"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot download the cloudraid file (error: " << lastError[0] << ")"; + ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 1)) << "Download cloudraid transfer with pauses failed"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")"; } ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; @@ -3506,7 +3463,7 @@ TEST_F(SdkTest, SdkTestCloudraidTransferWithConnectionFailures) MegaNode *rootnode = megaApi[0]->getRootNode(); ASSERT_NO_FATAL_FAILURE(importPublicLink(0, "https://mega.nz/#!zAJnUTYD!8YE5dXrnIEJ47NdDfFEvqtOefhuDMphyae0KY5zrhns", rootnode)); - MegaHandle imported_file_handle = h; + MegaHandle imported_file_handle = mApi[0].h; MegaNode *nimported = megaApi[0]->getNodeByHandle(imported_file_handle); @@ -3526,11 +3483,11 @@ TEST_F(SdkTest, SdkTestCloudraidTransferWithConnectionFailures) { onTransferUpdate_progress = 0; onTransferUpdate_filesize = 0; - transferFlags[0][MegaTransfer::TYPE_DOWNLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported, filename.c_str()); - ASSERT_TRUE(waitForResponse(&transferFlags[0][MegaTransfer::TYPE_DOWNLOAD], 180)) << "Cloudraid download with 404 and 403 errors time out (180 seconds)"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot download the cloudraid file (error: " << lastError[0] << ")"; + ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 180)) << "Cloudraid download with 404 and 403 errors time out (180 seconds)"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")"; ASSERT_GE(onTransferUpdate_filesize, 0u); ASSERT_TRUE(onTransferUpdate_progress == onTransferUpdate_filesize); ASSERT_LT(DebugTestHook::countdownTo404, 0); @@ -3560,7 +3517,7 @@ TEST_F(SdkTest, SdkTestCloudraidTransferWithSingleChannelTimeouts) MegaNode *rootnode = megaApi[0]->getRootNode(); ASSERT_NO_FATAL_FAILURE(importPublicLink(0, "https://mega.nz/#!zAJnUTYD!8YE5dXrnIEJ47NdDfFEvqtOefhuDMphyae0KY5zrhns", rootnode)); - MegaHandle imported_file_handle = h; + MegaHandle imported_file_handle = mApi[0].h; MegaNode *nimported = megaApi[0]->getNodeByHandle(imported_file_handle); @@ -3579,11 +3536,11 @@ TEST_F(SdkTest, SdkTestCloudraidTransferWithSingleChannelTimeouts) { onTransferUpdate_progress = 0; onTransferUpdate_filesize = 0; - transferFlags[0][MegaTransfer::TYPE_DOWNLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported, filename.c_str()); - ASSERT_TRUE(waitForResponse(&transferFlags[0][MegaTransfer::TYPE_DOWNLOAD], 180)) << "Cloudraid download with timeout errors timed out (180 seconds)"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot download the cloudraid file (error: " << lastError[0] << ")"; + ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 180)) << "Cloudraid download with timeout errors timed out (180 seconds)"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")"; ASSERT_GE(onTransferUpdate_filesize, 0u); ASSERT_TRUE(onTransferUpdate_progress == onTransferUpdate_filesize); ASSERT_LT(DebugTestHook::countdownToTimeout, 0); @@ -3612,11 +3569,11 @@ TEST_F(SdkTest, SdkTestOverquotaNonCloudraid) MegaNode *rootnode = megaApi[0]->getRootNode(); deleteFile(UPFILE); createFile(UPFILE, true); - transferFlags[0][MegaTransfer::TYPE_UPLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD] = false; megaApi[0]->startUpload(UPFILE.c_str(), rootnode); - ASSERT_TRUE(waitForResponse(&transferFlags[0][MegaTransfer::TYPE_UPLOAD], 600)) + ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD], 600)) << "Upload transfer failed after " << 600 << " seconds"; - MegaNode *n1 = megaApi[0]->getNodeByHandle(h); + MegaNode *n1 = megaApi[0]->getNodeByHandle(mApi[0].h); ASSERT_NE(n1, ((::mega::MegaNode *)NULL)); // set up to simulate 509 error @@ -3631,7 +3588,7 @@ TEST_F(SdkTest, SdkTestOverquotaNonCloudraid) // download - we should see a 30 second pause for 509 processing in the middle string filename2 = "./" + DOWNFILE; deleteFile(filename2); - transferFlags[0][MegaTransfer::TYPE_DOWNLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(n1, filename2.c_str()); // get to 30 sec pause point @@ -3654,9 +3611,9 @@ TEST_F(SdkTest, SdkTestOverquotaNonCloudraid) // Now wait for the file to finish - ASSERT_TRUE(waitForResponse(&transferFlags[0][MegaTransfer::TYPE_DOWNLOAD], 600)) + ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600)) << "Download transfer failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot download the file (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the file (error: " << mApi[0].lastError << ")"; ASSERT_LT(DebugTestHook::countdownToOverquota, 0); ASSERT_LT(DebugTestHook::countdownToOverquota, originalcount); // there should have been more http activity after the wait @@ -3681,7 +3638,7 @@ TEST_F(SdkTest, SdkTestOverquotaCloudraid) ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; ASSERT_NO_FATAL_FAILURE(importPublicLink(0, "https://mega.nz/#!zAJnUTYD!8YE5dXrnIEJ47NdDfFEvqtOefhuDMphyae0KY5zrhns", megaApi[0]->getRootNode())); - MegaHandle imported_file_handle = h; + MegaHandle imported_file_handle = mApi[0].h; MegaNode *nimported = megaApi[0]->getNodeByHandle(imported_file_handle); // set up to simulate 509 error @@ -3696,7 +3653,7 @@ TEST_F(SdkTest, SdkTestOverquotaCloudraid) // download - we should see a 30 second pause for 509 processing in the middle string filename2 = "./" + DOWNFILE; deleteFile(filename2); - transferFlags[0][MegaTransfer::TYPE_DOWNLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported, filename2.c_str()); // get to 30 sec pause point @@ -3719,9 +3676,9 @@ TEST_F(SdkTest, SdkTestOverquotaCloudraid) // Now wait for the file to finish - ASSERT_TRUE(waitForResponse(&transferFlags[0][MegaTransfer::TYPE_DOWNLOAD], 600)) + ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600)) << "Download transfer failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot download the file (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the file (error: " << mApi[0].lastError << ")"; ASSERT_LT(DebugTestHook::countdownToOverquota, 0); ASSERT_LT(DebugTestHook::countdownToOverquota, originalcount); // there should have been more http activity after the wait @@ -3847,7 +3804,7 @@ TEST_F(SdkTest, SdkCloudraidStreamingSoakTest) // ensure we have our standard raid test file ASSERT_NO_FATAL_FAILURE(importPublicLink(0, "https://mega.nz/#!zAJnUTYD!8YE5dXrnIEJ47NdDfFEvqtOefhuDMphyae0KY5zrhns", megaApi[0]->getRootNode())); - MegaHandle imported_file_handle = h; + MegaHandle imported_file_handle = mApi[0].h; MegaNode *nimported = megaApi[0]->getNodeByHandle(imported_file_handle); MegaNode *rootnode = megaApi[0]->getRootNode(); @@ -3856,10 +3813,10 @@ TEST_F(SdkTest, SdkCloudraidStreamingSoakTest) string filename2 = "./" + DOWNFILE; deleteFile(filename2); - transferFlags[0][MegaTransfer::TYPE_DOWNLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported, filename2.c_str()); - ASSERT_TRUE(waitForResponse(&transferFlags[0][MegaTransfer::TYPE_DOWNLOAD])) << "Setup transfer failed after " << maxTimeout << " seconds"; - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot download the initial file (error: " << lastError[0] << ")"; + ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD])) << "Setup transfer failed after " << maxTimeout << " seconds"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the initial file (error: " << mApi[0].lastError << ")"; char raidchar = 0; char nonraidchar = 'M'; @@ -3878,15 +3835,15 @@ TEST_F(SdkTest, SdkCloudraidStreamingSoakTest) } // actual upload - transferFlags[0][MegaTransfer::TYPE_UPLOAD] = false; + mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD] = false; megaApi[0]->startUpload(filename3.data(), rootnode); - waitForResponse(&transferFlags[0][MegaTransfer::TYPE_UPLOAD]); + waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD]); - ASSERT_EQ(MegaError::API_OK, lastError[0]) << "Cannot upload a test file (error: " << lastError[0] << ")"; + ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot upload a test file (error: " << mApi[0].lastError << ")"; - MegaNode *nonRaidNode = megaApi[0]->getNodeByHandle(h); + MegaNode *nonRaidNode = megaApi[0]->getNodeByHandle(mApi[0].h); - size_t filesize = getFilesize(filename2); + int64_t filesize = getFilesize(filename2); std::ifstream compareDecryptedFile(filename2.c_str(), ios::binary); ::mega::byte* compareDecryptedData = new ::mega::byte[filesize]; compareDecryptedFile.read((char*)compareDecryptedData, filesize); @@ -3947,7 +3904,7 @@ TEST_F(SdkTest, SdkCloudraidStreamingSoakTest) LOG_info << "beginning stream test, " << start << " to " << end << "(len " << end - start << ") " << (nonraid ? " non-raid " : " RAID ") << (!nonraid ? (smallpieces ? " smallpieces " : "normalpieces") : ""); - CheckStreamedFile_MegaTransferListener* p = StreamRaidFilePart(megaApi[0], start, end, !nonraid, smallpieces, nimported, nonRaidNode, compareDecryptedData); + CheckStreamedFile_MegaTransferListener* p = StreamRaidFilePart(megaApi[0].get(), start, end, !nonraid, smallpieces, nimported, nonRaidNode, compareDecryptedData); for (unsigned i = 0; p->comparedEqual; ++i) { @@ -4002,14 +3959,14 @@ TEST_F(SdkTest, SdkRecentsTest) string filename1 = UPFILE; createFile(filename1, false); - auto err = synchronousUpload(0, filename1.c_str(), rootnode); + auto err = synchronousStartUpload(0, filename1.c_str(), rootnode); ASSERT_EQ(MegaError::API_OK, err) << "Cannot upload a test file (error: " << err << ")"; ofstream f(filename1); f << "update"; f.close(); - err = synchronousUpload(0, filename1.c_str(), rootnode); + err = synchronousStartUpload(0, filename1.c_str(), rootnode); ASSERT_EQ(MegaError::API_OK, err) << "Cannot upload an updated test file (error: " << err << ")"; synchronousCatchup(0); @@ -4017,14 +3974,14 @@ TEST_F(SdkTest, SdkRecentsTest) string filename2 = DOWNFILE; createFile(filename2, false); - err = synchronousUpload(0, filename2.c_str(), rootnode); + err = synchronousStartUpload(0, filename2.c_str(), rootnode); ASSERT_EQ(MegaError::API_OK, err) << "Cannot upload a test file2 (error: " << err << ")"; ofstream f2(filename2); f2 << "update"; f2.close(); - err = synchronousUpload(0, filename2.c_str(), rootnode); + err = synchronousStartUpload(0, filename2.c_str(), rootnode); ASSERT_EQ(MegaError::API_OK, err) << "Cannot upload an updated test file2 (error: " << err << ")"; synchronousCatchup(0); diff --git a/tests/integration/SdkTest_test.h b/tests/integration/SdkTest_test.h index 283f1d6656..00e1c085f5 100644 --- a/tests/integration/SdkTest_test.h +++ b/tests/integration/SdkTest_test.h @@ -112,18 +112,39 @@ struct RequestTracker : public ::mega::MegaRequestListener class SdkTest : public ::testing::Test, public MegaListener, MegaRequestListener, MegaTransferListener, MegaLogger { public: - MegaApi* megaApi[2]; - string email[2]; - string pwd[2]; - int lastError[2]; + struct PerApi + { + MegaApi* megaApi = nullptr; + string email; + string pwd; + int lastError; + + // flags to monitor the completion of requests/transfers + bool requestFlags[MegaRequest::TOTAL_OF_REQUEST_TYPES]; + bool transferFlags[MegaTransfer::TYPE_LOCAL_HTTP_DOWNLOAD]; + + std::unique_ptr cr; + + // flags to monitor the updates of nodes/users/PCRs due to actionpackets + bool nodeUpdated; + bool userUpdated; + bool contactRequestUpdated; + bool accountUpdated; + + MegaHandle h; - // flags to monitor the completion of requests/transfers - bool requestFlags[2][MegaRequest::TOTAL_OF_REQUEST_TYPES]; - bool transferFlags[2][MegaTransfer::TYPE_LOCAL_HTTP_DOWNLOAD]; +#ifdef ENABLE_CHAT + bool chatUpdated; // flags to monitor the updates of chats due to actionpackets + map chats; // runtime cache of fetched/updated chats + MegaHandle chatid; // last chat added +#endif + }; + + std::vector mApi; + std::vector> megaApi; // relevant values received in response of requests - MegaHandle h; string link; MegaNode *publicNode; string attributeValue; @@ -131,20 +152,6 @@ class SdkTest : public ::testing::Test, public MegaListener, MegaRequestListener std::unique_ptr stringListMap; std::unique_ptr stringTable; - MegaContactRequest* cr[2]; - - // flags to monitor the updates of nodes/users/PCRs due to actionpackets - bool nodeUpdated[2]; - bool userUpdated[2]; - bool contactRequestUpdated[2]; - bool accountUpdated[2]; - -#ifdef ENABLE_CHAT - bool chatUpdated[2]; // flags to monitor the updates of chats due to actionpackets - map chats; // runtime cache of fetched/updated chats - MegaHandle chatid; // last chat added -#endif - MegaLoggerSDK *logger; m_off_t onTransferUpdate_progress; @@ -155,6 +162,8 @@ class SdkTest : public ::testing::Test, public MegaListener, MegaRequestListener virtual void SetUp(); virtual void TearDown(); + int getApiIndex(MegaApi* api); + bool checkAlert(int apiIndex, const string& title, const string& path); bool checkAlert(int apiIndex, const string& title, handle h, int n); @@ -193,25 +202,42 @@ class SdkTest : public ::testing::Test, public MegaListener, MegaRequestListener void purgeTree(MegaNode *p); bool waitForResponse(bool *responseReceived, unsigned int timeout = maxTimeout); - bool synchronousCall(bool &responseFlag, std::function f, unsigned int timeout = maxTimeout); + bool synchronousRequest(int apiIndex, int type, std::function f, unsigned int timeout = maxTimeout); + bool synchronousTransfer(int apiIndex, int type, std::function f, unsigned int timeout = maxTimeout); // convenience functions - template args just make it easy to code, no need to copy all the exact argument types with listener defaults etc. To add a new one, just copy a line and change the flag and the function called. - template int synchronousUpload(int apiIndex, uploadArgs... args) { synchronousCall(transferFlags[apiIndex][MegaTransfer::TYPE_UPLOAD], [this, apiIndex, args...]() { megaApi[apiIndex]->startUpload(args...); }); return lastError[apiIndex]; } - template int synchronousCatchup(int apiIndex, uploadArgs... args) { synchronousCall(requestFlags[apiIndex][MegaRequest::TYPE_CATCHUP], [this, apiIndex, args...]() { megaApi[apiIndex]->catchup(args...); }); return lastError[apiIndex]; } + template int synchronousStartUpload(int apiIndex, Args... args) { synchronousTransfer(apiIndex, MegaTransfer::TYPE_UPLOAD, [this, apiIndex, args...]() { megaApi[apiIndex]->startUpload(args...); }); return mApi[apiIndex].lastError; } + template int synchronousCatchup(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_CATCHUP, [this, apiIndex, args...]() { megaApi[apiIndex]->catchup(args...); }); return mApi[apiIndex].lastError; } + template int synchronousCreateAccount(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_CREATE_ACCOUNT, [this, apiIndex, args...]() { megaApi[apiIndex]->createAccount(args...); }); return mApi[apiIndex].lastError; } + template int synchronousResumeCreateAccount(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_CREATE_ACCOUNT, [this, apiIndex, args...]() { megaApi[apiIndex]->resumeCreateAccount(args...); }); return mApi[apiIndex].lastError; } + template int synchronousSendSignupLink(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_SEND_SIGNUP_LINK, [this, apiIndex, args...]() { megaApi[apiIndex]->sendSignupLink(args...); }); return mApi[apiIndex].lastError; } + template int synchronousFastLogin(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_LOGIN, [this, apiIndex, args...]() { megaApi[apiIndex]->fastLogin(args...); }); return mApi[apiIndex].lastError; } + template int synchronousRemove(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_REMOVE, [this, apiIndex, args...]() { megaApi[apiIndex]->remove(args...); }); return mApi[apiIndex].lastError; } + template int synchronousInviteContact(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_INVITE_CONTACT, [this, apiIndex, args...]() { megaApi[apiIndex]->inviteContact(args...); }); return mApi[apiIndex].lastError; } + template int synchronousReplyContactRequest(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_REPLY_CONTACT_REQUEST, [this, apiIndex, args...]() { megaApi[apiIndex]->replyContactRequest(args...); }); return mApi[apiIndex].lastError; } + template int synchronousRemoveContact(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_REMOVE_CONTACT, [this, apiIndex, args...]() { megaApi[apiIndex]->removeContact(args...); }); return mApi[apiIndex].lastError; } + template int synchronousShare(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_SHARE, [this, apiIndex, args...]() { megaApi[apiIndex]->share(args...); }); return mApi[apiIndex].lastError; } + template int synchronousExportNode(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_EXPORT, [this, apiIndex, args...]() { megaApi[apiIndex]->exportNode(args...); }); return mApi[apiIndex].lastError; } + template int synchronousGetRegisteredContacts(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_REGISTERED_CONTACTS, [this, apiIndex, args...]() { megaApi[apiIndex]->getRegisteredContacts(args...); }); return mApi[apiIndex].lastError; } + template int synchronousGetCountryCallingCodes(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_COUNTRY_CALLING_CODES, [this, apiIndex, args...]() { megaApi[apiIndex]->getCountryCallingCodes(args...); }); return mApi[apiIndex].lastError; } + template int synchronousGetUserAvatar(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_ATTR_USER, [this, apiIndex, args...]() { megaApi[apiIndex]->getUserAvatar(args...); }); return mApi[apiIndex].lastError; } + template int synchronousGetUserAttribute(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_ATTR_USER, [this, apiIndex, args...]() { megaApi[apiIndex]->getUserAttribute(args...); }); return mApi[apiIndex].lastError; } + template int synchronousSetNodeDuration(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_SET_ATTR_NODE, [this, apiIndex, args...]() { megaApi[apiIndex]->setNodeDuration(args...); }); return mApi[apiIndex].lastError; } + template int synchronousSetNodeCoordinates(int apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_SET_ATTR_NODE, [this, apiIndex, args...]() { megaApi[apiIndex]->setNodeCoordinates(args...); }); return mApi[apiIndex].lastError; } // convenience functions - make a request and wait for the result via listener, return the result code. To add new functions to call, just copy the line template int doRequestLogout(int apiIndex, requestArgs... args) { RequestTracker rt; megaApi[apiIndex]->logout(args..., &rt); return rt.waitForResult(); } void createFile(string filename, bool largeFile = true); - size_t getFilesize(string filename); + int64_t getFilesize(string filename); void deleteFile(string filename); - void getMegaApiAux(); + void getMegaApiAux(unsigned index = 1); void releaseMegaApi(unsigned int apiIndex); - void inviteContact(string email, string message, int action, int timeout = maxTimeout); - void replyContact(MegaContactRequest *cr, int action, int timeout = maxTimeout); + void inviteContact(string email, string message, int action); + void replyContact(MegaContactRequest *cr, int action); void removeContact(string email, int timeout = maxTimeout); void setUserAttribute(int type, string value, int timeout = maxTimeout); void getUserAttribute(MegaUser *u, int type, int timeout = maxTimeout, int accountIndex = 1); @@ -225,9 +251,9 @@ class SdkTest : public ::testing::Test, public MegaListener, MegaRequestListener void getContactRequest(unsigned int apiIndex, bool outgoing, int expectedSize = 1); - void createFolder(unsigned int apiIndex, char * name, MegaNode *n, int timeout = maxTimeout); + void createFolder(unsigned int apiIndex, const char * name, MegaNode *n, int timeout = maxTimeout); - void getRegisteredContacts(const std::map& contacts, int timeout = maxTimeout); + void getRegisteredContacts(const std::map& contacts); void getCountryCallingCodes(int timeout = maxTimeout); diff --git a/tests/integration/Sync_test.cpp b/tests/integration/Sync_test.cpp index 18da623c1f..094e3ff663 100644 --- a/tests/integration/Sync_test.cpp +++ b/tests/integration/Sync_test.cpp @@ -21,6 +21,7 @@ // Many of these tests are still being worked on. // The file uses some C++17 mainly for the very convenient std::filesystem library, though the main SDK must still build with C++11 (and prior) +#ifdef ENABLE_SYNC #include "test.h" #include @@ -377,7 +378,7 @@ struct StandardClient : public MegaApp // logout stalls in windows due to the issue above mc.purgenodesusersabortsc(); #else - mc.locallogout(); + mc.locallogout(false); #endif }); } @@ -1139,7 +1140,7 @@ struct StandardClient : public MegaApp descendants = 0; if (Sync* sync = syncByTag(syncid)) { - if (!recursiveConfirm(mnode, &sync->localroot, descendants, "Sync " + to_string(syncid), 0)) + if (!recursiveConfirm(mnode, sync->localroot.get(), descendants, "Sync " + to_string(syncid), 0)) { cout << "syncid " << syncid << " comparison against LocalNodes failed" << endl; return false; @@ -2410,7 +2411,7 @@ Node* makenode(MegaClient& mc, handle parent, ::mega::nodetype_t type, m_off_t s std::vector dp; auto newnode = new Node(&mc, &dp, ++handlegenerator, parent, type, size, owner, nullptr, 1); - newnode->nodekey.assign((char*)key, type == FILENODE ? FILENODEKEYLENGTH : FOLDERNODEKEYLENGTH); + newnode->setkey(key); newnode->attrstring = new string; SymmCipher sc; @@ -2681,3 +2682,5 @@ GTEST_TEST(Sync, BasicSync_CreateAndReplaceLinkUponSyncDown) } #endif + +#endif diff --git a/tests/integration/main.cpp b/tests/integration/main.cpp index 8918e4d4bc..ea72c3eded 100644 --- a/tests/integration/main.cpp +++ b/tests/integration/main.cpp @@ -50,20 +50,21 @@ class MegaLogger : public ::mega::Logger { mLogFile.open("test_integration.log"); } - mLogFile << os.str(); + mLogFile << os.str() << std::flush; } else { -#ifdef _WIN32 - OutputDebugStringA(os.str().c_str()); -#else - std::cout << os.str(); +#ifndef _WIN32 + std::cout << os.str() << std::flush; #endif if (!gTestingInvalidArgs) { ASSERT_NE(loglevel, logError) << os.str(); } } +#ifdef _WIN32 + OutputDebugStringA(os.str().c_str()); +#endif } } @@ -96,7 +97,7 @@ int main (int argc, char *argv[]) MegaLogger megaLogger; - SimpleLogger::setLogLevel(logDebug); + SimpleLogger::setLogLevel(logMax); SimpleLogger::setOutputClass(&megaLogger); #if defined(_WIN32) && defined(NO_READLINE) diff --git a/tests/unit/Commands_test.cpp b/tests/unit/Commands_test.cpp index af33c2da55..889c79f97c 100644 --- a/tests/unit/Commands_test.cpp +++ b/tests/unit/Commands_test.cpp @@ -1,7 +1,4 @@ /** - * @file tests/commands_test.cpp - * @brief Mega SDK unit tests for commands - * * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. @@ -19,8 +16,6 @@ * program. */ -// Note: The tests in this module are meant to be pure unit tests: Fast tests without I/O. - #include #include @@ -104,7 +99,7 @@ TEST(Commands, CommandGetRegisteredContacts_processResult_onlyOneContact) ASSERT_EQ(API_OK, app.mLastError); ASSERT_NE(nullptr, app.mRegisteredContacts); ASSERT_EQ(expected, *app.mRegisteredContacts); - ASSERT_EQ(jsonLength, std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way + ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } TEST(Commands, CommandGetRegisteredContacts_processResult_extraFieldShouldBeIgnored) @@ -126,7 +121,7 @@ TEST(Commands, CommandGetRegisteredContacts_processResult_extraFieldShouldBeIgno ASSERT_EQ(API_OK, app.mLastError); ASSERT_NE(nullptr, app.mRegisteredContacts); ASSERT_EQ(expected, *app.mRegisteredContacts); - ASSERT_EQ(jsonLength, std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way + ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } TEST(Commands, CommandGetRegisteredContacts_processResult_invalidResponse) @@ -143,7 +138,7 @@ TEST(Commands, CommandGetRegisteredContacts_processResult_invalidResponse) ASSERT_EQ(1, app.mCallCount); ASSERT_EQ(API_EINTERNAL, app.mLastError); ASSERT_EQ(nullptr, app.mRegisteredContacts); - ASSERT_EQ(jsonLength, std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way + ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } TEST(Commands, CommandGetRegisteredContacts_processResult_errorCodeReceived) @@ -160,7 +155,7 @@ TEST(Commands, CommandGetRegisteredContacts_processResult_errorCodeReceived) ASSERT_EQ(1, app.mCallCount); ASSERT_EQ(API_EEXPIRED, app.mLastError); ASSERT_EQ(nullptr, app.mRegisteredContacts); - ASSERT_EQ(jsonLength, std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way + ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } namespace { @@ -212,7 +207,7 @@ TEST(Commands, CommandGetCountryCallingCodes_processResult_happyPath) ASSERT_EQ(API_OK, app.mLastError); ASSERT_NE(nullptr, app.mCountryCallingCodes); ASSERT_EQ(expected, *app.mCountryCallingCodes); - ASSERT_EQ(jsonLength, std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way + ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } TEST(Commands, CommandGetCountryCallingCodes_processResult_onlyOneCountry) @@ -234,7 +229,7 @@ TEST(Commands, CommandGetCountryCallingCodes_processResult_onlyOneCountry) ASSERT_EQ(API_OK, app.mLastError); ASSERT_NE(nullptr, app.mCountryCallingCodes); ASSERT_EQ(expected, *app.mCountryCallingCodes); - ASSERT_EQ(jsonLength, std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way + ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } TEST(Commands, CommandGetCountryCallingCodes_processResult_extraFieldShouldBeIgnored) @@ -256,7 +251,7 @@ TEST(Commands, CommandGetCountryCallingCodes_processResult_extraFieldShouldBeIgn ASSERT_EQ(API_OK, app.mLastError); ASSERT_NE(nullptr, app.mCountryCallingCodes); ASSERT_EQ(expected, *app.mCountryCallingCodes); - ASSERT_EQ(jsonLength, std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way + ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } TEST(Commands, CommandGetCountryCallingCodes_processResult_invalidResponse) @@ -273,7 +268,7 @@ TEST(Commands, CommandGetCountryCallingCodes_processResult_invalidResponse) ASSERT_EQ(1, app.mCallCount); ASSERT_EQ(API_EINTERNAL, app.mLastError); ASSERT_EQ(nullptr, app.mCountryCallingCodes); - ASSERT_EQ(jsonLength, std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way + ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } TEST(Commands, CommandGetCountryCallingCodes_processResult_errorCodeReceived) @@ -290,5 +285,5 @@ TEST(Commands, CommandGetCountryCallingCodes_processResult_errorCodeReceived) ASSERT_EQ(1, app.mCallCount); ASSERT_EQ(API_EEXPIRED, app.mLastError); ASSERT_EQ(nullptr, app.mCountryCallingCodes); - ASSERT_EQ(jsonLength, std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way + ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } diff --git a/tests/unit/Crypto_test.cpp b/tests/unit/Crypto_test.cpp index 34da78987a..417b6f1f5c 100644 --- a/tests/unit/Crypto_test.cpp +++ b/tests/unit/Crypto_test.cpp @@ -1,8 +1,5 @@ /** - * @file tests/crypto_test.cpp - * @brief Mega SDK test for cryptographic functions - * - * (c) 2016 by Mega Limited, Wellsford, New Zealand + * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * diff --git a/tests/unit/DefaultedDirAccess.h b/tests/unit/DefaultedDirAccess.h new file mode 100644 index 0000000000..beb4541ece --- /dev/null +++ b/tests/unit/DefaultedDirAccess.h @@ -0,0 +1,40 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +#pragma once + +#include + +#include "NotImplemented.h" + +namespace mt { + +class DefaultedDirAccess : public mega::DirAccess +{ +public: + bool dopen(std::string*, mega::FileAccess*, bool) override + { + throw NotImplemented{__func__}; + } + bool dnext(std::string* localpath, std::string* localname, bool followsymlinks = true, mega::nodetype_t* = NULL) override + { + throw NotImplemented{__func__}; + } +}; + +} // mt diff --git a/tests/unit/DefaultedFileAccess.h b/tests/unit/DefaultedFileAccess.h new file mode 100644 index 0000000000..1896e6d0b0 --- /dev/null +++ b/tests/unit/DefaultedFileAccess.h @@ -0,0 +1,64 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +#pragma once + +#include + +#include "NotImplemented.h" + +namespace mt { + +class DefaultedFileAccess : public mega::FileAccess +{ +public: + DefaultedFileAccess() + : mega::FileAccess{nullptr} + {} + + bool fopen(std::string*, bool, bool) override + { + throw NotImplemented{__func__}; + } + void updatelocalname(std::string*) override + { + throw NotImplemented{__func__}; + } + bool fwrite(const mega::byte *, unsigned, m_off_t) override + { + throw NotImplemented{__func__}; + } + bool sysread(mega::byte *, unsigned, m_off_t) override + { + throw NotImplemented{__func__}; + } + bool sysstat(mega::m_time_t*, m_off_t*) override + { + throw NotImplemented{__func__}; + } + bool sysopen(bool async = false) override + { + throw NotImplemented{__func__}; + } + void sysclose() override + { + throw NotImplemented{__func__}; + } +}; + +} // mt diff --git a/tests/unit/DefaultedFileSystemAccess.h b/tests/unit/DefaultedFileSystemAccess.h new file mode 100644 index 0000000000..fc540aee77 --- /dev/null +++ b/tests/unit/DefaultedFileSystemAccess.h @@ -0,0 +1,100 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +#pragma once + +#include + +#include "NotImplemented.h" + +namespace mt { + +class DefaultedFileSystemAccess : public mega::FileSystemAccess +{ +public: + std::unique_ptr newfileaccess(bool followSymLinks = true) override + { + throw NotImplemented{__func__}; + } + mega::DirAccess* newdiraccess() override + { + throw NotImplemented{__func__}; + } + void path2local(std::string*, std::string*) const override + { + throw NotImplemented{__func__}; + } + void local2path(std::string* local, std::string* path) const override + { + throw NotImplemented{__func__}; + } + void tmpnamelocal(std::string*) const override + { + throw NotImplemented{__func__}; + } + bool getsname(std::string*, std::string*) const override + { + throw NotImplemented{__func__}; + } + bool renamelocal(std::string*, std::string*, bool = true) override + { + throw NotImplemented{__func__}; + } + bool copylocal(std::string*, std::string*, mega::m_time_t) override + { + throw NotImplemented{__func__}; + } + bool unlinklocal(std::string*) override + { + throw NotImplemented{__func__}; + } + bool rmdirlocal(std::string*) override + { + throw NotImplemented{__func__}; + } + bool mkdirlocal(std::string*, bool = false) override + { + throw NotImplemented{__func__}; + } + bool setmtimelocal(std::string *, mega::m_time_t) override + { + throw NotImplemented{__func__}; + } + bool chdirlocal(std::string*) const override + { + throw NotImplemented{__func__}; + } + size_t lastpartlocal(std::string*) const override + { + throw NotImplemented{__func__}; + } + bool getextension(std::string*, char*, size_t) const override + { + throw NotImplemented{__func__}; + } + bool expanselocalpath(std::string *path, std::string *absolutepath) override + { + throw NotImplemented{__func__}; + } + void addevents(mega::Waiter*, int) override + { + throw NotImplemented{__func__}; + } +}; + +} // mt diff --git a/tests/unit/FileFingerprint_test.cpp b/tests/unit/FileFingerprint_test.cpp new file mode 100644 index 0000000000..1359e3c4db --- /dev/null +++ b/tests/unit/FileFingerprint_test.cpp @@ -0,0 +1,578 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +#include +#include +#include +#include + +#include + +#include + +#include "DefaultedFileAccess.h" + +namespace { + +class MockFileAccess : public mt::DefaultedFileAccess +{ +public: + MockFileAccess(const mega::m_time_t mtime, std::vector content, const bool readFails = false) + : mContent{std::move(content)} + , mReadFails{readFails} + { + this->size = mContent.size(); + this->mtime = mtime; + } + + MEGA_DISABLE_COPY_MOVE(MockFileAccess) + + bool sysstat(mega::m_time_t* curr_mtime, m_off_t* curr_size) override + { + *curr_mtime = mtime; + *curr_size = size; + return true; + } + + bool sysopen(bool async = false) override + { + return true; + } + + bool sysread(mega::byte* buffer, const unsigned size, const m_off_t offset) override + { + if (mReadFails) + { + return false; + } + assert(static_cast(offset) + size <= mContent.size()); + std::copy(mContent.begin() + static_cast(offset), mContent.begin() + static_cast(offset) + size, buffer); + return true; + } + + void sysclose() override + {} + + bool getReadFails() const + { + return mReadFails; + } + +private: + const std::vector mContent; + const bool mReadFails = false; +}; + +class MockInputStreamAccess : public mega::InputStreamAccess +{ +public: + MockInputStreamAccess(const mega::m_time_t mtime, std::vector content, const bool readFails = false) + : mFa{mtime, std::move(content), readFails} + {} + + mega::m_time_t getMTime() const + { + return mFa.mtime; + } + + void setSize(const m_off_t size) + { + mFa.size = size; + } + + m_off_t size() override + { + return mFa.size; + } + + bool read(mega::byte* buffer, const unsigned size) override + { + if (mFa.getReadFails()) + { + return false; + } + if (!buffer) + { + return true; + } + return mFa.frawread(buffer, size, 0); + } + +private: + MockFileAccess mFa; +}; + +} // anonymous + +TEST(FileFingerprint, FileFingerprintCmp_compareNotSmaller) +{ + mega::FileFingerprint ffp; + ffp.size = 1; + ffp.mtime = 2; + std::iota(ffp.crc.begin(), ffp.crc.end(), 3); + ffp.isvalid = true; + + mega::FileFingerprint copiedFfp; + copiedFfp = ffp; + + ASSERT_FALSE(mega::FileFingerprintCmp{}(&ffp, &copiedFfp)); +} + +TEST(FileFingerprint, FileFingerprintCmp_compareSmallerBecauseOfSize) +{ + mega::FileFingerprint ffp; + ffp.size = 1; + + mega::FileFingerprint ffp2; + ffp2.size = 2; + + ASSERT_TRUE(mega::FileFingerprintCmp{}(&ffp, &ffp2)); +} + +TEST(FileFingerprint, FileFingerprintCmp_compareNotSmallerBecauseOfSize) +{ + mega::FileFingerprint ffp; + ffp.size = 2; + + mega::FileFingerprint ffp2; + ffp2.size = 1; + + ASSERT_FALSE(mega::FileFingerprintCmp{}(&ffp, &ffp2)); +} + +TEST(FileFingerprint, FileFingerprintCmp_compareSmallerBecauseOfMTime) +{ + mega::FileFingerprint ffp; + ffp.mtime = 1; + + mega::FileFingerprint ffp2; + ffp2.mtime = 2; + + ASSERT_TRUE(mega::FileFingerprintCmp{}(&ffp, &ffp2)); +} + +TEST(FileFingerprint, FileFingerprintCmp_compareNotSmallerBecauseOfMTime) +{ + mega::FileFingerprint ffp; + ffp.mtime = 2; + + mega::FileFingerprint ffp2; + ffp2.mtime = 1; + + ASSERT_FALSE(mega::FileFingerprintCmp{}(&ffp, &ffp2)); +} + +TEST(FileFingerprint, FileFingerprintCmp_compareSmallerBecauseOfCrc) +{ + mega::FileFingerprint ffp; + ffp.crc[0] = 1; + + mega::FileFingerprint ffp2; + ffp2.crc[0] = 2; + + ASSERT_TRUE(mega::FileFingerprintCmp{}(&ffp, &ffp2)); +} + +TEST(FileFingerprint, defaultConstructor) +{ + const mega::FileFingerprint ffp; + ASSERT_EQ(-1, ffp.size); + ASSERT_EQ(0, ffp.mtime); + const std::array expected = {0, 0, 0, 0}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(false, ffp.isvalid); +} + +TEST(FileFingerprint, copyAssignment) +{ + mega::FileFingerprint ffp; + ffp.size = 1; + ffp.mtime = 2; + std::iota(ffp.crc.begin(), ffp.crc.end(), 3); + ffp.isvalid = true; + + mega::FileFingerprint copiedFfp; + copiedFfp = ffp; + + ASSERT_EQ(copiedFfp.size, ffp.size); + ASSERT_EQ(copiedFfp.mtime, ffp.mtime); + ASSERT_EQ(copiedFfp.crc, ffp.crc); + ASSERT_EQ(copiedFfp.isvalid, ffp.isvalid); +} + +TEST(FileFingerprint, copyConstructor) +{ + mega::FileFingerprint ffp; + ffp.size = 1; + ffp.mtime = 2; + std::iota(ffp.crc.begin(), ffp.crc.end(), 3); + ffp.isvalid = true; + + const mega::FileFingerprint copiedFfp{ffp}; + + ASSERT_EQ(copiedFfp.size, ffp.size); + ASSERT_EQ(copiedFfp.mtime, ffp.mtime); + ASSERT_EQ(copiedFfp.crc, ffp.crc); + ASSERT_EQ(copiedFfp.isvalid, ffp.isvalid); +} + +TEST(FileFingerprint, comparisonOperator_compareEqual) +{ + mega::FileFingerprint ffp; + ffp.size = 1; + ffp.mtime = 2; + std::iota(ffp.crc.begin(), ffp.crc.end(), 3); + ffp.isvalid = true; + + mega::FileFingerprint copiedFfp; + copiedFfp = ffp; + + ASSERT_TRUE(ffp == copiedFfp); +} + +TEST(FileFingerprint, comparisonOperator_compareNotEqualBecauseOfSize) +{ + mega::FileFingerprint ffp; + ffp.isvalid = true; + ffp.size = 1; + + mega::FileFingerprint ffp2; + ffp2.isvalid = true; + + ASSERT_FALSE(ffp == ffp2); +} + +#ifndef __ANDROID__ +#ifndef WINDOWS_PHONE +TEST(FileFingerprint, comparisonOperator_compareNotEqualBecauseOfMTime) +{ + mega::FileFingerprint ffp; + ffp.isvalid = true; + ffp.mtime = 3; // difference must be at least 3 + + mega::FileFingerprint ffp2; + ffp2.isvalid = true; + + ASSERT_FALSE(ffp == ffp2); +} +#endif +#endif + +TEST(FileFingerprint, comparisonOperator_compareNotEqualBecauseOfValid) +{ + mega::FileFingerprint ffp; + ffp.isvalid = false; + + mega::FileFingerprint ffp2; + ffp2.isvalid = true; + + ASSERT_TRUE(ffp == ffp2); +} + +TEST(FileFingerprint, comparisonOperator_compareNotEqualBecauseOfCrc) +{ + mega::FileFingerprint ffp; + ffp.isvalid = true; + ffp.crc[0] = 1; + + mega::FileFingerprint ffp2; + ffp2.isvalid = true; + + ASSERT_FALSE(ffp == ffp2); +} + +TEST(FileFingerprint, serialize_unserialize) +{ + mega::FileFingerprint ffp; + ffp.size = 1; + ffp.mtime = 2; + std::iota(ffp.crc.begin(), ffp.crc.end(), 3); + ffp.isvalid = true; + + std::string data; + ASSERT_TRUE(ffp.serialize(&data)); + auto ffp2 = std::unique_ptr{mega::FileFingerprint::unserialize(&data)}; + + ASSERT_EQ(ffp2->size, ffp.size); + ASSERT_EQ(ffp2->mtime, ffp.mtime); + ASSERT_EQ(ffp2->crc, ffp.crc); + ASSERT_EQ(ffp2->isvalid, ffp.isvalid); +} + +TEST(FileFingerprint, unserialize_butStringTooShort) +{ + std::string data = "blah"; + ASSERT_EQ(nullptr, mega::FileFingerprint::unserialize(&data)); +} + +TEST(FileFingerprint, serializefingerprint_unserializefingerprint) +{ + mega::FileFingerprint ffp; + ffp.size = 1; + ffp.mtime = 2; + std::iota(ffp.crc.begin(), ffp.crc.end(), 3); + ffp.isvalid = true; + + std::string data; + ffp.serializefingerprint(&data); + mega::FileFingerprint ffp2; + ASSERT_TRUE(ffp2.unserializefingerprint(&data)); + + ASSERT_EQ(ffp2.size, -1); // it is not clear why `size` is dealed with + ASSERT_EQ(ffp2.mtime, ffp.mtime); + ASSERT_EQ(ffp2.crc, ffp.crc); + ASSERT_EQ(ffp2.isvalid, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_FileAccess_forTinyFile) +{ + mega::FileFingerprint ffp; + MockFileAccess fa{1, {3, 4, 5, 6}}; + ASSERT_TRUE(ffp.genfingerprint(&fa)); + ASSERT_EQ(4, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {100992003, 0, 0, 0}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(true, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_FileAccess_forTinyFile_butReadFails) +{ + mega::FileFingerprint ffp; + MockFileAccess fa{1, {3, 4, 5, 6}, true}; + ASSERT_TRUE(ffp.genfingerprint(&fa)); + ASSERT_EQ(-1, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {0, 0, 0, 0}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(false, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_FileAccess_forSmallFile) +{ + mega::FileFingerprint ffp; + std::vector content(100); + std::iota(content.begin(), content.end(), mega::byte{0}); + MockFileAccess fa{1, std::move(content)}; + ASSERT_TRUE(ffp.genfingerprint(&fa)); + ASSERT_EQ(100, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {215253208, 661795201, 937191950, 562141813}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(true, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_FileAccess_forSmallFile_butReadFails) +{ + mega::FileFingerprint ffp; + std::vector content(100); + std::iota(content.begin(), content.end(), mega::byte{0}); + MockFileAccess fa{1, std::move(content), true}; + ASSERT_TRUE(ffp.genfingerprint(&fa)); + ASSERT_EQ(-1, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {0, 0, 0, 0}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(false, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_FileAccess_forLargeFile) +{ + mega::FileFingerprint ffp; + std::vector content(20000); + std::iota(content.begin(), content.end(), mega::byte{0}); + MockFileAccess fa{1, std::move(content)}; + ASSERT_TRUE(ffp.genfingerprint(&fa)); + ASSERT_EQ(20000, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {-1424885571, 1204627086, 1194313128, -177560448}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(true, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_FileAccess_forLargeFile_butReadFails) +{ + mega::FileFingerprint ffp; + std::vector content(20000); + std::iota(content.begin(), content.end(), mega::byte{0}); + MockFileAccess fa{1, std::move(content), true}; + ASSERT_TRUE(ffp.genfingerprint(&fa)); + ASSERT_EQ(-1, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {0, 0, 0, 0}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(false, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_InputStreamAccess_forTinyFile) +{ + mega::FileFingerprint ffp; + MockInputStreamAccess is{1, {3, 4, 5, 6}}; + ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); + ASSERT_EQ(4, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {100992003, 0, 0, 0}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(true, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_InputStreamAccess_forTinyFile_butReadFails) +{ + mega::FileFingerprint ffp; + MockInputStreamAccess is{1, {3, 4, 5, 6}, true}; + ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); + ASSERT_EQ(-1, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {0, 0, 0, 0}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(false, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_InputStreamAccess_forTinyFile_butSizeNegative) +{ + mega::FileFingerprint ffp; + MockInputStreamAccess is{1, {3, 4, 5, 6}}; + is.setSize(-1); + ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); + ASSERT_EQ(-1, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {0, 0, 0, 0}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(false, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_InputStreamAccess_forSmallFile) +{ + mega::FileFingerprint ffp; + std::vector content(100); + std::iota(content.begin(), content.end(), mega::byte{0}); + MockInputStreamAccess is{1, std::move(content)}; + ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); + ASSERT_EQ(100, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {215253208, 661795201, 937191950, 562141813}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(true, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_InputStreamAccess_forSmallFile_butReadFails) +{ + mega::FileFingerprint ffp; + std::vector content(100); + std::iota(content.begin(), content.end(), mega::byte{0}); + MockInputStreamAccess is{1, std::move(content), true}; + ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); + ASSERT_EQ(-1, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {0, 0, 0, 0}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(false, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_InputStreamAccess_forLargeFile) +{ + mega::FileFingerprint ffp; + std::vector content(20000); + std::iota(content.begin(), content.end(), mega::byte{0}); + MockInputStreamAccess is{1, std::move(content)}; + ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); + ASSERT_EQ(20000, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {-1236811658, -1236811658, -1236811658, -1236811658}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(true, ffp.isvalid); +} + +TEST(FileFingerprint, genfingerprint_InputStreamAccess_forLargeFile_butReadFails) +{ + mega::FileFingerprint ffp; + std::vector content(20000); + std::iota(content.begin(), content.end(), mega::byte{0}); + MockInputStreamAccess is{1, std::move(content), true}; + ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); + ASSERT_EQ(-1, ffp.size); + ASSERT_EQ(1, ffp.mtime); + const std::array expected = {0, 0, 0, 0}; + ASSERT_EQ(expected, ffp.crc); + ASSERT_EQ(false, ffp.isvalid); +} + +TEST(FileFingerprint, light_genfingerprint) +{ + mega::LightFileFingerprint ffp; + const m_off_t filesize = 42; + const mega::m_time_t filemtime = 13; + ASSERT_TRUE(ffp.genfingerprint(filesize, filemtime)); + ASSERT_EQ(filesize, ffp.size); + ASSERT_EQ(filemtime, ffp.mtime); +} + +TEST(FileFingerprint, light_genfingerprint_compare_equal) +{ + mega::LightFileFingerprint ffp1; + ffp1.size = 42; + ffp1.mtime = 13; + mega::LightFileFingerprint ffp2; + ffp2.size = 42; + ffp2.mtime = 13; + ASSERT_TRUE(ffp1 == ffp2); +} + +TEST(FileFingerprint, light_genfingerprint_compare_not_equal) +{ + mega::LightFileFingerprint ffp1; + ffp1.size = 42; + ffp1.mtime = 13; + mega::LightFileFingerprint ffp2; + ffp2.size = 42; + ffp2.mtime = 12; + ASSERT_FALSE(ffp1 == ffp2); +} + +TEST(FileFingerprint, light_genfingerprint_firstSmaller_becauseOfSize) +{ + mega::LightFileFingerprint ffp1; + ffp1.size = 41; + ffp1.mtime = 13; + mega::LightFileFingerprint ffp2; + ffp2.size = 42; + ffp2.mtime = 13; + ASSERT_TRUE(mega::LightFileFingerprintCmp{}(&ffp1, &ffp2)); +} + +TEST(FileFingerprint, light_genfingerprint_firstSmaller_becauseOfMTime) +{ + mega::LightFileFingerprint ffp1; + ffp1.size = 42; + ffp1.mtime = 12; + mega::LightFileFingerprint ffp2; + ffp2.size = 42; + ffp2.mtime = 13; + ASSERT_TRUE(mega::LightFileFingerprintCmp{}(&ffp1, &ffp2)); +} + +TEST(FileFingerprint, light_genfingerprint_firstNotSmaller) +{ + mega::LightFileFingerprint ffp1; + ffp1.size = 42; + ffp1.mtime = 13; + mega::LightFileFingerprint ffp2; + ffp2.size = 42; + ffp2.mtime = 13; + ASSERT_FALSE(mega::LightFileFingerprintCmp{}(&ffp1, &ffp2)); +} diff --git a/tests/unit/FsNode.cpp b/tests/unit/FsNode.cpp new file mode 100644 index 0000000000..e7bb7e72c5 --- /dev/null +++ b/tests/unit/FsNode.cpp @@ -0,0 +1,105 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +#include "FsNode.h" + +#include "utils.h" + +namespace mt { + +FsNode::FsNode(FsNode* parent, const mega::nodetype_t type, std::string name) +: mFsId{mt::nextFsId()} +, mMTime{nextRandomInt()} +, mParent{parent} +, mType{type} +, mName{std::move(name)} +{ + assert(mType == mega::FILENODE || mType == mega::FOLDERNODE); + + if (parent) + { + assert(parent->getType() == mega::FOLDERNODE); + parent->mChildren.push_back(this); + } + + auto path = getPath(); + + if (mType == mega::FILENODE) + { + mSize = nextRandomInt(); + mContent.reserve(static_cast(mSize)); + for (m_off_t i = 0; i < mSize; ++i) + { + mContent.push_back(nextRandomByte()); + } + mFileAccess->fopen(&path, true, false); + mFingerprint.genfingerprint(mFileAccess.get()); + } + else + { + mFileAccess->fopen(&path, true, false); + mFingerprint.mtime = mMTime; + } +} + +FsNode::FileAccess::FileAccess(const FsNode& fsNode) +: mFsNode{fsNode} +{} + +bool FsNode::FileAccess::fopen(std::string* path, bool, bool) +{ + mPath = *path; + return sysopen(); +} + +bool FsNode::FileAccess::sysstat(mega::m_time_t* curr_mtime, m_off_t* curr_size) +{ + *curr_mtime = mtime; + *curr_size = size; + return true; +} + +bool FsNode::FileAccess::sysopen(bool async) +{ + if (mPath == mFsNode.getPath()) + { + fsidvalid = true; + fsid = mFsNode.getFsId(); + size = mFsNode.getSize(); + mtime = mFsNode.getMTime(); + type = mFsNode.getType(); + return true; + } + else + { + return false; + } +} + +bool FsNode::FileAccess::sysread(mega::byte* buffer, unsigned size, m_off_t offset) +{ + const auto& content = mFsNode.getContent(); + assert(static_cast(offset) + size <= content.size()); + std::copy(content.begin() + static_cast(offset), content.begin() + static_cast(offset) + size, buffer); + return true; +} + +void FsNode::FileAccess::sysclose() +{} + +} // mt diff --git a/tests/unit/FsNode.h b/tests/unit/FsNode.h new file mode 100644 index 0000000000..e1f3b101f8 --- /dev/null +++ b/tests/unit/FsNode.h @@ -0,0 +1,161 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +#pragma once + +#include +#include + +#include +#include + +#include "DefaultedFileAccess.h" + +namespace mt { + +// Represents a node on the filesystem (either file or directory) +class FsNode +{ +public: + FsNode(FsNode* parent, const mega::nodetype_t type, std::string name); + + MEGA_DISABLE_COPY_MOVE(FsNode) + + void setFsId(const mega::handle fsId) + { + mFsId = fsId; + } + + mega::handle getFsId() const + { + return mFsId; + } + + m_off_t getSize() const + { + return mSize; + } + + mega::m_time_t getMTime() const + { + return mMTime; + } + + const std::vector& getContent() const + { + return mContent; + } + + const mega::FileFingerprint& getFingerprint() const + { + return mFingerprint; + } + + void assignContentFrom(const FsNode& node) + { + assert(node.getType() == mega::FILENODE); + mSize = node.getSize(); + mMTime = node.getMTime(); + mContent = node.getContent(); + mFingerprint = node.getFingerprint(); + } + + mega::nodetype_t getType() const + { + return mType; + } + + const std::string& getName() const + { + return mName; + } + + void setOpenable(const bool openable) + { + mOpenable = openable; + } + + bool getOpenable() const + { + return mOpenable; + } + + void setReadable(const bool readable) + { + mReadable = readable; + } + + bool getReadable() const + { + return mReadable; + } + + std::string getPath() const + { + std::string path = mName; + auto parent = mParent; + while (parent) + { + path = parent->mName + "/" + path; + parent = parent->mParent; + } + return path; + } + + const std::vector& getChildren() const + { + return mChildren; + } + +private: + + class FileAccess : public DefaultedFileAccess + { + public: + explicit FileAccess(const FsNode& fsNode); + + bool fopen(std::string* path, bool, bool) override; + + bool sysstat(mega::m_time_t* curr_mtime, m_off_t* curr_size) override; + + bool sysopen(bool async = false) override; + + bool sysread(mega::byte* buffer, unsigned size, m_off_t offset) override; + + void sysclose() override; + + private: + std::string mPath; + const FsNode& mFsNode; + }; + + mega::handle mFsId = mega::UNDEF; + m_off_t mSize = -1; + mega::m_time_t mMTime = 0; + std::vector mContent; + mega::FileFingerprint mFingerprint; + std::unique_ptr mFileAccess = std::unique_ptr{new FileAccess{*this}}; + const FsNode* mParent = nullptr; + const mega::nodetype_t mType = mega::TYPE_UNKNOWN; + const std::string mName; + bool mOpenable = true; + bool mReadable = true; + std::vector mChildren; +}; + +} // mt diff --git a/tests/unit/Logging_test.cpp b/tests/unit/Logging_test.cpp new file mode 100644 index 0000000000..d4501c7d10 --- /dev/null +++ b/tests/unit/Logging_test.cpp @@ -0,0 +1,481 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ +#include + +#include + +#ifdef ENABLE_LOG_PERFORMANCE +namespace { + +class MockLogger : public mega::Logger +{ +public: + + MockLogger() + { + mega::SimpleLogger::logger = this; + } + + ~MockLogger() + { + mega::SimpleLogger::logger = nullptr; + } + + void log(const char *time, int loglevel, const char *source, const char *message) override + { + EXPECT_EQ(nullptr, time); + EXPECT_EQ(nullptr, source); + EXPECT_NE(nullptr, message); + mLogLevel.insert(loglevel); + mMessage.push_back(message); + } + + void checkLogLevel(const int expLogLevel) const + { + EXPECT_EQ(1, mLogLevel.size()); + EXPECT_EQ(expLogLevel, *mLogLevel.begin()); + } + + std::vector mMessage; + +private: + std::set mLogLevel; +}; + +std::string expMsg(const std::string& file, const int line, const std::string& message) +{ + return file + ":" + std::to_string(line) + " " + message; +} + +} + +TEST(Logging, performanceMode_forStdString) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + const std::string file = "file.cpp"; + const int line = 13; + const std::string message = "some message"; + mega::SimpleLogger{static_cast(level), file.c_str(), line} << message; + logger.checkLogLevel(level); + ASSERT_EQ(1, logger.mMessage.size()); + ASSERT_EQ(expMsg(file, line, message), logger.mMessage[0]); + } +} + +TEST(Logging, performanceMode_forCString) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + const std::string file = "file.cpp"; + const int line = 13; + const std::string message = "some message"; + mega::SimpleLogger{static_cast(level), file.c_str(), line} << message.c_str(); + logger.checkLogLevel(level); + ASSERT_EQ(1, logger.mMessage.size()); + ASSERT_EQ(expMsg(file, line, message), logger.mMessage[0]); + } +} + +TEST(Logging, performanceMode_forEnum) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + const std::string file = "file.cpp"; + const int line = 13; + const auto obj = mega::LogLevel::logDebug; + mega::SimpleLogger{static_cast(level), file.c_str(), line} << obj; + logger.checkLogLevel(level); + ASSERT_EQ(1, logger.mMessage.size()); + ASSERT_EQ(expMsg(file, line, "4"), logger.mMessage[0]); + } +} + +TEST(Logging, performanceMode_forPointer) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + const std::string file = "file.cpp"; + const int line = 13; + const double obj = 42; + mega::SimpleLogger{static_cast(level), file.c_str(), line} << &obj; + logger.checkLogLevel(level); + ASSERT_EQ(1, logger.mMessage.size()); + ASSERT_GE(logger.mMessage[0].size(), file.size() + 5); // 5 = ':13 ' plus null terminator + } +} + +TEST(Logging, performanceMode_forNullPointer) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + const std::string file = "file.cpp"; + const int line = 13; + const double* obj = nullptr; + mega::SimpleLogger{static_cast(level), file.c_str(), line} << obj; + logger.checkLogLevel(level); + ASSERT_EQ(1, logger.mMessage.size()); + ASSERT_EQ(expMsg(file, line, "(NULL)"), logger.mMessage[0]); + } +} + +namespace { + +template +void test_forIntegerNumber(const Type number) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + const std::string file = "file.cpp"; + const int line = 13; + mega::SimpleLogger{static_cast(level), file.c_str(), line} << number; + logger.checkLogLevel(level); + EXPECT_EQ(1, logger.mMessage.size()); + std::ostringstream expected; + expected << number; + EXPECT_EQ(expMsg(file, line, expected.str()), logger.mMessage[0]); + } +} + +template +void test_forFloatingNumber(const Type number) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + const std::string file = "file.cpp"; + const int line = 13; + mega::SimpleLogger{static_cast(level), file.c_str(), line} << number; + logger.checkLogLevel(level); + EXPECT_EQ(1, logger.mMessage.size()); + std::ostringstream expected; + expected << number; + const auto msg = expMsg(file, line, expected.str()); + EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); + } +} + +} + +TEST(Logging, performanceMode_forInt) +{ + test_forIntegerNumber(0); + test_forIntegerNumber(42); + test_forIntegerNumber(-42); + test_forIntegerNumber(std::numeric_limits::lowest()); + test_forIntegerNumber(std::numeric_limits::max()); +} + +TEST(Logging, performanceMode_forLong) +{ + test_forIntegerNumber(0); + test_forIntegerNumber(42); + test_forIntegerNumber(-42); + test_forIntegerNumber(std::numeric_limits::lowest()); + test_forIntegerNumber(std::numeric_limits::max()); +} + +TEST(Logging, performanceMode_forLongLong) +{ + test_forIntegerNumber(0); + test_forIntegerNumber(42); + test_forIntegerNumber(-42); + test_forIntegerNumber(std::numeric_limits::lowest()); + test_forIntegerNumber(std::numeric_limits::max()); +} + +TEST(Logging, performanceMode_forUnsignedInt) +{ + test_forIntegerNumber(0); + test_forIntegerNumber(42); + test_forIntegerNumber(std::numeric_limits::lowest()); + test_forIntegerNumber(std::numeric_limits::max()); +} + +TEST(Logging, performanceMode_forUnsignedLong) +{ + test_forIntegerNumber(0); + test_forIntegerNumber(42); + test_forIntegerNumber(std::numeric_limits::lowest()); + test_forIntegerNumber(std::numeric_limits::max()); +} + +TEST(Logging, performanceMode_forUnsignedLongLong) +{ + test_forIntegerNumber(0); + test_forIntegerNumber(42); + test_forIntegerNumber(std::numeric_limits::lowest()); + test_forIntegerNumber(std::numeric_limits::max()); +} + +TEST(Logging, performanceMode_forFloat) +{ + test_forFloatingNumber(0.f); + test_forFloatingNumber(42.123f); + test_forFloatingNumber(-42.123f); + test_forFloatingNumber(std::numeric_limits::lowest()); + test_forFloatingNumber(std::numeric_limits::min()); + test_forFloatingNumber(std::numeric_limits::max()); +} + +TEST(Logging, performanceMode_forDouble) +{ + test_forFloatingNumber(0.); + test_forFloatingNumber(42.123); + test_forFloatingNumber(-42.123); + test_forFloatingNumber(std::numeric_limits::lowest()); + test_forFloatingNumber(std::numeric_limits::min()); + test_forFloatingNumber(std::numeric_limits::max()); +} + +TEST(Logging, performanceMode_withMessageLargeThanLogBuffer) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + const std::string file = "file.cpp"; + const int line = 13; + const std::string firstMessage(256 - file.size() - 5, 'X'); // 5 = ':13 ' plus null terminator + const std::string secondMessage = "yay"; + const std::string message = firstMessage + secondMessage; + mega::SimpleLogger{static_cast(level), file.c_str(), line} << message; + logger.checkLogLevel(level); + ASSERT_EQ(2, logger.mMessage.size()); + ASSERT_EQ(expMsg(file, line, firstMessage), logger.mMessage[0]); + ASSERT_EQ(secondMessage, logger.mMessage[1]); + } +} + +TEST(Logging, performanceMode_withHugeMessage) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + const std::string file = "file.cpp"; + const int line = 13; + const std::string message(5000, 'X'); + mega::SimpleLogger{static_cast(level), file.c_str(), line} << message; + logger.checkLogLevel(level); + const size_t fullMsgCount = 5013 / 255; + ASSERT_EQ(fullMsgCount + 1, logger.mMessage.size()); + ASSERT_EQ(5013 % 255 - 1, logger.mMessage.back().size()); + } +} + +TEST(Logging, performanceMode_withHugeMessage_butNoLogger) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + mega::SimpleLogger::logger = nullptr; + const std::string file = "file.cpp"; + const int line = 13; + const std::string message(5000, 'X'); + mega::SimpleLogger{static_cast(level), file.c_str(), line} << message; + // ensure no crash or other funny business + } +} + +#else + +namespace { + +class MockLogger : public mega::Logger +{ +public: + + MockLogger() + { + mega::SimpleLogger::logger = this; + } + + ~MockLogger() + { + mega::SimpleLogger::logger = nullptr; + } + + void log(const char *time, int loglevel, const char *source, const char *message) override + { + EXPECT_NE(nullptr, time); + EXPECT_NE(nullptr, source); + EXPECT_NE(nullptr, message); + mLogLevel.insert(loglevel); + mMessage.push_back(message); + } + + void checkLogLevel(const int expLogLevel) const + { + EXPECT_EQ(1, mLogLevel.size()); + EXPECT_EQ(expLogLevel, *mLogLevel.begin()); + } + + std::vector mMessage; + +private: + std::set mLogLevel; +}; + +} + +#endif + +TEST(Logging, toStr) +{ + ASSERT_EQ(0, std::strcmp("verbose", mega::SimpleLogger::toStr(mega::LogLevel::logMax))); + ASSERT_EQ(0, std::strcmp("debug", mega::SimpleLogger::toStr(mega::LogLevel::logDebug))); + ASSERT_EQ(0, std::strcmp("info", mega::SimpleLogger::toStr(mega::LogLevel::logInfo))); + ASSERT_EQ(0, std::strcmp("warn", mega::SimpleLogger::toStr(mega::LogLevel::logWarning))); + ASSERT_EQ(0, std::strcmp("err", mega::SimpleLogger::toStr(mega::LogLevel::logError))); + ASSERT_EQ(0, std::strcmp("FATAL", mega::SimpleLogger::toStr(mega::LogLevel::logFatal))); +} + +#ifdef NDEBUG +TEST(Logging, toStr_withBadLogLevel) +{ + ASSERT_EQ(0, std::strcmp("", mega::SimpleLogger::toStr(static_cast(42)))); +} +#endif + +TEST(Logging, macroVerbose) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + mega::SimpleLogger::setLogLevel(static_cast(level)); + const std::string msg = "foobar"; + LOG_verbose << msg; + const auto currentLevel = mega::LogLevel::logMax; + if (level >= currentLevel) + { + logger.checkLogLevel(currentLevel); + ASSERT_EQ(1, logger.mMessage.size()); + EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); + } + else + { + ASSERT_EQ(0, logger.mMessage.size()); + } + } +} + +TEST(Logging, macroDebug) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + mega::SimpleLogger::setLogLevel(static_cast(level)); + const std::string msg = "foobar"; + LOG_debug << msg; + const auto currentLevel = mega::LogLevel::logDebug; + if (level >= currentLevel) + { + logger.checkLogLevel(currentLevel); + ASSERT_EQ(1, logger.mMessage.size()); + EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); + } + else + { + ASSERT_EQ(0, logger.mMessage.size()); + } + } +} + +TEST(Logging, macroInfo) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + mega::SimpleLogger::setLogLevel(static_cast(level)); + const std::string msg = "foobar"; + LOG_info << msg; + const auto currentLevel = mega::LogLevel::logInfo; + if (level >= currentLevel) + { + logger.checkLogLevel(currentLevel); + ASSERT_EQ(1, logger.mMessage.size()); + EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); + } + else + { + ASSERT_EQ(0, logger.mMessage.size()); + } + } +} + +TEST(Logging, macroWarn) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + mega::SimpleLogger::setLogLevel(static_cast(level)); + const std::string msg = "foobar"; + LOG_warn << msg; + const auto currentLevel = mega::LogLevel::logWarning; + if (level >= currentLevel) + { + logger.checkLogLevel(currentLevel); + ASSERT_EQ(1, logger.mMessage.size()); + EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); + } + else + { + ASSERT_EQ(0, logger.mMessage.size()); + } + } +} + +TEST(Logging, macroErr) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + mega::SimpleLogger::setLogLevel(static_cast(level)); + const std::string msg = "foobar"; + LOG_err << msg; + const auto currentLevel = mega::LogLevel::logError; + if (level >= currentLevel) + { + logger.checkLogLevel(currentLevel); + ASSERT_EQ(1, logger.mMessage.size()); + EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); + } + else + { + ASSERT_EQ(0, logger.mMessage.size()); + } + } +} + +TEST(Logging, macroFatal) +{ + for (int level = 0; level <= mega::LogLevel::logMax; ++level) + { + MockLogger logger; + mega::SimpleLogger::setLogLevel(static_cast(level)); + const std::string msg = "foobar"; + LOG_fatal << msg; + logger.checkLogLevel(mega::LogLevel::logFatal); + ASSERT_EQ(1, logger.mMessage.size()); + EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); + } +} diff --git a/tests/unit/MegaApi_test.cpp b/tests/unit/MegaApi_test.cpp index 7d71903f27..4c4fa0b27d 100644 --- a/tests/unit/MegaApi_test.cpp +++ b/tests/unit/MegaApi_test.cpp @@ -1,7 +1,4 @@ /** - * @file tests/commands_test.cpp - * @brief Mega SDK unit tests for megaapi - * * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. @@ -19,8 +16,6 @@ * program. */ -// Note: The tests in this module are meant to be pure unit tests: Fast tests without I/O. - #include #include #include diff --git a/tests/unit/NotImplemented.h b/tests/unit/NotImplemented.h new file mode 100644 index 0000000000..e41d23aec1 --- /dev/null +++ b/tests/unit/NotImplemented.h @@ -0,0 +1,33 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +#pragma once + +#include + +namespace mt { + +class NotImplemented : public std::runtime_error +{ +public: + NotImplemented(const std::string& function) + : std::runtime_error{"Function not implemented: " + function} + {} +}; + +} // mt diff --git a/tests/unit/PayCrypter_test.cpp b/tests/unit/PayCrypter_test.cpp index a97ef44ca5..d7b949f46a 100644 --- a/tests/unit/PayCrypter_test.cpp +++ b/tests/unit/PayCrypter_test.cpp @@ -1,8 +1,5 @@ /** - * @file tests/tests.cpp - * @brief Mega SDK main test file - * - * (c) 2013 by Mega Limited, Wellsford, New Zealand + * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * diff --git a/tests/unit/Serialization_test.cpp b/tests/unit/Serialization_test.cpp index 376a4e5098..cfcad744ac 100644 --- a/tests/unit/Serialization_test.cpp +++ b/tests/unit/Serialization_test.cpp @@ -1,8 +1,5 @@ /** - * @file tests/tests.cpp - * @brief Mega SDK main test file - * - * (c) 2013 by Mega Limited, Wellsford, New Zealand + * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * @@ -19,26 +16,23 @@ * program. */ -#include "mega.h" -#include "gtest/gtest.h" - -#include "megaapi.h" +#include #include +#include #include -#include -using namespace std; -using namespace mega; -using ::testing::Test; -using ::testing::TestCase; -using ::testing::TestInfo; -using ::testing::TestPartResult; -using ::testing::UnitTest; +#include + +#include +#include + +#include "DefaultedFileSystemAccess.h" +#include "utils.h" TEST(Serialization, JSON_storeobject) { std::string in_str("Test"); - JSON j; + mega::JSON j; j.begin(in_str.data()); j.storeobject(&in_str); } @@ -48,34 +42,34 @@ TEST(Serialization, Serialize64_serialize) { uint64_t in = 0xDEADBEEF; uint64_t out; - ::mega::byte buf[sizeof in]; + mega::byte buf[sizeof in]; - Serialize64::serialize(buf, in); - ASSERT_GT(Serialize64::unserialize(buf, sizeof buf, &out), 0); + mega::Serialize64::serialize(buf, in); + ASSERT_GT(mega::Serialize64::unserialize(buf, sizeof buf, &out), 0); ASSERT_EQ(in, out); } -size_t checksize(size_t& n, size_t added) -{ - n += added; - return n; -} - TEST(Serialization, CacheableReaderWriter) { - string writestring; - CacheableWriter w(writestring); - - ::mega::byte binary[] = { 1, 2, 3, 4, 5 }; - string cstr1("test1"); - string cstr2("test2diffdata"); - string stringtest("diffstringagaindefinitelybigger"); + auto checksize = [](size_t& n, size_t added) + { + n += added; + return n; + }; + + std::string writestring; + mega::CacheableWriter w(writestring); + + mega::byte binary[] = { 1, 2, 3, 4, 5 }; + std::string cstr1("test1"); + std::string cstr2("test2diffdata"); + std::string stringtest("diffstringagaindefinitelybigger"); int64_t i64 = 0x8765432112345678; uint32_t u32 = 0x87678765; - handle handle1 = 0x998; + mega::handle handle1 = 0x998; bool b = true; - ::mega::byte by = 5; - chunkmac_map cm; + mega::byte by = 5; + mega::chunkmac_map cm; cm[777].offset = 888; size_t sizeadded = 0; @@ -99,7 +93,7 @@ TEST(Serialization, CacheableReaderWriter) ASSERT_EQ(writestring.size(), checksize(sizeadded, 4)); w.serializehandle(handle1); - ASSERT_EQ(writestring.size(), checksize(sizeadded, sizeof(handle))); + ASSERT_EQ(writestring.size(), checksize(sizeadded, sizeof(mega::handle))); w.serializebool(b); ASSERT_EQ(writestring.size(), checksize(sizeadded, sizeof(bool))); @@ -108,7 +102,7 @@ TEST(Serialization, CacheableReaderWriter) ASSERT_EQ(writestring.size(), checksize(sizeadded, 1)); w.serializechunkmacs(cm); - ASSERT_EQ(writestring.size(), checksize(sizeadded, 2 + 1 * (sizeof(m_off_t) + sizeof(ChunkMAC)))); + ASSERT_EQ(writestring.size(), checksize(sizeadded, 2 + 1 * (sizeof(m_off_t) + sizeof(mega::ChunkMAC)))); w.serializeexpansionflags(1, 0, 1, 0, 0, 0, 1, 1); ASSERT_EQ(writestring.size(), checksize(sizeadded, 8)); @@ -116,19 +110,19 @@ TEST(Serialization, CacheableReaderWriter) writestring += "abc"; // now read the serialized data back - string readstring = writestring; - CacheableReader r(readstring); + std::string readstring = writestring; + mega::CacheableReader r(readstring); - ::mega::byte check_binary[5]; - string check_cstr1; - string check_cstr2; - string check_stringtest; + mega::byte check_binary[5]; + std::string check_cstr1; + std::string check_cstr2; + std::string check_stringtest; int64_t check_i64; uint32_t check_u32; - handle check_handle1; + mega::handle check_handle1; bool check_b; - ::mega::byte check_by; - chunkmac_map check_cm; + mega::byte check_by; + mega::chunkmac_map check_cm; ASSERT_TRUE(r.unserializebinary(check_binary, sizeof(check_binary))); ASSERT_EQ(0, memcmp(check_binary, binary, sizeof(binary))); @@ -175,7 +169,7 @@ TEST(Serialization, CacheableReaderWriter) r.eraseused(readstring); ASSERT_EQ(readstring, "abc"); - MediaProperties mp; + mega::MediaProperties mp; mp.shortformat = 1; mp.width = 2; mp.height = 3; @@ -186,8 +180,8 @@ TEST(Serialization, CacheableReaderWriter) mp.audiocodecid = 8; mp.is_VFR = true; mp.no_audio = false; - string mps = mp.serialize(); - MediaProperties mp2(mps); + std::string mps = mp.serialize(); + mega::MediaProperties mp2(mps); ASSERT_EQ(mps, mp2.serialize()); ASSERT_EQ(mp2.shortformat, 1); ASSERT_EQ(mp2.width, 2u); @@ -200,3 +194,444 @@ TEST(Serialization, CacheableReaderWriter) ASSERT_EQ(mp2.is_VFR, true); ASSERT_EQ(mp2.no_audio, false); } + +namespace { + +struct MockFileSystemAccess : mt::DefaultedFileSystemAccess +{ + void local2path(std::string* local, std::string* path) const override + { + *path = *local; + } +}; + +struct MockClient +{ + mega::MegaApp app; + MockFileSystemAccess fs; + std::shared_ptr cli = mt::makeClient(app, fs); +}; + +#ifdef ENABLE_SYNC +void checkDeserializedLocalNode(const mega::LocalNode& dl, const mega::LocalNode& ref) +{ + ASSERT_EQ(ref.type, dl.type); + ASSERT_EQ(ref.size < 0 ? 0 : ref.size, dl.size); + ASSERT_EQ(ref.parent_dbid, dl.parent_dbid); + ASSERT_EQ(ref.fsid, dl.fsid); + ASSERT_EQ(ref.localname, dl.localname); + ASSERT_EQ(nullptr, dl.slocalname); + ASSERT_EQ(ref.name, dl.name); + ASSERT_EQ(ref.crc, dl.crc); + ASSERT_EQ(ref.mtime, dl.mtime); + ASSERT_EQ(true, dl.isvalid); + ASSERT_EQ(nullptr, dl.parent); + ASSERT_EQ(ref.sync, dl.sync); + ASSERT_EQ(ref.mSyncable, dl.mSyncable); + ASSERT_EQ(false, dl.created); + ASSERT_EQ(false, dl.reported); + ASSERT_EQ(true, dl.checked); +} +#endif + +} + +#ifdef ENABLE_SYNC +TEST(Serialization, LocalNode_shortData) +{ + MockClient client; + auto sync = mt::makeSync(*client.cli, "wicked"); + std::string data = "I am too short"; + auto dl = std::unique_ptr{mega::LocalNode::unserialize(sync.get(), &data)}; + ASSERT_EQ(nullptr, dl); +} + +TEST(Serialization, LocalNode_forFolder_withoutParent_withoutNode) +{ + MockClient client; + auto sync = mt::makeSync(*client.cli, "wicked"); + auto& l = *sync->localroot; + l.mSyncable = false; + l.setfsid(10, client.cli->fsidnode); + std::string data; + ASSERT_TRUE(l.serialize(&data)); + ASSERT_EQ(43, data.size()); + auto dl = mega::LocalNode::unserialize(sync.get(), &data); + checkDeserializedLocalNode(*dl, l); +} + +TEST(Serialization, LocalNode_forFile_withoutNode) +{ + MockClient client; + auto sync = mt::makeSync(*client.cli, "wicked"); + auto l = mt::makeLocalNode(*sync, *sync->localroot, mega::FILENODE, "sweet"); + l->mSyncable = false; + l->size = 124; + l->setfsid(10, client.cli->fsidnode); + l->parent->dbid = 13; + l->parent_dbid = l->parent->dbid; + l->mtime = 124124124; + std::iota(l->crc.begin(), l->crc.end(), 1); + std::string data; + ASSERT_TRUE(l->serialize(&data)); + ASSERT_EQ(63, data.size()); + auto dl = mega::LocalNode::unserialize(sync.get(), &data); + checkDeserializedLocalNode(*dl, *l); +} + +TEST(Serialization, LocalNode_forFile_withoutNode_withMaxMtime) +{ + MockClient client; + auto sync = mt::makeSync(*client.cli, "wicked"); + auto l = mt::makeLocalNode(*sync, *sync->localroot, mega::FILENODE, "sweet"); + l->size = 124; + l->setfsid(10, client.cli->fsidnode); + l->parent->dbid = 13; + l->parent_dbid = l->parent->dbid; + l->mtime = std::numeric_limitsmtime)>::max(); + std::iota(l->crc.begin(), l->crc.end(), 1); + std::string data; + ASSERT_TRUE(l->serialize(&data)); + ASSERT_EQ(67, data.size()); + auto dl = mega::LocalNode::unserialize(sync.get(), &data); + checkDeserializedLocalNode(*dl, *l); +} + +TEST(Serialization, LocalNode_forFolder_withoutParent) +{ + MockClient client; + auto sync = mt::makeSync(*client.cli, "wicked"); + auto& n = mt::makeNode(*client.cli, mega::FOLDERNODE, 42); + auto& l = *sync->localroot; + l.setfsid(10, client.cli->fsidnode); + l.node = &n; + std::string data; + ASSERT_TRUE(l.serialize(&data)); + ASSERT_EQ(43, data.size()); + auto dl = mega::LocalNode::unserialize(sync.get(), &data); + checkDeserializedLocalNode(*dl, l); +} + +TEST(Serialization, LocalNode_forFolder) +{ + MockClient client; + auto sync = mt::makeSync(*client.cli, "wicked"); + auto l = mt::makeLocalNode(*sync, *sync->localroot, mega::FOLDERNODE, "sweet"); + l->mSyncable = false; + l->parent->dbid = 13; + l->parent_dbid = l->parent->dbid; + auto& n = mt::makeNode(*client.cli, mega::FOLDERNODE, 42); + l->setfsid(10, client.cli->fsidnode); + l->node = &n; + std::string data; + ASSERT_TRUE(l->serialize(&data)); + ASSERT_EQ(42, data.size()); + auto dl = mega::LocalNode::unserialize(sync.get(), &data); + checkDeserializedLocalNode(*dl, *l); +} + +TEST(Serialization, LocalNode_forFolder_oldLocalNodeWithoutSyncable) +{ + MockClient client; + auto sync = mt::makeSync(*client.cli, "wicked"); + auto l = mt::makeLocalNode(*sync, *sync->localroot, mega::FOLDERNODE, "sweet"); + l->parent->dbid = 13; + l->parent_dbid = l->parent->dbid; + auto& n = mt::makeNode(*client.cli, mega::FOLDERNODE, 42); + l->setfsid(10, client.cli->fsidnode); + l->node = &n; + + // This array represents an old LocalNode without extension bytes + const std::array rawData = { + static_cast(0xff), static_cast(0xff), static_cast(0xff), + static_cast(0xff), static_cast(0xff), static_cast(0xff), + static_cast(0xff), static_cast(0xff), 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x73, 0x77, 0x65, 0x65, 0x74 + }; + const std::string data(rawData.data(), rawData.size()); + + auto dl = mega::LocalNode::unserialize(sync.get(), &data); + checkDeserializedLocalNode(*dl, *l); +} + +TEST(Serialization, LocalNode_forFile) +{ + MockClient client; + auto sync = mt::makeSync(*client.cli, "wicked"); + auto l = mt::makeLocalNode(*sync, *sync->localroot, mega::FILENODE, "sweet"); + l->mSyncable = false; + auto& n = mt::makeNode(*client.cli, mega::FILENODE, 42); + l->node = &n; + l->size = 1; + l->setfsid(10, client.cli->fsidnode); + l->parent->dbid = 13; + l->parent_dbid = l->parent->dbid; + l->mtime = 0; + std::iota(l->crc.begin(), l->crc.end(), 1); + std::string data; + ASSERT_TRUE(l->serialize(&data)); + ASSERT_EQ(59, data.size()); + auto dl = mega::LocalNode::unserialize(sync.get(), &data); + checkDeserializedLocalNode(*dl, *l); +} + +TEST(Serialization, LocalNode_forFiles_oldLocalNodeWithoutSyncable) +{ + MockClient client; + auto sync = mt::makeSync(*client.cli, "wicked"); + auto l = mt::makeLocalNode(*sync, *sync->localroot, mega::FILENODE, "sweet"); + auto& n = mt::makeNode(*client.cli, mega::FILENODE, 42); + l->node = &n; + l->size = 1; + l->setfsid(10, client.cli->fsidnode); + l->parent->dbid = 13; + l->parent_dbid = l->parent->dbid; + l->mtime = 0; + std::iota(l->crc.begin(), l->crc.end(), 1); + + // This array represents an old LocalNode without syncable flag + const std::array rawData = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x73, 0x77, 0x65, 0x65, 0x74, 0x01, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00 + }; + const std::string data(rawData.data(), rawData.size()); + + auto dl = mega::LocalNode::unserialize(sync.get(), &data); + checkDeserializedLocalNode(*dl, *l); +} +#endif + +namespace { + +void checkDeserializedNode(const mega::Node& dl, const mega::Node& ref, bool ignore_fileattrstring = false) +{ + ASSERT_EQ(ref.type, dl.type); + ASSERT_EQ(ref.size, dl.size); + ASSERT_EQ(ref.nodehandle, dl.nodehandle); + ASSERT_EQ(ref.parenthandle, dl.parenthandle); + ASSERT_EQ(ref.owner, dl.owner); + ASSERT_EQ(ref.ctime, dl.ctime); + ASSERT_EQ(ref.nodekey(), dl.nodekey()); + ASSERT_EQ(ignore_fileattrstring ? "" : ref.fileattrstring, dl.fileattrstring); + ASSERT_EQ(ref.attrs.map, dl.attrs.map); + if (ref.plink) + { + ASSERT_NE(nullptr, dl.plink); + ASSERT_EQ(ref.plink->ph, dl.plink->ph); + ASSERT_EQ(ref.plink->cts, dl.plink->cts); + ASSERT_EQ(ref.plink->ets, dl.plink->ets); + ASSERT_EQ(ref.plink->takendown, dl.plink->takendown); + } + // TODO: deal with shares +} + +} + +TEST(Serialization, Node_whenNodeIsEncrypted) +{ + MockClient client; + auto& n = mt::makeNode(*client.cli, mega::FILENODE, 42); + n.attrstring = new std::string; // owned by Node + std::string data; + ASSERT_FALSE(n.serialize(&data)); +} + +TEST(Serialization, Node_whenTypeIsUnsupported) +{ + MockClient client; + auto& n = mt::makeNode(*client.cli, mega::TYPE_UNKNOWN, 42); + std::string data; + ASSERT_FALSE(n.serialize(&data)); +} + +TEST(Serialization, Node_forFile_withoutParent_withoutShares_withoutAttrs_withoutFileAttrString_withoutPlink) +{ + MockClient client; + auto& n = mt::makeNode(*client.cli, mega::FILENODE, 42); + n.size = 12; + n.owner = 43; + n.ctime = 44; + std::string data; + ASSERT_TRUE(n.serialize(&data)); + ASSERT_EQ(90, data.size()); + mega::node_vector dp; + auto dn = mega::Node::unserialize(client.cli.get(), &data, &dp); + checkDeserializedNode(*dn, n); +} + +TEST(Serialization, Node_forFolder_withoutParent_withoutShares_withoutAttrs_withoutFileAttrString_withoutPlink) +{ + MockClient client; + auto& n = mt::makeNode(*client.cli, mega::FOLDERNODE, 42); + n.size = -1; + n.owner = 43; + n.ctime = 44; + std::string data; + ASSERT_TRUE(n.serialize(&data)); + ASSERT_EQ(71, data.size()); + mega::node_vector dp; + auto dn = mega::Node::unserialize(client.cli.get(), &data, &dp); + checkDeserializedNode(*dn, n); +} + +TEST(Serialization, Node_forFile_withoutShares_withoutAttrs_withoutFileAttrString_withoutPlink) +{ + MockClient client; + auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, 43); + auto& n = mt::makeNode(*client.cli, mega::FILENODE, 42, &parent); + n.size = 12; + n.owner = 88; + n.ctime = 44; + std::string data; + ASSERT_TRUE(n.serialize(&data)); + ASSERT_EQ(90, data.size()); + mega::node_vector dp; + auto dn = mega::Node::unserialize(client.cli.get(), &data, &dp); + checkDeserializedNode(*dn, n); +} + +TEST(Serialization, Node_forFile_withoutShares_withoutFileAttrString_withoutPlink) +{ + MockClient client; + auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, 43); + auto& n = mt::makeNode(*client.cli, mega::FILENODE, 42, &parent); + n.size = 12; + n.owner = 88; + n.ctime = 44; + n.attrs.map = { + {101, "foo"}, + {102, "bar"}, + }; + std::string data; + ASSERT_TRUE(n.serialize(&data)); + ASSERT_EQ(104, data.size()); + mega::node_vector dp; + auto dn = mega::Node::unserialize(client.cli.get(), &data, &dp); + checkDeserializedNode(*dn, n); +} + +TEST(Serialization, Node_forFile_withoutShares_withoutPlink) +{ + MockClient client; + auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, 43); + auto& n = mt::makeNode(*client.cli, mega::FILENODE, 42, &parent); + n.size = 12; + n.owner = 88; + n.ctime = 44; + n.attrs.map = { + {101, "foo"}, + {102, "bar"}, + }; + n.fileattrstring = "blah"; + std::string data; + ASSERT_TRUE(n.serialize(&data)); + ASSERT_EQ(108, data.size()); + mega::node_vector dp; + auto dn = mega::Node::unserialize(client.cli.get(), &data, &dp); + checkDeserializedNode(*dn, n); +} + +TEST(Serialization, Node_forFile_withoutShares) +{ + MockClient client; + auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, 43); + auto& n = mt::makeNode(*client.cli, mega::FILENODE, 42, &parent); + n.size = 12; + n.owner = 88; + n.ctime = 44; + n.attrs.map = { + {101, "foo"}, + {102, "bar"}, + }; + n.fileattrstring = "blah"; + n.plink = new mega::PublicLink{n.nodehandle, 1, 2, false}; + std::string data; + ASSERT_TRUE(n.serialize(&data)); + ASSERT_EQ(131, data.size()); + mega::node_vector dp; + auto dn = mega::Node::unserialize(client.cli.get(), &data, &dp); + checkDeserializedNode(*dn, n); +} + +TEST(Serialization, Node_forFolder_withoutShares_withoutAttrs_withoutFileAttrString_withoutPlink) +{ + MockClient client; + auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, 43); + auto& n = mt::makeNode(*client.cli, mega::FOLDERNODE, 42, &parent); + n.size = -1; + n.owner = 88; + n.ctime = 44; + std::string data; + ASSERT_TRUE(n.serialize(&data)); + ASSERT_EQ(71, data.size()); + mega::node_vector dp; + auto dn = mega::Node::unserialize(client.cli.get(), &data, &dp); + checkDeserializedNode(*dn, n); +} + +TEST(Serialization, Node_forFolder_withoutShares_withoutFileAttrString_withoutPlink) +{ + MockClient client; + auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, 43); + auto& n = mt::makeNode(*client.cli, mega::FOLDERNODE, 42, &parent); + n.size = -1; + n.owner = 88; + n.ctime = 44; + n.attrs.map = { + {101, "foo"}, + {102, "bar"}, + }; + std::string data; + ASSERT_TRUE(n.serialize(&data)); + ASSERT_EQ(85, data.size()); + mega::node_vector dp; + auto dn = mega::Node::unserialize(client.cli.get(), &data, &dp); + checkDeserializedNode(*dn, n); +} + +TEST(Serialization, Node_forFolder_withoutShares_withoutPlink) +{ + MockClient client; + auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, 43); + auto& n = mt::makeNode(*client.cli, mega::FOLDERNODE, 42, &parent); + n.size = -1; + n.owner = 88; + n.ctime = 44; + n.attrs.map = { + {101, "foo"}, + {102, "bar"}, + }; + n.fileattrstring = "blah"; + std::string data; + ASSERT_TRUE(n.serialize(&data)); + ASSERT_EQ(85, data.size()); + mega::node_vector dp; + auto dn = mega::Node::unserialize(client.cli.get(), &data, &dp); + checkDeserializedNode(*dn, n, true); +} + +TEST(Serialization, Node_forFolder_withoutShares) +{ + MockClient client; + auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, 43); + auto& n = mt::makeNode(*client.cli, mega::FOLDERNODE, 42, &parent); + n.size = -1; + n.owner = 88; + n.ctime = 44; + n.attrs.map = { + {101, "foo"}, + {102, "bar"}, + }; + n.fileattrstring = "blah"; + n.plink = new mega::PublicLink{n.nodehandle, 1, 2, false}; + std::string data; + ASSERT_TRUE(n.serialize(&data)); + ASSERT_EQ(108, data.size()); + mega::node_vector dp; + auto dn = mega::Node::unserialize(client.cli.get(), &data, &dp); + checkDeserializedNode(*dn, n, true); +} diff --git a/tests/unit/Sync_test.cpp b/tests/unit/Sync_test.cpp new file mode 100644 index 0000000000..f06a895791 --- /dev/null +++ b/tests/unit/Sync_test.cpp @@ -0,0 +1,1043 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ +#ifdef ENABLE_SYNC + +#include + +#include + +#include +#include +#include +#include + +#include "constants.h" +#include "FsNode.h" +#include "DefaultedDirAccess.h" +#include "DefaultedFileAccess.h" +#include "DefaultedFileSystemAccess.h" +#include "utils.h" + +namespace { + +class MockApp : public mega::MegaApp +{ +public: + + bool sync_syncable(mega::Sync*, const char*, std::string* localpath) override + { + return mNotSyncablePaths.find(*localpath) == mNotSyncablePaths.end(); + } + + void addNotSyncablePath(std::string path) + { + mNotSyncablePaths.insert(std::move(path)); + } + +private: + std::set mNotSyncablePaths; +}; + +class MockFileAccess : public mt::DefaultedFileAccess +{ +public: + explicit MockFileAccess(std::map& fsNodes) + : mFsNodes{fsNodes} + {} + + ~MockFileAccess() + { + assert(sOpenFileCount <= 2); // Ensure there's not more than two files open at a time + if (mOpen) + { + --sOpenFileCount; + } + } + + MEGA_DISABLE_COPY_MOVE(MockFileAccess) + + bool fopen(std::string* path, bool, bool) override + { + mPath = *path; + return sysopen(); + } + + bool sysstat(mega::m_time_t* curr_mtime, m_off_t* curr_size) override + { + *curr_mtime = mtime; + *curr_size = size; + return true; + } + + bool sysopen(bool async = false) override + { + const auto fsNodePair = mFsNodes.find(mPath); + if (fsNodePair != mFsNodes.end()) + { + mCurrentFsNode = fsNodePair->second; + if (!mCurrentFsNode->getOpenable()) + { + return false; + } + fsid = mCurrentFsNode->getFsId(); + fsidvalid = fsid != mega::UNDEF; + size = mCurrentFsNode->getSize(); + mtime = mCurrentFsNode->getMTime(); + type = mCurrentFsNode->getType(); + mOpen = true; + ++sOpenFileCount; + return true; + } + else + { + return false; + } + } + + bool sysread(mega::byte* buffer, unsigned size, m_off_t offset) override + { + assert(mOpen); + assert(mCurrentFsNode); + if (!mCurrentFsNode->getReadable()) + { + return false; + } + const auto& content = mCurrentFsNode->getContent(); + assert(static_cast(offset) + size <= content.size()); + std::copy(content.begin() + static_cast(offset), content.begin() + static_cast(offset) + size, buffer); + return true; + } + + void sysclose() override + {} + +private: + static int sOpenFileCount; + std::string mPath; + bool mOpen = false; + const mt::FsNode* mCurrentFsNode{}; + std::map& mFsNodes; +}; + +int MockFileAccess::sOpenFileCount{0}; + +class MockDirAccess : public mt::DefaultedDirAccess +{ +public: + explicit MockDirAccess(std::map& fsNodes) + : mFsNodes{fsNodes} + {} + + MEGA_DISABLE_COPY_MOVE(MockDirAccess) + + bool dopen(std::string* path, mega::FileAccess* fa, bool) override + { + assert(fa->type == mega::FOLDERNODE); + const auto fsNodePair = mFsNodes.find(*path); + if (fsNodePair != mFsNodes.end()) + { + mCurrentFsNode = fsNodePair->second; + return mCurrentFsNode->getOpenable(); + } + else + { + return false; + } + } + + bool dnext(std::string* localpath, std::string* localname, bool = true, mega::nodetype_t* = NULL) override + { + assert(mCurrentFsNode); + assert(mCurrentFsNode->getPath() == *localpath); + const auto& children = mCurrentFsNode->getChildren(); + if (mCurrentChildIndex < children.size()) + { + *localname = children[mCurrentChildIndex]->getName(); + ++mCurrentChildIndex; + return true; + } + else + { + mCurrentChildIndex = 0; + mCurrentFsNode = nullptr; + return false; + } + } + +private: + const mt::FsNode* mCurrentFsNode{}; + std::size_t mCurrentChildIndex{}; + std::map& mFsNodes; +}; + +class MockFileSystemAccess : public mt::DefaultedFileSystemAccess +{ +public: + explicit MockFileSystemAccess(std::map& fsNodes) + : mFsNodes{fsNodes} + {} + + std::unique_ptr newfileaccess(bool) override + { + return std::unique_ptr{new MockFileAccess{mFsNodes}}; + } + + mega::DirAccess* newdiraccess() override + { + return new MockDirAccess{mFsNodes}; + } + + void local2path(std::string* local, std::string* path) const override + { + *path = *local; + } + + size_t lastpartlocal(std::string* localname) const override + { + const char* ptr = localname->data(); + if ((ptr = strrchr(ptr, '/'))) + { + return ptr - localname->data() + 1; + } + return 0; + } + +private: + std::map& mFsNodes; +}; + +struct Fixture +{ + explicit Fixture(std::string localname) + : mSync{mt::makeSync(*mClient, std::move(localname))} + {} + + MEGA_DISABLE_COPY_MOVE(Fixture) + + MockApp mApp; + std::map mFsNodes; + MockFileSystemAccess mFsAccess{mFsNodes}; + std::shared_ptr mClient = mt::makeClient(mApp, mFsAccess); + mega::handlelocalnode_map& mLocalNodes = mClient->fsidnode; + std::unique_ptr mSync; + + bool iteratorsCorrect(mega::LocalNode& l) const + { + if (l.fsid_it == mLocalNodes.end()) + { + return false; + } + auto localNodePair = mLocalNodes.find(l.fsid); + if (l.fsid_it != localNodePair) + { + return false; + } + if (&l != localNodePair->second) + { + return false; + } + return true; + } +}; + +} + +TEST(Sync, isPathSyncable) +{ + ASSERT_TRUE(mega::isPathSyncable("dir/foo", "dir/foo" + mt::gLocalDebris, "/")); + ASSERT_FALSE(mega::isPathSyncable("dir/foo" + mt::gLocalDebris, "dir/foo" + mt::gLocalDebris, "/")); + ASSERT_TRUE(mega::isPathSyncable(mt::gLocalDebris + "bar", mt::gLocalDebris, "/")); + ASSERT_FALSE(mega::isPathSyncable(mt::gLocalDebris + "/", mt::gLocalDebris, "/")); +} + +namespace { + +void test_computeReversePathMatchScore(const std::string& sep) +{ + std::string acc; + ASSERT_EQ(0, mega::computeReversePathMatchScore(acc, "", "", sep)); + ASSERT_EQ(0, mega::computeReversePathMatchScore(acc, "", sep + "a", sep)); + ASSERT_EQ(0, mega::computeReversePathMatchScore(acc, sep + "b", "", sep)); + ASSERT_EQ(0, mega::computeReversePathMatchScore(acc, "a", "b", sep)); + ASSERT_EQ(2, mega::computeReversePathMatchScore(acc, "cc", "cc", sep)); + ASSERT_EQ(0, mega::computeReversePathMatchScore(acc, sep, sep, sep)); + ASSERT_EQ(0, mega::computeReversePathMatchScore(acc, sep + "b", sep + "a", sep)); + ASSERT_EQ(2, mega::computeReversePathMatchScore(acc, sep + "cc", sep + "cc", sep)); + ASSERT_EQ(0, mega::computeReversePathMatchScore(acc, sep + "b", sep + "b" + sep, sep)); + ASSERT_EQ(2, mega::computeReversePathMatchScore(acc, sep + "a" + sep + "b", sep + "a" + sep + "b", sep)); + ASSERT_EQ(2, mega::computeReversePathMatchScore(acc, sep + "a" + sep + "c" + sep + "a" + sep + "b", sep + "a" + sep + "b", sep)); + ASSERT_EQ(3, mega::computeReversePathMatchScore(acc, sep + "aaa" + sep + "bbbb" + sep + "ccc", sep + "aaa" + sep + "bbb" + sep + "ccc", sep)); + ASSERT_EQ(2, mega::computeReversePathMatchScore(acc, "a" + sep + "b", "a" + sep + "b", sep)); + const std::string base = sep + "a" + sep + "b"; + const std::string reference = sep + "c12" + sep + "e34"; + ASSERT_EQ(6, mega::computeReversePathMatchScore(acc, base + reference, base + sep + "a65" + reference, sep)); + ASSERT_EQ(6, mega::computeReversePathMatchScore(acc, base + reference, base + sep + ".debris" + reference, sep)); + ASSERT_EQ(6, mega::computeReversePathMatchScore(acc, base + reference, base + sep + "ab" + reference, sep)); +} + +} + +TEST(Sync, computeReverseMatchScore_oneByteSeparator) +{ + test_computeReversePathMatchScore("/"); +} + +TEST(Sync, computeReverseMatchScore_twoByteSeparator) +{ + test_computeReversePathMatchScore("//"); +} + +TEST(Sync, assignFilesystemIds_whenFilesystemFingerprintsMatchLocalNodes) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode d_0{&d, mega::FOLDERNODE, "d_0"}; + auto ld_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FOLDERNODE, "d_0", d_0.getFingerprint()); + mt::FsNode d_1{&d, mega::FOLDERNODE, "d_1"}; + auto ld_1 = mt::makeLocalNode(*fx.mSync, ld, mega::FOLDERNODE, "d_1", d_1.getFingerprint()); + mt::FsNode f_2{&d, mega::FILENODE, "f_2"}; + auto lf_2 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_2", f_2.getFingerprint()); + + // Level 2 + mt::FsNode f_0_0{&d_0, mega::FILENODE, "f_0_0"}; + auto lf_0_0 = mt::makeLocalNode(*fx.mSync, *ld_0, mega::FILENODE, "f_0_0", f_0_0.getFingerprint()); + mt::FsNode f_0_1{&d_0, mega::FILENODE, "f_0_1"}; + auto lf_0_1 = mt::makeLocalNode(*fx.mSync, *ld_0, mega::FILENODE, "f_0_1", f_0_1.getFingerprint()); + mt::FsNode f_1_0{&d_1, mega::FILENODE, "f_1_0"}; + auto lf_1_0 = mt::makeLocalNode(*fx.mSync, *ld_1, mega::FILENODE, "f_1_0", f_1_0.getFingerprint()); + mt::FsNode d_1_1{&d_1, mega::FOLDERNODE, "d_1_1"}; + auto ld_1_1 = mt::makeLocalNode(*fx.mSync, *ld_1, mega::FOLDERNODE, "d_1_1", d_1_1.getFingerprint()); + + // Level 3 + mt::FsNode f_1_1_0{&d_1_1, mega::FILENODE, "f_1_1_0"}; + auto lf_1_1_0 = mt::makeLocalNode(*fx.mSync, *ld_1_1, mega::FILENODE, "f_1_1_0", f_1_1_0.getFingerprint()); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + // assert that directories have correct fs IDs + ASSERT_EQ(mega::UNDEF, ld.fsid); + ASSERT_EQ(d_0.getFsId(), ld_0->fsid); + ASSERT_EQ(d_1.getFsId(), ld_1->fsid); + ASSERT_EQ(d_1_1.getFsId(), ld_1_1->fsid); + + // assert that all file `LocalNode`s have same fs IDs as the corresponding `FsNode`s + ASSERT_EQ(f_2.getFsId(), lf_2->fsid); + ASSERT_EQ(f_0_0.getFsId(), lf_0_0->fsid); + ASSERT_EQ(f_0_1.getFsId(), lf_0_1->fsid); + ASSERT_EQ(f_1_0.getFsId(), lf_1_0->fsid); + ASSERT_EQ(f_1_1_0.getFsId(), lf_1_1_0->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 8; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_TRUE(fx.iteratorsCorrect(*ld_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*ld_1)); + ASSERT_TRUE(fx.iteratorsCorrect(*ld_1_1)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_2)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0_1)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_1_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_1_1_0)); +} + +TEST(Sync, assignFilesystemIds_whenFilesystemFingerprintsMatchLocalNodes_oppositeDeclarationOrder) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode d_0{&d, mega::FOLDERNODE, "d_0"}; + auto ld_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FOLDERNODE, "d_0", d_0.getFingerprint()); + + // Level 2 + + // reverse order of declaration should still lead to same results (files vs localnodes) + mt::FsNode f_0_1{&d_0, mega::FILENODE, "f_0_1"}; + mt::FsNode f_0_0{&d_0, mega::FILENODE, "f_0_0"}; + auto lf_0_0 = mt::makeLocalNode(*fx.mSync, *ld_0, mega::FILENODE, "f_0_0", f_0_0.getFingerprint()); + auto lf_0_1 = mt::makeLocalNode(*fx.mSync, *ld_0, mega::FILENODE, "f_0_1", f_0_1.getFingerprint()); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + // assert that directories have correct fs IDs + ASSERT_EQ(mega::UNDEF, ld.fsid); + ASSERT_EQ(d_0.getFsId(), ld_0->fsid); + + // assert that all file `LocalNode`s have same fs IDs as the corresponding `FsNode`s + ASSERT_EQ(f_0_0.getFsId(), lf_0_0->fsid); + ASSERT_EQ(f_0_1.getFsId(), lf_0_1->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 3; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_TRUE(fx.iteratorsCorrect(*ld_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0_1)); +} + +TEST(Sync, assignFilesystemIds_whenNoLocalNodesMatchFilesystemFingerprints) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + + // Level 1 + mt::FsNode d_0{&d, mega::FOLDERNODE, "d_0"}; + auto ld_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FOLDERNODE, "d_0"); + mt::FsNode d_1{&d, mega::FOLDERNODE, "d_1"}; + auto ld_1 = mt::makeLocalNode(*fx.mSync, ld, mega::FOLDERNODE, "d_1"); + mt::FsNode f_2{&d, mega::FILENODE, "f_2"}; + auto lf_2 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_2"); + + // Level 2 + mt::FsNode f_0_0{&d_0, mega::FILENODE, "f_0_0"}; + auto lf_0_0 = mt::makeLocalNode(*fx.mSync, *ld_0, mega::FILENODE, "f_0_0"); + mt::FsNode f_0_1{&d_0, mega::FILENODE, "f_0_1"}; + auto lf_0_1 = mt::makeLocalNode(*fx.mSync, *ld_0, mega::FILENODE, "f_0_1"); + mt::FsNode f_1_0{&d_1, mega::FILENODE, "f_1_0"}; + auto lf_1_0 = mt::makeLocalNode(*fx.mSync, *ld_1, mega::FILENODE, "f_1_0"); + mt::FsNode d_1_1{&d_1, mega::FOLDERNODE, "d_1_1"}; + auto ld_1_1 = mt::makeLocalNode(*fx.mSync, *ld_1, mega::FOLDERNODE, "d_1_1"); + + // Level 3 + mt::FsNode f_1_1_0{&d_1_1, mega::FILENODE, "f_1_1_0"}; + auto lf_1_1_0 = mt::makeLocalNode(*fx.mSync, *ld_1_1, mega::FILENODE, "f_1_1_0"); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + // assert that files and directories have invalid fs IDs (no fingerprint matches) + ASSERT_EQ(mega::UNDEF, ld.fsid); + ASSERT_EQ(mega::UNDEF, ld_0->fsid); + ASSERT_EQ(mega::UNDEF, ld_1->fsid); + ASSERT_EQ(mega::UNDEF, ld_1_1->fsid); + ASSERT_EQ(mega::UNDEF, lf_2->fsid); + ASSERT_EQ(mega::UNDEF, lf_0_0->fsid); + ASSERT_EQ(mega::UNDEF, lf_0_1->fsid); + ASSERT_EQ(mega::UNDEF, lf_1_0->fsid); + ASSERT_EQ(mega::UNDEF, lf_1_1_0->fsid); +} + +TEST(Sync, assignFilesystemIds_whenTwoLocalNodesHaveSameFingerprint) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode d_0{&d, mega::FOLDERNODE, "d_0"}; + auto ld_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FOLDERNODE, "d_0", d_0.getFingerprint()); + mt::FsNode d_1{&d, mega::FOLDERNODE, "d_1"}; + auto ld_1 = mt::makeLocalNode(*fx.mSync, ld, mega::FOLDERNODE, "d_1", d_1.getFingerprint()); + mt::FsNode f_2{&d, mega::FILENODE, "f_2"}; + auto lf_2 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_2", f_2.getFingerprint()); + + // Level 2 + mt::FsNode f_0_0{&d_0, mega::FILENODE, "f_0_0"}; + auto lf_0_0 = mt::makeLocalNode(*fx.mSync, *ld_0, mega::FILENODE, "f_0_0", f_0_0.getFingerprint()); + mt::FsNode f_0_1{&d_0, mega::FILENODE, "f_0_1"}; + auto lf_0_1 = mt::makeLocalNode(*fx.mSync, *ld_0, mega::FILENODE, "f_0_1", f_0_1.getFingerprint()); + mt::FsNode f_1_0{&d_1, mega::FILENODE, "f_1_0"}; + auto lf_1_0 = mt::makeLocalNode(*fx.mSync, *ld_1, mega::FILENODE, "f_1_0", f_1_0.getFingerprint()); + mt::FsNode d_1_1{&d_1, mega::FOLDERNODE, "d_1_1"}; + auto ld_1_1 = mt::makeLocalNode(*fx.mSync, *ld_1, mega::FOLDERNODE, "d_1_1", d_1_1.getFingerprint()); + + // Level 3 + mt::FsNode f_1_1_0{&d_1_1, mega::FILENODE, "f_1_1_0"}; + f_1_1_0.assignContentFrom(f_1_0); + auto lf_1_1_0 = mt::makeLocalNode(*fx.mSync, *ld_1_1, mega::FILENODE, "f_1_1_0", f_1_1_0.getFingerprint()); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + // assert that directories have correct fs IDs + ASSERT_EQ(mega::UNDEF, ld.fsid); + ASSERT_EQ(d_0.getFsId(), ld_0->fsid); + ASSERT_EQ(d_1.getFsId(), ld_1->fsid); + ASSERT_EQ(d_1_1.getFsId(), ld_1_1->fsid); + + // assert that all file `LocalNode`s have same fs IDs as the corresponding `FsNode`s + ASSERT_EQ(f_2.getFsId(), lf_2->fsid); + ASSERT_EQ(f_0_0.getFsId(), lf_0_0->fsid); + ASSERT_EQ(f_0_1.getFsId(), lf_0_1->fsid); + ASSERT_EQ(f_1_0.getFsId(), lf_1_0->fsid); + ASSERT_EQ(f_1_1_0.getFsId(), lf_1_1_0->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 8; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_TRUE(fx.iteratorsCorrect(*ld_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*ld_1)); + ASSERT_TRUE(fx.iteratorsCorrect(*ld_1_1)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_2)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0_1)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_1_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_1_1_0)); +} + +TEST(Sync, assignFilesystemIds_whenSomeFsIdIsNotValid) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode f_0{&d, mega::FILENODE, "f_0"}; + f_0.setFsId(mega::UNDEF); + auto lf_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_0", f_0.getFingerprint()); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_FALSE(success); + + // assert that directories have correct fs IDs + ASSERT_EQ(mega::UNDEF, ld.fsid); + + // file node must have undef fs ID + ASSERT_EQ(mega::UNDEF, lf_0->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 0; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_FALSE(fx.iteratorsCorrect(*lf_0)); +} + +TEST(Sync, assignFilesystemIds_whenSomeFileCannotBeOpened) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode f_0{&d, mega::FILENODE, "f_0"}; + f_0.setOpenable(false); + auto lf_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_0", f_0.getFingerprint()); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_FALSE(success); +} + +TEST(Sync, assignFilesystemIds_whenRootDirCannotBeOpened) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + d.setOpenable(false); + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode f_0{&d, mega::FILENODE, "f_0"}; + auto lf_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_0", f_0.getFingerprint()); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_FALSE(success); +} + +TEST(Sync, assignFilesystemIds_whenSubDirCannotBeOpened) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode f_0{&d, mega::FILENODE, "f_0"}; + auto lf_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_0", f_0.getFingerprint()); + mt::FsNode d_0{&d, mega::FOLDERNODE, "d_0"}; + d_0.setOpenable(false); + auto ld_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FOLDERNODE, "d_0", d_0.getFingerprint()); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_FALSE(success); + + // assert that directories have invalid fs IDs + ASSERT_EQ(mega::UNDEF, ld.fsid); + ASSERT_EQ(mega::UNDEF, ld_0->fsid); + + // check file nodes + ASSERT_EQ(f_0.getFsId(), lf_0->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 1; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0)); + ASSERT_FALSE(fx.iteratorsCorrect(*ld_0)); +} + +TEST(Sync, assignFilesystemIds_forSingleFile) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode f_0{&d, mega::FILENODE, "f_0"}; + auto lf_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_0", f_0.getFingerprint()); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + ASSERT_EQ(mega::UNDEF, ld.fsid); + ASSERT_EQ(f_0.getFsId(), lf_0->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 1; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0)); +} + +TEST(Sync, assignFilesystemIds_whenPathIsNotSyncableThroughApp) +{ + Fixture fx{"d"}; + fx.mApp.addNotSyncablePath("d/f_1"); + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode f_0{&d, mega::FILENODE, "f_0"}; + auto lf_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_0", f_0.getFingerprint()); + mt::FsNode f_1{&d, mega::FILENODE, "f_1"}; + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + ASSERT_EQ(mega::UNDEF, ld.fsid); + ASSERT_EQ(f_0.getFsId(), lf_0->fsid); + + constexpr std::size_t fileCount = 1; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0)); +} + +TEST(Sync, assignFilesystemIds_whenDebrisIsPartOfFiles) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode f_0{&d, mega::FILENODE, "f_0"}; + auto lf_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_0", f_0.getFingerprint()); + mt::FsNode d_1{&d, mega::FOLDERNODE, mt::gLocalDebris}; + + // Level 2 + mt::FsNode f_1_0{&d_1, mega::FILENODE, "f_1_0"}; + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + // assert that directories have correct fs IDs + ASSERT_EQ(mega::UNDEF, ld.fsid); + + // assert that all file `LocalNode`s have same fs IDs as the corresponding `FsNode`s + ASSERT_EQ(f_0.getFsId(), lf_0->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 1; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0)); +} + +TEST(Sync, assignFilesystemIds_preferredPathMatchAssignsFinalFsId) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode f_0{&d, mega::FILENODE, "f_0"}; + auto lf_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_0", f_0.getFingerprint()); + mt::FsNode d_1{&d, mega::FOLDERNODE, "d_1"}; + + // the local node for f_1_0 is still at level 1 but the file moved to level 2 under a new folder (d_1) + auto lf_1 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_1_0", f_0.getFingerprint()); + + // Level 2 + mt::FsNode f_1_0{&d_1, mega::FILENODE, "f_1_0"}; + f_1_0.assignContentFrom(f_0); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + // assert that directories have correct fs IDs + ASSERT_EQ(mega::UNDEF, ld.fsid); + + // assert that all file `LocalNode`s have same fs IDs as the corresponding `FsNode`s + ASSERT_EQ(f_0.getFsId(), lf_0->fsid); + ASSERT_EQ(f_1_0.getFsId(), lf_1->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 2; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_1)); +} + +TEST(Sync, assignFilesystemIds_whenFolderWasMoved_differentLeafName) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode d_0_renamed{&d, mega::FOLDERNODE, "d_0_renamed"}; + auto ld_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FOLDERNODE, "d_0", d_0_renamed.getFingerprint()); + + // Level 2 + mt::FsNode f_0_0{&d_0_renamed, mega::FILENODE, "f_0_0"}; + auto lf_0_0 = mt::makeLocalNode(*fx.mSync, *ld_0, mega::FILENODE, "f_0_0", f_0_0.getFingerprint()); + mt::FsNode f_0_1{&d_0_renamed, mega::FILENODE, "f_0_1"}; + auto lf_0_1 = mt::makeLocalNode(*fx.mSync, *ld_0, mega::FILENODE, "f_0_1", f_0_1.getFingerprint()); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + // assert that directories have correct fs IDs + ASSERT_EQ(mega::UNDEF, ld.fsid); + ASSERT_EQ(mega::UNDEF, ld_0->fsid); + + // assert that all file `LocalNode`s have same fs IDs as the corresponding `FsNode`s + ASSERT_EQ(f_0_0.getFsId(), lf_0_0->fsid); + ASSERT_EQ(f_0_1.getFsId(), lf_0_1->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 2; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_FALSE(fx.iteratorsCorrect(*ld_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0_1)); +} + +TEST(Sync, assignFilesystemIds_whenFolderWasMoved_sameLeafName) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode d_0_renamed{&d, mega::FOLDERNODE, "d_0_renamed"}; + auto ld_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FOLDERNODE, "d_0", d_0_renamed.getFingerprint()); + + // Level 2 + mt::FsNode d_0{&d_0_renamed, mega::FOLDERNODE, "d_0"}; + + // Level 3 + mt::FsNode f_0_0{&d_0, mega::FILENODE, "f_0_0"}; + auto lf_0_0 = mt::makeLocalNode(*fx.mSync, *ld_0, mega::FILENODE, "f_0_0", f_0_0.getFingerprint()); + mt::FsNode f_0_1{&d_0, mega::FILENODE, "f_0_1"}; + auto lf_0_1 = mt::makeLocalNode(*fx.mSync, *ld_0, mega::FILENODE, "f_0_1", f_0_1.getFingerprint()); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + // assert that directories have correct fs IDs + ASSERT_EQ(mega::UNDEF, ld.fsid); + ASSERT_EQ(d_0.getFsId(), ld_0->fsid); + + // assert that all file `LocalNode`s have same fs IDs as the corresponding `FsNode`s + ASSERT_EQ(f_0_0.getFsId(), lf_0_0->fsid); + ASSERT_EQ(f_0_1.getFsId(), lf_0_1->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 3; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_TRUE(fx.iteratorsCorrect(*ld_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0_0)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0_1)); +} + +TEST(Sync, assignFilesystemIds_whenFileWasCopied) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode f_0{&d, mega::FILENODE, "f_0"}; + mt::FsNode d_0{&d, mega::FOLDERNODE, "d_0"}; + + auto lf_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_0", f_0.getFingerprint()); + + // Level 2 + mt::FsNode f_1{&d_0, mega::FILENODE, "f_0"}; // same name as `f_0` + f_1.assignContentFrom(f_0); // file was copied maintaining mtime + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + // assert that directories have correct fs IDs + ASSERT_EQ(mega::UNDEF, ld.fsid); + + // assert that all file `LocalNode`s have same fs IDs as the corresponding `FsNode`s + ASSERT_EQ(f_0.getFsId(), lf_0->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 1; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0)); +} + +TEST(Sync, assignFilesystemIds_whenFileWasMoved_differentLeafName) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode f_0{&d, mega::FILENODE, "f_0_renamed"}; + auto lf_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_0", f_0.getFingerprint()); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + ASSERT_EQ(mega::UNDEF, ld.fsid); + ASSERT_EQ(mega::UNDEF, lf_0->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 0; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_FALSE(fx.iteratorsCorrect(*lf_0)); +} + +TEST(Sync, assignFilesystemIds_whenFileWasMoved_sameLeafName) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode d_0{&d, mega::FOLDERNODE, "d_0"}; + + // Level 2 + mt::FsNode f_0{&d_0, mega::FILENODE, "f_0"}; + + auto lf_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_0", f_0.getFingerprint()); // still at level 1 + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + ASSERT_EQ(mega::UNDEF, ld.fsid); + ASSERT_EQ(f_0.getFsId(), lf_0->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 1; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_TRUE(fx.iteratorsCorrect(*lf_0)); +} + +TEST(Sync, assignFilesystemIds_emptyFolderStaysUnassigned) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode d_0{&d, mega::FOLDERNODE, "d_0"}; + auto ld_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FOLDERNODE, "d_0"); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_TRUE(success); + + ASSERT_EQ(mega::UNDEF, ld.fsid); + ASSERT_EQ(mega::UNDEF, ld_0->fsid); + + // assert that the local node map is correct + constexpr std::size_t fileCount = 0; + ASSERT_EQ(fileCount, fx.mLocalNodes.size()); + + ASSERT_FALSE(fx.iteratorsCorrect(ld)); + ASSERT_FALSE(fx.iteratorsCorrect(*ld_0)); +} + +#ifdef NDEBUG +TEST(Sync, assignFilesystemIds_whenRootPathIsNotAFolder_hittingAssert) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FILENODE, "d"}; + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_FALSE(success); +} +#endif + +#ifdef NDEBUG +TEST(Sync, assignFilesystemIds_whenFileTypeIsUnexpected_hittingAssert) +{ + Fixture fx{"d"}; + + // Level 0 + mt::FsNode d{nullptr, mega::FOLDERNODE, "d"}; + mega::LocalNode& ld = *fx.mSync->localroot; + static_cast(ld) = d.getFingerprint(); + + // Level 1 + mt::FsNode f_0{&d, mega::TYPE_UNKNOWN, "f_0"}; + auto lf_0 = mt::makeLocalNode(*fx.mSync, ld, mega::FILENODE, "f_0", f_0.getFingerprint()); + + mt::collectAllFsNodes(fx.mFsNodes, d); + + const auto success = mega::assignFilesystemIds(*fx.mSync, fx.mApp, fx.mFsAccess, fx.mLocalNodes, "d/" + mt::gLocalDebris, "/"); + + ASSERT_FALSE(success); +} +#endif + +#endif diff --git a/tests/unit/constants.h b/tests/unit/constants.h new file mode 100644 index 0000000000..0e5438f498 --- /dev/null +++ b/tests/unit/constants.h @@ -0,0 +1,27 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +#pragma once + +#include + +namespace mt { + +const std::string gLocalDebris = ".debris"; + +} // mt diff --git a/tests/unit/main.cpp b/tests/unit/main.cpp index 7cbdd17bb7..678eedfc77 100644 --- a/tests/unit/main.cpp +++ b/tests/unit/main.cpp @@ -1,3 +1,21 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + #include int main (int argc, char *argv[]) diff --git a/tests/unit/utils.cpp b/tests/unit/utils.cpp new file mode 100644 index 0000000000..1fde776fb7 --- /dev/null +++ b/tests/unit/utils.cpp @@ -0,0 +1,165 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +#include "utils.h" + +#include + +#include + +#include "constants.h" +#include "DefaultedFileSystemAccess.h" +#include "FsNode.h" + +namespace mt { + +namespace { + +std::mt19937 gRandomGenerator{1}; + +void collectAllFsNodesImpl(std::map& nodes, const mt::FsNode& node) +{ + const auto path = node.getPath(); + assert(nodes.find(path) == nodes.end()); + nodes[path] = &node; + if (node.getType() == mega::FOLDERNODE) + { + for (const auto child : node.getChildren()) + { + collectAllFsNodesImpl(nodes, *child); + } + } +} + +#ifdef ENABLE_SYNC +void initializeLocalNode(mega::LocalNode& l, mega::Sync& sync, mega::LocalNode* parent, mega::handlelocalnode_map& fsidnodes, + mega::nodetype_t type, const std::string& name, const mega::FileFingerprint& ffp) +{ + l.sync = &sync; + l.parent = parent; + l.type = type; + l.name = name; + l.localname = name; + l.slocalname = new std::string{name}; + l.fsid_it = fsidnodes.end(); + l.setfsid(nextFsId(), fsidnodes); + if (parent) + { + assert(parent->children.find(&l.name) == parent->children.end()); + parent->children[&l.name] = &l; + } + static_cast(l) = ffp; +} +#endif + +} // anonymous + +mega::handle nextFsId() +{ + static mega::handle fsId{0}; + return fsId++; +} + +std::shared_ptr makeClient(mega::MegaApp& app, mega::FileSystemAccess& fsaccess) +{ + struct HttpIo : mega::HttpIO + { + void addevents(mega::Waiter*, int) override {} + void post(struct mega::HttpReq*, const char* = NULL, unsigned = 0) override {} + void cancel(mega::HttpReq*) override {} + m_off_t postpos(void*) override { return {}; } + bool doio(void) override { return {}; } + void setuseragent(std::string*) override {} + }; + + auto httpio = new HttpIo; + + auto deleter = [httpio](mega::MegaClient* client) + { + delete client; + delete httpio; + }; + + std::shared_ptr client{new mega::MegaClient{ + &app, nullptr, httpio, &fsaccess, nullptr, nullptr, "XXX", "unit_test" + }, deleter}; + + return client; +} + +mega::Node& makeNode(mega::MegaClient& client, const mega::nodetype_t type, const mega::handle handle, mega::Node* const parent) +{ + assert(client.nodes.find(handle) == client.nodes.end()); + mega::node_vector dp; + const auto ph = parent ? parent->nodehandle : mega::UNDEF; + auto n = new mega::Node{&client, &dp, handle, ph, type, -1, mega::UNDEF, nullptr, 0}; // owned by the client + n->setkey(reinterpret_cast(std::string((type == mega::FILENODE) ? mega::FILENODEKEYLENGTH : mega::FOLDERNODEKEYLENGTH, 'X').c_str())); + return *n; +} + +#ifdef ENABLE_SYNC +std::unique_ptr makeSync(mega::MegaClient& client, const std::string& localname) +{ + auto sync = std::unique_ptr{new mega::Sync}; + sync->localroot = std::unique_ptr{new mega::LocalNode}; + sync->state = mega::SYNC_CANCELED; // to avoid the asssertion in Sync::~Sync() + initializeLocalNode(*sync->localroot, *sync, nullptr, client.fsidnode, mega::FOLDERNODE, localname, {}); + sync->localdebris = sync->localroot->localname + "/" + mt::gLocalDebris; + sync->client = &client; + client.syncs.push_front(sync.get()); + sync->sync_it = client.syncs.begin(); + return sync; +} + +std::unique_ptr makeLocalNode(mega::Sync& sync, mega::LocalNode& parent, + mega::nodetype_t type, const std::string& name, + const mega::FileFingerprint& ffp) +{ + auto l = std::unique_ptr{new mega::LocalNode}; + initializeLocalNode(*l, sync, &parent, sync.client->fsidnode, type, name, ffp); + return l; +} +#endif + +void collectAllFsNodes(std::map& nodes, const mt::FsNode& node) +{ + const auto path = node.getPath(); + assert(nodes.find(path) == nodes.end()); + nodes[path] = &node; + if (node.getType() == mega::FOLDERNODE) + { + for (const auto child : node.getChildren()) + { + collectAllFsNodes(nodes, *child); + } + } +} + +std::uint16_t nextRandomInt() +{ + std::uniform_int_distribution dist{0, std::numeric_limits::max()}; + return dist(gRandomGenerator); +} + +mega::byte nextRandomByte() +{ + std::uniform_int_distribution dist{0, std::numeric_limits::max()}; + return static_cast(dist(gRandomGenerator)); +} + +} // mt diff --git a/tests/unit/utils.h b/tests/unit/utils.h new file mode 100644 index 0000000000..af77a2578a --- /dev/null +++ b/tests/unit/utils.h @@ -0,0 +1,52 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace mt { + +class FsNode; + +mega::handle nextFsId(); + +std::shared_ptr makeClient(mega::MegaApp& app, mega::FileSystemAccess& fsaccess); + +mega::Node& makeNode(mega::MegaClient& client, mega::nodetype_t type, mega::handle handle, mega::Node* parent = nullptr); + +#ifdef ENABLE_SYNC +std::unique_ptr makeSync(mega::MegaClient& client, const std::string& localname); + +std::unique_ptr makeLocalNode(mega::Sync& sync, mega::LocalNode& parent, + mega::nodetype_t type, const std::string& name, + const mega::FileFingerprint& ffp = {}); +#endif + +void collectAllFsNodes(std::map& nodes, const mt::FsNode& node); + +std::uint16_t nextRandomInt(); + +mega::byte nextRandomByte(); + +} // mt diff --git a/tests/unit/utils_test.cpp b/tests/unit/utils_test.cpp new file mode 100644 index 0000000000..b7a92f1a14 --- /dev/null +++ b/tests/unit/utils_test.cpp @@ -0,0 +1,36 @@ +/** + * (c) 2019 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +#include +#include + +#include + +#include + +TEST(utils, hashCombine_integer) +{ + size_t hash = 0; + mega::hashCombine(hash, 42); +#ifdef _WIN32 + // MSVC's std::hash gives different values than that of gcc/clang + ASSERT_EQ(sizeof(hash) == 4 ? 286246808ul : 10203658983813110072ull, hash); +#else + ASSERT_EQ(2654435811ull, hash); +#endif +}