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