diff options
author | Joel Fischer <joeljfischer@gmail.com> | 2020-08-13 16:02:57 -0400 |
---|---|---|
committer | Joel Fischer <joeljfischer@gmail.com> | 2020-08-13 16:02:57 -0400 |
commit | b8c36591299ff43b7792650ee14f73ac6979a701 (patch) | |
tree | c6b05480c0ef1f6819557dd75fd29e6200d7e202 | |
parent | 062957f2c2963b30f4719909a1f795dcb7ca5961 (diff) | |
download | sdl_ios-b8c36591299ff43b7792650ee14f73ac6979a701.tar.gz |
*VERY* In-progress updates
-rw-r--r-- | SmartDeviceLink-iOS.xcodeproj/project.pbxproj | 16 | ||||
-rw-r--r-- | SmartDeviceLink/SDLTextAndGraphicManager.m | 420 | ||||
-rw-r--r-- | SmartDeviceLink/SDLTextAndGraphicState.h | 39 | ||||
-rw-r--r-- | SmartDeviceLink/SDLTextAndGraphicState.m | 34 | ||||
-rw-r--r-- | SmartDeviceLink/SDLTextAndGraphicUpdateOperation.h | 40 | ||||
-rw-r--r-- | SmartDeviceLink/SDLTextAndGraphicUpdateOperation.m | 427 |
6 files changed, 594 insertions, 382 deletions
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj index 1bae109fe..c86df5d47 100644 --- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj +++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj @@ -441,6 +441,10 @@ 4A457DD924A5137100386CBA /* SDLLifecycleProtocolHandlerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A457DD824A5137100386CBA /* SDLLifecycleProtocolHandlerSpec.m */; }; 4A4AD8A424894260008FC414 /* TestOldConfigurationUpdateManagerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A4AD8A324894260008FC414 /* TestOldConfigurationUpdateManagerDelegate.m */; }; 4A4AD8A724894270008FC414 /* TestNewConfigurationUpdateManagerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A4AD8A624894270008FC414 /* TestNewConfigurationUpdateManagerDelegate.m */; }; + 4A89AE1824E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A89AE1624E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.h */; }; + 4A89AE1924E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A89AE1724E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.m */; }; + 4A89AE1C24E595410017EBDC /* SDLTextAndGraphicState.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A89AE1A24E595410017EBDC /* SDLTextAndGraphicState.h */; }; + 4A89AE1D24E595410017EBDC /* SDLTextAndGraphicState.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A89AE1B24E595410017EBDC /* SDLTextAndGraphicState.m */; }; 4A99D00E247576B7009B43E6 /* SDLTextField+ScreenManagerExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A99D00C247576B7009B43E6 /* SDLTextField+ScreenManagerExtensions.h */; }; 4A99D00F247576B7009B43E6 /* SDLTextField+ScreenManagerExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A99D00D247576B7009B43E6 /* SDLTextField+ScreenManagerExtensions.m */; }; 4A99D0122475773C009B43E6 /* SDLImageField+ScreenManagerExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A99D0102475773C009B43E6 /* SDLImageField+ScreenManagerExtensions.h */; }; @@ -2192,6 +2196,10 @@ 4A4AD8A324894260008FC414 /* TestOldConfigurationUpdateManagerDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = TestOldConfigurationUpdateManagerDelegate.m; path = DevAPISpecs/TestOldConfigurationUpdateManagerDelegate.m; sourceTree = "<group>"; }; 4A4AD8A524894270008FC414 /* TestNewConfigurationUpdateManagerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestNewConfigurationUpdateManagerDelegate.h; sourceTree = "<group>"; }; 4A4AD8A624894270008FC414 /* TestNewConfigurationUpdateManagerDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestNewConfigurationUpdateManagerDelegate.m; sourceTree = "<group>"; }; + 4A89AE1624E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLTextAndGraphicUpdateOperation.h; sourceTree = "<group>"; }; + 4A89AE1724E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLTextAndGraphicUpdateOperation.m; sourceTree = "<group>"; }; + 4A89AE1A24E595410017EBDC /* SDLTextAndGraphicState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLTextAndGraphicState.h; sourceTree = "<group>"; }; + 4A89AE1B24E595410017EBDC /* SDLTextAndGraphicState.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLTextAndGraphicState.m; sourceTree = "<group>"; }; 4A99D00C247576B7009B43E6 /* SDLTextField+ScreenManagerExtensions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SDLTextField+ScreenManagerExtensions.h"; sourceTree = "<group>"; }; 4A99D00D247576B7009B43E6 /* SDLTextField+ScreenManagerExtensions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SDLTextField+ScreenManagerExtensions.m"; sourceTree = "<group>"; }; 4A99D0102475773C009B43E6 /* SDLImageField+ScreenManagerExtensions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SDLImageField+ScreenManagerExtensions.h"; sourceTree = "<group>"; }; @@ -4221,6 +4229,10 @@ children = ( 5D0A7372203F0C730001595D /* SDLTextAndGraphicManager.h */, 5D0A7373203F0C730001595D /* SDLTextAndGraphicManager.m */, + 4A89AE1A24E595410017EBDC /* SDLTextAndGraphicState.h */, + 4A89AE1B24E595410017EBDC /* SDLTextAndGraphicState.m */, + 4A89AE1624E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.h */, + 4A89AE1724E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.m */, ); name = "Text and Graphic"; sourceTree = "<group>"; @@ -6803,9 +6815,11 @@ 884E702321FBA952008D53BA /* SDLAppServiceType.h in Headers */, DAC572571D1067270004288B /* SDLTouchManager.h in Headers */, 5D61FE0D1A84238C00846EE7 /* SDLVrCapabilities.h in Headers */, + 4A89AE1824E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.h in Headers */, EEB1932E205028B700A8940C /* SDLControlFramePayloadTransportEventUpdate.h in Headers */, EE798CA420561210008EDE8E /* SDLSecondaryTransportManager.h in Headers */, 5DBF06271E64A91D00A5CF03 /* SDLLogFileModule.h in Headers */, + 4A89AE1C24E595410017EBDC /* SDLTextAndGraphicState.h in Headers */, 5D61FC531A84238C00846EE7 /* SDLButtonEventMode.h in Headers */, 88E6F1AD220E19DF006156F9 /* SDLMediaServiceData.h in Headers */, 1FF7DAB61F75B27300B46C30 /* SDLFocusableItemLocatorType.h in Headers */, @@ -7809,6 +7823,7 @@ 9FE2471222D77AA400F8D2FC /* SDLCreateWindowResponse.m in Sources */, 5D61FDBC1A84238C00846EE7 /* SDLSystemAction.m in Sources */, 5D61FC381A84238C00846EE7 /* SDLAlert.m in Sources */, + 4A89AE1D24E595410017EBDC /* SDLTextAndGraphicState.m in Sources */, 88AAD4BD2211B76800F1E6D7 /* SDLMediaServiceManifest.m in Sources */, 884E701C21FB8D0F008D53BA /* SDLPublishAppService.m in Sources */, 8831FA49220235B000B8FFB7 /* SDLAppServicesCapabilities.m in Sources */, @@ -7913,6 +7928,7 @@ 88EED83F1F33C5A400E6C42E /* SDLSendHapticData.m in Sources */, 5D16545B1D3E7A1600554D93 /* SDLLifecycleManager.m in Sources */, E9C32B971AB20BA200F283AF /* SDLTimer.m in Sources */, + 4A89AE1924E589D60017EBDC /* SDLTextAndGraphicUpdateOperation.m in Sources */, 10893C172417D78300BA347E /* SDLIconArchiveFile.m in Sources */, 5D61FCB61A84238C00846EE7 /* SDLGetVehicleData.m in Sources */, 5DAB5F572098E5D100A020C8 /* SDLProtocolConstants.m in Sources */, diff --git a/SmartDeviceLink/SDLTextAndGraphicManager.m b/SmartDeviceLink/SDLTextAndGraphicManager.m index 6324e13e3..624068602 100644 --- a/SmartDeviceLink/SDLTextAndGraphicManager.m +++ b/SmartDeviceLink/SDLTextAndGraphicManager.m @@ -26,6 +26,8 @@ #import "SDLSystemCapability.h" #import "SDLSystemCapabilityManager.h" #import "SDLTextField.h" +#import "SDLTextAndGraphicUpdateOperation.h" +#import "SDLTextAndGraphicState.h" #import "SDLWindowCapability.h" #import "SDLWindowCapability+ScreenManagerExtensions.h" @@ -43,15 +45,7 @@ NS_ASSUME_NONNULL_BEGIN */ @property (strong, nonatomic) SDLShow *currentScreenData; -/** - This is the "full" update, including both text and image names, whether or not that will succeed at the moment (e.g. if images are in the process of uploading) - */ -@property (strong, nonatomic, nullable) SDLShow *inProgressUpdate; -@property (copy, nonatomic, nullable) SDLTextAndGraphicUpdateCompletionHandler inProgressHandler; - -@property (strong, nonatomic, nullable) SDLShow *queuedImageUpdate; -@property (assign, nonatomic) BOOL hasQueuedUpdate; -@property (copy, nonatomic, nullable) SDLTextAndGraphicUpdateCompletionHandler queuedUpdateHandler; +@property (strong, nonatomic) NSOperationQueue *transactionQueue; @property (strong, nonatomic, nullable) SDLWindowCapability *windowCapability; @property (strong, nonatomic, nullable) SDLHMILevel currentLevel; @@ -72,6 +66,7 @@ NS_ASSUME_NONNULL_BEGIN _connectionManager = connectionManager; _fileManager = fileManager; _systemCapabilityManager = systemCapabilityManager; + _transactionQueue = [self sdl_newTransactionQueue]; _alignment = SDLTextAlignmentCenter; @@ -118,11 +113,7 @@ NS_ASSUME_NONNULL_BEGIN _textField4Type = nil; _currentScreenData = [[SDLShow alloc] init]; - _inProgressUpdate = nil; - _inProgressHandler = nil; - _queuedImageUpdate = nil; - _hasQueuedUpdate = NO; - _queuedUpdateHandler = nil; + _transactionQueue = [self sdl_newTransactionQueue]; _windowCapability = nil; _currentLevel = SDLHMILevelNone; _blankArtwork = nil; @@ -130,6 +121,28 @@ NS_ASSUME_NONNULL_BEGIN _isDirty = NO; } +- (NSOperationQueue *)sdl_newTransactionQueue { + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + queue.name = @"SDLTextAndGraphicManager Transaction Queue"; + queue.maxConcurrentOperationCount = 1; + queue.qualityOfService = NSQualityOfServiceUserInitiated; + queue.suspended = YES; + + return queue; +} + +/// Suspend the queue if the soft button capabilities are nil (we assume that soft buttons are not supported) +/// OR if the HMI level is NONE since we want to delay sending RPCs until we're in non-NONE +- (void)sdl_updateTransactionQueueSuspended { + if (self.windowCapability == nil || [self.currentLevel isEqualToEnum:SDLHMILevelNone]) { + SDLLogD(@"Suspending the transaction queue. Current HMI level is NONE: %@, window capabilities are nil: %@", ([self.currentLevel isEqualToEnum:SDLHMILevelNone] ? @"YES" : @"NO"), (self.windowCapability == nil ? @"YES" : @"NO")); + self.transactionQueue.suspended = YES; + } else { + SDLLogD(@"Starting the transaction queue"); + self.transactionQueue.suspended = NO; + } +} + #pragma mark - Upload / Send - (void)updateWithCompletionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)handler { @@ -151,336 +164,28 @@ NS_ASSUME_NONNULL_BEGIN - (void)sdl_updateWithCompletionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)handler { SDLLogD(@"Updating text and graphics"); - if (self.inProgressUpdate != nil) { - SDLLogV(@"In progress update exists, queueing update"); - // If we already have a pending update, we're going to tell the old handler that it was superseded by a new update and then return - if (self.queuedUpdateHandler != nil) { - SDLLogV(@"Queued update already exists, superseding previous queued update"); - self.queuedUpdateHandler([NSError sdl_textAndGraphicManager_pendingUpdateSuperseded]); - self.queuedUpdateHandler = nil; - } - - if (handler != nil) { - self.queuedUpdateHandler = handler; - } else { - self.hasQueuedUpdate = YES; - } + if (self.transactionQueue.operationCount > 0) { + SDLLogV(@"Transactions already exist, cancelling them"); + [self.transactionQueue cancelAllOperations]; return; } - SDLShow *fullShow = [[SDLShow alloc] init]; - fullShow.alignment = self.alignment; - fullShow.metadataTags = [[SDLMetadataTags alloc] init]; - fullShow = [self sdl_assembleShowText:fullShow]; - fullShow = [self sdl_assembleShowImages:fullShow]; - - self.inProgressHandler = handler; - - __weak typeof(self)weakSelf = self; - if (!([self sdl_shouldUpdatePrimaryImage] || [self sdl_shouldUpdateSecondaryImage])) { - SDLLogV(@"No images to send, sending text"); - // If there are no images to update, just send the text - self.inProgressUpdate = [self sdl_extractTextFromShow:fullShow]; - } else if (![self sdl_artworkNeedsUpload:self.primaryGraphic] && ![self sdl_artworkNeedsUpload:self.secondaryGraphic]) { - SDLLogV(@"Images already uploaded, sending full update"); - // The files to be updated are already uploaded, send the full show immediately - self.inProgressUpdate = fullShow; - } else { - SDLLogV(@"Images need to be uploaded, sending text and uploading images"); - - // We need to upload or queue the upload of the images - // Send the text immediately - self.inProgressUpdate = [self sdl_extractTextFromShow:fullShow]; - - // Start uploading the images - __block SDLShow *thisUpdate = fullShow; - [self sdl_uploadImagesWithCompletionHandler:^(NSError *_Nullable error) { - __strong typeof(weakSelf) strongSelf = weakSelf; - - if (error != nil) { - SDLShow *showWithGraphics = [self sdl_createImageOnlyShowWithPrimaryArtwork:self.primaryGraphic secondaryArtwork:self.secondaryGraphic]; - if (showWithGraphics != nil) { - SDLLogW(@"Some images failed to upload. Sending update with the successfully uploaded images"); - self.inProgressUpdate = showWithGraphics; - } else { - SDLLogE(@"All images failed to upload. No graphics to show, skipping update."); - self.inProgressUpdate = nil; - } - return; - } - - // Check if queued image update still matches our images (there could have been a new Show in the meantime) and send a new update if it does. Since the images will already be on the head unit, the whole show will be sent - // TODO: Send delete if it doesn't? - if ([strongSelf sdl_showImages:thisUpdate isEqualToShowImages:strongSelf.queuedImageUpdate]) { - SDLLogV(@"Queued image update matches the images we need, sending update"); - return [strongSelf sdl_updateWithCompletionHandler:strongSelf.inProgressHandler]; - } else { - SDLLogV(@"Queued image update does not match the images we need, skipping update"); - } - }]; - // When the images are done uploading, send another show with the images - self.queuedImageUpdate = fullShow; - } - - if (self.inProgressUpdate == nil) { return; } - - [self.connectionManager sendConnectionRequest:self.inProgressUpdate withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) { - __strong typeof(weakSelf) strongSelf = weakSelf; - SDLLogD(@"Text and Graphic update completed"); - - // TODO: Monitor and delete old images when space is low? - if (response.success) { - [strongSelf sdl_updateCurrentScreenDataFromShow:(SDLShow *)request]; - } - - strongSelf.inProgressUpdate = nil; - if (strongSelf.inProgressHandler != nil) { - strongSelf.inProgressHandler(error); - strongSelf.inProgressHandler = nil; - } - - if (strongSelf.hasQueuedUpdate) { - SDLLogV(@"Queued update exists, sending another update"); - [strongSelf sdl_updateWithCompletionHandler:[strongSelf.queuedUpdateHandler copy]]; - strongSelf.queuedUpdateHandler = nil; - strongSelf.hasQueuedUpdate = NO; - } - }]; -} - -- (void)sdl_uploadImagesWithCompletionHandler:(void (^)(NSError *_Nullable error))handler { - NSMutableArray<SDLArtwork *> *artworksToUpload = [NSMutableArray array]; - if ([self sdl_shouldUpdatePrimaryImage] && !self.primaryGraphic.isStaticIcon) { - [artworksToUpload addObject:self.primaryGraphic]; - } - if ([self sdl_shouldUpdateSecondaryImage] && !self.secondaryGraphic.isStaticIcon) { - [artworksToUpload addObject:self.secondaryGraphic]; - } - - if (artworksToUpload.count == 0 - && (self.primaryGraphic.isStaticIcon || self.secondaryGraphic.isStaticIcon)) { - SDLLogD(@"Upload attempted on static icons, sending them without upload instead"); - handler(nil); - } - - [self.fileManager uploadArtworks:artworksToUpload completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) { - if (error != nil) { - SDLLogW(@"Text and graphic manager artwork failed to upload with error: %@", error.localizedDescription); - } - - handler(error); - }]; -} - -#pragma mark - Assembly of Shows - -#pragma mark Images - -- (SDLShow *)sdl_assembleShowImages:(SDLShow *)show { - if (![self sdl_shouldUpdatePrimaryImage] && ![self sdl_shouldUpdateSecondaryImage]) { - return show; - } - - if ([self sdl_shouldUpdatePrimaryImage]) { - show.graphic = self.primaryGraphic.imageRPC; - } - if ([self sdl_shouldUpdateSecondaryImage]) { - show.secondaryGraphic = self.secondaryGraphic.imageRPC; - } - - return show; -} - -#pragma mark Text - -- (SDLShow *)sdl_assembleShowText:(SDLShow *)show { - [self sdl_setBlankTextFieldsWithShow:show]; - - if (self.mediaTrackTextField != nil && [self sdl_shouldUpdateMediaTextField]) { - show.mediaTrack = self.mediaTrackTextField; - } else { - show.mediaTrack = @""; - } - - if (self.title != nil && [self sdl_shouldUpdateTitleField]) { - show.templateTitle = self.title; - } else { - show.templateTitle = @""; - } - - NSArray *nonNilFields = [self sdl_findNonNilTextFields]; - if (nonNilFields.count == 0) { return show; } - - NSUInteger numberOfLines = self.windowCapability.maxNumberOfMainFieldLines; - if (numberOfLines == 1) { - show = [self sdl_assembleOneLineShowText:show withShowFields:nonNilFields]; - } else if (numberOfLines == 2) { - show = [self sdl_assembleTwoLineShowText:show]; - } else if (numberOfLines == 3) { - show = [self sdl_assembleThreeLineShowText:show]; - } else if (numberOfLines == 4) { - show = [self sdl_assembleFourLineShowText:show]; - } - - return show; -} - -- (SDLShow *)sdl_assembleOneLineShowText:(SDLShow *)show withShowFields:(NSArray<NSString *> *)fields { - NSMutableString *showString1 = [NSMutableString stringWithString:fields[0]]; - for (NSUInteger i = 1; i < fields.count; i++) { - [showString1 appendFormat:@" - %@", fields[i]]; - } - show.mainField1 = showString1.copy; - - SDLMetadataTags *tags = [[SDLMetadataTags alloc] init]; - NSMutableArray<SDLMetadataType> *metadataArray = [NSMutableArray array]; - self.textField1Type ? [metadataArray addObject:self.textField1Type] : nil; - self.textField2Type ? [metadataArray addObject:self.textField2Type] : nil; - self.textField3Type ? [metadataArray addObject:self.textField3Type] : nil; - self.textField4Type ? [metadataArray addObject:self.textField4Type] : nil; - tags.mainField1 = [metadataArray copy]; - show.metadataTags = tags; - - return show; -} - -- (SDLShow *)sdl_assembleTwoLineShowText:(SDLShow *)show { - NSMutableString *tempString = [NSMutableString string]; - if (self.textField1.length > 0) { - // If text 1 exists, put it in slot 1 - [tempString appendString:self.textField1]; - show.metadataTags.mainField1 = self.textField1Type.length > 0 ? @[self.textField1Type] : @[]; - } - - if (self.textField2.length > 0) { - if (!(self.textField3.length > 0 || self.textField4.length > 0)) { - // If text 3 & 4 do not exist, put it in slot 2 - show.mainField2 = self.textField2; - show.metadataTags.mainField2 = self.textField2Type.length > 0 ? @[self.textField2Type] : @[]; - } else if (self.textField1.length > 0) { - // If text 1 exists, put it in slot 1 formatted - [tempString appendFormat:@" - %@", self.textField2]; - show.metadataTags.mainField1 = self.textField2Type.length > 0 ? [show.metadataTags.mainField1 arrayByAddingObjectsFromArray:@[self.textField2Type]] : show.metadataTags.mainField1; - } else { - // If text 1 does not exist, put it in slot 1 unformatted - [tempString appendString:self.textField2]; - show.metadataTags.mainField1 = self.textField2Type.length > 0 ? @[self.textField2Type] : @[]; - } - } - - show.mainField1 = [tempString copy]; - - tempString = [NSMutableString string]; - if (self.textField3.length > 0) { - // If text 3 exists, put it in slot 2 - [tempString appendString:self.textField3]; - show.metadataTags.mainField2 = self.textField3Type.length > 0 ? @[self.textField3Type] : @[]; - } - - if (self.textField4.length > 0) { - if (self.textField3.length > 0) { - // If text 3 exists, put it in slot 2 formatted - [tempString appendFormat:@" - %@", self.textField4]; - show.metadataTags.mainField2 = self.textField4Type.length > 0 ? [show.metadataTags.mainField2 arrayByAddingObjectsFromArray:@[self.textField4Type]] : show.metadataTags.mainField2; - } else { - // If text 3 does not exist, put it in slot 3 unformatted - [tempString appendString:self.textField4]; - show.metadataTags.mainField2 = self.textField4Type.length > 0 ? @[self.textField4Type] : @[]; - } - } - - if (tempString.length > 0) { - show.mainField2 = [tempString copy]; - } - - return show; -} - -- (SDLShow *)sdl_assembleThreeLineShowText:(SDLShow *)show { - if (self.textField1.length > 0) { - show.mainField1 = self.textField1; - show.metadataTags.mainField1 = self.textField1Type.length > 0 ? @[self.textField1Type] : @[]; - } - - if (self.textField2.length > 0) { - show.mainField2 = self.textField2; - show.metadataTags.mainField2 = self.textField2Type.length > 0 ? @[self.textField2Type] : @[]; - } - - NSMutableString *tempString = [NSMutableString string]; - if (self.textField3.length > 0) { - [tempString appendString:self.textField3]; - show.metadataTags.mainField3 = self.textField3Type.length > 0 ? @[self.textField3Type] : @[]; - } - - if (self.textField4.length > 0) { - if (self.textField3.length > 0) { - // If text 3 exists, put it in slot 3 formatted - [tempString appendFormat:@" - %@", self.textField4]; - show.metadataTags.mainField3 = self.textField4Type.length > 0 ? [show.metadataTags.mainField3 arrayByAddingObjectsFromArray:@[self.textField4Type]] : show.metadataTags.mainField3; - } else { - // If text 3 does not exist, put it in slot 3 formatted - [tempString appendString:self.textField4]; - show.metadataTags.mainField3 = self.textField4Type.length > 0 ? @[self.textField4Type] : @[]; - } - } - - show.mainField3 = [tempString copy]; - - return show; + SDLTextAndGraphicUpdateOperation *updateOperation = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:self.connectionManager fileManager:self.fileManager currentCapabilities:self.windowCapability currentScreenData:self.currentScreenData newState:[self currentState] updateCompletionHandler:handler]; + updateOperation.completionBlock = ^{ + // Check for error and `sentShow` and update our own current state, then update other pending transactions + }; + [self.transactionQueue addOperation:updateOperation]; } -- (SDLShow *)sdl_assembleFourLineShowText:(SDLShow *)show { - if (self.textField1.length > 0) { - show.mainField1 = self.textField1; - show.metadataTags.mainField1 = self.textField1Type.length > 0 ? @[self.textField1Type] : @[]; - } +#pragma mark - Convert to State - if (self.textField2.length > 0) { - show.mainField2 = self.textField2; - show.metadataTags.mainField2 = self.textField2Type.length > 0 ? @[self.textField2Type] : @[]; - } - - if (self.textField3.length > 0) { - show.mainField3 = self.textField3; - show.metadataTags.mainField3 = self.textField3Type.length > 0 ? @[self.textField3Type] : @[]; - } - - if (self.textField4.length > 0) { - show.mainField4 = self.textField4; - show.metadataTags.mainField4 = self.textField4Type.length > 0 ? @[self.textField4Type] : @[]; - } - - return show; -} - -- (SDLShow *)sdl_setBlankTextFieldsWithShow:(SDLShow *)show { - show.mainField1 = @""; - show.mainField2 = @""; - show.mainField3 = @""; - show.mainField4 = @""; - show.mediaTrack = @""; - show.templateTitle = @""; - - return show; +- (SDLTextAndGraphicState *)currentState { + return [[SDLTextAndGraphicState alloc] initWithTextField1:_textField1 textField2:_textField2 textField3:_textField3 textField4:_textField4 mediaText:_mediaTrackTextField title:_title primaryGraphic:_primaryGraphic secondaryGraphic:_secondaryGraphic alignment:_alignment textField1Type:_textField1Type textField2Type:_textField2Type textField3Type:_textField3Type textField4Type:_textField4Type]; } #pragma mark - Extraction -- (SDLShow *)sdl_extractTextFromShow:(SDLShow *)show { - SDLShow *newShow = [[SDLShow alloc] init]; - newShow.mainField1 = show.mainField1; - newShow.mainField2 = show.mainField2; - newShow.mainField3 = show.mainField3; - newShow.mainField4 = show.mainField4; - newShow.mediaTrack = show.mediaTrack; - newShow.templateTitle = show.templateTitle; - newShow.metadataTags = show.metadataTags; - - return newShow; -} - - (SDLShow *)sdl_extractImageFromShow:(SDLShow *)show { SDLShow *newShow = [[SDLShow alloc] init]; newShow.graphic = show.graphic; @@ -518,55 +223,6 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Helpers -- (BOOL)sdl_artworkNeedsUpload:(SDLArtwork *)artwork { - return (artwork != nil && ![self.fileManager hasUploadedFile:artwork] && !artwork.isStaticIcon); -} - -- (BOOL)sdl_shouldUpdatePrimaryImage { - BOOL templateSupportsPrimaryArtwork = [self.windowCapability hasImageFieldOfName:SDLImageFieldNameGraphic]; - BOOL graphicMatchesExisting = [self.currentScreenData.graphic.value isEqualToString:self.primaryGraphic.name]; - BOOL graphicExists = (self.primaryGraphic != nil); - - return (templateSupportsPrimaryArtwork && !graphicMatchesExisting && graphicExists); -} - -- (BOOL)sdl_shouldUpdateSecondaryImage { - BOOL templateSupportsSecondaryArtwork = [self.windowCapability hasImageFieldOfName:SDLImageFieldNameSecondaryGraphic]; - BOOL graphicMatchesExisting = [self.currentScreenData.secondaryGraphic.value isEqualToString:self.secondaryGraphic.name]; - BOOL graphicExists = (self.secondaryGraphic != nil); - - // Cannot detect if there is a secondary image, so we'll just try to detect if there's a primary image and allow it if there is. - return (templateSupportsSecondaryArtwork && !graphicMatchesExisting && graphicExists); -} - -- (BOOL)sdl_shouldUpdateMediaTextField { - return [self.windowCapability hasTextFieldOfName:SDLTextFieldNameMediaTrack]; -} - -- (BOOL)sdl_shouldUpdateTitleField { - return [self.windowCapability hasTextFieldOfName:SDLTextFieldNameTemplateTitle]; -} - -- (NSArray<NSString *> *)sdl_findNonNilTextFields { - NSMutableArray *array = [NSMutableArray array]; - (self.textField1.length > 0) ? [array addObject:self.textField1] : nil; - (self.textField2.length > 0) ? [array addObject:self.textField2] : nil; - (self.textField3.length > 0) ? [array addObject:self.textField3] : nil; - (self.textField4.length > 0) ? [array addObject:self.textField4] : nil; - - return [array copy]; -} - -- (NSArray<SDLMetadataType> *)sdl_findNonNilMetadataFields { - NSMutableArray *array = [NSMutableArray array]; - (self.textField1Type.length) > 0 ? [array addObject:self.textField1Type] : nil; - (self.textField2Type.length) > 0 ? [array addObject:self.textField2Type] : nil; - (self.textField3Type.length) > 0 ? [array addObject:self.textField3Type] : nil; - (self.textField4Type.length) > 0 ? [array addObject:self.textField4Type] : nil; - - return [array copy]; -} - - (BOOL)sdl_hasData { BOOL hasTextFields = ([self sdl_findNonNilTextFields].count > 0) || (self.title.length > 0) || (self.mediaTrackTextField.length > 0); BOOL hasImageFields = (self.primaryGraphic != nil) || (self.secondaryGraphic != nil); diff --git a/SmartDeviceLink/SDLTextAndGraphicState.h b/SmartDeviceLink/SDLTextAndGraphicState.h new file mode 100644 index 000000000..3fd2b3f93 --- /dev/null +++ b/SmartDeviceLink/SDLTextAndGraphicState.h @@ -0,0 +1,39 @@ +// +// SDLTextAndGraphicState.h +// SmartDeviceLink +// +// Created by Joel Fischer on 8/13/20. +// Copyright © 2020 smartdevicelink. All rights reserved. +// + +#import <Foundation/Foundation.h> + +#import "SDLMetadataType.h" +#import "SDLTextAlignment.h" + +@class SDLArtwork; + +NS_ASSUME_NONNULL_BEGIN + +@interface SDLTextAndGraphicState : NSObject <NSCopying> + +@property (copy, nonatomic, nullable) NSString *textField1; +@property (copy, nonatomic, nullable) NSString *textField2; +@property (copy, nonatomic, nullable) NSString *textField3; +@property (copy, nonatomic, nullable) NSString *textField4; +@property (copy, nonatomic, nullable) NSString *mediaTrackTextField; +@property (copy, nonatomic, nullable) NSString *title; +@property (strong, nonatomic, nullable) SDLArtwork *primaryGraphic; +@property (strong, nonatomic, nullable) SDLArtwork *secondaryGraphic; + +@property (copy, nonatomic, nullable) SDLTextAlignment alignment; +@property (copy, nonatomic, nullable) SDLMetadataType textField1Type; +@property (copy, nonatomic, nullable) SDLMetadataType textField2Type; +@property (copy, nonatomic, nullable) SDLMetadataType textField3Type; +@property (copy, nonatomic, nullable) SDLMetadataType textField4Type; + +- (instancetype)initWithTextField1:(nullable NSString *)textField1 textField2:(nullable NSString *)textField2 textField3:(nullable NSString *)textField3 textField4:(nullable NSString *)textField4 mediaText:(nullable NSString *)mediaTrackTextField title:(nullable NSString *)title primaryGraphic:(nullable SDLArtwork *)primaryGraphic secondaryGraphic:(nullable SDLArtwork *)secondaryGraphic alignment:(nullable SDLTextAlignment)alignment textField1Type:(nullable SDLMetadataType)textField1Type textField2Type:(nullable SDLMetadataType)textField2Type textField3Type:(nullable SDLMetadataType)textField3Type textField4Type:(nullable SDLMetadataType)textField4Type; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLink/SDLTextAndGraphicState.m b/SmartDeviceLink/SDLTextAndGraphicState.m new file mode 100644 index 000000000..24c94089e --- /dev/null +++ b/SmartDeviceLink/SDLTextAndGraphicState.m @@ -0,0 +1,34 @@ +// +// SDLTextAndGraphicState.m +// SmartDeviceLink +// +// Created by Joel Fischer on 8/13/20. +// Copyright © 2020 smartdevicelink. All rights reserved. +// + +#import "SDLTextAndGraphicState.h" + +@implementation SDLTextAndGraphicState + +- (instancetype)initWithTextField1:(NSString *)textField1 textField2:(NSString *)textField2 textField3:(NSString *)textField3 textField4:(NSString *)textField4 mediaText:(NSString *)mediaTrackTextField title:(NSString *)title primaryGraphic:(SDLArtwork *)primaryGraphic secondaryGraphic:(SDLArtwork *)secondaryGraphic alignment:(SDLTextAlignment)alignment textField1Type:(SDLMetadataType)textField1Type textField2Type:(SDLMetadataType)textField2Type textField3Type:(SDLMetadataType)textField3Type textField4Type:(SDLMetadataType)textField4Type { + self = [self init]; + if (!self) { return nil; } + + _textField1 = textField1; + _textField2 = textField2; + _textField3 = textField3; + _textField4 = textField4; + _mediaTrackTextField = mediaTrackTextField; + _title = title; + _primaryGraphic = primaryGraphic; + _secondaryGraphic = secondaryGraphic; + _alignment = alignment; + _textField1Type = textField1Type; + _textField2Type = textField2Type; + _textField3Type = textField3Type; + _textField4Type = textField4Type; + + return self; +} + +@end diff --git a/SmartDeviceLink/SDLTextAndGraphicUpdateOperation.h b/SmartDeviceLink/SDLTextAndGraphicUpdateOperation.h new file mode 100644 index 000000000..99f59de31 --- /dev/null +++ b/SmartDeviceLink/SDLTextAndGraphicUpdateOperation.h @@ -0,0 +1,40 @@ +// +// SDLTextAndGraphicUpdateOperation.h +// SmartDeviceLink +// +// Created by Joel Fischer on 8/13/20. +// Copyright © 2020 smartdevicelink. All rights reserved. +// + +#import "SDLAsynchronousOperation.h" + +@class SDLArtwork; +@class SDLFileManager; +@class SDLImageField; +@class SDLTextField; +@class SDLShow; +@class SDLTextAndGraphicState; +@class SDLWindowCapability; + +@protocol SDLConnectionManagerType; + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^SDLTextAndGraphicUpdateCompletionHandler)(NSError *__nullable error); + +@interface SDLTextAndGraphicUpdateOperation : SDLAsynchronousOperation + +@property (strong, nonatomic, nullable, readonly) SDLShow *sentShow; + +/// Initialize the operation with its dependencies +/// @param connectionManager The connection manager to send RPCs +/// @param fileManager The file manager to upload artwork +/// @param currentCapabilities The current window capability describing whether or not image fields and text fields are supported +/// @param currentData The current show data to determine which text and image fields need to be sent +/// @param newState The new text and graphic manager state to be compared with currentData and sent in a Show update if needed. +/// @param updateCompletionHandler The handler potentially passed by the developer to be called when the update finishes +- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager currentCapabilities:(SDLWindowCapability *)currentCapabilities currentScreenData:(SDLShow *)currentData newState:(SDLTextAndGraphicState *)newState updateCompletionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)updateCompletionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLink/SDLTextAndGraphicUpdateOperation.m b/SmartDeviceLink/SDLTextAndGraphicUpdateOperation.m new file mode 100644 index 000000000..179cf2603 --- /dev/null +++ b/SmartDeviceLink/SDLTextAndGraphicUpdateOperation.m @@ -0,0 +1,427 @@ +// +// SDLTextAndGraphicUpdateOperation.m +// SmartDeviceLink +// +// Created by Joel Fischer on 8/13/20. +// Copyright © 2020 smartdevicelink. All rights reserved. +// + +#import "SDLTextAndGraphicUpdateOperation.h" + +#import "SDLArtwork.h" +#import "SDLConnectionManagerType.h" +#import "SDLFileManager.h" +#import "SDLImage.h" +#import "SDLLogMacros.h" +#import "SDLMetadataTags.h" +#import "SDLShow.h" +#import "SDLTextAndGraphicState.h" +#import "SDLWindowCapability.h" +#import "SDLWindowCapability+ScreenManagerExtensions.h" + +@interface SDLTextAndGraphicUpdateOperation () + +@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager; +@property (weak, nonatomic) SDLFileManager *fileManager; +@property (strong, nonatomic) SDLWindowCapability *currentCapabilities; +@property (strong, nonatomic) SDLShow *currentScreenData; +@property (strong, nonatomic) SDLTextAndGraphicState *updatedState; + +@property (copy, nonatomic, nullable) SDLTextAndGraphicUpdateCompletionHandler updateCompletionHandler; + +@property (copy, nonatomic, nullable) NSError *internalError; + +@end + +@implementation SDLTextAndGraphicUpdateOperation + +- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager currentCapabilities:(SDLWindowCapability *)currentCapabilities currentScreenData:(SDLShow *)currentData newState:(nonnull SDLTextAndGraphicState *)newState { + self = [self init]; + if (!self) { return nil; } + + _connectionManager = connectionManager; + _fileManager = fileManager; + _currentCapabilities = currentCapabilities; + _currentScreenData = currentData; + _updatedState = newState; + + return self; +} + +- (void)start { + [super start]; + if (self.cancelled) { return; } + + // Build a show with everything from `self.newState`, we'll pull things out later if we can. + SDLShow *fullShow = [[SDLShow alloc] init]; + fullShow.alignment = self.updatedState.alignment; + fullShow.metadataTags = [[SDLMetadataTags alloc] init]; + fullShow = [self sdl_assembleShowText:fullShow]; + fullShow = [self sdl_assembleShowImages:fullShow]; + + SDLShow *showToSend = nil; + + if (!([self sdl_shouldUpdatePrimaryImage] || [self sdl_shouldUpdateSecondaryImage])) { + SDLLogV(@"No images to send, sending text"); + // If there are no images to update, just send the text + showToSend = [self sdl_extractTextFromShow:fullShow]; + } else if (![self sdl_artworkNeedsUpload:self.updatedState.primaryGraphic] && ![self sdl_artworkNeedsUpload:self.updatedState.secondaryGraphic]) { + SDLLogV(@"Images already uploaded, sending full update"); + // The files to be updated are already uploaded, send the full show immediately + showToSend = fullShow; + } else { + SDLLogV(@"Images need to be uploaded, sending text and uploading images"); + + // We need to upload or queue the upload of the images + // Send the text immediately + showToSend = [self sdl_extractTextFromShow:fullShow]; + } + + // Send the initial, and potentially only, Show request + __weak typeof(self)weakSelf = self; + [self.connectionManager sendConnectionRequest:showToSend withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + SDLLogD(@"Text and Graphic update completed"); + + // TODO: Monitor and delete old images when space is low? + if (response.success) { + [strongSelf sdl_updateCurrentScreenDataFromShow:(SDLShow *)request]; + [self sdl_uploadImagesAndSendWhenDone]; + } else { + // It failed, store the error and pass it along + self.internalError = error; + } + }]; +} + +- (void)sdl_uploadImagesAndSendWhenDone { + // Start uploading the images + __weak typeof(self)weakSelf = self; + [self sdl_uploadImagesWithCompletionHandler:^(NSError *_Nullable error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + + if (error != nil) { + SDLShow *showWithGraphics = [self sdl_createImageOnlyShowWithPrimaryArtwork:self.newState.primaryGraphic secondaryArtwork:self.newState.secondaryGraphic]; + if (showWithGraphics != nil) { + SDLLogW(@"Some images failed to upload. Sending update with the successfully uploaded images"); + self.inProgressUpdate = showWithGraphics; + } else { + SDLLogE(@"All images failed to upload. No graphics to show, skipping update."); + self.inProgressUpdate = nil; + } + return; + } + + // Check if queued image update still matches our images (there could have been a new Show in the meantime) and send a new update if it does. Since the images will already be on the head unit, the whole show will be sent + // TODO: Send delete if it doesn't? + if ([strongSelf sdl_showImages:thisUpdate isEqualToShowImages:strongSelf.queuedImageUpdate]) { + SDLLogV(@"Queued image update matches the images we need, sending update"); + return [strongSelf sdl_updateWithCompletionHandler:strongSelf.inProgressHandler]; + } else { + SDLLogV(@"Queued image update does not match the images we need, skipping update"); + } + }]; + // When the images are done uploading, send another show with the images + self.queuedImageUpdate = fullShow; +} + +#pragma mark - Uploading Images + +- (void)sdl_uploadImagesWithCompletionHandler:(void (^)(NSError *_Nullable error))handler { + NSMutableArray<SDLArtwork *> *artworksToUpload = [NSMutableArray array]; + if ([self sdl_shouldUpdatePrimaryImage] && !self.updatedState.primaryGraphic.isStaticIcon) { + [artworksToUpload addObject:self.updatedState.primaryGraphic]; + } + if ([self sdl_shouldUpdateSecondaryImage] && !self.updatedState.secondaryGraphic.isStaticIcon) { + [artworksToUpload addObject:self.updatedState.secondaryGraphic]; + } + + if (artworksToUpload.count == 0) { + SDLLogD(@"No artworks need an upload, sending them without upload instead"); + return handler(nil); + } + + [self.fileManager uploadArtworks:artworksToUpload completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) { + if (error != nil) { + SDLLogW(@"Text and graphic manager artwork failed to upload with error: %@", error.localizedDescription); + } + + handler(error); + }]; +} + + +#pragma mark - Assembly of Shows + +#pragma mark Images + +- (SDLShow *)sdl_assembleShowImages:(SDLShow *)show { + if (![self sdl_shouldUpdatePrimaryImage] && ![self sdl_shouldUpdateSecondaryImage]) { + return show; + } + + if ([self sdl_shouldUpdatePrimaryImage]) { + show.graphic = self.updatedState.primaryGraphic.imageRPC; + } + if ([self sdl_shouldUpdateSecondaryImage]) { + show.secondaryGraphic = self.updatedState.secondaryGraphic.imageRPC; + } + + return show; +} + +#pragma mark Text + +- (SDLShow *)sdl_assembleShowText:(SDLShow *)show { + [self sdl_setBlankTextFieldsWithShow:show]; + + if (self.updatedState.mediaTrackTextField != nil && [self sdl_shouldUpdateMediaTextField]) { + show.mediaTrack = self.updatedState.mediaTrackTextField; + } else { + show.mediaTrack = @""; + } + + if (self.updatedState.title != nil && [self sdl_shouldUpdateTitleField]) { + show.templateTitle = self.updatedState.title; + } else { + show.templateTitle = @""; + } + + NSArray *nonNilFields = [self sdl_findNonNilTextFields]; + if (nonNilFields.count == 0) { return show; } + + NSUInteger numberOfLines = self.currentCapabilities.maxNumberOfMainFieldLines; + if (numberOfLines == 1) { + show = [self sdl_assembleOneLineShowText:show withShowFields:nonNilFields]; + } else if (numberOfLines == 2) { + show = [self sdl_assembleTwoLineShowText:show]; + } else if (numberOfLines == 3) { + show = [self sdl_assembleThreeLineShowText:show]; + } else if (numberOfLines == 4) { + show = [self sdl_assembleFourLineShowText:show]; + } + + return show; +} + +- (SDLShow *)sdl_assembleOneLineShowText:(SDLShow *)show withShowFields:(NSArray<NSString *> *)fields { + NSMutableString *showString1 = [NSMutableString stringWithString:fields[0]]; + for (NSUInteger i = 1; i < fields.count; i++) { + [showString1 appendFormat:@" - %@", fields[i]]; + } + show.mainField1 = showString1.copy; + + SDLMetadataTags *tags = [[SDLMetadataTags alloc] init]; + NSMutableArray<SDLMetadataType> *metadataArray = [NSMutableArray array]; + self.updatedState.textField1Type ? [metadataArray addObject:self.updatedState.textField1Type] : nil; + self.updatedState.textField2Type ? [metadataArray addObject:self.updatedState.textField2Type] : nil; + self.updatedState.textField3Type ? [metadataArray addObject:self.updatedState.textField3Type] : nil; + self.updatedState.textField4Type ? [metadataArray addObject:self.updatedState.textField4Type] : nil; + tags.mainField1 = [metadataArray copy]; + show.metadataTags = tags; + + return show; +} + +- (SDLShow *)sdl_assembleTwoLineShowText:(SDLShow *)show { + NSMutableString *tempString = [NSMutableString string]; + if (self.updatedState.textField1.length > 0) { + // If text 1 exists, put it in slot 1 + [tempString appendString:self.updatedState.textField1]; + show.metadataTags.mainField1 = self.updatedState.textField1Type.length > 0 ? @[self.updatedState.textField1Type] : @[]; + } + + if (self.updatedState.textField2.length > 0) { + if (!(self.updatedState.textField3.length > 0 || self.updatedState.textField4.length > 0)) { + // If text 3 & 4 do not exist, put it in slot 2 + show.mainField2 = self.updatedState.textField2; + show.metadataTags.mainField2 = self.updatedState.textField2Type.length > 0 ? @[self.updatedState.textField2Type] : @[]; + } else if (self.updatedState.textField1.length > 0) { + // If text 1 exists, put it in slot 1 formatted + [tempString appendFormat:@" - %@", self.updatedState.textField2]; + show.metadataTags.mainField1 = self.updatedState.textField2Type.length > 0 ? [show.metadataTags.mainField1 arrayByAddingObjectsFromArray:@[self.updatedState.textField2Type]] : show.metadataTags.mainField1; + } else { + // If text 1 does not exist, put it in slot 1 unformatted + [tempString appendString:self.updatedState.textField2]; + show.metadataTags.mainField1 = self.updatedState.textField2Type.length > 0 ? @[self.updatedState.textField2Type] : @[]; + } + } + + show.mainField1 = [tempString copy]; + + tempString = [NSMutableString string]; + if (self.updatedState.textField3.length > 0) { + // If text 3 exists, put it in slot 2 + [tempString appendString:self.updatedState.textField3]; + show.metadataTags.mainField2 = self.updatedState.textField3Type.length > 0 ? @[self.updatedState.textField3Type] : @[]; + } + + if (self.updatedState.textField4.length > 0) { + if (self.updatedState.textField3.length > 0) { + // If text 3 exists, put it in slot 2 formatted + [tempString appendFormat:@" - %@", self.updatedState.textField4]; + show.metadataTags.mainField2 = self.updatedState.textField4Type.length > 0 ? [show.metadataTags.mainField2 arrayByAddingObjectsFromArray:@[self.updatedState.textField4Type]] : show.metadataTags.mainField2; + } else { + // If text 3 does not exist, put it in slot 3 unformatted + [tempString appendString:self.updatedState.textField4]; + show.metadataTags.mainField2 = self.updatedState.textField4Type.length > 0 ? @[self.updatedState.textField4Type] : @[]; + } + } + + if (tempString.length > 0) { + show.mainField2 = [tempString copy]; + } + + return show; +} + +- (SDLShow *)sdl_assembleThreeLineShowText:(SDLShow *)show { + if (self.updatedState.textField1.length > 0) { + show.mainField1 = self.updatedState.textField1; + show.metadataTags.mainField1 = self.updatedState.textField1Type.length > 0 ? @[self.updatedState.textField1Type] : @[]; + } + + if (self.updatedState.textField2.length > 0) { + show.mainField2 = self.updatedState.textField2; + show.metadataTags.mainField2 = self.updatedState.textField2Type.length > 0 ? @[self.updatedState.textField2Type] : @[]; + } + + NSMutableString *tempString = [NSMutableString string]; + if (self.updatedState.textField3.length > 0) { + [tempString appendString:self.updatedState.textField3]; + show.metadataTags.mainField3 = self.updatedState.textField3Type.length > 0 ? @[self.updatedState.textField3Type] : @[]; + } + + if (self.updatedState.textField4.length > 0) { + if (self.updatedState.textField3.length > 0) { + // If text 3 exists, put it in slot 3 formatted + [tempString appendFormat:@" - %@", self.updatedState.textField4]; + show.metadataTags.mainField3 = self.updatedState.textField4Type.length > 0 ? [show.metadataTags.mainField3 arrayByAddingObjectsFromArray:@[self.updatedState.textField4Type]] : show.metadataTags.mainField3; + } else { + // If text 3 does not exist, put it in slot 3 formatted + [tempString appendString:self.updatedState.textField4]; + show.metadataTags.mainField3 = self.updatedState.textField4Type.length > 0 ? @[self.updatedState.textField4Type] : @[]; + } + } + + show.mainField3 = [tempString copy]; + + return show; +} + +- (SDLShow *)sdl_assembleFourLineShowText:(SDLShow *)show { + if (self.updatedState.textField1.length > 0) { + show.mainField1 = self.updatedState.textField1; + show.metadataTags.mainField1 = self.updatedState.textField1Type.length > 0 ? @[self.updatedState.textField1Type] : @[]; + } + + if (self.updatedState.textField2.length > 0) { + show.mainField2 = self.updatedState.textField2; + show.metadataTags.mainField2 = self.updatedState.textField2Type.length > 0 ? @[self.updatedState.textField2Type] : @[]; + } + + if (self.updatedState.textField3.length > 0) { + show.mainField3 = self.updatedState.textField3; + show.metadataTags.mainField3 = self.updatedState.textField3Type.length > 0 ? @[self.updatedState.textField3Type] : @[]; + } + + if (self.updatedState.textField4.length > 0) { + show.mainField4 = self.updatedState.textField4; + show.metadataTags.mainField4 = self.updatedState.textField4Type.length > 0 ? @[self.updatedState.textField4Type] : @[]; + } + + return show; +} + +- (SDLShow *)sdl_setBlankTextFieldsWithShow:(SDLShow *)show { + show.mainField1 = @""; + show.mainField2 = @""; + show.mainField3 = @""; + show.mainField4 = @""; + show.mediaTrack = @""; + show.templateTitle = @""; + + return show; +} + +#pragma mark - Extraction + +- (SDLShow *)sdl_extractTextFromShow:(SDLShow *)show { + SDLShow *newShow = [[SDLShow alloc] init]; + newShow.mainField1 = show.mainField1; + newShow.mainField2 = show.mainField2; + newShow.mainField3 = show.mainField3; + newShow.mainField4 = show.mainField4; + newShow.mediaTrack = show.mediaTrack; + newShow.templateTitle = show.templateTitle; + newShow.metadataTags = show.metadataTags; + + return newShow; +} + +- (void)sdl_updateCurrentScreenDataFromShow:(SDLShow *)show { + // If the items are nil, they were not updated, so we can't just set it directly + self.currentScreenData.mainField1 = show.mainField1 ?: self.currentScreenData.mainField1; + self.currentScreenData.mainField2 = show.mainField2 ?: self.currentScreenData.mainField2; + self.currentScreenData.mainField3 = show.mainField3 ?: self.currentScreenData.mainField3; + self.currentScreenData.mainField4 = show.mainField4 ?: self.currentScreenData.mainField4; + self.currentScreenData.mediaTrack = show.mediaTrack ?: self.currentScreenData.mediaTrack; + self.currentScreenData.templateTitle = show.templateTitle ?: self.currentScreenData.templateTitle; + self.currentScreenData.metadataTags = show.metadataTags ?: self.currentScreenData.metadataTags; + self.currentScreenData.alignment = show.alignment ?: self.currentScreenData.alignment; + self.currentScreenData.graphic = show.graphic ?: self.currentScreenData.graphic; + self.currentScreenData.secondaryGraphic = show.secondaryGraphic ?: self.currentScreenData.secondaryGraphic; +} + +#pragma mark - Should Update + +- (BOOL)sdl_artworkNeedsUpload:(SDLArtwork *)artwork { + return (artwork != nil && ![self.fileManager hasUploadedFile:artwork] && !artwork.isStaticIcon); +} + +- (BOOL)sdl_shouldUpdatePrimaryImage { + BOOL templateSupportsPrimaryArtwork = [self.currentCapabilities hasImageFieldOfName:SDLImageFieldNameGraphic]; + BOOL graphicMatchesExisting = [self.currentScreenData.graphic.value isEqualToString:self.updatedState.primaryGraphic.name]; + BOOL graphicExists = (self.updatedState.primaryGraphic != nil); + + return (templateSupportsPrimaryArtwork && !graphicMatchesExisting && graphicExists); +} + +- (BOOL)sdl_shouldUpdateSecondaryImage { + BOOL templateSupportsSecondaryArtwork = [self.currentCapabilities hasImageFieldOfName:SDLImageFieldNameSecondaryGraphic]; + BOOL graphicMatchesExisting = [self.currentScreenData.secondaryGraphic.value isEqualToString:self.updatedState.secondaryGraphic.name]; + BOOL graphicExists = (self.updatedState.secondaryGraphic != nil); + + // Cannot detect if there is a secondary image, so we'll just try to detect if there's a primary image and allow it if there is. + return (templateSupportsSecondaryArtwork && !graphicMatchesExisting && graphicExists); +} + +- (BOOL)sdl_shouldUpdateMediaTextField { + return [self.currentCapabilities hasTextFieldOfName:SDLTextFieldNameMediaTrack]; +} + +- (BOOL)sdl_shouldUpdateTitleField { + return [self.currentCapabilities hasTextFieldOfName:SDLTextFieldNameTemplateTitle]; +} + +- (NSArray<NSString *> *)sdl_findNonNilTextFields { + NSMutableArray *array = [NSMutableArray array]; + (self.updatedState.textField1.length > 0) ? [array addObject:self.updatedState.textField1] : nil; + (self.updatedState.textField2.length > 0) ? [array addObject:self.updatedState.textField2] : nil; + (self.updatedState.textField3.length > 0) ? [array addObject:self.updatedState.textField3] : nil; + (self.updatedState.textField4.length > 0) ? [array addObject:self.updatedState.textField4] : nil; + + return [array copy]; +} + +- (NSArray<SDLMetadataType> *)sdl_findNonNilMetadataFields { + NSMutableArray *array = [NSMutableArray array]; + (self.updatedState.textField1Type.length) > 0 ? [array addObject:self.updatedState.textField1Type] : nil; + (self.updatedState.textField2Type.length) > 0 ? [array addObject:self.updatedState.textField2Type] : nil; + (self.updatedState.textField3Type.length) > 0 ? [array addObject:self.updatedState.textField3Type] : nil; + (self.updatedState.textField4Type.length) > 0 ? [array addObject:self.updatedState.textField4Type] : nil; + + return [array copy]; +} + +@end |