• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.file.backends;
17 
18 import android.net.Uri;
19 import android.text.TextUtils;
20 import com.google.android.libraries.mobiledatadownload.file.common.MalformedUriException;
21 import com.google.android.libraries.mobiledatadownload.file.spi.Backend;
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.nio.ByteBuffer;
29 import java.util.HashMap;
30 import java.util.Map;
31 
32 /**
33  * A {@link Backend} whose files exist only in memory. Files are never cached to disk and exist only
34  * for the duration of the backend itself. It has the following limitations:
35  *
36  * <ul>
37  *   <li>The backend is flat; directory operations are unsupported
38  *   <li>{@link Backend#openForAppend} is unsupported
39  *   <li>Native filesytem features such as FD's and syncing are unsupported
40  * </ul>
41  *
42  * <p>See <internal> for further documentation.
43  */
44 // TODO(b/111694348): consider adding directory support
45 public final class MemoryBackend implements Backend {
46 
47   // Uri scheme the backend is registered under (package-private for use in {@link MemoryUri})
48   static final String URI_SCHEME = "memory";
49 
50   private final Map<Uri, ByteBuffer> files = new HashMap<>();
51 
52   @Override
name()53   public String name() {
54     return URI_SCHEME;
55   }
56 
57   @Override
openForRead(Uri uri)58   public InputStream openForRead(Uri uri) throws IOException {
59     validate(uri);
60     ByteBuffer buf = files.get(uri);
61     if (buf == null) {
62       throw new FileNotFoundException(String.format("%s (No such file)", uri));
63     }
64     ByteBuffer slice = buf.slice(); // Points to the same data but has an independent read position
65     return new ByteArrayInputStream(slice.array(), slice.arrayOffset(), slice.limit());
66   }
67 
68   @Override
openForWrite(Uri uri)69   public OutputStream openForWrite(Uri uri) throws IOException {
70     validate(uri);
71     // Note that this is atomic in the sense that the data isn't flushed until closing the stream
72     return new ByteArrayOutputStream() {
73       @Override
74       public void close() throws IOException {
75         files.put(uri, ByteBuffer.wrap(this.buf, 0, this.count));
76       }
77     };
78   }
79 
80   @Override
81   public void deleteFile(Uri uri) throws IOException {
82     validate(uri);
83     if (files.remove(uri) == null) {
84       throw new FileNotFoundException(String.format("%s does not exist", uri));
85     }
86   }
87 
88   @Override
89   public void rename(Uri from, Uri to) throws IOException {
90     validate(from);
91     validate(to);
92     ByteBuffer buf = files.remove(from);
93     if (buf == null) {
94       throw new IOException(String.format("%s could not be renamed to %s", from, to));
95     }
96     files.put(to, buf);
97   }
98 
99   @Override
100   public boolean exists(Uri uri) throws IOException {
101     validate(uri);
102     return files.containsKey(uri);
103   }
104 
105   @Override
106   public long fileSize(Uri uri) throws IOException {
107     validate(uri);
108     ByteBuffer buf = files.get(uri);
109     return buf != null ? buf.limit() : 0;
110   }
111 
112   /** Validates that {@code uri} is a non-empty memory: Uri with no authority or query. */
113   private static void validate(Uri uri) throws MalformedUriException {
114     if (!URI_SCHEME.equals(uri.getScheme())) {
115       throw new MalformedUriException("Expected scheme to be " + URI_SCHEME);
116     }
117     if (TextUtils.isEmpty(uri.getSchemeSpecificPart())) {
118       throw new MalformedUriException("Expected a non-empty file identifier");
119     }
120     if (!TextUtils.isEmpty(uri.getAuthority())) {
121       throw new MalformedUriException("Did not expect an authority");
122     }
123     if (!TextUtils.isEmpty(uri.getQuery())) {
124       throw new MalformedUriException("Did not expect a query");
125     }
126   }
127 }
128