blob: e1a8ed0ef5b3dbb74c4e15167ec515c3927ae4ef [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ios/chrome/browser/ui/ntp/ntp_tile_saver.h"
#include "base/md5.h"
#include "base/strings/sys_string_conversions.h"
#include "components/favicon/core/fallback_url_util.h"
#include "components/ntp_tiles/ntp_tile.h"
#import "ios/chrome/browser/ui/ntp/google_landing_data_source.h"
#import "ios/chrome/browser/ui/ntp/ntp_tile.h"
#include "ios/chrome/common/app_group/app_group_constants.h"
#import "net/base/mac/url_conversions.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace ntp_tile_saver {
// Write the |mostVisitedSites| to disk.
void WriteSavedMostVisited(NSDictionary<NSURL*, NTPTile*>* mostVisitedSites);
// Returns the path for the temporary favicon folder.
NSURL* TmpFaviconFolderPath();
// Replaces the current saved favicons at |faviconsURL| by the contents of the
// tmp folder.
void ReplaceSavedFavicons(NSURL* faviconsURL);
// Checks if every site in |tiles| has had its favicons fetched. If so, writes
// the info to disk, saving the favicons to |faviconsURL|.
void WriteToDiskIfComplete(NSDictionary<NSURL*, NTPTile*>* tiles,
NSURL* faviconsURL);
// Gets a name for the favicon file.
NSString* GetFaviconFileName(const GURL& url);
// If the sites currently saved include one with |tile|'s url, replace it by
// |tile|.
void WriteSingleUpdatedTileToDisk(NTPTile* tile);
} // namespace ntp_tile_saver
namespace ntp_tile_saver {
NSURL* TmpFaviconFolderPath() {
return [[NSURL fileURLWithPath:NSTemporaryDirectory()]
URLByAppendingPathComponent:@"tmpFaviconFolder"];
}
void ReplaceSavedFavicons(NSURL* faviconsURL) {
if ([[NSFileManager defaultManager] fileExistsAtPath:[faviconsURL path]]) {
[[NSFileManager defaultManager] removeItemAtURL:faviconsURL error:nil];
}
if ([[NSFileManager defaultManager]
fileExistsAtPath:[TmpFaviconFolderPath() path]]) {
[[NSFileManager defaultManager]
createDirectoryAtURL:faviconsURL.URLByDeletingLastPathComponent
withIntermediateDirectories:YES
attributes:nil
error:nil];
[[NSFileManager defaultManager] moveItemAtURL:TmpFaviconFolderPath()
toURL:faviconsURL
error:nil];
}
}
void WriteToDiskIfComplete(NSDictionary<NSURL*, NTPTile*>* tiles,
NSURL* faviconsURL) {
for (NSURL* siteURL : tiles) {
NTPTile* tile = [tiles objectForKey:siteURL];
if (!tile.faviconFetched) {
return;
}
// Any fetched tile must have a file name or a fallback monogram.
DCHECK(tile.faviconFileName || tile.fallbackMonogram);
}
ReplaceSavedFavicons(faviconsURL);
WriteSavedMostVisited(tiles);
}
NSString* GetFaviconFileName(const GURL& url) {
return [base::SysUTF8ToNSString(base::MD5String(url.spec()))
stringByAppendingString:@".png"];
}
void SaveMostVisitedToDisk(const ntp_tiles::NTPTilesVector& mostVisitedData,
id<GoogleLandingDataSource> faviconFetcher,
NSURL* faviconsURL) {
if (faviconsURL == nil) {
return;
}
NSMutableDictionary<NSURL*, NTPTile*>* tiles =
[[NSMutableDictionary alloc] init];
// Clear the tmp favicon folder and recreate it.
NSURL* tmpFaviconURL = TmpFaviconFolderPath();
if ([[NSFileManager defaultManager] fileExistsAtPath:[tmpFaviconURL path]]) {
[[NSFileManager defaultManager] removeItemAtURL:tmpFaviconURL error:nil];
}
[[NSFileManager defaultManager] createDirectoryAtPath:[tmpFaviconURL path]
withIntermediateDirectories:YES
attributes:nil
error:nil];
// If there are 0 sites to display, the for loop below will not be entered.
// Write the updated empty list of sites to disk before returning.
if (mostVisitedData.size() == 0) {
WriteToDiskIfComplete(tiles, faviconsURL);
}
// For each site, get the favicon. If it is returned, write it to the favicon
// tmp folder. If a fallback value is returned, update the tile info. Calls
// WriteToDiskIfComplete after each callback execution.
// All the sites are added first to the list so that the WriteToDiskIfComplete
// command is not passed an incomplete list.
for (size_t i = 0; i < mostVisitedData.size(); i++) {
const ntp_tiles::NTPTile& ntpTile = mostVisitedData[i];
NTPTile* tile =
[[NTPTile alloc] initWithTitle:base::SysUTF16ToNSString(ntpTile.title)
URL:net::NSURLWithGURL(ntpTile.url)
position:i];
[tiles setObject:tile forKey:tile.URL];
}
for (NTPTile* tile : [tiles objectEnumerator]) {
const GURL& gurl = net::GURLWithNSURL(tile.URL);
NSString* faviconFileName = GetFaviconFileName(gurl);
NSURL* fileURL =
[tmpFaviconURL URLByAppendingPathComponent:faviconFileName];
void (^faviconImageBlock)(UIImage*) = ^(UIImage* favicon) {
NSData* imageData = UIImagePNGRepresentation(favicon);
if ([imageData writeToURL:fileURL atomically:YES]) {
// If saving the image is not possible, the best thing to do is to keep
// the previous tile.
tile.faviconFetched = YES;
tile.faviconFileName = faviconFileName;
WriteToDiskIfComplete(tiles, faviconsURL);
}
};
void (^fallbackBlock)(UIColor*, UIColor*, BOOL) =
^(UIColor* textColor, UIColor* backgroundColor, BOOL isDefaultColor) {
tile.faviconFetched = YES;
tile.fallbackTextColor = textColor;
tile.fallbackBackgroundColor = backgroundColor;
tile.fallbackIsDefaultColor = isDefaultColor;
tile.fallbackMonogram = base::SysUTF16ToNSString(
favicon::GetFallbackIconText(net::GURLWithNSURL(tile.URL)));
DCHECK(tile.fallbackMonogram && tile.fallbackTextColor &&
tile.fallbackBackgroundColor);
WriteToDiskIfComplete(tiles, faviconsURL);
};
// The cache is not used here as it is simply a way to have a synchronous
// immediate return. In this case it is unnecessary.
[faviconFetcher getFaviconForURL:gurl
size:48
useCache:NO
imageCallback:faviconImageBlock
fallbackCallback:fallbackBlock];
}
}
void WriteSingleUpdatedTileToDisk(NTPTile* tile) {
NSMutableDictionary* tiles = [ReadSavedMostVisited() mutableCopy];
[tiles setObject:tile forKey:tile.URL];
WriteSavedMostVisited(tiles);
}
void WriteSavedMostVisited(NSDictionary<NSURL*, NTPTile*>* mostVisitedSites) {
NSData* data = [NSKeyedArchiver archivedDataWithRootObject:mostVisitedSites];
NSUserDefaults* sharedDefaults = app_group::GetGroupUserDefaults();
[sharedDefaults setObject:data forKey:app_group::kSuggestedItems];
// TODO(crbug.com/750673): Update the widget's visibility depending on
// availability of sites.
}
NSDictionary* ReadSavedMostVisited() {
NSUserDefaults* sharedDefaults = app_group::GetGroupUserDefaults();
return [NSKeyedUnarchiver
unarchiveObjectWithData:[sharedDefaults
objectForKey:app_group::kSuggestedItems]];
}
void UpdateSingleFavicon(const GURL& siteURL,
id<GoogleLandingDataSource> faviconFetcher,
NSURL* faviconsURL) {
NSDictionary* tiles = ReadSavedMostVisited();
NSURL* siteNSURL = net::NSURLWithGURL(siteURL);
NTPTile* tile = [tiles objectForKey:siteNSURL];
if (!tile) {
return;
}
// Remove existing favicon info
tile.fallbackTextColor = nil;
tile.fallbackBackgroundColor = nil;
tile.faviconFetched = NO;
NSString* previousFaviconFileName = tile.faviconFileName;
tile.faviconFileName = nil;
// Fetch favicon and update saved defaults.
NSString* faviconFileName = GetFaviconFileName(siteURL);
NSURL* fileURL = [faviconsURL URLByAppendingPathComponent:faviconFileName];
NSURL* previousFileURL =
previousFaviconFileName
? [faviconsURL URLByAppendingPathComponent:previousFaviconFileName]
: nil;
NSString* monogram =
base::SysUTF16ToNSString(favicon::GetFallbackIconText(siteURL));
void (^faviconImageBlock)(UIImage*) = ^(UIImage* favicon) {
tile.faviconFetched = YES;
NSData* imageData = UIImagePNGRepresentation(favicon);
[[NSFileManager defaultManager] removeItemAtURL:previousFileURL error:nil];
if ([imageData writeToURL:fileURL atomically:YES]) {
tile.faviconFileName = faviconFileName;
}
WriteSingleUpdatedTileToDisk(tile);
};
void (^fallbackBlock)(UIColor*, UIColor*, BOOL) =
^(UIColor* textColor, UIColor* backgroundColor, BOOL isDefaultColor) {
tile.faviconFetched = YES;
tile.fallbackTextColor = textColor;
tile.fallbackBackgroundColor = backgroundColor;
tile.fallbackIsDefaultColor = isDefaultColor;
tile.fallbackMonogram = monogram;
[[NSFileManager defaultManager] removeItemAtURL:previousFileURL
error:nil];
WriteSingleUpdatedTileToDisk(tile);
};
// The cache is not used here as it is simply a way to have a synchronous
// immediate return. In this case it is unnecessary.
[faviconFetcher getFaviconForURL:siteURL
size:48
useCache:NO
imageCallback:faviconImageBlock
fallbackCallback:fallbackBlock];
}
} // namespace ntp_tile_saver