1 /* 2 * Copyright (C) 2017 The Android Open Source Project 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 org.mozilla.thirdparty.com.google.android.exoplayer2.offline; 17 18 import android.net.Uri; 19 import androidx.annotation.Nullable; 20 import org.mozilla.thirdparty.com.google.android.exoplayer2.offline.DownloadRequest.UnsupportedRequestException; 21 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.AtomicFile; 22 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util; 23 import java.io.DataInputStream; 24 import java.io.File; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.util.ArrayList; 28 import java.util.List; 29 30 /** 31 * Loads {@link DownloadRequest DownloadRequests} from legacy action files. 32 * 33 * @deprecated Legacy action files should be merged into download indices using {@link 34 * ActionFileUpgradeUtil}. 35 */ 36 @Deprecated 37 /* package */ final class ActionFile { 38 39 private static final int VERSION = 0; 40 41 private final AtomicFile atomicFile; 42 43 /** 44 * @param actionFile The file from which {@link DownloadRequest DownloadRequests} will be loaded. 45 */ ActionFile(File actionFile)46 public ActionFile(File actionFile) { 47 atomicFile = new AtomicFile(actionFile); 48 } 49 50 /** Returns whether the file or its backup exists. */ exists()51 public boolean exists() { 52 return atomicFile.exists(); 53 } 54 55 /** Deletes the action file and its backup. */ delete()56 public void delete() { 57 atomicFile.delete(); 58 } 59 60 /** 61 * Loads {@link DownloadRequest DownloadRequests} from the file. 62 * 63 * @return The loaded {@link DownloadRequest DownloadRequests}, or an empty array if the file does 64 * not exist. 65 * @throws IOException If there is an error reading the file. 66 */ load()67 public DownloadRequest[] load() throws IOException { 68 if (!exists()) { 69 return new DownloadRequest[0]; 70 } 71 @Nullable InputStream inputStream = null; 72 try { 73 inputStream = atomicFile.openRead(); 74 DataInputStream dataInputStream = new DataInputStream(inputStream); 75 int version = dataInputStream.readInt(); 76 if (version > VERSION) { 77 throw new IOException("Unsupported action file version: " + version); 78 } 79 int actionCount = dataInputStream.readInt(); 80 ArrayList<DownloadRequest> actions = new ArrayList<>(); 81 for (int i = 0; i < actionCount; i++) { 82 try { 83 actions.add(readDownloadRequest(dataInputStream)); 84 } catch (UnsupportedRequestException e) { 85 // remove DownloadRequest is not supported. Ignore and continue loading rest. 86 } 87 } 88 return actions.toArray(new DownloadRequest[0]); 89 } finally { 90 Util.closeQuietly(inputStream); 91 } 92 } 93 readDownloadRequest(DataInputStream input)94 private static DownloadRequest readDownloadRequest(DataInputStream input) throws IOException { 95 String type = input.readUTF(); 96 int version = input.readInt(); 97 98 Uri uri = Uri.parse(input.readUTF()); 99 boolean isRemoveAction = input.readBoolean(); 100 101 int dataLength = input.readInt(); 102 @Nullable byte[] data; 103 if (dataLength != 0) { 104 data = new byte[dataLength]; 105 input.readFully(data); 106 } else { 107 data = null; 108 } 109 110 // Serialized version 0 progressive actions did not contain keys. 111 boolean isLegacyProgressive = version == 0 && DownloadRequest.TYPE_PROGRESSIVE.equals(type); 112 List<StreamKey> keys = new ArrayList<>(); 113 if (!isLegacyProgressive) { 114 int keyCount = input.readInt(); 115 for (int i = 0; i < keyCount; i++) { 116 keys.add(readKey(type, version, input)); 117 } 118 } 119 120 // Serialized version 0 and 1 DASH/HLS/SS actions did not contain a custom cache key. 121 boolean isLegacySegmented = 122 version < 2 123 && (DownloadRequest.TYPE_DASH.equals(type) 124 || DownloadRequest.TYPE_HLS.equals(type) 125 || DownloadRequest.TYPE_SS.equals(type)); 126 @Nullable String customCacheKey = null; 127 if (!isLegacySegmented) { 128 customCacheKey = input.readBoolean() ? input.readUTF() : null; 129 } 130 131 // Serialized version 0, 1 and 2 did not contain an id. We need to generate one. 132 String id = version < 3 ? generateDownloadId(uri, customCacheKey) : input.readUTF(); 133 134 if (isRemoveAction) { 135 // Remove actions are not supported anymore. 136 throw new UnsupportedRequestException(); 137 } 138 return new DownloadRequest(id, type, uri, keys, customCacheKey, data); 139 } 140 141 private static StreamKey readKey(String type, int version, DataInputStream input) 142 throws IOException { 143 int periodIndex; 144 int groupIndex; 145 int trackIndex; 146 147 // Serialized version 0 HLS/SS actions did not contain a period index. 148 if ((DownloadRequest.TYPE_HLS.equals(type) || DownloadRequest.TYPE_SS.equals(type)) 149 && version == 0) { 150 periodIndex = 0; 151 groupIndex = input.readInt(); 152 trackIndex = input.readInt(); 153 } else { 154 periodIndex = input.readInt(); 155 groupIndex = input.readInt(); 156 trackIndex = input.readInt(); 157 } 158 return new StreamKey(periodIndex, groupIndex, trackIndex); 159 } 160 161 private static String generateDownloadId(Uri uri, @Nullable String customCacheKey) { 162 return customCacheKey != null ? customCacheKey : uri.toString(); 163 } 164 } 165