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
17package com.android.documentsui.clipping;
18
19import android.net.Uri;
20
21import java.io.Closeable;
22import java.io.File;
23import java.io.FileInputStream;
24import java.io.IOException;
25import java.nio.channels.FileLock;
26import java.util.HashMap;
27import java.util.Map;
28import 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 */
35class 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
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
67    public Iterator iterator() {
68        return new Iterator(mScanner);
69    }
70
71    @Override
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
95        private Iterator(Scanner scanner) {
96            mScanner = scanner;
97        }
98
99        @Override
100        public boolean hasNext() {
101            return mScanner.hasNextLine();
102        }
103
104        @Override
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
119        private FileLockEntry(int count, FileLock lock, Scanner scanner) {
120            mCount = count;
121            mLock = lock;
122            mScanner = scanner;
123        }
124    }
125}
126