1 /*
2  * Copyright (C) 2016 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 
17 package com.android.documentsui.clipping;
18 
19 import android.net.Uri;
20 
21 import java.io.Closeable;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.nio.channels.FileLock;
26 import java.util.HashMap;
27 import java.util.Map;
28 import java.util.Scanner;
29 
30 /**
31  * Reader class used to read uris from clip files stored in {@link ClipStorage}. It provides
32  * synchronization within a single process as an addition to {@link FileLock} which is for
33  * cross-process synchronization.
34  */
35 class ClipStorageReader implements Iterable<Uri>, Closeable {
36 
37     /**
38      * FileLock can't be held multiple times in a single JVM, but it's possible to have multiple
39      * readers reading the same clip file. Share the FileLock here so that it can be released
40      * when it's not needed.
41      */
42     private static final Map<String, FileLockEntry> sLocks = new HashMap<>();
43 
44     private final String mCanonicalPath;
45     private final Scanner mScanner;
46 
ClipStorageReader(File file)47     ClipStorageReader(File file) throws IOException {
48         FileInputStream inStream = new FileInputStream(file);
49         mScanner = new Scanner(inStream);
50 
51         mCanonicalPath = file.getCanonicalPath(); // Resolve symlink
52         synchronized (sLocks) {
53             if (sLocks.containsKey(mCanonicalPath)) {
54                 // Read lock is already held by someone in this JVM, just increment the ref
55                 // count.
56                 sLocks.get(mCanonicalPath).mCount++;
57             } else {
58                 // No map entry, need to lock the file so it won't pass this line until the
59                 // corresponding writer is done writing.
60                 FileLock lock = inStream.getChannel().lock(0L, Long.MAX_VALUE, true);
61                 sLocks.put(mCanonicalPath, new FileLockEntry(1, lock, mScanner));
62             }
63         }
64     }
65 
66     @Override
iterator()67     public Iterator iterator() {
68         return new Iterator(mScanner);
69     }
70 
71     @Override
close()72     public void close() throws IOException {
73         FileLockEntry ref;
74         synchronized (sLocks) {
75             ref = sLocks.get(mCanonicalPath);
76 
77             assert(ref.mCount > 0);
78             if (--ref.mCount == 0) {
79                 // If ref count is 0 now, then there is no one who needs to hold the read lock.
80                 // Release the lock, and remove the entry.
81                 ref.mLock.release();
82                 ref.mScanner.close();
83                 sLocks.remove(mCanonicalPath);
84             }
85         }
86 
87         if (mScanner != ref.mScanner) {
88             mScanner.close();
89         }
90     }
91 
92     private static final class Iterator implements java.util.Iterator {
93         private final Scanner mScanner;
94 
Iterator(Scanner scanner)95         private Iterator(Scanner scanner) {
96             mScanner = scanner;
97         }
98 
99         @Override
hasNext()100         public boolean hasNext() {
101             return mScanner.hasNextLine();
102         }
103 
104         @Override
next()105         public Uri next() {
106             String line = mScanner.nextLine();
107             return Uri.parse(line);
108         }
109     }
110 
111     private static final class FileLockEntry {
112         private final FileLock mLock;
113         // We need to keep this scanner here because if the scanner is closed, the file lock is
114         // closed too.
115         private final Scanner mScanner;
116 
117         private int mCount;
118 
FileLockEntry(int count, FileLock lock, Scanner scanner)119         private FileLockEntry(int count, FileLock lock, Scanner scanner) {
120             mCount = count;
121             mLock = lock;
122             mScanner = scanner;
123         }
124     }
125 }
126