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