# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Handles the download of the search engine favicons.
For all search engines referenced in search_engine_countries-inc.cc,
downloads their Favicon, scales it and puts it as resource into the repository
for display, e.g. in the search engine choice UI and settings.
This should be run whenever search_engine_countries-inc.cc changes the list of
search engines used per country, or whenever prepopulated_engines.json changes
a favicon.
To run:
`python3 tools/search_engine_choice/download_search_engine_icons.py`.
"""
import hashlib
import json
import os
import sys
import requests
import search_engine_icons_utils
def get_image_hash(image_path):
"""Gets the hash of the image that's passed as argument.
This is needed to check whether the downloaded image was already added in the
repo or not.
Args:
image_path: The path of the image for which we want to get the hash.
Returns:
The hash of the image that's passed as argument.
"""
with open(image_path, 'rb') as image:
return hashlib.sha256(image.read()).hexdigest()
def delete_files_in_directory(directory_path):
"""Deletes previously generated icons.
Deletes the icons that were previously created and added to directory_path.
Args:
directory_path: The path of the directory where the icons live.
Raises:
OSError: Error occurred while deleting files in {directory_path}
"""
try:
files = os.listdir(directory_path)
for file in files:
file_path = os.path.join(directory_path, file)
# Only remove pngs.
filename = os.path.basename(file_path)
if filename.endswith('.png') and os.path.isfile(file_path):
os.remove(file_path)
print('All files deleted successfully from ' + directory_path)
except OSError:
print('Error occurred while deleting files in ' + directory_path)
def download_icons_from_android_search():
"""Downloads icons from the android_search gstatic directory.
Goes through all search engines in `prepopulated_engines.json` and downloads
the corresponding 96x96 icon from the appropriate subfolder of
https://www.gstatic.com/android_search/search_providers/. Because there is no
way to list the contents of a directory on gstatic and because some
subfolders are not named exactly the same as in `prepopulated_engines.json`,
this function loads a config file `generate_search_engine_icons_config.json`
with the extra information needed to locate the icons.
The Google Search icon is not downloaded because it already exists in the
repo. Search engines not relevant to the to the default search engine choice
screen are ignored.
"""
for percent in ['100', '200', '300']:
delete_files_in_directory(
f'components/resources/default_{percent}_percent/search_engine_choice')
with open(search_engine_icons_utils.config_file_path, 'r',
encoding='utf-8') as config_json:
config_data = json.loads(json_comment_eater.Nom(config_json.read()))
icon_hash_to_name = {}
for (engine, keyword) in sorted(
search_engine_icons_utils.get_used_engines_with_keywords("")):
icon_name = search_engine_icons_utils.keyword_to_identifer(keyword)
icon_full_path = f'components/resources/default_100_percent/search_engine_choice/{icon_name}.png'
if engine in config_data['engine_aliases']:
engine = config_data['engine_aliases'][engine]
directory_url = f'https://www.gstatic.com/android_search/search_providers/{engine}/'
try_filenames = []
if engine in config_data['non_default_icon_filenames']:
try_filenames = [
config_data['non_default_icon_filenames'][engine] + 'mdpi.png'
]
try_filenames = try_filenames + [
f'{engine}_icon_mdpi.png',
f'{engine}_mdpi.png',
'mdpi.png',
]
any_found = False
for filename in try_filenames:
icon_url = directory_url + filename
try:
img_data = requests.get(icon_url)
except requests.exceptions.RequestException as e:
print('Error when loading URL {icon_url}: {e}')
continue
if img_data.status_code == 200:
with open(icon_full_path, 'wb') as icon_file:
icon_file.write(img_data.content)
any_found = True
break
if not any_found:
print('WARNING: no icon found for search engine: ' + engine)
continue
icon_hash = get_image_hash(icon_full_path)
if icon_hash in icon_hash_to_name:
# We already have this icon.
engine_keyword_to_icon_name[keyword] = icon_hash_to_name[icon_hash]
os.remove(icon_full_path)
continue
# Download hidpi versions
# If the low dpi version is basename_mdpi.png, download basename_xhdpi.png
# and basename_xxhdpi.png.
for (resource_path, hidpi) in [('default_200_percent', 'xhdpi'),
('default_300_percent', 'xxhdpi')]:
# Replace the substring "mdpi" by "xhdpi" or "xxhdpi" from the end.
(basename, mdpi_suffix, png_extension) = icon_url.rpartition('mdpi')
hidpi_url = basename + hidpi + png_extension
hidpi_path = f'components/resources/{resource_path}/search_engine_choice/{icon_name}.png'
try:
img_data = requests.get(hidpi_url)
except requests.exceptions.RequestException as e:
print('Error when loading URL {hidpi_url}: {e}')
continue
if img_data.status_code == 200:
with open(hidpi_path, 'wb') as icon_file:
icon_file.write(img_data.content)
else:
print('WARNING: no %s icon found for search engine: %s' %
(hidpi, engine))
engine_keyword_to_icon_name[keyword] = icon_name
icon_hash_to_name[icon_hash] = icon_name
print('Finished downloading icons')
os.system('tools/resources/optimize-png-files.sh search_engine_choice')
def generate_icon_resource_code():
"""Links the downloaded icons to their respective resource id.
Generates the code to link the icons to a resource ID in
`search_engine_choice_scaled_resources.grdp`
"""
print('Writing to search_engine_choice_scaled_resources.grdp...')
with open('components/resources/search_engine_choice_scaled_resources.grdp',
'w',
encoding='utf-8',
newline='') as grdp_file:
grdp_file.write('<?xml version="1.0" encoding="utf-8"?>\n')
grdp_file.write(
'<!-- This file is generated using generate_search_engine_icons.py'
' -->\n')
grdp_file.write("<!-- Don't modify it manually -->\n")
grdp_file.write('<grit-part>\n')
# Add the google resource id.
grdp_file.write(' <if expr="_google_chrome">\n')
grdp_file.write(' <structure type="chrome_scaled_image"'
' name="IDR_GOOGLE_COM_PNG"'
' file="google_chrome/google_search_logo.png" />\n')
grdp_file.write(' </if>\n')
# Add the remaining resource ids, sorted alphabetically.
resources = []
for engine_keyword, icon_name in engine_keyword_to_icon_name.items():
resource_id = search_engine_icons_utils.keyword_to_resource_name(
engine_keyword)
resources.append((resource_id, icon_name))
for resource_id, icon_name in sorted(resources):
grdp_file.write(
f' <structure type="chrome_scaled_image" name="{resource_id}" file="search_engine_choice/{icon_name}.png" />\n'
)
grdp_file.write('</grit-part>\n')
if sys.platform != 'linux':
print(
'Warning: This script has not been tested outside of the Linux platform')
# Move to working directory to `src/`.
current_file_path = os.path.dirname(__file__)
os.chdir(current_file_path)
os.chdir('../../')
sys.path.insert(0,
os.path.normpath(current_file_path + "/../json_comment_eater"))
try:
import json_comment_eater
finally:
sys.path.pop(0)
# This is a dictionary of engine keyword to corresponding icon name. Have an
# empty icon name would mean that we weren't able to download the favicon for
# that engine.
engine_keyword_to_icon_name = {}
download_icons_from_android_search()
generate_icon_resource_code()
print('Icon download completed.')