Fix bug: App crash when try to share link with api disabled
[pub/Android/ownCloud.git] / src / com / owncloud / android / files / services / IndexedForest.java
1 /**
2 * ownCloud Android client application
3 *
4 * @author David A. Velasco
5 * Copyright (C) 2015 ownCloud Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2,
9 * as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20
21 package com.owncloud.android.files.services;
22
23 import android.accounts.Account;
24 import android.util.Pair;
25
26 import com.owncloud.android.datamodel.OCFile;
27 import com.owncloud.android.lib.common.utils.Log_OC;
28
29 import java.io.File;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.Set;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.ConcurrentMap;
35
36 /**
37 * Helper structure to keep the trees of folders containing any file downloading or synchronizing.
38 *
39 * A map provides the indexation based in hashing.
40 *
41 * A tree is created per account.
42 */
43 public class IndexedForest<V> {
44
45 private ConcurrentMap<String, Node<V>> mMap = new ConcurrentHashMap<String, Node<V>>();
46
47 private class Node<V> {
48 String mKey = null;
49 Node<V> mParent = null;
50 Set<Node<V>> mChildren = new HashSet<Node<V>>(); // TODO be careful with hash()
51 V mPayload = null;
52
53 // payload is optional
54 public Node(String key, V payload) {
55 if (key == null) {
56 throw new IllegalArgumentException("Argument key MUST NOT be null");
57 }
58 mKey = key;
59 mPayload = payload;
60 }
61
62 public Node<V> getParent() {
63 return mParent;
64 };
65
66 public Set<Node<V>> getChildren() {
67 return mChildren;
68 }
69
70 public String getKey() {
71 return mKey;
72 }
73
74 public V getPayload() {
75 return mPayload;
76 }
77
78 public void addChild(Node<V> child) {
79 mChildren.add(child);
80 child.setParent(this);
81 }
82
83 private void setParent(Node<V> parent) {
84 mParent = parent;
85 }
86
87 public boolean hasChildren() {
88 return mChildren.size() > 0;
89 }
90
91 public void removeChild(Node<V> removed) {
92 mChildren.remove(removed);
93 }
94
95 public void clearPayload() {
96 mPayload = null;
97 }
98 }
99
100
101 public /* synchronized */ Pair<String, String> putIfAbsent(Account account, String remotePath, V value) {
102 String targetKey = buildKey(account, remotePath);
103 Node<V> valuedNode = new Node(targetKey, value);
104 Node<V> previousValue = mMap.putIfAbsent(
105 targetKey,
106 valuedNode
107 );
108 if (previousValue != null) {
109 // remotePath already known; not replaced
110 return null;
111
112 } else {
113 // value really added
114 String currentPath = remotePath, parentPath = null, parentKey = null;
115 Node<V> currentNode = valuedNode, parentNode = null;
116 boolean linked = false;
117 while (!OCFile.ROOT_PATH.equals(currentPath) && !linked) {
118 parentPath = new File(currentPath).getParent();
119 if (!parentPath.endsWith(OCFile.PATH_SEPARATOR)) {
120 parentPath += OCFile.PATH_SEPARATOR;
121 }
122 parentKey = buildKey(account, parentPath);
123 parentNode = mMap.get(parentKey);
124 if (parentNode == null) {
125 parentNode = new Node(parentKey, null);
126 parentNode.addChild(currentNode);
127 mMap.put(parentKey, parentNode);
128 } else {
129 parentNode.addChild(currentNode);
130 linked = true;
131 }
132 currentPath = parentPath;
133 currentNode = parentNode;
134 }
135
136 String linkedTo = OCFile.ROOT_PATH;
137 if (linked) {
138 linkedTo = parentNode.getKey().substring(account.name.length());
139 }
140
141 return new Pair<String, String>(targetKey, linkedTo);
142 }
143 };
144
145
146 public Pair<V, String> removePayload(Account account, String remotePath) {
147 String targetKey = buildKey(account, remotePath);
148 Node<V> target = mMap.get(targetKey);
149 if (target != null) {
150 target.clearPayload();
151 if (!target.hasChildren()) {
152 return remove(account, remotePath);
153 }
154 }
155 return new Pair<V, String>(null, null);
156 }
157
158
159 public /* synchronized */ Pair<V, String> remove(Account account, String remotePath) {
160 String targetKey = buildKey(account, remotePath);
161 Node<V> firstRemoved = mMap.remove(targetKey);
162 String unlinkedFrom = null;
163
164 if (firstRemoved != null) {
165 /// remove children
166 removeDescendants(firstRemoved);
167
168 /// remove ancestors if only here due to firstRemoved
169 Node<V> removed = firstRemoved;
170 Node<V> parent = removed.getParent();
171 boolean unlinked = false;
172 while (parent != null) {
173 parent.removeChild(removed);
174 if (!parent.hasChildren()) {
175 removed = mMap.remove(parent.getKey());
176 parent = removed.getParent();
177 } else {
178 break;
179 }
180 }
181
182 if (parent != null) {
183 unlinkedFrom = parent.getKey().substring(account.name.length());
184 }
185
186 return new Pair<V, String>(firstRemoved.getPayload(), unlinkedFrom);
187 }
188
189 return new Pair<V, String>(null, null);
190 }
191
192 private void removeDescendants(Node<V> removed) {
193 Iterator<Node<V>> childrenIt = removed.getChildren().iterator();
194 Node<V> child = null;
195 while (childrenIt.hasNext()) {
196 child = childrenIt.next();
197 mMap.remove(child.getKey());
198 removeDescendants(child);
199 }
200 }
201
202 public boolean contains(Account account, String remotePath) {
203 String targetKey = buildKey(account, remotePath);
204 return mMap.containsKey(targetKey);
205 }
206
207 public /* synchronized */ V get(String key) {
208 Node<V> node = mMap.get(key);
209 if (node != null) {
210 return node.getPayload();
211 } else {
212 return null;
213 }
214 }
215
216 public V get(Account account, String remotePath) {
217 String key = buildKey(account, remotePath);
218 return get(key);
219 }
220
221
222 /**
223 * Remove the elements that contains account as a part of its key
224 * @param account
225 */
226 public void remove(Account account){
227 Iterator<String> it = mMap.keySet().iterator();
228 while (it.hasNext()) {
229 String key = it.next();
230 Log_OC.d("IndexedForest", "Number of pending downloads= " + mMap.size());
231 if (key.startsWith(account.name)) {
232 mMap.remove(key);
233 }
234 }
235 }
236
237 /**
238 * Builds a key to index files
239 *
240 * @param account Account where the file to download is stored
241 * @param remotePath Path of the file in the server
242 */
243 private String buildKey(Account account, String remotePath) {
244 return account.name + remotePath;
245 }
246
247 }