2 * ownCloud Android client application
4 * @author David A. Velasco
5 * Copyright (C) 2015 ownCloud Inc.
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.
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.
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/>.
21 package com
.owncloud
.android
.files
.services
;
23 import android
.accounts
.Account
;
24 import android
.util
.Pair
;
26 import com
.owncloud
.android
.datamodel
.OCFile
;
27 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
;
30 import java
.util
.HashSet
;
31 import java
.util
.Iterator
;
33 import java
.util
.concurrent
.ConcurrentHashMap
;
34 import java
.util
.concurrent
.ConcurrentMap
;
37 * Helper structure to keep the trees of folders containing any file downloading or synchronizing.
39 * A map provides the indexation based in hashing.
41 * A tree is created per account.
43 public class IndexedForest
<V
> {
45 private ConcurrentMap
<String
, Node
<V
>> mMap
= new ConcurrentHashMap
<String
, Node
<V
>>();
47 private class Node
<V
> {
49 Node
<V
> mParent
= null
;
50 Set
<Node
<V
>> mChildren
= new HashSet
<Node
<V
>>(); // TODO be careful with hash()
53 // payload is optional
54 public Node(String key
, V payload
) {
56 throw new IllegalArgumentException("Argument key MUST NOT be null");
62 public Node
<V
> getParent() {
66 public Set
<Node
<V
>> getChildren() {
70 public String
getKey() {
74 public V
getPayload() {
78 public void addChild(Node
<V
> child
) {
80 child
.setParent(this);
83 private void setParent(Node
<V
> parent
) {
87 public boolean hasChildren() {
88 return mChildren
.size() > 0;
91 public void removeChild(Node
<V
> removed
) {
92 mChildren
.remove(removed
);
95 public void clearPayload() {
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(
108 if (previousValue
!= null
) {
109 // remotePath already known; not replaced
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
;
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
);
129 parentNode
.addChild(currentNode
);
132 currentPath
= parentPath
;
133 currentNode
= parentNode
;
136 String linkedTo
= OCFile
.ROOT_PATH
;
138 linkedTo
= parentNode
.getKey().substring(account
.name
.length());
141 return new Pair
<String
, String
>(targetKey
, linkedTo
);
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
);
155 return new Pair
<V
, String
>(null
, null
);
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
;
164 if (firstRemoved
!= null
) {
166 removeDescendants(firstRemoved
);
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();
182 if (parent
!= null
) {
183 unlinkedFrom
= parent
.getKey().substring(account
.name
.length());
186 return new Pair
<V
, String
>(firstRemoved
.getPayload(), unlinkedFrom
);
189 return new Pair
<V
, String
>(null
, null
);
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
);
202 public boolean contains(Account account
, String remotePath
) {
203 String targetKey
= buildKey(account
, remotePath
);
204 return mMap
.containsKey(targetKey
);
207 public /* synchronized */ V
get(String key
) {
208 Node
<V
> node
= mMap
.get(key
);
210 return node
.getPayload();
216 public V
get(Account account
, String remotePath
) {
217 String key
= buildKey(account
, remotePath
);
223 * Remove the elements that contains account as a part of its key
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
)) {
238 * Builds a key to index files
240 * @param account Account where the file to download is stored
241 * @param remotePath Path of the file in the server
243 private String
buildKey(Account account
, String remotePath
) {
244 return account
.name
+ remotePath
;