1 package com.atlassian.plugin.cache.filecache.impl;
2
3 import com.atlassian.plugin.cache.filecache.FileCacheStreamProvider;
4 import com.atlassian.plugin.servlet.DownloadException;
5 import org.apache.commons.io.IOUtils;
6 import org.slf4j.Logger;
7 import org.slf4j.LoggerFactory;
8
9 import java.io.BufferedOutputStream;
10 import java.io.File;
11 import java.io.FileInputStream;
12 import java.io.FileOutputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.OutputStream;
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 public class CachedFile {
36 static enum State {UNCACHED, CACHED, NEEDSDELETE, DELETED}
37 interface FileOpener {
38 OutputStream open(File file) throws IOException;
39 }
40 static class FileInputStreamOpener implements FileOpener {
41 @Override
42 public OutputStream open(File file) throws IOException {
43 return new FileOutputStream(file);
44 }
45 }
46
47 private static final Logger log = LoggerFactory.getLogger(CachedFile.class);
48
49 private final File tmpFile;
50 private final FileOpener fileOpener;
51
52
53 private final Object lock = new Object();
54 private int concurrentCount;
55 private State state = State.UNCACHED;
56
57 public CachedFile(File tmpFile) {
58 this(tmpFile, new FileInputStreamOpener());
59 }
60
61 CachedFile(File tmpFile, final FileOpener fileOpener) {
62 this.tmpFile = tmpFile;
63 this.fileOpener = fileOpener;
64 }
65
66 public void stream(OutputStream dest, FileCacheStreamProvider input) throws DownloadException {
67
68 boolean fromCache = doEnter(input);
69 try {
70 if (fromCache) {
71 streamFromCache(dest);
72 } else {
73 input.writeStream(dest);
74 }
75 } finally {
76 doExit();
77 }
78 }
79
80 State getState() {
81 return state;
82 }
83
84
85
86
87
88
89
90 private boolean doEnter(FileCacheStreamProvider input) {
91 synchronized (lock) {
92 boolean useCache = false;
93 if (state == State.UNCACHED) {
94 try {
95 streamToCache(input);
96 state = State.CACHED;
97 useCache = true;
98 } catch (Exception e) {
99 log.warn("Problem caching to disk, skipping cache for this entry", e);
100 state = State.DELETED;
101 }
102 } else if (state == State.CACHED) {
103 useCache = true;
104 } else if (state == State.NEEDSDELETE) {
105 useCache = true;
106 }
107
108 concurrentCount++;
109 return useCache && tmpFile.isFile();
110 }
111 }
112
113
114
115
116
117 private void doExit() {
118 synchronized (lock) {
119 concurrentCount--;
120 if (state == State.NEEDSDELETE && concurrentCount == 0) {
121 tmpFile.delete();
122 state = State.DELETED;
123 }
124 }
125 }
126
127 public void deleteWhenPossible() {
128 synchronized (lock) {
129 if (state == State.UNCACHED) {
130 state = State.DELETED;
131 } else if (state == State.CACHED) {
132 state = State.NEEDSDELETE;
133 }
134
135 if (state == State.NEEDSDELETE && concurrentCount == 0) {
136 tmpFile.delete();
137 state = State.DELETED;
138 }
139 }
140 }
141
142 private void streamToCache(FileCacheStreamProvider input) throws IOException, DownloadException {
143 OutputStream cacheout = null;
144 try {
145 cacheout = new BufferedOutputStream(fileOpener.open(tmpFile));
146 input.writeStream(cacheout);
147 } finally {
148 if (cacheout != null) {
149 cacheout.close();
150 }
151 }
152 }
153
154 private void streamFromCache(OutputStream out) throws DownloadException {
155 InputStream in = null;
156 try {
157 in = new FileInputStream(tmpFile);
158 IOUtils.copyLarge(in, out);
159 out.flush();
160 } catch (IOException e) {
161 throw new DownloadException(e);
162 } finally {
163 IOUtils.closeQuietly(in);
164 }
165 }
166
167 }