blob: 986c385aa17111f9d96b5cb4c2341f8742ad719a [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/bind.h"
#import "base/mac/bind_objc_block.h"
#include "base/md5.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task_scheduler/post_task.h"
#include "base/threading/thread_restrictions.h"
#include "components/favicon/core/fallback_url_util.h"
#include "components/ntp_tiles/ntp_tile.h"
#import "ios/chrome/browser/ui/favicon/favicon_attributes.h"
#import "ios/chrome/browser/ui/favicon/favicon_attributes_provider.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 |most_visited_sites| to disk.
void WriteSavedMostVisited(NSDictionary<NSURL*, NTPTile*>* most_visited_sites);
// Returns the path for the temporary favicon folder.
NSURL* GetTemporaryFaviconFolderPath();
// Replaces the current saved favicons at |favicons_folder| by the contents of
// the tmp folder.
void ReplaceSavedFavicons(NSURL* favicons_folder);
// Checks if every site in |tiles| has had its favicons fetched. If so, writes
// the info to disk, saving the favicons to |favicons_folder|.
void WriteToDiskIfComplete(NSDictionary<NSURL*, NTPTile*>* tiles,
NSURL* favicons_folder);
// 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);
// Empty the temporary favicon folder.
void ClearTemporaryFaviconFolder();
// Get the favicons using |favicon_provider| and writes them to disk.
void GetFaviconsAndSave(const ntp_tiles::NTPTilesVector& most_visited_data,
FaviconAttributesProvider* favicon_provider,
NSURL* favicons_folder);
} // namespace ntp_tile_saver
namespace ntp_tile_saver {
NSURL* GetTemporaryFaviconFolderPath() {
return [[NSURL fileURLWithPath:NSTemporaryDirectory()]
URLByAppendingPathComponent:@"tmpFaviconFolder"];
}
void ReplaceSavedFavicons(NSURL* favicons_folder) {
base::AssertBlockingAllowed();
if ([[NSFileManager defaultManager]
fileExistsAtPath:[favicons_folder path]]) {
[[NSFileManager defaultManager] removeItemAtURL:favicons_folder error:nil];
}
if ([[NSFileManager defaultManager]
fileExistsAtPath:[GetTemporaryFaviconFolderPath() path]]) {
[[NSFileManager defaultManager]
createDirectoryAtURL:favicons_folder
.URLByDeletingLastPathComponent
withIntermediateDirectories:YES
attributes:nil
error:nil];
[[NSFileManager defaultManager]
moveItemAtURL:GetTemporaryFaviconFolderPath()
toURL:favicons_folder
error:nil];
}
}
void WriteToDiskIfComplete(NSDictionary<NSURL*, NTPTile*>* tiles,
NSURL* favicons_folder) {
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);
}
base::PostTaskWithTraitsAndReply(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
base::BindBlockArc(^{
ReplaceSavedFavicons(favicons_folder);
}),
base::BindBlockArc(^{
WriteSavedMostVisited(tiles);
}));
}
void ClearTemporaryFaviconFolder() {
base::AssertBlockingAllowed();
NSURL* tmpFaviconURL = GetTemporaryFaviconFolderPath();
if ([[NSFileManager defaultManager] fileExistsAtPath:[tmpFaviconURL path]]) {
[[NSFileManager defaultManager] removeItemAtURL:tmpFaviconURL error:nil];
}
[[NSFileManager defaultManager] createDirectoryAtPath:[tmpFaviconURL path]
withIntermediateDirectories:YES
attributes:nil
error:nil];
}
NSString* GetFaviconFileName(const GURL& url) {
return [base::SysUTF8ToNSString(base::MD5String(url.spec()))
stringByAppendingString:@".png"];
}
void GetFaviconsAndSave(const ntp_tiles::NTPTilesVector& most_visited_data,
FaviconAttributesProvider* favicon_provider,
NSURL* favicons_folder) {
NSMutableDictionary<NSURL*, NTPTile*>* tiles =
[[NSMutableDictionary alloc] init];
// 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 (most_visited_data.empty()) {
WriteToDiskIfComplete(tiles, favicons_folder);
return;
}
// 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 < most_visited_data.size(); i++) {
const ntp_tiles::NTPTile& ntp_tile = most_visited_data[i];
NTPTile* tile =
[[NTPTile alloc] initWithTitle:base::SysUTF16ToNSString(ntp_tile.title)
URL:net::NSURLWithGURL(ntp_tile.url)
position:i];
[tiles setObject:tile forKey:tile.URL];
}
NSURL* tmpFaviconURL = GetTemporaryFaviconFolderPath();
for (NTPTile* tile : [tiles objectEnumerator]) {
const GURL& gurl = net::GURLWithNSURL(tile.URL);
NSString* faviconFileName = GetFaviconFileName(gurl);
NSURL* fileURL =
[tmpFaviconURL URLByAppendingPathComponent:faviconFileName];
void (^faviconAttributesBlock)(FaviconAttributes*) =
^(FaviconAttributes* attributes) {
if (attributes.faviconImage) {
NSData* imageData =
UIImagePNGRepresentation(attributes.faviconImage);
base::OnceCallback<BOOL()> writeImage = base::BindBlockArc(^BOOL() {
return [imageData writeToURL:fileURL atomically:YES];
});
base::OnceCallback<void(BOOL)> updateTile =
base::BindBlockArc(^(BOOL imageWriteSuccess) {
if (imageWriteSuccess) {
// If saving the image is not possible, the best thing to do
// is to keep the previous tiles.
tile.faviconFetched = YES;
tile.faviconFileName = faviconFileName;
WriteToDiskIfComplete(tiles, favicons_folder);
}
});
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
std::move(writeImage), std::move(updateTile));
} else {
tile.faviconFetched = YES;
tile.fallbackTextColor = attributes.textColor;
tile.fallbackBackgroundColor = attributes.backgroundColor;
tile.fallbackIsDefaultColor = attributes.defaultBackgroundColor;
tile.fallbackMonogram = attributes.monogramString;
DCHECK(tile.fallbackMonogram && tile.fallbackTextColor &&
tile.fallbackBackgroundColor);
WriteToDiskIfComplete(tiles, favicons_folder);
}
};
// The cache is not used here as it is simply a way to have a synchronous
// immediate return. In this case it is unnecessary.
[favicon_provider fetchFaviconAttributesForURL:gurl
completion:faviconAttributesBlock];
}
}
void SaveMostVisitedToDisk(const ntp_tiles::NTPTilesVector& most_visited_data,
FaviconAttributesProvider* favicon_provider,
NSURL* favicons_folder) {
if (favicons_folder == nil) {
return;
}
base::PostTaskWithTraitsAndReply(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
base::BindOnce(&ClearTemporaryFaviconFolder),
base::Bind(base::BindBlockArc(
^(const ntp_tiles::NTPTilesVector& most_visited_data) {
GetFaviconsAndSave(most_visited_data, favicon_provider,
favicons_folder);
}),
most_visited_data));
}
void WriteSingleUpdatedTileToDisk(NTPTile* tile) {
NSMutableDictionary* tiles = [ReadSavedMostVisited() mutableCopy];
[tiles setObject:tile forKey:tile.URL];
WriteSavedMostVisited(tiles);
}
void WriteSavedMostVisited(NSDictionary<NSURL*, NTPTile*>* most_visited_data) {
NSData* data = [NSKeyedArchiver archivedDataWithRootObject:most_visited_data];
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& site_url,
FaviconAttributesProvider* favicon_provider,
NSURL* favicons_folder) {
NSDictionary* tiles = ReadSavedMostVisited();
NSURL* siteNSURL = net::NSURLWithGURL(site_url);
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(site_url);
NSURL* fileURL =
[favicons_folder URLByAppendingPathComponent:faviconFileName];
NSURL* previousFileURL =
previousFaviconFileName
? [favicons_folder
URLByAppendingPathComponent:previousFaviconFileName]
: nil;
void (^faviconAttributesBlock)(FaviconAttributes*) =
^(FaviconAttributes* attributes) {
if (attributes.faviconImage) {
tile.faviconFetched = YES;
NSData* imageData = UIImagePNGRepresentation(attributes.faviconImage);
base::OnceCallback<BOOL()> writeImage = base::BindBlockArc(^BOOL {
base::AssertBlockingAllowed();
[[NSFileManager defaultManager] removeItemAtURL:previousFileURL
error:nil];
return [imageData writeToURL:fileURL atomically:YES];
});
base::OnceCallback<void(BOOL)> updateTile =
base::BindBlockArc(^(BOOL imageWriteSuccess) {
if (imageWriteSuccess) {
tile.faviconFileName = faviconFileName;
}
WriteSingleUpdatedTileToDisk(tile);
});
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
std::move(writeImage), std::move(updateTile));
} else {
tile.faviconFetched = YES;
tile.fallbackTextColor = attributes.textColor;
tile.fallbackBackgroundColor = attributes.backgroundColor;
tile.fallbackIsDefaultColor = attributes.defaultBackgroundColor;
tile.fallbackMonogram = attributes.monogramString;
base::OnceCallback<void()> removeImage = base::BindBlockArc(^{
base::AssertBlockingAllowed();
[[NSFileManager defaultManager] removeItemAtURL:previousFileURL
error:nil];
});
base::PostTaskWithTraitsAndReply(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
std::move(removeImage), base::BindBlockArc(^{
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.
[favicon_provider fetchFaviconAttributesForURL:site_url
completion:faviconAttributesBlock];
}
} // namespace ntp_tile_saver