1package eu.siacs.conversations.persistance;
2
3import java.io.File;
4import java.io.FileInputStream;
5import java.io.FileNotFoundException;
6import java.io.FileOutputStream;
7import java.io.IOException;
8import java.io.InputStream;
9import java.io.OutputStream;
10
11import android.content.Context;
12import android.graphics.Bitmap;
13import android.graphics.BitmapFactory;
14import android.net.Uri;
15import android.util.Log;
16import android.util.LruCache;
17import eu.siacs.conversations.R;
18import eu.siacs.conversations.entities.Conversation;
19import eu.siacs.conversations.entities.Message;
20import eu.siacs.conversations.xmpp.jingle.JingleFile;
21
22public class FileBackend {
23
24 private static int IMAGE_SIZE = 1920;
25
26 private Context context;
27 private LruCache<String, Bitmap> thumbnailCache;
28
29 public FileBackend(Context context) {
30 this.context = context;
31 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
32 int cacheSize = maxMemory / 8;
33 thumbnailCache = new LruCache<String, Bitmap>(cacheSize) {
34 @Override
35 protected int sizeOf(String key, Bitmap bitmap) {
36 return bitmap.getByteCount() / 1024;
37 }
38 };
39
40 }
41
42 public LruCache<String, Bitmap> getThumbnailCache() {
43 return thumbnailCache;
44 }
45
46 public JingleFile getJingleFile(Message message) {
47 return getJingleFile(message, true);
48 }
49
50 public JingleFile getJingleFile(Message message, boolean decrypted) {
51 Conversation conversation = message.getConversation();
52 String prefix = context.getFilesDir().getAbsolutePath();
53 String path = prefix + "/" + conversation.getAccount().getJid() + "/"
54 + conversation.getContactJid();
55 String filename;
56 if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) {
57 filename = message.getUuid() + ".webp";
58 } else {
59 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
60 filename = message.getUuid() + ".webp";
61 } else {
62 filename = message.getUuid() + ".webp.pgp";
63 }
64 }
65 return new JingleFile(path + "/" + filename);
66 }
67
68 public Bitmap resize(Bitmap originalBitmap, int size) {
69 int w = originalBitmap.getWidth();
70 int h = originalBitmap.getHeight();
71 if (Math.max(w, h) > size) {
72 int scalledW;
73 int scalledH;
74 if (w <= h) {
75 scalledW = (int) (w / ((double) h / size));
76 scalledH = size;
77 } else {
78 scalledW = size;
79 scalledH = (int) (h / ((double) w / size));
80 }
81 Bitmap scalledBitmap = Bitmap.createScaledBitmap(originalBitmap,
82 scalledW, scalledH, true);
83 return scalledBitmap;
84 } else {
85 return originalBitmap;
86 }
87 }
88
89 public JingleFile copyImageToPrivateStorage(Message message, Uri image) throws ImageCopyException {
90 return this.copyImageToPrivateStorage(message, image,0);
91 }
92
93 private JingleFile copyImageToPrivateStorage(Message message, Uri image, int sampleSize)
94 throws ImageCopyException {
95 try {
96 InputStream is;
97 if (image != null) {
98 is = context.getContentResolver().openInputStream(image);
99 } else {
100 is = new FileInputStream(getIncomingFile());
101 }
102 JingleFile file = getJingleFile(message);
103 file.getParentFile().mkdirs();
104 file.createNewFile();
105 Bitmap originalBitmap;
106 BitmapFactory.Options options = new BitmapFactory.Options();
107 int inSampleSize = (int) Math.pow(2, sampleSize);
108 Log.d("xmppService","reading bitmap with sample size "+inSampleSize);
109 options.inSampleSize = inSampleSize;
110 originalBitmap = BitmapFactory.decodeStream(is, null, options);
111 is.close();
112 if (originalBitmap == null) {
113 throw new ImageCopyException(R.string.error_not_an_image_file);
114 }
115 if (image == null) {
116 getIncomingFile().delete();
117 }
118 Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
119 OutputStream os = new FileOutputStream(file);
120 boolean success = scalledBitmap.compress(
121 Bitmap.CompressFormat.WEBP, 75, os);
122 if (!success) {
123 throw new ImageCopyException(R.string.error_compressing_image);
124 }
125 os.flush();
126 os.close();
127 long size = file.getSize();
128 int width = scalledBitmap.getWidth();
129 int height = scalledBitmap.getHeight();
130 message.setBody("" + size + "," + width + "," + height);
131 return file;
132 } catch (FileNotFoundException e) {
133 throw new ImageCopyException(R.string.error_file_not_found);
134 } catch (IOException e) {
135 throw new ImageCopyException(R.string.error_io_exception);
136 } catch (SecurityException e) {
137 throw new ImageCopyException(
138 R.string.error_security_exception_during_image_copy);
139 } catch (OutOfMemoryError e) {
140 ++sampleSize;
141 if (sampleSize<=3) {
142 return copyImageToPrivateStorage(message, image, sampleSize);
143 } else {
144 throw new ImageCopyException(R.string.error_out_of_memory);
145 }
146 }
147 }
148
149 public Bitmap getImageFromMessage(Message message) {
150 return BitmapFactory.decodeFile(getJingleFile(message)
151 .getAbsolutePath());
152 }
153
154 public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
155 throws FileNotFoundException {
156 Bitmap thumbnail = thumbnailCache.get(message.getUuid());
157 if ((thumbnail == null) && (!cacheOnly)) {
158 Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message)
159 .getAbsolutePath());
160 if (fullsize == null) {
161 throw new FileNotFoundException();
162 }
163 thumbnail = resize(fullsize, size);
164 this.thumbnailCache.put(message.getUuid(), thumbnail);
165 }
166 return thumbnail;
167 }
168
169 public void removeFiles(Conversation conversation) {
170 String prefix = context.getFilesDir().getAbsolutePath();
171 String path = prefix + "/" + conversation.getAccount().getJid() + "/"
172 + conversation.getContactJid();
173 File file = new File(path);
174 try {
175 this.deleteFile(file);
176 } catch (IOException e) {
177 Log.d("xmppService",
178 "error deleting file: " + file.getAbsolutePath());
179 }
180 }
181
182 private void deleteFile(File f) throws IOException {
183 if (f.isDirectory()) {
184 for (File c : f.listFiles())
185 deleteFile(c);
186 }
187 f.delete();
188 }
189
190 public File getIncomingFile() {
191 return new File(context.getFilesDir().getAbsolutePath() + "/incoming");
192 }
193
194 public class ImageCopyException extends Exception {
195 private static final long serialVersionUID = -1010013599132881427L;
196 private int resId;
197
198 public ImageCopyException(int resId) {
199 this.resId = resId;
200 }
201
202 public int getResId() {
203 return resId;
204 }
205 }
206}