/externals/gdata-objectivec-client/Examples/YouTubeSample/YouTubeSampleWindowController.m

http://update-engine.googlecode.com/ · Objective C · 907 lines · 565 code · 187 blank · 155 comment · 75 complexity · f36959529646a25eaeea2290cec6ba93 MD5 · raw file

  1. /* Copyright (c) 2008 Google Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. //
  16. // YouTubeSampleWindowController.m
  17. //
  18. //
  19. // IMPORTANT:
  20. //
  21. // The XML-based API for YouTube has been replaced with a more efficient
  22. // and easier-to-use JSON API. The JSON API is documented at
  23. //
  24. // https://developers.google.com/youtube/v3/
  25. //
  26. // See the new Objective-C client library and sample code at
  27. // http://code.google.com/p/google-api-objectivec-client/
  28. //
  29. // This sample application and library support for the XML-based YouTube
  30. // API will eventually be removed.
  31. //
  32. #import "YouTubeSampleWindowController.h"
  33. #import "GData/GDataServiceGoogleYouTube.h"
  34. #import "GData/GDataEntryPhotoAlbum.h"
  35. #import "GData/GDataEntryPhoto.h"
  36. #import "GData/GDataFeedPhoto.h"
  37. #import "GData/GDataEntryYouTubeUpload.h"
  38. #import "GData/GTMOAuth2WindowController.h"
  39. static NSString* const kActivityFeed = @"activity";
  40. static NSString* const kChannelsFeed = @"channels";
  41. static NSString* const kMostPopularFeed = @"most popular";
  42. @interface YouTubeSampleWindowController (PrivateMethods)
  43. - (void)updateUI;
  44. - (void)fetchEntryImageURLString:(NSString *)urlString;
  45. - (GDataEntryBase *)selectedEntry;
  46. - (void)fetchAllEntries;
  47. - (void)uploadVideoFile;
  48. - (void)restartUpload;
  49. - (GDataFeedYouTubeVideo *)entriesFeed;
  50. - (void)setEntriesFeed:(GDataFeedYouTubeVideo *)feed;
  51. - (NSError *)entriesFetchError;
  52. - (void)setEntriesFetchError:(NSError *)error;
  53. - (GDataServiceTicket *)entriesFetchTicket;
  54. - (void)setEntriesFetchTicket:(GDataServiceTicket *)ticket;
  55. - (NSString *)entryImageURLString;
  56. - (void)setEntryImageURLString:(NSString *)str;
  57. - (GDataServiceTicket *)uploadTicket;
  58. - (void)setUploadTicket:(GDataServiceTicket *)ticket;
  59. - (NSURL *)uploadLocationURL;
  60. - (void)setUploadLocationURL:(NSURL *)url;
  61. - (GDataServiceGoogleYouTube *)youTubeService;
  62. - (void)ticket:(GDataServiceTicket *)ticket
  63. hasDeliveredByteCount:(unsigned long long)numberOfBytesRead
  64. ofTotalByteCount:(unsigned long long)dataLength;
  65. - (void)fetchStandardCategories;
  66. @end
  67. @implementation YouTubeSampleWindowController
  68. static YouTubeSampleWindowController* gYouTubeSampleWindowController = nil;
  69. static NSString *const kKeychainItemName = @"YouTubeSample: YouTube";
  70. + (YouTubeSampleWindowController *)sharedYouTubeSampleWindowController {
  71. if (!gYouTubeSampleWindowController) {
  72. gYouTubeSampleWindowController = [[YouTubeSampleWindowController alloc] init];
  73. }
  74. return gYouTubeSampleWindowController;
  75. }
  76. - (id)init {
  77. return [self initWithWindowNibName:@"YouTubeSampleWindow"];
  78. }
  79. - (void)awakeFromNib {
  80. // Load the OAuth token from the keychain, if it was previously saved
  81. NSString *clientID = [mClientIDField stringValue];
  82. NSString *clientSecret = [mClientSecretField stringValue];
  83. GTMOAuth2Authentication *auth;
  84. auth = [GTMOAuth2WindowController authForGoogleFromKeychainForName:kKeychainItemName
  85. clientID:clientID
  86. clientSecret:clientSecret];
  87. [[self youTubeService] setAuthorizer:auth];
  88. // Set the result text fields to have a distinctive color and mono-spaced font
  89. // to aid in understanding of each album and photo query operation.
  90. [mEntriesResultTextField setTextColor:[NSColor darkGrayColor]];
  91. NSFont *resultTextFont = [NSFont fontWithName:@"Monaco" size:9];
  92. [mEntriesResultTextField setFont:resultTextFont];
  93. // load the user feed types into the pop-up menu, and default to showing
  94. // the feed of the user's uploads, as it's generally most interesting
  95. NSArray *userFeedTypes = [NSArray arrayWithObjects:
  96. kChannelsFeed,
  97. kMostPopularFeed,
  98. kGDataYouTubeUserFeedIDContacts,
  99. kGDataYouTubeUserFeedIDFavorites,
  100. kGDataYouTubeUserFeedIDInbox,
  101. kGDataYouTubeUserFeedIDPlaylists,
  102. kGDataYouTubeUserFeedIDSubscriptions,
  103. kActivityFeed,
  104. kGDataYouTubeUserFeedIDFriendsActivity,
  105. kGDataYouTubeUserFeedIDUploads,
  106. nil];
  107. [mUserFeedPopup removeAllItems];
  108. [mUserFeedPopup addItemsWithTitles:userFeedTypes];
  109. [mUserFeedPopup selectItemWithTitle:kGDataYouTubeUserFeedIDUploads];
  110. // reset the upload file path
  111. [mFilePathField setStringValue:@""];
  112. [self updateUI];
  113. // start retrieving the list of assignable upload categories
  114. [self fetchStandardCategories];
  115. }
  116. - (void)dealloc {
  117. [mEntriesFeed release];
  118. [mEntriesFetchError release];
  119. [mEntriesFetchTicket release];
  120. [mEntryImageURLString release];
  121. [mUploadTicket release];
  122. [mUploadLocationURL release];
  123. [super dealloc];
  124. }
  125. #pragma mark -
  126. - (NSString *)signedInUsername {
  127. // Get the email address of the signed-in user
  128. GTMOAuth2Authentication *auth = [[self youTubeService] authorizer];
  129. BOOL isSignedIn = auth.canAuthorize;
  130. if (isSignedIn) {
  131. return auth.userEmail;
  132. } else {
  133. return nil;
  134. }
  135. }
  136. - (BOOL)isSignedIn {
  137. NSString *name = [self signedInUsername];
  138. return (name != nil);
  139. }
  140. - (void)runSigninThenInvokeSelector:(SEL)signInDoneSel {
  141. // Applications should have client ID and client secret strings
  142. // hardcoded into the source, but the sample application asks the
  143. // developer for the strings
  144. NSString *clientID = [mClientIDField stringValue];
  145. NSString *clientSecret = [mClientSecretField stringValue];
  146. if ([clientID length] == 0 || [clientSecret length] == 0) {
  147. // Remind the developer that client ID and client secret are needed
  148. [mClientIDButton performSelector:@selector(performClick:)
  149. withObject:self
  150. afterDelay:0.5];
  151. return;
  152. }
  153. // Show the OAuth 2 sign-in controller
  154. NSString *scope = [GDataServiceGoogleYouTube authorizationScope];
  155. NSBundle *frameworkBundle = [NSBundle bundleForClass:[GTMOAuth2WindowController class]];
  156. GTMOAuth2WindowController *windowController;
  157. windowController = [GTMOAuth2WindowController controllerWithScope:scope
  158. clientID:clientID
  159. clientSecret:clientSecret
  160. keychainItemName:kKeychainItemName
  161. resourceBundle:frameworkBundle];
  162. [windowController setUserData:NSStringFromSelector(signInDoneSel)];
  163. [windowController signInSheetModalForWindow:[self window]
  164. delegate:self
  165. finishedSelector:@selector(windowController:finishedWithAuth:error:)];
  166. }
  167. - (void)windowController:(GTMOAuth2WindowController *)windowController
  168. finishedWithAuth:(GTMOAuth2Authentication *)auth
  169. error:(NSError *)error {
  170. // Callback from OAuth 2 sign-in
  171. if (error == nil) {
  172. [[self youTubeService] setAuthorizer:auth];
  173. NSString *selStr = [windowController userData];
  174. if (selStr) {
  175. [self performSelector:NSSelectorFromString(selStr)];
  176. }
  177. } else {
  178. [self setEntriesFetchError:error];
  179. [self updateUI];
  180. }
  181. }
  182. #pragma mark -
  183. // album and photo thumbnail display
  184. // fetch or clear the thumbnail for this specified entry
  185. - (void)updateImageForEntry:(GDataEntryBase *)entry {
  186. if (!entry || ![entry respondsToSelector:@selector(mediaGroup)]) {
  187. // clear the image; no entry is selected, or it's not an entry type with a
  188. // thumbnail
  189. [mEntryImageView setImage:nil];
  190. [self setEntryImageURLString:nil];
  191. } else {
  192. // if the new thumbnail URL string is different from the previous one,
  193. // save the new URL, clear the existing image and fetch the new image
  194. GDataEntryYouTubeVideo *video = (GDataEntryYouTubeVideo *)entry;
  195. GDataMediaThumbnail *thumbnail = [[video mediaGroup] highQualityThumbnail];
  196. if (thumbnail != nil) {
  197. NSString *imageURLString = [thumbnail URLString];
  198. if (!imageURLString || ![mEntryImageURLString isEqual:imageURLString]) {
  199. [self setEntryImageURLString:imageURLString];
  200. [mEntryImageView setImage:nil];
  201. if (imageURLString) {
  202. [self fetchEntryImageURLString:imageURLString];
  203. }
  204. }
  205. }
  206. }
  207. }
  208. - (void)fetchEntryImageURLString:(NSString *)urlString {
  209. GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithURLString:urlString];
  210. [fetcher setComment:@"thumbnail"];
  211. [fetcher beginFetchWithDelegate:self
  212. didFinishSelector:@selector(imageFetcher:finishedWithData:error:)];
  213. }
  214. - (void)imageFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error {
  215. if (error == nil) {
  216. // got the data; display it in the image view
  217. NSImage *image = [[[NSImage alloc] initWithData:data] autorelease];
  218. [mEntryImageView setImage:image];
  219. } else {
  220. NSLog(@"imageFetcher:%@ failedWithError:%@", fetcher, error);
  221. }
  222. }
  223. #pragma mark -
  224. - (void)updateUI {
  225. BOOL isSignedIn = [self isSignedIn];
  226. NSString *username = [self signedInUsername];
  227. [mSignedInButton setTitle:(isSignedIn ? @"Sign Out" : @"Sign In")];
  228. [mSignedInField setStringValue:(isSignedIn ? username : @"No")];
  229. // entry list display
  230. [mEntriesTable reloadData];
  231. if (mEntriesFetchTicket != nil) {
  232. [mEntriesProgressIndicator startAnimation:self];
  233. } else {
  234. [mEntriesProgressIndicator stopAnimation:self];
  235. }
  236. // entry fetch result or selected item
  237. NSString *entriesResultStr = @"";
  238. if (mEntriesFetchError) {
  239. entriesResultStr = [mEntriesFetchError description];
  240. [self updateImageForEntry:nil];
  241. } else {
  242. GDataEntryBase *entry = [self selectedEntry];
  243. if (entry) {
  244. entriesResultStr = [entry description];
  245. }
  246. // fetch or clear the entry thumbnail
  247. [self updateImageForEntry:entry];
  248. }
  249. [mEntriesResultTextField setString:entriesResultStr];
  250. // show how many entries are in the table
  251. NSString *countStr = @"Entries: -";
  252. if (mEntriesFetchTicket == nil) {
  253. // not currently fetching; show the count
  254. unsigned int numEntries = [[mEntriesFeed entries] count];
  255. countStr = [NSString stringWithFormat:@"Entries: %u", numEntries];
  256. }
  257. [mEntryCountField setStringValue:countStr];
  258. // enable the upload button only if all preconditions are met
  259. BOOL hasDevKey = [[mDeveloperKeyField stringValue] length] > 0;
  260. BOOL hasTitle = [[mTitleField stringValue] length] > 0;
  261. BOOL hasPath = [[mFilePathField stringValue] length] > 0;
  262. BOOL canUpload = isSignedIn && hasDevKey
  263. && hasTitle && hasPath;
  264. BOOL isUploading = (mUploadTicket != nil);
  265. BOOL canRestartUpload = !isUploading && (mUploadLocationURL != nil);
  266. BOOL isUploadPaused = [mUploadTicket isUploadPaused];
  267. [mUploadButton setEnabled:(canUpload && !isUploading)];
  268. [mPauseUploadButton setEnabled:isUploading];
  269. [mStopUploadButton setEnabled:isUploading];
  270. [mRestartUploadButton setEnabled:canRestartUpload];
  271. NSString *pauseTitle = (isUploadPaused ? @"Resume" : @"Pause");
  272. [mPauseUploadButton setTitle:pauseTitle];
  273. // Show or hide the text indicating that the client ID or client secret are
  274. // needed
  275. BOOL hasClientIDStrings = [[mClientIDField stringValue] length] > 0
  276. && [[mClientSecretField stringValue] length] > 0;
  277. [mClientIDRequiredTextField setHidden:hasClientIDStrings];
  278. }
  279. - (void)displayAlert:(NSString *)title format:(NSString *)format, ... {
  280. NSString *result = format;
  281. if (format) {
  282. va_list argList;
  283. va_start(argList, format);
  284. result = [[[NSString alloc] initWithFormat:format
  285. arguments:argList] autorelease];
  286. va_end(argList);
  287. }
  288. NSBeginAlertSheet(title, nil, nil, nil, [self window], nil, nil,
  289. nil, nil, @"%@", result);
  290. }
  291. #pragma mark IBActions
  292. - (IBAction)signInClicked:(id)sender {
  293. if (![self isSignedIn]) {
  294. // Sign in
  295. [self runSigninThenInvokeSelector:@selector(updateUI)];
  296. } else {
  297. // Sign out
  298. GDataServiceGoogleYouTube *service = [self youTubeService];
  299. [GTMOAuth2WindowController removeAuthFromKeychainForName:kKeychainItemName];
  300. [service setAuthorizer:nil];
  301. [self updateUI];
  302. }
  303. }
  304. - (IBAction)getEntriesClicked:(id)sender {
  305. if (![self isSignedIn]) {
  306. // Sign in
  307. [self runSigninThenInvokeSelector:@selector(fetchAllEntries)];
  308. } else {
  309. [self fetchAllEntries];
  310. }
  311. }
  312. - (IBAction)cancelEntriesFetchClicked:(id)sender {
  313. [mEntriesFetchTicket cancelTicket];
  314. [self setEntriesFetchTicket:nil];
  315. [self updateUI];
  316. }
  317. - (IBAction)APIConsoleClicked:(id)sender {
  318. NSURL *url = [NSURL URLWithString:@"https://code.google.com/apis/console"];
  319. [[NSWorkspace sharedWorkspace] openURL:url];
  320. }
  321. - (IBAction)loggingCheckboxClicked:(id)sender {
  322. [GTMHTTPFetcher setLoggingEnabled:[sender state]];
  323. }
  324. - (IBAction)chooseFileClicked:(id)sender {
  325. // ask the user to choose a video file
  326. NSOpenPanel *openPanel = [NSOpenPanel openPanel];
  327. [openPanel setPrompt:@"Choose"];
  328. NSArray *movieTypes = [NSArray arrayWithObjects:@"mov", @"mp4", nil];
  329. [openPanel setAllowedFileTypes:movieTypes];
  330. [openPanel beginSheetModalForWindow:[self window]
  331. completionHandler:^(NSInteger result) {
  332. // callback
  333. if (result == NSOKButton) {
  334. // the user chose a file
  335. NSString *path = [[openPanel URL] path];
  336. [mFilePathField setStringValue:path];
  337. [self updateUI]; // update UI in case we need to enable the upload button
  338. }
  339. }];
  340. }
  341. - (IBAction)uploadClicked:(id)sender {
  342. [self uploadVideoFile];
  343. }
  344. - (IBAction)pauseUploadClicked:(id)sender {
  345. if ([mUploadTicket isUploadPaused]) {
  346. // Resume from pause
  347. [mUploadTicket resumeUpload];
  348. } else {
  349. // Pause
  350. [mUploadTicket pauseUpload];
  351. }
  352. [self updateUI];
  353. }
  354. - (IBAction)stopUploadClicked:(id)sender {
  355. [mUploadTicket cancelTicket];
  356. [self setUploadTicket:nil];
  357. [mUploadProgressIndicator setDoubleValue:0.0];
  358. [self updateUI];
  359. }
  360. - (IBAction)restartUploadClicked:(id)sender {
  361. [self restartUpload];
  362. }
  363. #pragma mark -
  364. // get a YouTube service object
  365. //
  366. // A "service" object handles networking tasks. Service objects
  367. // contain user authentication information as well as networking
  368. // state information (such as cookies and the "last modified" date for
  369. // fetched data.)
  370. - (GDataServiceGoogleYouTube *)youTubeService {
  371. static GDataServiceGoogleYouTube* service = nil;
  372. if (!service) {
  373. service = [[GDataServiceGoogleYouTube alloc] init];
  374. [service setShouldCacheResponseData:YES];
  375. [service setServiceShouldFollowNextLinks:YES];
  376. [service setIsServiceRetryEnabled:YES];
  377. }
  378. NSString *devKey = [mDeveloperKeyField stringValue];
  379. [service setYouTubeDeveloperKey:devKey];
  380. return service;
  381. }
  382. // get the entry selected in the list, or nil if none
  383. - (GDataEntryBase *)selectedEntry {
  384. NSArray *entries = [mEntriesFeed entries];
  385. int rowIndex = [mEntriesTable selectedRow];
  386. if ([entries count] > 0 && rowIndex > -1) {
  387. GDataEntryBase *entry = [entries objectAtIndex:rowIndex];
  388. return entry;
  389. }
  390. return nil;
  391. }
  392. #pragma mark Fetch all entries
  393. // begin retrieving the list of the user's entries
  394. - (void)fetchAllEntries {
  395. [self setEntriesFeed:nil];
  396. [self setEntriesFetchError:nil];
  397. [self setEntriesFetchTicket:nil];
  398. GDataServiceGoogleYouTube *service = [self youTubeService];
  399. GDataServiceTicket *ticket;
  400. // feedID is uploads, favorites, etc
  401. //
  402. // note that activity feeds require a developer key
  403. NSString *feedID = [[mUserFeedPopup selectedItem] title];
  404. NSURL *feedURL;
  405. if ([feedID isEqual:kActivityFeed]) {
  406. // the activity feed uses a unique URL
  407. feedURL = [GDataServiceGoogleYouTube youTubeActivityFeedURLForUserID:kGDataServiceDefaultUser];
  408. } else if ([feedID isEqual:kChannelsFeed]) {
  409. feedURL = [GDataServiceGoogleYouTube youTubeURLForChannelsFeeds];
  410. } else if ([feedID isEqual:kMostPopularFeed]) {
  411. feedURL = [GDataServiceGoogleYouTube youTubeURLForFeedID:kGDataYouTubeFeedIDMostPopular];
  412. } else {
  413. feedURL = [GDataServiceGoogleYouTube youTubeURLForUserID:kGDataServiceDefaultUser
  414. userFeedID:feedID];
  415. }
  416. ticket = [service fetchFeedWithURL:feedURL
  417. delegate:self
  418. didFinishSelector:@selector(entryListFetchTicket:finishedWithFeed:error:)];
  419. if ([feedID isEqual:kChannelsFeed] || [feedID isEqual:kMostPopularFeed]) {
  420. // when using feeds which search all public videos, we don't want
  421. // to follow the feed's next links, since there could be a huge
  422. // number of pages of results
  423. [ticket setShouldFollowNextLinks:NO];
  424. }
  425. [self setEntriesFetchTicket:ticket];
  426. [self updateUI];
  427. }
  428. // feed fetch callback
  429. - (void)entryListFetchTicket:(GDataServiceTicket *)ticket
  430. finishedWithFeed:(GDataFeedYouTubeVideo *)feed
  431. error:(NSError *)error {
  432. [self setEntriesFeed:feed];
  433. [self setEntriesFetchError:error];
  434. [self setEntriesFetchTicket:nil];
  435. [self updateUI];
  436. }
  437. #pragma mark -
  438. - (void)uploadVideoFile {
  439. NSString *devKey = [mDeveloperKeyField stringValue];
  440. GDataServiceGoogleYouTube *service = [self youTubeService];
  441. [service setYouTubeDeveloperKey:devKey];
  442. NSURL *url = [GDataServiceGoogleYouTube youTubeUploadURLForUserID:kGDataServiceDefaultUser];
  443. // load the file data
  444. NSString *path = [mFilePathField stringValue];
  445. NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:path];
  446. NSString *filename = [path lastPathComponent];
  447. // gather all the metadata needed for the mediaGroup
  448. NSString *titleStr = [mTitleField stringValue];
  449. GDataMediaTitle *title = [GDataMediaTitle textConstructWithString:titleStr];
  450. NSString *categoryStr = [[mCategoryPopup selectedItem] representedObject];
  451. GDataMediaCategory *category = [GDataMediaCategory mediaCategoryWithString:categoryStr];
  452. [category setScheme:kGDataSchemeYouTubeCategory];
  453. NSString *descStr = [mDescriptionField stringValue];
  454. GDataMediaDescription *desc = [GDataMediaDescription textConstructWithString:descStr];
  455. NSString *keywordsStr = [mKeywordsField stringValue];
  456. GDataMediaKeywords *keywords = [GDataMediaKeywords keywordsWithString:keywordsStr];
  457. BOOL isPrivate = ([mPrivateCheckbox state] == NSOnState);
  458. GDataYouTubeMediaGroup *mediaGroup = [GDataYouTubeMediaGroup mediaGroup];
  459. [mediaGroup setMediaTitle:title];
  460. [mediaGroup setMediaDescription:desc];
  461. [mediaGroup addMediaCategory:category];
  462. [mediaGroup setMediaKeywords:keywords];
  463. [mediaGroup setIsPrivate:isPrivate];
  464. NSString *mimeType = [GDataUtilities MIMETypeForFileAtPath:path
  465. defaultMIMEType:@"video/mp4"];
  466. // create the upload entry with the mediaGroup and the file
  467. GDataEntryYouTubeUpload *entry;
  468. entry = [GDataEntryYouTubeUpload uploadEntryWithMediaGroup:mediaGroup
  469. fileHandle:fileHandle
  470. MIMEType:mimeType
  471. slug:filename];
  472. SEL progressSel = @selector(ticket:hasDeliveredByteCount:ofTotalByteCount:);
  473. [service setServiceUploadProgressSelector:progressSel];
  474. GDataServiceTicket *ticket;
  475. ticket = [service fetchEntryByInsertingEntry:entry
  476. forFeedURL:url
  477. delegate:self
  478. didFinishSelector:@selector(uploadTicket:finishedWithEntry:error:)];
  479. [self setUploadTicket:ticket];
  480. // To allow restarting after stopping, we need to track the upload location
  481. // URL. The location URL will be a different address than the upload URL that
  482. // is used to start a new upload.
  483. //
  484. // For compatibility with systems that do not support Objective-C blocks
  485. // (iOS 3 and Mac OS X 10.5), the location URL may also be obtained in the
  486. // progress callback as ((GTMHTTPUploadFetcher *)[ticket objectFetcher]).locationURL
  487. //
  488. GTMHTTPUploadFetcher *uploadFetcher = (GTMHTTPUploadFetcher *)[ticket objectFetcher];
  489. [uploadFetcher setLocationChangeBlock:^(NSURL *url) {
  490. [self setUploadLocationURL:url];
  491. [self updateUI];
  492. }];
  493. [self updateUI];
  494. }
  495. - (void)restartUpload {
  496. // Restart a stopped upload, using the location URL from the previous
  497. // upload attempt
  498. if (mUploadLocationURL == nil) return;
  499. GDataServiceGoogleYouTube *service = [self youTubeService];
  500. NSString *path = [mFilePathField stringValue];
  501. NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:path];
  502. NSString *mimeType = [GDataUtilities MIMETypeForFileAtPath:path
  503. defaultMIMEType:@"video/mp4"];
  504. GDataEntryYouTubeUpload *entry;
  505. entry = [GDataEntryYouTubeUpload uploadEntryWithMediaGroup:nil
  506. fileHandle:fileHandle
  507. MIMEType:mimeType
  508. slug:nil];
  509. [entry setUploadLocationURL:mUploadLocationURL];
  510. SEL progressSel = @selector(ticket:hasDeliveredByteCount:ofTotalByteCount:);
  511. [service setServiceUploadProgressSelector:progressSel];
  512. GDataServiceTicket *ticket;
  513. ticket = [service fetchEntryByInsertingEntry:entry
  514. forFeedURL:nil
  515. delegate:self
  516. didFinishSelector:@selector(uploadTicket:finishedWithEntry:error:)];
  517. [self setUploadTicket:ticket];
  518. // To allow restarting after stopping, we need to track the upload location
  519. // URL.
  520. GTMHTTPUploadFetcher *uploadFetcher = (GTMHTTPUploadFetcher *)[ticket objectFetcher];
  521. [uploadFetcher setLocationChangeBlock:^(NSURL *url) {
  522. [self setUploadLocationURL:url];
  523. [self updateUI];
  524. }];
  525. [self updateUI];
  526. }
  527. // progress callback
  528. - (void)ticket:(GDataServiceTicket *)ticket
  529. hasDeliveredByteCount:(unsigned long long)numberOfBytesRead
  530. ofTotalByteCount:(unsigned long long)dataLength {
  531. [mUploadProgressIndicator setMinValue:0.0];
  532. [mUploadProgressIndicator setMaxValue:(double)dataLength];
  533. [mUploadProgressIndicator setDoubleValue:(double)numberOfBytesRead];
  534. }
  535. // upload callback
  536. - (void)uploadTicket:(GDataServiceTicket *)ticket
  537. finishedWithEntry:(GDataEntryYouTubeVideo *)videoEntry
  538. error:(NSError *)error {
  539. if (error == nil) {
  540. // tell the user that the add worked
  541. [self displayAlert:@"Uploaded"
  542. format:@"Uploaded video: %@",
  543. [[videoEntry title] stringValue]];
  544. // refetch the current entries, in case the list of uploads
  545. // has changed
  546. [self fetchAllEntries];
  547. } else {
  548. [self displayAlert:@"Upload failed"
  549. format:@"Upload failed: %@", error];
  550. }
  551. [mUploadProgressIndicator setDoubleValue:0.0];
  552. [self setUploadTicket:nil];
  553. [self updateUI];
  554. }
  555. // Setting likes/dislikes
  556. //
  557. // To set the authenticated user's rating for a video entry, insert an entry
  558. // into the ratings feed for the video. The value may be
  559. // kGDataYouTubeRatingValueLike or kGDataYouTubeRatingValueDislike
  560. //
  561. // Example:
  562. //
  563. // - (void)setLikesValue:(NSString *)value
  564. // forVideoEntry:(GDataEntryYouTubeVideo *)videoEntry {
  565. //
  566. // GDataEntryYouTubeRating *ratingEntry = [GDataEntryYouTubeRating ratingEntryWithValue:value];
  567. //
  568. // GDataServiceGoogleYouTube *service = [self youTubeService];
  569. // [service fetchEntryByInsertingEntry:ratingEntry
  570. // forFeedURL:[[videoEntry ratingsLink] URL]
  571. // delegate:self
  572. // didFinishSelector:@selector(likesTicket:finishedWithEntry:error:)];
  573. // }
  574. #pragma mark Client ID Sheet
  575. // Client ID and Client Secret Sheet
  576. //
  577. // Sample apps need this sheet to ask for the client ID and client secret
  578. // strings
  579. //
  580. // Your application will just hardcode the client ID and client secret strings
  581. // into the source rather than ask the user for them.
  582. //
  583. // The string values are obtained from the API Console,
  584. // https://code.google.com/apis/console
  585. - (IBAction)clientIDClicked:(id)sender {
  586. // Show the sheet for developers to enter their client ID and client secret
  587. [NSApp beginSheet:mClientIDSheet
  588. modalForWindow:[self window]
  589. modalDelegate:self
  590. didEndSelector:@selector(clientIDSheetDidEnd:returnCode:contextInfo:)
  591. contextInfo:NULL];
  592. }
  593. - (IBAction)clientIDDoneClicked:(id)sender {
  594. [NSApp endSheet:mClientIDSheet returnCode:NSOKButton];
  595. }
  596. - (void)clientIDSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
  597. [sheet orderOut:self];
  598. [self updateUI];
  599. }
  600. #pragma mark Text field delegate methods
  601. - (void)controlTextDidChange:(NSNotification *)note {
  602. [self updateUI]; // enable/disable the upload button
  603. }
  604. #pragma mark TableView delegate methods
  605. //
  606. // table view delegate methods
  607. //
  608. - (void)tableViewSelectionDidChange:(NSNotification *)notification {
  609. [self updateUI];
  610. }
  611. // table view data source methods
  612. - (int)numberOfRowsInTableView:(NSTableView *)tableView {
  613. return [[mEntriesFeed entries] count];
  614. }
  615. - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row {
  616. // get the entry entry's title
  617. GDataEntryBase *entry = [[mEntriesFeed entries] objectAtIndex:row];
  618. return [[entry title] stringValue];
  619. }
  620. #pragma mark Fetch the Categories
  621. - (void)fetchStandardCategories {
  622. // This method initiates a fetch and parse of the assignable categories.
  623. // If successful, the callback loads the category pop-up with the
  624. // categories.
  625. NSURL *categoriesURL = [NSURL URLWithString:kGDataSchemeYouTubeCategory];
  626. GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithURL:categoriesURL];
  627. [fetcher setComment:@"YouTube categories"];
  628. [fetcher beginFetchWithDelegate:self
  629. didFinishSelector:@selector(categoryFetcher:finishedWithData:error:)];
  630. }
  631. - (void)categoryFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error {
  632. if (error) {
  633. NSLog(@"categoryFetcher:%@ failedWithError:%@", fetcher, error);
  634. return;
  635. }
  636. // The categories document looks like
  637. // <app:categories>
  638. // <atom:category term='Film' label='Film &amp; Animation'>
  639. // <yt:browsable />
  640. // <yt:assignable />
  641. // </atom:category>
  642. // </app:categories>
  643. //
  644. // We only want the categories which are assignable. We'll use XPath to
  645. // select those, then get the string value of the resulting term attribute
  646. // nodes.
  647. NSString *const path = @"app:categories/atom:category[yt:assignable]";
  648. NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:data
  649. options:0
  650. error:&error] autorelease];
  651. if (xmlDoc == nil) {
  652. NSLog(@"category fetch could not parse XML: %@", error);
  653. } else {
  654. NSArray *nodes = [xmlDoc nodesForXPath:path
  655. error:&error];
  656. unsigned int numberOfNodes = [nodes count];
  657. if (numberOfNodes == 0) {
  658. NSLog(@"category fetch could not find nodes: %@", error);
  659. } else {
  660. // add the category labels as menu items, and the category terms as
  661. // the menu item representedObjects.
  662. [mCategoryPopup removeAllItems];
  663. NSMenu *menu = [mCategoryPopup menu];
  664. for (int idx = 0; idx < numberOfNodes; idx++) {
  665. NSXMLElement *category = [nodes objectAtIndex:idx];
  666. NSString *term = [[category attributeForName:@"term"] stringValue];
  667. NSString *label = [[category attributeForName:@"label"] stringValue];
  668. if (label == nil) label = term;
  669. NSMenuItem *item = [menu addItemWithTitle:label
  670. action:nil
  671. keyEquivalent:@""];
  672. [item setRepresentedObject:term];
  673. }
  674. }
  675. }
  676. }
  677. #pragma mark Setters and Getters
  678. - (GDataFeedYouTubeVideo *)entriesFeed {
  679. return mEntriesFeed;
  680. }
  681. - (void)setEntriesFeed:(GDataFeedYouTubeVideo *)feed {
  682. [mEntriesFeed autorelease];
  683. mEntriesFeed = [feed retain];
  684. }
  685. - (NSError *)entryFetchError {
  686. return mEntriesFetchError;
  687. }
  688. - (void)setEntriesFetchError:(NSError *)error {
  689. [mEntriesFetchError release];
  690. mEntriesFetchError = [error retain];
  691. }
  692. - (GDataServiceTicket *)entriesFetchTicket {
  693. return mEntriesFetchTicket;
  694. }
  695. - (void)setEntriesFetchTicket:(GDataServiceTicket *)ticket {
  696. [mEntriesFetchTicket release];
  697. mEntriesFetchTicket = [ticket retain];
  698. }
  699. - (NSString *)entryImageURLString {
  700. return mEntryImageURLString;
  701. }
  702. - (void)setEntryImageURLString:(NSString *)str {
  703. [mEntryImageURLString autorelease];
  704. mEntryImageURLString = [str copy];
  705. }
  706. - (GDataServiceTicket *)uploadTicket {
  707. return mUploadTicket;
  708. }
  709. - (void)setUploadTicket:(GDataServiceTicket *)ticket {
  710. [mUploadTicket release];
  711. mUploadTicket = [ticket retain];
  712. }
  713. - (NSURL *)uploadLocationURL {
  714. return mUploadLocationURL;
  715. }
  716. - (void)setUploadLocationURL:(NSURL *)url {
  717. [mUploadLocationURL release];
  718. mUploadLocationURL = [url retain];
  719. }
  720. @end