Merge branch 'develop' into accessibility
[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 mMap.putIfAbsent(
105 targetKey,
106 valuedNode
107 );
108
109 String currentPath = remotePath, parentPath = null, parentKey = null;
110 Node<V> currentNode = valuedNode, parentNode = null;
111 boolean linked = false;
112 while (!OCFile.ROOT_PATH.equals(currentPath) && !linked) {
113 parentPath = new File(currentPath).getParent();
114 if (!parentPath.endsWith(OCFile.PATH_SEPARATOR)) {
115 parentPath += OCFile.PATH_SEPARATOR;
116 }
117 parentKey = buildKey(account, parentPath);
118 parentNode = mMap.get(parentKey);
119 if (parentNode == null) {
120 parentNode = new Node(parentKey, null);
121 parentNode.addChild(currentNode);
122 mMap.put(parentKey, parentNode);
123 } else {
124 parentNode.addChild(currentNode);
125 linked = true;
126 }
127 currentPath = parentPath;
128 currentNode = parentNode;
129 }
130
131 String linkedTo = OCFile.ROOT_PATH;
132 if (linked) {
133 linkedTo = parentNode.getKey().substring(account.name.length());
134 }
135 return new Pair<String, String>(targetKey, linkedTo);
136 };
137
138
139 public Pair<V, String> removePayload(Account account, String remotePath) {
140 String targetKey = buildKey(account, remotePath);
141 Node<V> target = mMap.get(targetKey);
142 if (target != null) {
143 target.clearPayload();
144 if (!target.hasChildren()) {
145 return remove(account, remotePath);
146 }
147 }
148 return new Pair<V, String>(null, null);
149 }
150
151
152 public /* synchronized */ Pair<V, String> remove(Account account, String remotePath) {
153 String targetKey = buildKey(account, remotePath);
154 Node<V> firstRemoved = mMap.remove(targetKey);
155 String unlinkedFrom = null;
156
157 if (firstRemoved != null) {
158 /// remove children
159 removeDescendants(firstRemoved);
160
161 /// remove ancestors if only here due to firstRemoved
162 Node<V> removed = firstRemoved;
163 Node<V> parent = removed.getParent();
164 boolean unlinked = false;
165 while (parent != null) {
166 parent.removeChild(removed);
167 if (!parent.hasChildren()) {
168 removed = mMap.remove(parent.getKey());
169 parent = removed.getParent();
170 } else {
171 break;
172 }
173 }
174
175 if (parent != null) {
176 unlinkedFrom = parent.getKey().substring(account.name.length());
177 }
178
179 return new Pair<V, String>(firstRemoved.getPayload(), unlinkedFrom);
180 }
181
182 return new Pair<V, String>(null, null);
183 }
184
185 private void removeDescendants(Node<V> removed) {
186 Iterator<Node<V>> childrenIt = removed.getChildren().iterator();
187 Node<V> child = null;
188 while (childrenIt.hasNext()) {
189 child = childrenIt.next();
190 mMap.remove(child.getKey());
191 removeDescendants(child);
192 }
193 }
194
195 public boolean contains(Account account, String remotePath) {
196 String targetKey = buildKey(account, remotePath);
197 return mMap.containsKey(targetKey);
198 }
199
200 public /* synchronized */ V get(String key) {
201 Node<V> node = mMap.get(key);
202 if (node != null) {
203 return node.getPayload();
204 } else {
205 return null;
206 }
207 }
208
209 public V get(Account account, String remotePath) {
210 String key = buildKey(account, remotePath);
211 return get(key);
212 }
213
214
215 /**
216 * Remove the elements that contains account as a part of its key
217 * @param account
218 */
219 public void remove(Account account){
220 Iterator<String> it = mMap.keySet().iterator();
221 while (it.hasNext()) {
222 String key = it.next();
223 Log_OC.d("IndexedForest", "Number of pending downloads= " + mMap.size());
224 if (key.startsWith(account.name)) {
225 mMap.remove(key);
226 }
227 }
228 }
229
230 /**
231 * Builds a key to index files
232 *
233 * @param account Account where the file to download is stored
234 * @param remotePath Path of the file in the server
235 */
236 private String buildKey(Account account, String remotePath) {
237 return account.name + remotePath;
238 }
239
240 }