1 /* 2 * Copyright 2022 Google LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.libraries.mobiledatadownload.downloader; 17 18 import android.net.Uri; 19 import androidx.annotation.VisibleForTesting; 20 import com.google.android.libraries.mobiledatadownload.DownloadException; 21 import com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode; 22 import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil; 23 import com.google.common.base.Preconditions; 24 import com.google.common.collect.ImmutableMap; 25 import com.google.common.util.concurrent.Futures; 26 import com.google.common.util.concurrent.ListenableFuture; 27 import com.google.errorprone.annotations.CanIgnoreReturnValue; 28 import com.google.errorprone.annotations.CheckReturnValue; 29 import java.net.MalformedURLException; 30 import java.util.HashMap; 31 import java.util.Map; 32 33 /** 34 * A composite {@link FileDownloader} that delegates to specific registered FileDownloaders based on 35 * URL scheme. 36 */ 37 public final class MultiSchemeFileDownloader implements FileDownloader { 38 private static final String TAG = "MultiSchemeFileDownloader"; 39 40 /** Builder for {@link MultiSchemeFileDownloader}. */ 41 public static final class Builder { 42 private final Map<String, FileDownloader> schemeToDownloader = new HashMap<>(); 43 44 /** Associates a url scheme (e.g. "http") with a specific {@link FileDownloader} delegate. */ 45 @CanIgnoreReturnValue addScheme(String scheme, FileDownloader downloader)46 public MultiSchemeFileDownloader.Builder addScheme(String scheme, FileDownloader downloader) { 47 schemeToDownloader.put( 48 Preconditions.checkNotNull(scheme), Preconditions.checkNotNull(downloader)); 49 return this; 50 } 51 build()52 public MultiSchemeFileDownloader build() { 53 return new MultiSchemeFileDownloader(this); 54 } 55 } 56 57 private final ImmutableMap<String, FileDownloader> schemeToDownloader; 58 59 /** Returns a Builder for {@link MultiSchemeFileDownloader}. */ builder()60 public static Builder builder() { 61 return new Builder(); 62 } 63 MultiSchemeFileDownloader(Builder builder)64 private MultiSchemeFileDownloader(Builder builder) { 65 this.schemeToDownloader = ImmutableMap.copyOf(builder.schemeToDownloader); 66 } 67 68 @Override 69 @CheckReturnValue startDownloading(DownloadRequest downloadRequest)70 public ListenableFuture<Void> startDownloading(DownloadRequest downloadRequest) { 71 FileDownloader delegate; 72 try { 73 delegate = getDelegate(downloadRequest.urlToDownload()); 74 } catch (DownloadException e) { 75 return Futures.immediateFailedFuture(e); 76 } 77 return delegate.startDownloading(downloadRequest); 78 } 79 80 @Override 81 @CheckReturnValue isContentChanged( CheckContentChangeRequest checkContentChangeRequest)82 public ListenableFuture<CheckContentChangeResponse> isContentChanged( 83 CheckContentChangeRequest checkContentChangeRequest) { 84 FileDownloader delegate; 85 try { 86 delegate = getDelegate(checkContentChangeRequest.url()); 87 } catch (DownloadException e) { 88 return Futures.immediateFailedFuture(e); 89 } 90 return delegate.isContentChanged(checkContentChangeRequest); 91 } 92 93 /** Extract the scheme of a url string. */ 94 @VisibleForTesting getScheme(String url)95 static String getScheme(String url) throws MalformedURLException { 96 Uri parsed = Uri.parse(url); 97 if (parsed == null) { 98 throw new MalformedURLException("Could not parse URL."); 99 } 100 String scheme = parsed.getScheme(); 101 if (scheme == null) { 102 throw new MalformedURLException("URL contained no scheme."); 103 } 104 return scheme; 105 } 106 107 /** 108 * Lookup the delegate FileDownloader that can handle a url, based on the url's scheme. 109 * 110 * @throws DownloadException If an appropriate delegate FileDownloader could not be found. 111 */ getDelegate(String url)112 FileDownloader getDelegate(String url) throws DownloadException { 113 String scheme; 114 try { 115 scheme = getScheme(url); 116 } catch (MalformedURLException e) { 117 LogUtil.e("%s: The download url is malformed, url = %s", TAG, url); 118 throw DownloadException.builder() 119 .setDownloadResultCode(DownloadResultCode.MALFORMED_DOWNLOAD_URL) 120 .setCause(e) 121 .build(); 122 } 123 124 FileDownloader downloader = schemeToDownloader.get(scheme); 125 if (downloader == null) { 126 LogUtil.e( 127 "%s: No registered downloader supports the download url scheme, scheme = %s", 128 TAG, scheme); 129 throw DownloadException.builder() 130 .setDownloadResultCode(DownloadResultCode.UNSUPPORTED_DOWNLOAD_URL_SCHEME) 131 .build(); 132 } 133 return downloader; 134 } 135 } 136