Passed
Push — master ( 90401e...63cb31 )
by Roeland
20:41 queued 20s
created
lib/public/Files/Search/ISearchQuery.php 1 patch
Indentation   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -29,49 +29,49 @@
 block discarded – undo
29 29
  * @since 12.0.0
30 30
  */
31 31
 interface ISearchQuery {
32
-	/**
33
-	 * @return ISearchOperator
34
-	 * @since 12.0.0
35
-	 */
36
-	public function getSearchOperation();
32
+    /**
33
+     * @return ISearchOperator
34
+     * @since 12.0.0
35
+     */
36
+    public function getSearchOperation();
37 37
 
38
-	/**
39
-	 * Get the maximum number of results to return
40
-	 *
41
-	 * @return integer
42
-	 * @since 12.0.0
43
-	 */
44
-	public function getLimit();
38
+    /**
39
+     * Get the maximum number of results to return
40
+     *
41
+     * @return integer
42
+     * @since 12.0.0
43
+     */
44
+    public function getLimit();
45 45
 
46
-	/**
47
-	 * Get the offset for returned results
48
-	 *
49
-	 * @return integer
50
-	 * @since 12.0.0
51
-	 */
52
-	public function getOffset();
46
+    /**
47
+     * Get the offset for returned results
48
+     *
49
+     * @return integer
50
+     * @since 12.0.0
51
+     */
52
+    public function getOffset();
53 53
 
54
-	/**
55
-	 * The fields and directions to order by
56
-	 *
57
-	 * @return ISearchOrder[]
58
-	 * @since 12.0.0
59
-	 */
60
-	public function getOrder();
54
+    /**
55
+     * The fields and directions to order by
56
+     *
57
+     * @return ISearchOrder[]
58
+     * @since 12.0.0
59
+     */
60
+    public function getOrder();
61 61
 
62
-	/**
63
-	 * The user that issued the search
64
-	 *
65
-	 * @return IUser
66
-	 * @since 12.0.0
67
-	 */
68
-	public function getUser();
62
+    /**
63
+     * The user that issued the search
64
+     *
65
+     * @return IUser
66
+     * @since 12.0.0
67
+     */
68
+    public function getUser();
69 69
 
70
-	/**
71
-	 * Whether or not the search should be limited to the users home storage
72
-	 *
73
-	 * @return bool
74
-	 * @since 18.0.0
75
-	 */
76
-	public function limitToHome(): bool;
70
+    /**
71
+     * Whether or not the search should be limited to the users home storage
72
+     *
73
+     * @return bool
74
+     * @since 18.0.0
75
+     */
76
+    public function limitToHome(): bool;
77 77
 }
Please login to merge, or discard this patch.
lib/private/Files/Cache/QuerySearchHelper.php 2 patches
Indentation   +183 added lines, -183 removed lines patch added patch discarded remove patch
@@ -34,187 +34,187 @@
 block discarded – undo
34 34
  * Tools for transforming search queries into database queries
35 35
  */
36 36
 class QuerySearchHelper {
37
-	static protected $searchOperatorMap = [
38
-		ISearchComparison::COMPARE_LIKE => 'iLike',
39
-		ISearchComparison::COMPARE_EQUAL => 'eq',
40
-		ISearchComparison::COMPARE_GREATER_THAN => 'gt',
41
-		ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'gte',
42
-		ISearchComparison::COMPARE_LESS_THAN => 'lt',
43
-		ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lte'
44
-	];
45
-
46
-	static protected $searchOperatorNegativeMap = [
47
-		ISearchComparison::COMPARE_LIKE => 'notLike',
48
-		ISearchComparison::COMPARE_EQUAL => 'neq',
49
-		ISearchComparison::COMPARE_GREATER_THAN => 'lte',
50
-		ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'lt',
51
-		ISearchComparison::COMPARE_LESS_THAN => 'gte',
52
-		ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lt'
53
-	];
54
-
55
-	const TAG_FAVORITE = '_$!<Favorite>!$_';
56
-
57
-	/** @var IMimeTypeLoader */
58
-	private $mimetypeLoader;
59
-
60
-	/**
61
-	 * QuerySearchUtil constructor.
62
-	 *
63
-	 * @param IMimeTypeLoader $mimetypeLoader
64
-	 */
65
-	public function __construct(IMimeTypeLoader $mimetypeLoader) {
66
-		$this->mimetypeLoader = $mimetypeLoader;
67
-	}
68
-
69
-	/**
70
-	 * Whether or not the tag tables should be joined to complete the search
71
-	 *
72
-	 * @param ISearchOperator $operator
73
-	 * @return boolean
74
-	 */
75
-	public function shouldJoinTags(ISearchOperator $operator) {
76
-		if ($operator instanceof ISearchBinaryOperator) {
77
-			return array_reduce($operator->getArguments(), function ($shouldJoin, ISearchOperator $operator) {
78
-				return $shouldJoin || $this->shouldJoinTags($operator);
79
-			}, false);
80
-		} else if ($operator instanceof ISearchComparison) {
81
-			return $operator->getField() === 'tagname' || $operator->getField() === 'favorite';
82
-		}
83
-		return false;
84
-	}
85
-
86
-	/**
87
-	 * @param IQueryBuilder $builder
88
-	 * @param ISearchOperator $operator
89
-	 */
90
-	public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) {
91
-		return array_filter(array_map(function ($operator) use ($builder) {
92
-			return $this->searchOperatorToDBExpr($builder, $operator);
93
-		}, $operators));
94
-	}
95
-
96
-	public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) {
97
-		$expr = $builder->expr();
98
-		if ($operator instanceof ISearchBinaryOperator) {
99
-			if (count($operator->getArguments()) === 0) {
100
-				return null;
101
-			}
102
-
103
-			switch ($operator->getType()) {
104
-				case ISearchBinaryOperator::OPERATOR_NOT:
105
-					$negativeOperator = $operator->getArguments()[0];
106
-					if ($negativeOperator instanceof ISearchComparison) {
107
-						return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap);
108
-					} else {
109
-						throw new \InvalidArgumentException('Binary operators inside "not" is not supported');
110
-					}
111
-				case ISearchBinaryOperator::OPERATOR_AND:
112
-					return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
113
-				case ISearchBinaryOperator::OPERATOR_OR:
114
-					return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
115
-				default:
116
-					throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType());
117
-			}
118
-		} else if ($operator instanceof ISearchComparison) {
119
-			return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap);
120
-		} else {
121
-			throw new \InvalidArgumentException('Invalid operator type: ' . get_class($operator));
122
-		}
123
-	}
124
-
125
-	private function searchComparisonToDBExpr(IQueryBuilder $builder, ISearchComparison $comparison, array $operatorMap) {
126
-		$this->validateComparison($comparison);
127
-
128
-		list($field, $value, $type) = $this->getOperatorFieldAndValue($comparison);
129
-		if (isset($operatorMap[$type])) {
130
-			$queryOperator = $operatorMap[$type];
131
-			return $builder->expr()->$queryOperator($field, $this->getParameterForValue($builder, $value));
132
-		} else {
133
-			throw new \InvalidArgumentException('Invalid operator type: ' . $comparison->getType());
134
-		}
135
-	}
136
-
137
-	private function getOperatorFieldAndValue(ISearchComparison $operator) {
138
-		$field = $operator->getField();
139
-		$value = $operator->getValue();
140
-		$type = $operator->getType();
141
-		if ($field === 'mimetype') {
142
-			if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) {
143
-				$value = (int)$this->mimetypeLoader->getId($value);
144
-			} else if ($operator->getType() === ISearchComparison::COMPARE_LIKE) {
145
-				// transform "mimetype='foo/%'" to "mimepart='foo'"
146
-				if (preg_match('|(.+)/%|', $value, $matches)) {
147
-					$field = 'mimepart';
148
-					$value = (int)$this->mimetypeLoader->getId($matches[1]);
149
-					$type = ISearchComparison::COMPARE_EQUAL;
150
-				} else if (strpos($value, '%') !== false) {
151
-					throw new \InvalidArgumentException('Unsupported query value for mimetype: ' . $value . ', only values in the format "mime/type" or "mime/%" are supported');
152
-				} else {
153
-					$field = 'mimetype';
154
-					$value = (int)$this->mimetypeLoader->getId($value);
155
-					$type = ISearchComparison::COMPARE_EQUAL;
156
-				}
157
-			}
158
-		} else if ($field === 'favorite') {
159
-			$field = 'tag.category';
160
-			$value = self::TAG_FAVORITE;
161
-		} else if ($field === 'tagname') {
162
-			$field = 'tag.category';
163
-		}
164
-		return [$field, $value, $type];
165
-	}
166
-
167
-	private function validateComparison(ISearchComparison $operator) {
168
-		$types = [
169
-			'mimetype' => 'string',
170
-			'mtime' => 'integer',
171
-			'name' => 'string',
172
-			'size' => 'integer',
173
-			'tagname' => 'string',
174
-			'favorite' => 'boolean',
175
-			'fileid' => 'integer'
176
-		];
177
-		$comparisons = [
178
-			'mimetype' => ['eq', 'like'],
179
-			'mtime' => ['eq', 'gt', 'lt', 'gte', 'lte'],
180
-			'name' => ['eq', 'like'],
181
-			'size' => ['eq', 'gt', 'lt', 'gte', 'lte'],
182
-			'tagname' => ['eq', 'like'],
183
-			'favorite' => ['eq'],
184
-			'fileid' => ['eq']
185
-		];
186
-
187
-		if (!isset($types[$operator->getField()])) {
188
-			throw new \InvalidArgumentException('Unsupported comparison field ' . $operator->getField());
189
-		}
190
-		$type = $types[$operator->getField()];
191
-		if (gettype($operator->getValue()) !== $type) {
192
-			throw new \InvalidArgumentException('Invalid type for field ' . $operator->getField());
193
-		}
194
-		if (!in_array($operator->getType(), $comparisons[$operator->getField()])) {
195
-			throw new \InvalidArgumentException('Unsupported comparison for field  ' . $operator->getField() . ': ' . $operator->getType());
196
-		}
197
-	}
198
-
199
-	private function getParameterForValue(IQueryBuilder $builder, $value) {
200
-		if ($value instanceof \DateTime) {
201
-			$value = $value->getTimestamp();
202
-		}
203
-		if (is_numeric($value)) {
204
-			$type = IQueryBuilder::PARAM_INT;
205
-		} else {
206
-			$type = IQueryBuilder::PARAM_STR;
207
-		}
208
-		return $builder->createNamedParameter($value, $type);
209
-	}
210
-
211
-	/**
212
-	 * @param IQueryBuilder $query
213
-	 * @param ISearchOrder[] $orders
214
-	 */
215
-	public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders) {
216
-		foreach ($orders as $order) {
217
-			$query->addOrderBy($order->getField(), $order->getDirection());
218
-		}
219
-	}
37
+    static protected $searchOperatorMap = [
38
+        ISearchComparison::COMPARE_LIKE => 'iLike',
39
+        ISearchComparison::COMPARE_EQUAL => 'eq',
40
+        ISearchComparison::COMPARE_GREATER_THAN => 'gt',
41
+        ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'gte',
42
+        ISearchComparison::COMPARE_LESS_THAN => 'lt',
43
+        ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lte'
44
+    ];
45
+
46
+    static protected $searchOperatorNegativeMap = [
47
+        ISearchComparison::COMPARE_LIKE => 'notLike',
48
+        ISearchComparison::COMPARE_EQUAL => 'neq',
49
+        ISearchComparison::COMPARE_GREATER_THAN => 'lte',
50
+        ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'lt',
51
+        ISearchComparison::COMPARE_LESS_THAN => 'gte',
52
+        ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lt'
53
+    ];
54
+
55
+    const TAG_FAVORITE = '_$!<Favorite>!$_';
56
+
57
+    /** @var IMimeTypeLoader */
58
+    private $mimetypeLoader;
59
+
60
+    /**
61
+     * QuerySearchUtil constructor.
62
+     *
63
+     * @param IMimeTypeLoader $mimetypeLoader
64
+     */
65
+    public function __construct(IMimeTypeLoader $mimetypeLoader) {
66
+        $this->mimetypeLoader = $mimetypeLoader;
67
+    }
68
+
69
+    /**
70
+     * Whether or not the tag tables should be joined to complete the search
71
+     *
72
+     * @param ISearchOperator $operator
73
+     * @return boolean
74
+     */
75
+    public function shouldJoinTags(ISearchOperator $operator) {
76
+        if ($operator instanceof ISearchBinaryOperator) {
77
+            return array_reduce($operator->getArguments(), function ($shouldJoin, ISearchOperator $operator) {
78
+                return $shouldJoin || $this->shouldJoinTags($operator);
79
+            }, false);
80
+        } else if ($operator instanceof ISearchComparison) {
81
+            return $operator->getField() === 'tagname' || $operator->getField() === 'favorite';
82
+        }
83
+        return false;
84
+    }
85
+
86
+    /**
87
+     * @param IQueryBuilder $builder
88
+     * @param ISearchOperator $operator
89
+     */
90
+    public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) {
91
+        return array_filter(array_map(function ($operator) use ($builder) {
92
+            return $this->searchOperatorToDBExpr($builder, $operator);
93
+        }, $operators));
94
+    }
95
+
96
+    public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) {
97
+        $expr = $builder->expr();
98
+        if ($operator instanceof ISearchBinaryOperator) {
99
+            if (count($operator->getArguments()) === 0) {
100
+                return null;
101
+            }
102
+
103
+            switch ($operator->getType()) {
104
+                case ISearchBinaryOperator::OPERATOR_NOT:
105
+                    $negativeOperator = $operator->getArguments()[0];
106
+                    if ($negativeOperator instanceof ISearchComparison) {
107
+                        return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap);
108
+                    } else {
109
+                        throw new \InvalidArgumentException('Binary operators inside "not" is not supported');
110
+                    }
111
+                case ISearchBinaryOperator::OPERATOR_AND:
112
+                    return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
113
+                case ISearchBinaryOperator::OPERATOR_OR:
114
+                    return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
115
+                default:
116
+                    throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType());
117
+            }
118
+        } else if ($operator instanceof ISearchComparison) {
119
+            return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap);
120
+        } else {
121
+            throw new \InvalidArgumentException('Invalid operator type: ' . get_class($operator));
122
+        }
123
+    }
124
+
125
+    private function searchComparisonToDBExpr(IQueryBuilder $builder, ISearchComparison $comparison, array $operatorMap) {
126
+        $this->validateComparison($comparison);
127
+
128
+        list($field, $value, $type) = $this->getOperatorFieldAndValue($comparison);
129
+        if (isset($operatorMap[$type])) {
130
+            $queryOperator = $operatorMap[$type];
131
+            return $builder->expr()->$queryOperator($field, $this->getParameterForValue($builder, $value));
132
+        } else {
133
+            throw new \InvalidArgumentException('Invalid operator type: ' . $comparison->getType());
134
+        }
135
+    }
136
+
137
+    private function getOperatorFieldAndValue(ISearchComparison $operator) {
138
+        $field = $operator->getField();
139
+        $value = $operator->getValue();
140
+        $type = $operator->getType();
141
+        if ($field === 'mimetype') {
142
+            if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) {
143
+                $value = (int)$this->mimetypeLoader->getId($value);
144
+            } else if ($operator->getType() === ISearchComparison::COMPARE_LIKE) {
145
+                // transform "mimetype='foo/%'" to "mimepart='foo'"
146
+                if (preg_match('|(.+)/%|', $value, $matches)) {
147
+                    $field = 'mimepart';
148
+                    $value = (int)$this->mimetypeLoader->getId($matches[1]);
149
+                    $type = ISearchComparison::COMPARE_EQUAL;
150
+                } else if (strpos($value, '%') !== false) {
151
+                    throw new \InvalidArgumentException('Unsupported query value for mimetype: ' . $value . ', only values in the format "mime/type" or "mime/%" are supported');
152
+                } else {
153
+                    $field = 'mimetype';
154
+                    $value = (int)$this->mimetypeLoader->getId($value);
155
+                    $type = ISearchComparison::COMPARE_EQUAL;
156
+                }
157
+            }
158
+        } else if ($field === 'favorite') {
159
+            $field = 'tag.category';
160
+            $value = self::TAG_FAVORITE;
161
+        } else if ($field === 'tagname') {
162
+            $field = 'tag.category';
163
+        }
164
+        return [$field, $value, $type];
165
+    }
166
+
167
+    private function validateComparison(ISearchComparison $operator) {
168
+        $types = [
169
+            'mimetype' => 'string',
170
+            'mtime' => 'integer',
171
+            'name' => 'string',
172
+            'size' => 'integer',
173
+            'tagname' => 'string',
174
+            'favorite' => 'boolean',
175
+            'fileid' => 'integer'
176
+        ];
177
+        $comparisons = [
178
+            'mimetype' => ['eq', 'like'],
179
+            'mtime' => ['eq', 'gt', 'lt', 'gte', 'lte'],
180
+            'name' => ['eq', 'like'],
181
+            'size' => ['eq', 'gt', 'lt', 'gte', 'lte'],
182
+            'tagname' => ['eq', 'like'],
183
+            'favorite' => ['eq'],
184
+            'fileid' => ['eq']
185
+        ];
186
+
187
+        if (!isset($types[$operator->getField()])) {
188
+            throw new \InvalidArgumentException('Unsupported comparison field ' . $operator->getField());
189
+        }
190
+        $type = $types[$operator->getField()];
191
+        if (gettype($operator->getValue()) !== $type) {
192
+            throw new \InvalidArgumentException('Invalid type for field ' . $operator->getField());
193
+        }
194
+        if (!in_array($operator->getType(), $comparisons[$operator->getField()])) {
195
+            throw new \InvalidArgumentException('Unsupported comparison for field  ' . $operator->getField() . ': ' . $operator->getType());
196
+        }
197
+    }
198
+
199
+    private function getParameterForValue(IQueryBuilder $builder, $value) {
200
+        if ($value instanceof \DateTime) {
201
+            $value = $value->getTimestamp();
202
+        }
203
+        if (is_numeric($value)) {
204
+            $type = IQueryBuilder::PARAM_INT;
205
+        } else {
206
+            $type = IQueryBuilder::PARAM_STR;
207
+        }
208
+        return $builder->createNamedParameter($value, $type);
209
+    }
210
+
211
+    /**
212
+     * @param IQueryBuilder $query
213
+     * @param ISearchOrder[] $orders
214
+     */
215
+    public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders) {
216
+        foreach ($orders as $order) {
217
+            $query->addOrderBy($order->getField(), $order->getDirection());
218
+        }
219
+    }
220 220
 }
Please login to merge, or discard this patch.
Spacing   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -74,7 +74,7 @@  discard block
 block discarded – undo
74 74
 	 */
75 75
 	public function shouldJoinTags(ISearchOperator $operator) {
76 76
 		if ($operator instanceof ISearchBinaryOperator) {
77
-			return array_reduce($operator->getArguments(), function ($shouldJoin, ISearchOperator $operator) {
77
+			return array_reduce($operator->getArguments(), function($shouldJoin, ISearchOperator $operator) {
78 78
 				return $shouldJoin || $this->shouldJoinTags($operator);
79 79
 			}, false);
80 80
 		} else if ($operator instanceof ISearchComparison) {
@@ -88,7 +88,7 @@  discard block
 block discarded – undo
88 88
 	 * @param ISearchOperator $operator
89 89
 	 */
90 90
 	public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) {
91
-		return array_filter(array_map(function ($operator) use ($builder) {
91
+		return array_filter(array_map(function($operator) use ($builder) {
92 92
 			return $this->searchOperatorToDBExpr($builder, $operator);
93 93
 		}, $operators));
94 94
 	}
@@ -113,12 +113,12 @@  discard block
 block discarded – undo
113 113
 				case ISearchBinaryOperator::OPERATOR_OR:
114 114
 					return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
115 115
 				default:
116
-					throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType());
116
+					throw new \InvalidArgumentException('Invalid operator type: '.$operator->getType());
117 117
 			}
118 118
 		} else if ($operator instanceof ISearchComparison) {
119 119
 			return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap);
120 120
 		} else {
121
-			throw new \InvalidArgumentException('Invalid operator type: ' . get_class($operator));
121
+			throw new \InvalidArgumentException('Invalid operator type: '.get_class($operator));
122 122
 		}
123 123
 	}
124 124
 
@@ -130,7 +130,7 @@  discard block
 block discarded – undo
130 130
 			$queryOperator = $operatorMap[$type];
131 131
 			return $builder->expr()->$queryOperator($field, $this->getParameterForValue($builder, $value));
132 132
 		} else {
133
-			throw new \InvalidArgumentException('Invalid operator type: ' . $comparison->getType());
133
+			throw new \InvalidArgumentException('Invalid operator type: '.$comparison->getType());
134 134
 		}
135 135
 	}
136 136
 
@@ -140,18 +140,18 @@  discard block
 block discarded – undo
140 140
 		$type = $operator->getType();
141 141
 		if ($field === 'mimetype') {
142 142
 			if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) {
143
-				$value = (int)$this->mimetypeLoader->getId($value);
143
+				$value = (int) $this->mimetypeLoader->getId($value);
144 144
 			} else if ($operator->getType() === ISearchComparison::COMPARE_LIKE) {
145 145
 				// transform "mimetype='foo/%'" to "mimepart='foo'"
146 146
 				if (preg_match('|(.+)/%|', $value, $matches)) {
147 147
 					$field = 'mimepart';
148
-					$value = (int)$this->mimetypeLoader->getId($matches[1]);
148
+					$value = (int) $this->mimetypeLoader->getId($matches[1]);
149 149
 					$type = ISearchComparison::COMPARE_EQUAL;
150 150
 				} else if (strpos($value, '%') !== false) {
151
-					throw new \InvalidArgumentException('Unsupported query value for mimetype: ' . $value . ', only values in the format "mime/type" or "mime/%" are supported');
151
+					throw new \InvalidArgumentException('Unsupported query value for mimetype: '.$value.', only values in the format "mime/type" or "mime/%" are supported');
152 152
 				} else {
153 153
 					$field = 'mimetype';
154
-					$value = (int)$this->mimetypeLoader->getId($value);
154
+					$value = (int) $this->mimetypeLoader->getId($value);
155 155
 					$type = ISearchComparison::COMPARE_EQUAL;
156 156
 				}
157 157
 			}
@@ -185,14 +185,14 @@  discard block
 block discarded – undo
185 185
 		];
186 186
 
187 187
 		if (!isset($types[$operator->getField()])) {
188
-			throw new \InvalidArgumentException('Unsupported comparison field ' . $operator->getField());
188
+			throw new \InvalidArgumentException('Unsupported comparison field '.$operator->getField());
189 189
 		}
190 190
 		$type = $types[$operator->getField()];
191 191
 		if (gettype($operator->getValue()) !== $type) {
192
-			throw new \InvalidArgumentException('Invalid type for field ' . $operator->getField());
192
+			throw new \InvalidArgumentException('Invalid type for field '.$operator->getField());
193 193
 		}
194 194
 		if (!in_array($operator->getType(), $comparisons[$operator->getField()])) {
195
-			throw new \InvalidArgumentException('Unsupported comparison for field  ' . $operator->getField() . ': ' . $operator->getType());
195
+			throw new \InvalidArgumentException('Unsupported comparison for field  '.$operator->getField().': '.$operator->getType());
196 196
 		}
197 197
 	}
198 198
 
Please login to merge, or discard this patch.
lib/private/Files/Cache/Cache.php 1 patch
Indentation   +922 added lines, -922 removed lines patch added patch discarded remove patch
@@ -62,926 +62,926 @@
 block discarded – undo
62 62
  * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater
63 63
  */
64 64
 class Cache implements ICache {
65
-	use MoveFromCacheTrait {
66
-		MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
67
-	}
68
-
69
-	/**
70
-	 * @var array partial data for the cache
71
-	 */
72
-	protected $partial = [];
73
-
74
-	/**
75
-	 * @var string
76
-	 */
77
-	protected $storageId;
78
-
79
-	private $storage;
80
-
81
-	/**
82
-	 * @var Storage $storageCache
83
-	 */
84
-	protected $storageCache;
85
-
86
-	/** @var IMimeTypeLoader */
87
-	protected $mimetypeLoader;
88
-
89
-	/**
90
-	 * @var IDBConnection
91
-	 */
92
-	protected $connection;
93
-
94
-	protected $eventDispatcher;
95
-
96
-	/** @var QuerySearchHelper */
97
-	protected $querySearchHelper;
98
-
99
-	/**
100
-	 * @param IStorage $storage
101
-	 */
102
-	public function __construct(IStorage $storage) {
103
-		$this->storageId = $storage->getId();
104
-		$this->storage = $storage;
105
-		if (strlen($this->storageId) > 64) {
106
-			$this->storageId = md5($this->storageId);
107
-		}
108
-
109
-		$this->storageCache = new Storage($storage);
110
-		$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
111
-		$this->connection = \OC::$server->getDatabaseConnection();
112
-		$this->eventDispatcher = \OC::$server->getEventDispatcher();
113
-		$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
114
-	}
115
-
116
-	private function getQueryBuilder() {
117
-		return new CacheQueryBuilder(
118
-			$this->connection,
119
-			\OC::$server->getSystemConfig(),
120
-			\OC::$server->getLogger(),
121
-			$this
122
-		);
123
-	}
124
-
125
-	/**
126
-	 * Get the numeric storage id for this cache's storage
127
-	 *
128
-	 * @return int
129
-	 */
130
-	public function getNumericStorageId() {
131
-		return $this->storageCache->getNumericId();
132
-	}
133
-
134
-	/**
135
-	 * get the stored metadata of a file or folder
136
-	 *
137
-	 * @param string | int $file either the path of a file or folder or the file id for a file or folder
138
-	 * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
139
-	 */
140
-	public function get($file) {
141
-		$query = $this->getQueryBuilder();
142
-		$query->selectFileCache();
143
-
144
-		if (is_string($file) or $file == '') {
145
-			// normalize file
146
-			$file = $this->normalize($file);
147
-
148
-			$query->whereStorageId()
149
-				->wherePath($file);
150
-		} else { //file id
151
-			$query->whereFileId($file);
152
-		}
153
-
154
-		$data = $query->execute()->fetch();
155
-
156
-		//merge partial data
157
-		if (!$data and is_string($file) and isset($this->partial[$file])) {
158
-			return $this->partial[$file];
159
-		} else if (!$data) {
160
-			return $data;
161
-		} else {
162
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
163
-		}
164
-	}
165
-
166
-	/**
167
-	 * Create a CacheEntry from database row
168
-	 *
169
-	 * @param array $data
170
-	 * @param IMimeTypeLoader $mimetypeLoader
171
-	 * @return CacheEntry
172
-	 */
173
-	public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
174
-		//fix types
175
-		$data['fileid'] = (int)$data['fileid'];
176
-		$data['parent'] = (int)$data['parent'];
177
-		$data['size'] = 0 + $data['size'];
178
-		$data['mtime'] = (int)$data['mtime'];
179
-		$data['storage_mtime'] = (int)$data['storage_mtime'];
180
-		$data['encryptedVersion'] = (int)$data['encrypted'];
181
-		$data['encrypted'] = (bool)$data['encrypted'];
182
-		$data['storage_id'] = $data['storage'];
183
-		$data['storage'] = (int)$data['storage'];
184
-		$data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
185
-		$data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
186
-		if ($data['storage_mtime'] == 0) {
187
-			$data['storage_mtime'] = $data['mtime'];
188
-		}
189
-		$data['permissions'] = (int)$data['permissions'];
190
-		if (isset($data['creation_time'])) {
191
-			$data['creation_time'] = (int) $data['creation_time'];
192
-		}
193
-		if (isset($data['upload_time'])) {
194
-			$data['upload_time'] = (int) $data['upload_time'];
195
-		}
196
-		return new CacheEntry($data);
197
-	}
198
-
199
-	/**
200
-	 * get the metadata of all files stored in $folder
201
-	 *
202
-	 * @param string $folder
203
-	 * @return ICacheEntry[]
204
-	 */
205
-	public function getFolderContents($folder) {
206
-		$fileId = $this->getId($folder);
207
-		return $this->getFolderContentsById($fileId);
208
-	}
209
-
210
-	/**
211
-	 * get the metadata of all files stored in $folder
212
-	 *
213
-	 * @param int $fileId the file id of the folder
214
-	 * @return ICacheEntry[]
215
-	 */
216
-	public function getFolderContentsById($fileId) {
217
-		if ($fileId > -1) {
218
-			$query = $this->getQueryBuilder();
219
-			$query->selectFileCache()
220
-				->whereParent($fileId)
221
-				->orderBy('name', 'ASC');
222
-
223
-			$files = $query->execute()->fetchAll();
224
-			return array_map(function (array $data) {
225
-				return self::cacheEntryFromData($data, $this->mimetypeLoader);
226
-			}, $files);
227
-		}
228
-		return [];
229
-	}
230
-
231
-	/**
232
-	 * insert or update meta data for a file or folder
233
-	 *
234
-	 * @param string $file
235
-	 * @param array $data
236
-	 *
237
-	 * @return int file id
238
-	 * @throws \RuntimeException
239
-	 */
240
-	public function put($file, array $data) {
241
-		if (($id = $this->getId($file)) > -1) {
242
-			$this->update($id, $data);
243
-			return $id;
244
-		} else {
245
-			return $this->insert($file, $data);
246
-		}
247
-	}
248
-
249
-	/**
250
-	 * insert meta data for a new file or folder
251
-	 *
252
-	 * @param string $file
253
-	 * @param array $data
254
-	 *
255
-	 * @return int file id
256
-	 * @throws \RuntimeException
257
-	 *
258
-	 * @suppress SqlInjectionChecker
259
-	 */
260
-	public function insert($file, array $data) {
261
-		// normalize file
262
-		$file = $this->normalize($file);
263
-
264
-		if (isset($this->partial[$file])) { //add any saved partial data
265
-			$data = array_merge($this->partial[$file], $data);
266
-			unset($this->partial[$file]);
267
-		}
268
-
269
-		$requiredFields = ['size', 'mtime', 'mimetype'];
270
-		foreach ($requiredFields as $field) {
271
-			if (!isset($data[$field])) { //data not complete save as partial and return
272
-				$this->partial[$file] = $data;
273
-				return -1;
274
-			}
275
-		}
276
-
277
-		$data['path'] = $file;
278
-		$data['parent'] = $this->getParentId($file);
279
-		$data['name'] = basename($file);
280
-
281
-		[$values, $extensionValues] = $this->normalizeData($data);
282
-		$values['storage'] = $this->getNumericStorageId();
283
-
284
-		try {
285
-			$builder = $this->connection->getQueryBuilder();
286
-			$builder->insert('filecache');
287
-
288
-			foreach ($values as $column => $value) {
289
-				$builder->setValue($column, $builder->createNamedParameter($value));
290
-			}
291
-
292
-			if ($builder->execute()) {
293
-				$fileId = $builder->getLastInsertId();
294
-
295
-				if (count($extensionValues)) {
296
-					$query = $this->getQueryBuilder();
297
-					$query->insert('filecache_extended');
298
-
299
-					$query->setValue('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
300
-					foreach ($extensionValues as $column => $value) {
301
-						$query->setValue($column, $query->createNamedParameter($value));
302
-					}
303
-					$query->execute();
304
-				}
305
-
306
-				$this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId));
307
-				return $fileId;
308
-			}
309
-		} catch (UniqueConstraintViolationException $e) {
310
-			// entry exists already
311
-		}
312
-
313
-		// The file was created in the mean time
314
-		if (($id = $this->getId($file)) > -1) {
315
-			$this->update($id, $data);
316
-			return $id;
317
-		} else {
318
-			throw new \RuntimeException('File entry could not be inserted but could also not be selected with getId() in order to perform an update. Please try again.');
319
-		}
320
-	}
321
-
322
-	/**
323
-	 * update the metadata of an existing file or folder in the cache
324
-	 *
325
-	 * @param int $id the fileid of the existing file or folder
326
-	 * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged
327
-	 */
328
-	public function update($id, array $data) {
329
-
330
-		if (isset($data['path'])) {
331
-			// normalize path
332
-			$data['path'] = $this->normalize($data['path']);
333
-		}
334
-
335
-		if (isset($data['name'])) {
336
-			// normalize path
337
-			$data['name'] = $this->normalize($data['name']);
338
-		}
339
-
340
-		[$values, $extensionValues] = $this->normalizeData($data);
341
-
342
-		if (count($values)) {
343
-			$query = $this->getQueryBuilder();
344
-
345
-			$query->update('filecache')
346
-				->whereFileId($id)
347
-				->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
348
-					return $query->expr()->orX(
349
-						$query->expr()->neq($key, $query->createNamedParameter($value)),
350
-						$query->expr()->isNull($key)
351
-					);
352
-				}, array_keys($values), array_values($values))));
353
-
354
-			foreach ($values as $key => $value) {
355
-				$query->set($key, $query->createNamedParameter($value));
356
-			}
357
-
358
-			$query->execute();
359
-		}
360
-
361
-		if (count($extensionValues)) {
362
-			try {
363
-				$query = $this->getQueryBuilder();
364
-				$query->insert('filecache_extended');
365
-
366
-				$query->setValue('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT));
367
-				foreach ($extensionValues as $column => $value) {
368
-					$query->setValue($column, $query->createNamedParameter($value));
369
-				}
370
-
371
-				$query->execute();
372
-			} catch (UniqueConstraintViolationException $e) {
373
-				$query = $this->getQueryBuilder();
374
-				$query->update('filecache_extended')
375
-					->whereFileId($id)
376
-					->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
377
-						return $query->expr()->orX(
378
-							$query->expr()->neq($key, $query->createNamedParameter($value)),
379
-							$query->expr()->isNull($key)
380
-						);
381
-					}, array_keys($extensionValues), array_values($extensionValues))));
382
-
383
-				foreach ($extensionValues as $key => $value) {
384
-					$query->set($key, $query->createNamedParameter($value));
385
-				}
386
-
387
-				$query->execute();
388
-			}
389
-		}
390
-
391
-		$path = $this->getPathById($id);
392
-		// path can still be null if the file doesn't exist
393
-		if ($path !== null) {
394
-			$this->eventDispatcher->dispatch(CacheUpdateEvent::class, new CacheUpdateEvent($this->storage, $path, $id));
395
-		}
396
-	}
397
-
398
-	/**
399
-	 * extract query parts and params array from data array
400
-	 *
401
-	 * @param array $data
402
-	 * @return array
403
-	 */
404
-	protected function normalizeData(array $data): array {
405
-		$fields = [
406
-			'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
407
-			'etag', 'permissions', 'checksum', 'storage'];
408
-		$extensionFields = ['metadata_etag', 'creation_time', 'upload_time'];
409
-
410
-		$doNotCopyStorageMTime = false;
411
-		if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
412
-			// this horrific magic tells it to not copy storage_mtime to mtime
413
-			unset($data['mtime']);
414
-			$doNotCopyStorageMTime = true;
415
-		}
416
-
417
-		$params = [];
418
-		$extensionParams = [];
419
-		foreach ($data as $name => $value) {
420
-			if (array_search($name, $fields) !== false) {
421
-				if ($name === 'path') {
422
-					$params['path_hash'] = md5($value);
423
-				} else if ($name === 'mimetype') {
424
-					$params['mimepart'] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
425
-					$value = $this->mimetypeLoader->getId($value);
426
-				} else if ($name === 'storage_mtime') {
427
-					if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
428
-						$params['mtime'] = $value;
429
-					}
430
-				} else if ($name === 'encrypted') {
431
-					if (isset($data['encryptedVersion'])) {
432
-						$value = $data['encryptedVersion'];
433
-					} else {
434
-						// Boolean to integer conversion
435
-						$value = $value ? 1 : 0;
436
-					}
437
-				}
438
-				$params[$name] = $value;
439
-			}
440
-			if (array_search($name, $extensionFields) !== false) {
441
-				$extensionParams[$name] = $value;
442
-			}
443
-		}
444
-		return [$params, array_filter($extensionParams)];
445
-	}
446
-
447
-	/**
448
-	 * get the file id for a file
449
-	 *
450
-	 * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file
451
-	 *
452
-	 * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
453
-	 *
454
-	 * @param string $file
455
-	 * @return int
456
-	 */
457
-	public function getId($file) {
458
-		// normalize file
459
-		$file = $this->normalize($file);
460
-
461
-		$query = $this->getQueryBuilder();
462
-		$query->select('fileid')
463
-			->from('filecache')
464
-			->whereStorageId()
465
-			->wherePath($file);
466
-
467
-		$id = $query->execute()->fetchColumn();
468
-		return $id === false ? -1 : (int)$id;
469
-	}
470
-
471
-	/**
472
-	 * get the id of the parent folder of a file
473
-	 *
474
-	 * @param string $file
475
-	 * @return int
476
-	 */
477
-	public function getParentId($file) {
478
-		if ($file === '') {
479
-			return -1;
480
-		} else {
481
-			$parent = $this->getParentPath($file);
482
-			return (int)$this->getId($parent);
483
-		}
484
-	}
485
-
486
-	private function getParentPath($path) {
487
-		$parent = dirname($path);
488
-		if ($parent === '.') {
489
-			$parent = '';
490
-		}
491
-		return $parent;
492
-	}
493
-
494
-	/**
495
-	 * check if a file is available in the cache
496
-	 *
497
-	 * @param string $file
498
-	 * @return bool
499
-	 */
500
-	public function inCache($file) {
501
-		return $this->getId($file) != -1;
502
-	}
503
-
504
-	/**
505
-	 * remove a file or folder from the cache
506
-	 *
507
-	 * when removing a folder from the cache all files and folders inside the folder will be removed as well
508
-	 *
509
-	 * @param string $file
510
-	 */
511
-	public function remove($file) {
512
-		$entry = $this->get($file);
513
-
514
-		if ($entry) {
515
-			$query = $this->getQueryBuilder();
516
-			$query->delete('filecache')
517
-				->whereFileId($entry->getId());
518
-			$query->execute();
519
-
520
-			$query = $this->getQueryBuilder();
521
-			$query->delete('filecache_extended')
522
-				->whereFileId($entry->getId());
523
-			$query->execute();
524
-
525
-			if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
526
-				$this->removeChildren($entry);
527
-			}
528
-		}
529
-	}
530
-
531
-	/**
532
-	 * Get all sub folders of a folder
533
-	 *
534
-	 * @param ICacheEntry $entry the cache entry of the folder to get the subfolders for
535
-	 * @return ICacheEntry[] the cache entries for the subfolders
536
-	 */
537
-	private function getSubFolders(ICacheEntry $entry) {
538
-		$children = $this->getFolderContentsById($entry->getId());
539
-		return array_filter($children, function ($child) {
540
-			return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
541
-		});
542
-	}
543
-
544
-	/**
545
-	 * Recursively remove all children of a folder
546
-	 *
547
-	 * @param ICacheEntry $entry the cache entry of the folder to remove the children of
548
-	 * @throws \OC\DatabaseException
549
-	 */
550
-	private function removeChildren(ICacheEntry $entry) {
551
-		$children = $this->getFolderContentsById($entry->getId());
552
-		$childIds = array_map(function(ICacheEntry $cacheEntry) {
553
-			return $cacheEntry->getId();
554
-		}, $children);
555
-		$childFolders = array_filter($children, function ($child) {
556
-			return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
557
-		});
558
-		foreach ($childFolders as $folder) {
559
-			$this->removeChildren($folder);
560
-		}
561
-
562
-		$query = $this->getQueryBuilder();
563
-		$query->delete('filecache')
564
-			->whereParent($entry->getId());
565
-		$query->execute();
566
-
567
-		$query = $this->getQueryBuilder();
568
-		$query->delete('filecache_extended')
569
-			->where($query->expr()->in('fileid', $query->createNamedParameter($childIds, IQueryBuilder::PARAM_INT_ARRAY)));
570
-		$query->execute();
571
-	}
572
-
573
-	/**
574
-	 * Move a file or folder in the cache
575
-	 *
576
-	 * @param string $source
577
-	 * @param string $target
578
-	 */
579
-	public function move($source, $target) {
580
-		$this->moveFromCache($this, $source, $target);
581
-	}
582
-
583
-	/**
584
-	 * Get the storage id and path needed for a move
585
-	 *
586
-	 * @param string $path
587
-	 * @return array [$storageId, $internalPath]
588
-	 */
589
-	protected function getMoveInfo($path) {
590
-		return [$this->getNumericStorageId(), $path];
591
-	}
592
-
593
-	/**
594
-	 * Move a file or folder in the cache
595
-	 *
596
-	 * @param \OCP\Files\Cache\ICache $sourceCache
597
-	 * @param string $sourcePath
598
-	 * @param string $targetPath
599
-	 * @throws \OC\DatabaseException
600
-	 * @throws \Exception if the given storages have an invalid id
601
-	 * @suppress SqlInjectionChecker
602
-	 */
603
-	public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
604
-		if ($sourceCache instanceof Cache) {
605
-			// normalize source and target
606
-			$sourcePath = $this->normalize($sourcePath);
607
-			$targetPath = $this->normalize($targetPath);
608
-
609
-			$sourceData = $sourceCache->get($sourcePath);
610
-			$sourceId = $sourceData['fileid'];
611
-			$newParentId = $this->getParentId($targetPath);
612
-
613
-			list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
614
-			list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
615
-
616
-			if (is_null($sourceStorageId) || $sourceStorageId === false) {
617
-				throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
618
-			}
619
-			if (is_null($targetStorageId) || $targetStorageId === false) {
620
-				throw new \Exception('Invalid target storage id: ' . $targetStorageId);
621
-			}
622
-
623
-			$this->connection->beginTransaction();
624
-			if ($sourceData['mimetype'] === 'httpd/unix-directory') {
625
-				//update all child entries
626
-				$sourceLength = mb_strlen($sourcePath);
627
-				$query = $this->connection->getQueryBuilder();
628
-
629
-				$fun = $query->func();
630
-				$newPathFunction = $fun->concat(
631
-					$query->createNamedParameter($targetPath),
632
-					$fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
633
-				);
634
-				$query->update('filecache')
635
-					->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
636
-					->set('path_hash', $fun->md5($newPathFunction))
637
-					->set('path', $newPathFunction)
638
-					->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
639
-					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
640
-
641
-				try {
642
-					$query->execute();
643
-				} catch (\OC\DatabaseException $e) {
644
-					$this->connection->rollBack();
645
-					throw $e;
646
-				}
647
-			}
648
-
649
-			$query = $this->getQueryBuilder();
650
-			$query->update('filecache')
651
-				->set('storage', $query->createNamedParameter($targetStorageId))
652
-				->set('path', $query->createNamedParameter($targetPath))
653
-				->set('path_hash', $query->createNamedParameter(md5($targetPath)))
654
-				->set('name', $query->createNamedParameter(basename($targetPath)))
655
-				->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT))
656
-				->whereFileId($sourceId);
657
-			$query->execute();
658
-
659
-			$this->connection->commit();
660
-		} else {
661
-			$this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
662
-		}
663
-	}
664
-
665
-	/**
666
-	 * remove all entries for files that are stored on the storage from the cache
667
-	 */
668
-	public function clear() {
669
-		$query = $this->getQueryBuilder();
670
-		$query->delete('filecache')
671
-			->whereStorageId();
672
-		$query->execute();
673
-
674
-		$query = $this->connection->getQueryBuilder();
675
-		$query->delete('storages')
676
-			->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
677
-		$query->execute();
678
-	}
679
-
680
-	/**
681
-	 * Get the scan status of a file
682
-	 *
683
-	 * - Cache::NOT_FOUND: File is not in the cache
684
-	 * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
685
-	 * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
686
-	 * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
687
-	 *
688
-	 * @param string $file
689
-	 *
690
-	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
691
-	 */
692
-	public function getStatus($file) {
693
-		// normalize file
694
-		$file = $this->normalize($file);
695
-
696
-		$query = $this->getQueryBuilder();
697
-		$query->select('size')
698
-			->from('filecache')
699
-			->whereStorageId()
700
-			->wherePath($file);
701
-		$size = $query->execute()->fetchColumn();
702
-		if ($size !== false) {
703
-			if ((int)$size === -1) {
704
-				return self::SHALLOW;
705
-			} else {
706
-				return self::COMPLETE;
707
-			}
708
-		} else {
709
-			if (isset($this->partial[$file])) {
710
-				return self::PARTIAL;
711
-			} else {
712
-				return self::NOT_FOUND;
713
-			}
714
-		}
715
-	}
716
-
717
-	/**
718
-	 * search for files matching $pattern
719
-	 *
720
-	 * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
721
-	 * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
722
-	 */
723
-	public function search($pattern) {
724
-		// normalize pattern
725
-		$pattern = $this->normalize($pattern);
726
-
727
-		if ($pattern === '%%') {
728
-			return [];
729
-		}
730
-
731
-		$query = $this->getQueryBuilder();
732
-		$query->selectFileCache()
733
-			->whereStorageId()
734
-			->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
735
-
736
-		return array_map(function (array $data) {
737
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
738
-		}, $query->execute()->fetchAll());
739
-	}
740
-
741
-	/**
742
-	 * @param Statement $result
743
-	 * @return CacheEntry[]
744
-	 */
745
-	private function searchResultToCacheEntries(Statement $result) {
746
-		$files = $result->fetchAll();
747
-
748
-		return array_map(function (array $data) {
749
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
750
-		}, $files);
751
-	}
752
-
753
-	/**
754
-	 * search for files by mimetype
755
-	 *
756
-	 * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
757
-	 *        where it will search for all mimetypes in the group ('image/*')
758
-	 * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
759
-	 */
760
-	public function searchByMime($mimetype) {
761
-		$mimeId = $this->mimetypeLoader->getId($mimetype);
762
-
763
-		$query = $this->getQueryBuilder();
764
-		$query->selectFileCache()
765
-			->whereStorageId();
766
-
767
-		if (strpos($mimetype, '/')) {
768
-			$query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
769
-		} else {
770
-			$query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
771
-		}
772
-
773
-		return array_map(function (array $data) {
774
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
775
-		}, $query->execute()->fetchAll());
776
-	}
777
-
778
-	public function searchQuery(ISearchQuery $searchQuery) {
779
-		$builder = $this->getQueryBuilder();
780
-
781
-		$query = $builder->selectFileCache('file');
782
-
783
-		$query->whereStorageId();
784
-
785
-		if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
786
-			$query
787
-				->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
788
-				->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
789
-					$builder->expr()->eq('tagmap.type', 'tag.type'),
790
-					$builder->expr()->eq('tagmap.categoryid', 'tag.id')
791
-				))
792
-				->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
793
-				->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
794
-		}
795
-
796
-		$searchExpr = $this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
797
-		if ($searchExpr) {
798
-			$query->andWhere($searchExpr);
799
-		}
800
-
801
-		$this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
802
-
803
-		if ($searchQuery->getLimit()) {
804
-			$query->setMaxResults($searchQuery->getLimit());
805
-		}
806
-		if ($searchQuery->getOffset()) {
807
-			$query->setFirstResult($searchQuery->getOffset());
808
-		}
809
-
810
-		$result = $query->execute();
811
-		return $this->searchResultToCacheEntries($result);
812
-	}
813
-
814
-	/**
815
-	 * Re-calculate the folder size and the size of all parent folders
816
-	 *
817
-	 * @param string|boolean $path
818
-	 * @param array $data (optional) meta data of the folder
819
-	 */
820
-	public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
821
-		$this->calculateFolderSize($path, $data);
822
-		if ($path !== '') {
823
-			$parent = dirname($path);
824
-			if ($parent === '.' or $parent === '/') {
825
-				$parent = '';
826
-			}
827
-			if ($isBackgroundScan) {
828
-				$parentData = $this->get($parent);
829
-				if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) {
830
-					$this->correctFolderSize($parent, $parentData, $isBackgroundScan);
831
-				}
832
-			} else {
833
-				$this->correctFolderSize($parent);
834
-			}
835
-		}
836
-	}
837
-
838
-	/**
839
-	 * get the incomplete count that shares parent $folder
840
-	 *
841
-	 * @param int $fileId the file id of the folder
842
-	 * @return int
843
-	 */
844
-	public function getIncompleteChildrenCount($fileId) {
845
-		if ($fileId > -1) {
846
-			$query = $this->getQueryBuilder();
847
-			$query->select($query->func()->count())
848
-				->from('filecache')
849
-				->whereParent($fileId)
850
-				->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
851
-
852
-			return (int)$query->execute()->fetchColumn();
853
-		}
854
-		return -1;
855
-	}
856
-
857
-	/**
858
-	 * calculate the size of a folder and set it in the cache
859
-	 *
860
-	 * @param string $path
861
-	 * @param array $entry (optional) meta data of the folder
862
-	 * @return int
863
-	 */
864
-	public function calculateFolderSize($path, $entry = null) {
865
-		$totalSize = 0;
866
-		if (is_null($entry) or !isset($entry['fileid'])) {
867
-			$entry = $this->get($path);
868
-		}
869
-		if (isset($entry['mimetype']) && $entry['mimetype'] === FileInfo::MIMETYPE_FOLDER) {
870
-			$id = $entry['fileid'];
871
-
872
-			$query = $this->getQueryBuilder();
873
-			$query->selectAlias($query->func()->sum('size'), 'f1')
874
-				->selectAlias($query->func()->min('size'), 'f2')
875
-				->from('filecache')
876
-				->whereStorageId()
877
-				->whereParent($id);
878
-
879
-			if ($row = $query->execute()->fetch()) {
880
-				list($sum, $min) = array_values($row);
881
-				$sum = 0 + $sum;
882
-				$min = 0 + $min;
883
-				if ($min === -1) {
884
-					$totalSize = $min;
885
-				} else {
886
-					$totalSize = $sum;
887
-				}
888
-				if ($entry['size'] !== $totalSize) {
889
-					$this->update($id, ['size' => $totalSize]);
890
-				}
891
-			}
892
-		}
893
-		return $totalSize;
894
-	}
895
-
896
-	/**
897
-	 * get all file ids on the files on the storage
898
-	 *
899
-	 * @return int[]
900
-	 */
901
-	public function getAll() {
902
-		$query = $this->getQueryBuilder();
903
-		$query->select('fileid')
904
-			->from('filecache')
905
-			->whereStorageId();
906
-
907
-		return array_map(function ($id) {
908
-			return (int)$id;
909
-		}, $query->execute()->fetchAll(\PDO::FETCH_COLUMN));
910
-	}
911
-
912
-	/**
913
-	 * find a folder in the cache which has not been fully scanned
914
-	 *
915
-	 * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
916
-	 * use the one with the highest id gives the best result with the background scanner, since that is most
917
-	 * likely the folder where we stopped scanning previously
918
-	 *
919
-	 * @return string|bool the path of the folder or false when no folder matched
920
-	 */
921
-	public function getIncomplete() {
922
-		$query = $this->getQueryBuilder();
923
-		$query->select('path')
924
-			->from('filecache')
925
-			->whereStorageId()
926
-			->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
927
-			->orderBy('fileid', 'DESC');
928
-
929
-		return $query->execute()->fetchColumn();
930
-	}
931
-
932
-	/**
933
-	 * get the path of a file on this storage by it's file id
934
-	 *
935
-	 * @param int $id the file id of the file or folder to search
936
-	 * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
937
-	 */
938
-	public function getPathById($id) {
939
-		$query = $this->getQueryBuilder();
940
-		$query->select('path')
941
-			->from('filecache')
942
-			->whereStorageId()
943
-			->whereFileId($id);
944
-
945
-		$path = $query->execute()->fetchColumn();
946
-		return $path === false ? null : $path;
947
-	}
948
-
949
-	/**
950
-	 * get the storage id of the storage for a file and the internal path of the file
951
-	 * unlike getPathById this does not limit the search to files on this storage and
952
-	 * instead does a global search in the cache table
953
-	 *
954
-	 * @param int $id
955
-	 * @return array first element holding the storage id, second the path
956
-	 * @deprecated use getPathById() instead
957
-	 */
958
-	static public function getById($id) {
959
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
960
-		$query->select('path', 'storage')
961
-			->from('filecache')
962
-			->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
963
-		if ($row = $query->execute()->fetch()) {
964
-			$numericId = $row['storage'];
965
-			$path = $row['path'];
966
-		} else {
967
-			return null;
968
-		}
969
-
970
-		if ($id = Storage::getStorageId($numericId)) {
971
-			return [$id, $path];
972
-		} else {
973
-			return null;
974
-		}
975
-	}
976
-
977
-	/**
978
-	 * normalize the given path
979
-	 *
980
-	 * @param string $path
981
-	 * @return string
982
-	 */
983
-	public function normalize($path) {
984
-
985
-		return trim(\OC_Util::normalizeUnicode($path), '/');
986
-	}
65
+    use MoveFromCacheTrait {
66
+        MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
67
+    }
68
+
69
+    /**
70
+     * @var array partial data for the cache
71
+     */
72
+    protected $partial = [];
73
+
74
+    /**
75
+     * @var string
76
+     */
77
+    protected $storageId;
78
+
79
+    private $storage;
80
+
81
+    /**
82
+     * @var Storage $storageCache
83
+     */
84
+    protected $storageCache;
85
+
86
+    /** @var IMimeTypeLoader */
87
+    protected $mimetypeLoader;
88
+
89
+    /**
90
+     * @var IDBConnection
91
+     */
92
+    protected $connection;
93
+
94
+    protected $eventDispatcher;
95
+
96
+    /** @var QuerySearchHelper */
97
+    protected $querySearchHelper;
98
+
99
+    /**
100
+     * @param IStorage $storage
101
+     */
102
+    public function __construct(IStorage $storage) {
103
+        $this->storageId = $storage->getId();
104
+        $this->storage = $storage;
105
+        if (strlen($this->storageId) > 64) {
106
+            $this->storageId = md5($this->storageId);
107
+        }
108
+
109
+        $this->storageCache = new Storage($storage);
110
+        $this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
111
+        $this->connection = \OC::$server->getDatabaseConnection();
112
+        $this->eventDispatcher = \OC::$server->getEventDispatcher();
113
+        $this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
114
+    }
115
+
116
+    private function getQueryBuilder() {
117
+        return new CacheQueryBuilder(
118
+            $this->connection,
119
+            \OC::$server->getSystemConfig(),
120
+            \OC::$server->getLogger(),
121
+            $this
122
+        );
123
+    }
124
+
125
+    /**
126
+     * Get the numeric storage id for this cache's storage
127
+     *
128
+     * @return int
129
+     */
130
+    public function getNumericStorageId() {
131
+        return $this->storageCache->getNumericId();
132
+    }
133
+
134
+    /**
135
+     * get the stored metadata of a file or folder
136
+     *
137
+     * @param string | int $file either the path of a file or folder or the file id for a file or folder
138
+     * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
139
+     */
140
+    public function get($file) {
141
+        $query = $this->getQueryBuilder();
142
+        $query->selectFileCache();
143
+
144
+        if (is_string($file) or $file == '') {
145
+            // normalize file
146
+            $file = $this->normalize($file);
147
+
148
+            $query->whereStorageId()
149
+                ->wherePath($file);
150
+        } else { //file id
151
+            $query->whereFileId($file);
152
+        }
153
+
154
+        $data = $query->execute()->fetch();
155
+
156
+        //merge partial data
157
+        if (!$data and is_string($file) and isset($this->partial[$file])) {
158
+            return $this->partial[$file];
159
+        } else if (!$data) {
160
+            return $data;
161
+        } else {
162
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
163
+        }
164
+    }
165
+
166
+    /**
167
+     * Create a CacheEntry from database row
168
+     *
169
+     * @param array $data
170
+     * @param IMimeTypeLoader $mimetypeLoader
171
+     * @return CacheEntry
172
+     */
173
+    public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
174
+        //fix types
175
+        $data['fileid'] = (int)$data['fileid'];
176
+        $data['parent'] = (int)$data['parent'];
177
+        $data['size'] = 0 + $data['size'];
178
+        $data['mtime'] = (int)$data['mtime'];
179
+        $data['storage_mtime'] = (int)$data['storage_mtime'];
180
+        $data['encryptedVersion'] = (int)$data['encrypted'];
181
+        $data['encrypted'] = (bool)$data['encrypted'];
182
+        $data['storage_id'] = $data['storage'];
183
+        $data['storage'] = (int)$data['storage'];
184
+        $data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
185
+        $data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
186
+        if ($data['storage_mtime'] == 0) {
187
+            $data['storage_mtime'] = $data['mtime'];
188
+        }
189
+        $data['permissions'] = (int)$data['permissions'];
190
+        if (isset($data['creation_time'])) {
191
+            $data['creation_time'] = (int) $data['creation_time'];
192
+        }
193
+        if (isset($data['upload_time'])) {
194
+            $data['upload_time'] = (int) $data['upload_time'];
195
+        }
196
+        return new CacheEntry($data);
197
+    }
198
+
199
+    /**
200
+     * get the metadata of all files stored in $folder
201
+     *
202
+     * @param string $folder
203
+     * @return ICacheEntry[]
204
+     */
205
+    public function getFolderContents($folder) {
206
+        $fileId = $this->getId($folder);
207
+        return $this->getFolderContentsById($fileId);
208
+    }
209
+
210
+    /**
211
+     * get the metadata of all files stored in $folder
212
+     *
213
+     * @param int $fileId the file id of the folder
214
+     * @return ICacheEntry[]
215
+     */
216
+    public function getFolderContentsById($fileId) {
217
+        if ($fileId > -1) {
218
+            $query = $this->getQueryBuilder();
219
+            $query->selectFileCache()
220
+                ->whereParent($fileId)
221
+                ->orderBy('name', 'ASC');
222
+
223
+            $files = $query->execute()->fetchAll();
224
+            return array_map(function (array $data) {
225
+                return self::cacheEntryFromData($data, $this->mimetypeLoader);
226
+            }, $files);
227
+        }
228
+        return [];
229
+    }
230
+
231
+    /**
232
+     * insert or update meta data for a file or folder
233
+     *
234
+     * @param string $file
235
+     * @param array $data
236
+     *
237
+     * @return int file id
238
+     * @throws \RuntimeException
239
+     */
240
+    public function put($file, array $data) {
241
+        if (($id = $this->getId($file)) > -1) {
242
+            $this->update($id, $data);
243
+            return $id;
244
+        } else {
245
+            return $this->insert($file, $data);
246
+        }
247
+    }
248
+
249
+    /**
250
+     * insert meta data for a new file or folder
251
+     *
252
+     * @param string $file
253
+     * @param array $data
254
+     *
255
+     * @return int file id
256
+     * @throws \RuntimeException
257
+     *
258
+     * @suppress SqlInjectionChecker
259
+     */
260
+    public function insert($file, array $data) {
261
+        // normalize file
262
+        $file = $this->normalize($file);
263
+
264
+        if (isset($this->partial[$file])) { //add any saved partial data
265
+            $data = array_merge($this->partial[$file], $data);
266
+            unset($this->partial[$file]);
267
+        }
268
+
269
+        $requiredFields = ['size', 'mtime', 'mimetype'];
270
+        foreach ($requiredFields as $field) {
271
+            if (!isset($data[$field])) { //data not complete save as partial and return
272
+                $this->partial[$file] = $data;
273
+                return -1;
274
+            }
275
+        }
276
+
277
+        $data['path'] = $file;
278
+        $data['parent'] = $this->getParentId($file);
279
+        $data['name'] = basename($file);
280
+
281
+        [$values, $extensionValues] = $this->normalizeData($data);
282
+        $values['storage'] = $this->getNumericStorageId();
283
+
284
+        try {
285
+            $builder = $this->connection->getQueryBuilder();
286
+            $builder->insert('filecache');
287
+
288
+            foreach ($values as $column => $value) {
289
+                $builder->setValue($column, $builder->createNamedParameter($value));
290
+            }
291
+
292
+            if ($builder->execute()) {
293
+                $fileId = $builder->getLastInsertId();
294
+
295
+                if (count($extensionValues)) {
296
+                    $query = $this->getQueryBuilder();
297
+                    $query->insert('filecache_extended');
298
+
299
+                    $query->setValue('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
300
+                    foreach ($extensionValues as $column => $value) {
301
+                        $query->setValue($column, $query->createNamedParameter($value));
302
+                    }
303
+                    $query->execute();
304
+                }
305
+
306
+                $this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId));
307
+                return $fileId;
308
+            }
309
+        } catch (UniqueConstraintViolationException $e) {
310
+            // entry exists already
311
+        }
312
+
313
+        // The file was created in the mean time
314
+        if (($id = $this->getId($file)) > -1) {
315
+            $this->update($id, $data);
316
+            return $id;
317
+        } else {
318
+            throw new \RuntimeException('File entry could not be inserted but could also not be selected with getId() in order to perform an update. Please try again.');
319
+        }
320
+    }
321
+
322
+    /**
323
+     * update the metadata of an existing file or folder in the cache
324
+     *
325
+     * @param int $id the fileid of the existing file or folder
326
+     * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged
327
+     */
328
+    public function update($id, array $data) {
329
+
330
+        if (isset($data['path'])) {
331
+            // normalize path
332
+            $data['path'] = $this->normalize($data['path']);
333
+        }
334
+
335
+        if (isset($data['name'])) {
336
+            // normalize path
337
+            $data['name'] = $this->normalize($data['name']);
338
+        }
339
+
340
+        [$values, $extensionValues] = $this->normalizeData($data);
341
+
342
+        if (count($values)) {
343
+            $query = $this->getQueryBuilder();
344
+
345
+            $query->update('filecache')
346
+                ->whereFileId($id)
347
+                ->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
348
+                    return $query->expr()->orX(
349
+                        $query->expr()->neq($key, $query->createNamedParameter($value)),
350
+                        $query->expr()->isNull($key)
351
+                    );
352
+                }, array_keys($values), array_values($values))));
353
+
354
+            foreach ($values as $key => $value) {
355
+                $query->set($key, $query->createNamedParameter($value));
356
+            }
357
+
358
+            $query->execute();
359
+        }
360
+
361
+        if (count($extensionValues)) {
362
+            try {
363
+                $query = $this->getQueryBuilder();
364
+                $query->insert('filecache_extended');
365
+
366
+                $query->setValue('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT));
367
+                foreach ($extensionValues as $column => $value) {
368
+                    $query->setValue($column, $query->createNamedParameter($value));
369
+                }
370
+
371
+                $query->execute();
372
+            } catch (UniqueConstraintViolationException $e) {
373
+                $query = $this->getQueryBuilder();
374
+                $query->update('filecache_extended')
375
+                    ->whereFileId($id)
376
+                    ->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
377
+                        return $query->expr()->orX(
378
+                            $query->expr()->neq($key, $query->createNamedParameter($value)),
379
+                            $query->expr()->isNull($key)
380
+                        );
381
+                    }, array_keys($extensionValues), array_values($extensionValues))));
382
+
383
+                foreach ($extensionValues as $key => $value) {
384
+                    $query->set($key, $query->createNamedParameter($value));
385
+                }
386
+
387
+                $query->execute();
388
+            }
389
+        }
390
+
391
+        $path = $this->getPathById($id);
392
+        // path can still be null if the file doesn't exist
393
+        if ($path !== null) {
394
+            $this->eventDispatcher->dispatch(CacheUpdateEvent::class, new CacheUpdateEvent($this->storage, $path, $id));
395
+        }
396
+    }
397
+
398
+    /**
399
+     * extract query parts and params array from data array
400
+     *
401
+     * @param array $data
402
+     * @return array
403
+     */
404
+    protected function normalizeData(array $data): array {
405
+        $fields = [
406
+            'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
407
+            'etag', 'permissions', 'checksum', 'storage'];
408
+        $extensionFields = ['metadata_etag', 'creation_time', 'upload_time'];
409
+
410
+        $doNotCopyStorageMTime = false;
411
+        if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
412
+            // this horrific magic tells it to not copy storage_mtime to mtime
413
+            unset($data['mtime']);
414
+            $doNotCopyStorageMTime = true;
415
+        }
416
+
417
+        $params = [];
418
+        $extensionParams = [];
419
+        foreach ($data as $name => $value) {
420
+            if (array_search($name, $fields) !== false) {
421
+                if ($name === 'path') {
422
+                    $params['path_hash'] = md5($value);
423
+                } else if ($name === 'mimetype') {
424
+                    $params['mimepart'] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
425
+                    $value = $this->mimetypeLoader->getId($value);
426
+                } else if ($name === 'storage_mtime') {
427
+                    if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
428
+                        $params['mtime'] = $value;
429
+                    }
430
+                } else if ($name === 'encrypted') {
431
+                    if (isset($data['encryptedVersion'])) {
432
+                        $value = $data['encryptedVersion'];
433
+                    } else {
434
+                        // Boolean to integer conversion
435
+                        $value = $value ? 1 : 0;
436
+                    }
437
+                }
438
+                $params[$name] = $value;
439
+            }
440
+            if (array_search($name, $extensionFields) !== false) {
441
+                $extensionParams[$name] = $value;
442
+            }
443
+        }
444
+        return [$params, array_filter($extensionParams)];
445
+    }
446
+
447
+    /**
448
+     * get the file id for a file
449
+     *
450
+     * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file
451
+     *
452
+     * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
453
+     *
454
+     * @param string $file
455
+     * @return int
456
+     */
457
+    public function getId($file) {
458
+        // normalize file
459
+        $file = $this->normalize($file);
460
+
461
+        $query = $this->getQueryBuilder();
462
+        $query->select('fileid')
463
+            ->from('filecache')
464
+            ->whereStorageId()
465
+            ->wherePath($file);
466
+
467
+        $id = $query->execute()->fetchColumn();
468
+        return $id === false ? -1 : (int)$id;
469
+    }
470
+
471
+    /**
472
+     * get the id of the parent folder of a file
473
+     *
474
+     * @param string $file
475
+     * @return int
476
+     */
477
+    public function getParentId($file) {
478
+        if ($file === '') {
479
+            return -1;
480
+        } else {
481
+            $parent = $this->getParentPath($file);
482
+            return (int)$this->getId($parent);
483
+        }
484
+    }
485
+
486
+    private function getParentPath($path) {
487
+        $parent = dirname($path);
488
+        if ($parent === '.') {
489
+            $parent = '';
490
+        }
491
+        return $parent;
492
+    }
493
+
494
+    /**
495
+     * check if a file is available in the cache
496
+     *
497
+     * @param string $file
498
+     * @return bool
499
+     */
500
+    public function inCache($file) {
501
+        return $this->getId($file) != -1;
502
+    }
503
+
504
+    /**
505
+     * remove a file or folder from the cache
506
+     *
507
+     * when removing a folder from the cache all files and folders inside the folder will be removed as well
508
+     *
509
+     * @param string $file
510
+     */
511
+    public function remove($file) {
512
+        $entry = $this->get($file);
513
+
514
+        if ($entry) {
515
+            $query = $this->getQueryBuilder();
516
+            $query->delete('filecache')
517
+                ->whereFileId($entry->getId());
518
+            $query->execute();
519
+
520
+            $query = $this->getQueryBuilder();
521
+            $query->delete('filecache_extended')
522
+                ->whereFileId($entry->getId());
523
+            $query->execute();
524
+
525
+            if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
526
+                $this->removeChildren($entry);
527
+            }
528
+        }
529
+    }
530
+
531
+    /**
532
+     * Get all sub folders of a folder
533
+     *
534
+     * @param ICacheEntry $entry the cache entry of the folder to get the subfolders for
535
+     * @return ICacheEntry[] the cache entries for the subfolders
536
+     */
537
+    private function getSubFolders(ICacheEntry $entry) {
538
+        $children = $this->getFolderContentsById($entry->getId());
539
+        return array_filter($children, function ($child) {
540
+            return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
541
+        });
542
+    }
543
+
544
+    /**
545
+     * Recursively remove all children of a folder
546
+     *
547
+     * @param ICacheEntry $entry the cache entry of the folder to remove the children of
548
+     * @throws \OC\DatabaseException
549
+     */
550
+    private function removeChildren(ICacheEntry $entry) {
551
+        $children = $this->getFolderContentsById($entry->getId());
552
+        $childIds = array_map(function(ICacheEntry $cacheEntry) {
553
+            return $cacheEntry->getId();
554
+        }, $children);
555
+        $childFolders = array_filter($children, function ($child) {
556
+            return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
557
+        });
558
+        foreach ($childFolders as $folder) {
559
+            $this->removeChildren($folder);
560
+        }
561
+
562
+        $query = $this->getQueryBuilder();
563
+        $query->delete('filecache')
564
+            ->whereParent($entry->getId());
565
+        $query->execute();
566
+
567
+        $query = $this->getQueryBuilder();
568
+        $query->delete('filecache_extended')
569
+            ->where($query->expr()->in('fileid', $query->createNamedParameter($childIds, IQueryBuilder::PARAM_INT_ARRAY)));
570
+        $query->execute();
571
+    }
572
+
573
+    /**
574
+     * Move a file or folder in the cache
575
+     *
576
+     * @param string $source
577
+     * @param string $target
578
+     */
579
+    public function move($source, $target) {
580
+        $this->moveFromCache($this, $source, $target);
581
+    }
582
+
583
+    /**
584
+     * Get the storage id and path needed for a move
585
+     *
586
+     * @param string $path
587
+     * @return array [$storageId, $internalPath]
588
+     */
589
+    protected function getMoveInfo($path) {
590
+        return [$this->getNumericStorageId(), $path];
591
+    }
592
+
593
+    /**
594
+     * Move a file or folder in the cache
595
+     *
596
+     * @param \OCP\Files\Cache\ICache $sourceCache
597
+     * @param string $sourcePath
598
+     * @param string $targetPath
599
+     * @throws \OC\DatabaseException
600
+     * @throws \Exception if the given storages have an invalid id
601
+     * @suppress SqlInjectionChecker
602
+     */
603
+    public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
604
+        if ($sourceCache instanceof Cache) {
605
+            // normalize source and target
606
+            $sourcePath = $this->normalize($sourcePath);
607
+            $targetPath = $this->normalize($targetPath);
608
+
609
+            $sourceData = $sourceCache->get($sourcePath);
610
+            $sourceId = $sourceData['fileid'];
611
+            $newParentId = $this->getParentId($targetPath);
612
+
613
+            list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
614
+            list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
615
+
616
+            if (is_null($sourceStorageId) || $sourceStorageId === false) {
617
+                throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
618
+            }
619
+            if (is_null($targetStorageId) || $targetStorageId === false) {
620
+                throw new \Exception('Invalid target storage id: ' . $targetStorageId);
621
+            }
622
+
623
+            $this->connection->beginTransaction();
624
+            if ($sourceData['mimetype'] === 'httpd/unix-directory') {
625
+                //update all child entries
626
+                $sourceLength = mb_strlen($sourcePath);
627
+                $query = $this->connection->getQueryBuilder();
628
+
629
+                $fun = $query->func();
630
+                $newPathFunction = $fun->concat(
631
+                    $query->createNamedParameter($targetPath),
632
+                    $fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
633
+                );
634
+                $query->update('filecache')
635
+                    ->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
636
+                    ->set('path_hash', $fun->md5($newPathFunction))
637
+                    ->set('path', $newPathFunction)
638
+                    ->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
639
+                    ->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
640
+
641
+                try {
642
+                    $query->execute();
643
+                } catch (\OC\DatabaseException $e) {
644
+                    $this->connection->rollBack();
645
+                    throw $e;
646
+                }
647
+            }
648
+
649
+            $query = $this->getQueryBuilder();
650
+            $query->update('filecache')
651
+                ->set('storage', $query->createNamedParameter($targetStorageId))
652
+                ->set('path', $query->createNamedParameter($targetPath))
653
+                ->set('path_hash', $query->createNamedParameter(md5($targetPath)))
654
+                ->set('name', $query->createNamedParameter(basename($targetPath)))
655
+                ->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT))
656
+                ->whereFileId($sourceId);
657
+            $query->execute();
658
+
659
+            $this->connection->commit();
660
+        } else {
661
+            $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
662
+        }
663
+    }
664
+
665
+    /**
666
+     * remove all entries for files that are stored on the storage from the cache
667
+     */
668
+    public function clear() {
669
+        $query = $this->getQueryBuilder();
670
+        $query->delete('filecache')
671
+            ->whereStorageId();
672
+        $query->execute();
673
+
674
+        $query = $this->connection->getQueryBuilder();
675
+        $query->delete('storages')
676
+            ->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
677
+        $query->execute();
678
+    }
679
+
680
+    /**
681
+     * Get the scan status of a file
682
+     *
683
+     * - Cache::NOT_FOUND: File is not in the cache
684
+     * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
685
+     * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
686
+     * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
687
+     *
688
+     * @param string $file
689
+     *
690
+     * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
691
+     */
692
+    public function getStatus($file) {
693
+        // normalize file
694
+        $file = $this->normalize($file);
695
+
696
+        $query = $this->getQueryBuilder();
697
+        $query->select('size')
698
+            ->from('filecache')
699
+            ->whereStorageId()
700
+            ->wherePath($file);
701
+        $size = $query->execute()->fetchColumn();
702
+        if ($size !== false) {
703
+            if ((int)$size === -1) {
704
+                return self::SHALLOW;
705
+            } else {
706
+                return self::COMPLETE;
707
+            }
708
+        } else {
709
+            if (isset($this->partial[$file])) {
710
+                return self::PARTIAL;
711
+            } else {
712
+                return self::NOT_FOUND;
713
+            }
714
+        }
715
+    }
716
+
717
+    /**
718
+     * search for files matching $pattern
719
+     *
720
+     * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
721
+     * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
722
+     */
723
+    public function search($pattern) {
724
+        // normalize pattern
725
+        $pattern = $this->normalize($pattern);
726
+
727
+        if ($pattern === '%%') {
728
+            return [];
729
+        }
730
+
731
+        $query = $this->getQueryBuilder();
732
+        $query->selectFileCache()
733
+            ->whereStorageId()
734
+            ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
735
+
736
+        return array_map(function (array $data) {
737
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
738
+        }, $query->execute()->fetchAll());
739
+    }
740
+
741
+    /**
742
+     * @param Statement $result
743
+     * @return CacheEntry[]
744
+     */
745
+    private function searchResultToCacheEntries(Statement $result) {
746
+        $files = $result->fetchAll();
747
+
748
+        return array_map(function (array $data) {
749
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
750
+        }, $files);
751
+    }
752
+
753
+    /**
754
+     * search for files by mimetype
755
+     *
756
+     * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
757
+     *        where it will search for all mimetypes in the group ('image/*')
758
+     * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
759
+     */
760
+    public function searchByMime($mimetype) {
761
+        $mimeId = $this->mimetypeLoader->getId($mimetype);
762
+
763
+        $query = $this->getQueryBuilder();
764
+        $query->selectFileCache()
765
+            ->whereStorageId();
766
+
767
+        if (strpos($mimetype, '/')) {
768
+            $query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
769
+        } else {
770
+            $query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
771
+        }
772
+
773
+        return array_map(function (array $data) {
774
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
775
+        }, $query->execute()->fetchAll());
776
+    }
777
+
778
+    public function searchQuery(ISearchQuery $searchQuery) {
779
+        $builder = $this->getQueryBuilder();
780
+
781
+        $query = $builder->selectFileCache('file');
782
+
783
+        $query->whereStorageId();
784
+
785
+        if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
786
+            $query
787
+                ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
788
+                ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
789
+                    $builder->expr()->eq('tagmap.type', 'tag.type'),
790
+                    $builder->expr()->eq('tagmap.categoryid', 'tag.id')
791
+                ))
792
+                ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
793
+                ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
794
+        }
795
+
796
+        $searchExpr = $this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
797
+        if ($searchExpr) {
798
+            $query->andWhere($searchExpr);
799
+        }
800
+
801
+        $this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
802
+
803
+        if ($searchQuery->getLimit()) {
804
+            $query->setMaxResults($searchQuery->getLimit());
805
+        }
806
+        if ($searchQuery->getOffset()) {
807
+            $query->setFirstResult($searchQuery->getOffset());
808
+        }
809
+
810
+        $result = $query->execute();
811
+        return $this->searchResultToCacheEntries($result);
812
+    }
813
+
814
+    /**
815
+     * Re-calculate the folder size and the size of all parent folders
816
+     *
817
+     * @param string|boolean $path
818
+     * @param array $data (optional) meta data of the folder
819
+     */
820
+    public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
821
+        $this->calculateFolderSize($path, $data);
822
+        if ($path !== '') {
823
+            $parent = dirname($path);
824
+            if ($parent === '.' or $parent === '/') {
825
+                $parent = '';
826
+            }
827
+            if ($isBackgroundScan) {
828
+                $parentData = $this->get($parent);
829
+                if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) {
830
+                    $this->correctFolderSize($parent, $parentData, $isBackgroundScan);
831
+                }
832
+            } else {
833
+                $this->correctFolderSize($parent);
834
+            }
835
+        }
836
+    }
837
+
838
+    /**
839
+     * get the incomplete count that shares parent $folder
840
+     *
841
+     * @param int $fileId the file id of the folder
842
+     * @return int
843
+     */
844
+    public function getIncompleteChildrenCount($fileId) {
845
+        if ($fileId > -1) {
846
+            $query = $this->getQueryBuilder();
847
+            $query->select($query->func()->count())
848
+                ->from('filecache')
849
+                ->whereParent($fileId)
850
+                ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
851
+
852
+            return (int)$query->execute()->fetchColumn();
853
+        }
854
+        return -1;
855
+    }
856
+
857
+    /**
858
+     * calculate the size of a folder and set it in the cache
859
+     *
860
+     * @param string $path
861
+     * @param array $entry (optional) meta data of the folder
862
+     * @return int
863
+     */
864
+    public function calculateFolderSize($path, $entry = null) {
865
+        $totalSize = 0;
866
+        if (is_null($entry) or !isset($entry['fileid'])) {
867
+            $entry = $this->get($path);
868
+        }
869
+        if (isset($entry['mimetype']) && $entry['mimetype'] === FileInfo::MIMETYPE_FOLDER) {
870
+            $id = $entry['fileid'];
871
+
872
+            $query = $this->getQueryBuilder();
873
+            $query->selectAlias($query->func()->sum('size'), 'f1')
874
+                ->selectAlias($query->func()->min('size'), 'f2')
875
+                ->from('filecache')
876
+                ->whereStorageId()
877
+                ->whereParent($id);
878
+
879
+            if ($row = $query->execute()->fetch()) {
880
+                list($sum, $min) = array_values($row);
881
+                $sum = 0 + $sum;
882
+                $min = 0 + $min;
883
+                if ($min === -1) {
884
+                    $totalSize = $min;
885
+                } else {
886
+                    $totalSize = $sum;
887
+                }
888
+                if ($entry['size'] !== $totalSize) {
889
+                    $this->update($id, ['size' => $totalSize]);
890
+                }
891
+            }
892
+        }
893
+        return $totalSize;
894
+    }
895
+
896
+    /**
897
+     * get all file ids on the files on the storage
898
+     *
899
+     * @return int[]
900
+     */
901
+    public function getAll() {
902
+        $query = $this->getQueryBuilder();
903
+        $query->select('fileid')
904
+            ->from('filecache')
905
+            ->whereStorageId();
906
+
907
+        return array_map(function ($id) {
908
+            return (int)$id;
909
+        }, $query->execute()->fetchAll(\PDO::FETCH_COLUMN));
910
+    }
911
+
912
+    /**
913
+     * find a folder in the cache which has not been fully scanned
914
+     *
915
+     * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
916
+     * use the one with the highest id gives the best result with the background scanner, since that is most
917
+     * likely the folder where we stopped scanning previously
918
+     *
919
+     * @return string|bool the path of the folder or false when no folder matched
920
+     */
921
+    public function getIncomplete() {
922
+        $query = $this->getQueryBuilder();
923
+        $query->select('path')
924
+            ->from('filecache')
925
+            ->whereStorageId()
926
+            ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
927
+            ->orderBy('fileid', 'DESC');
928
+
929
+        return $query->execute()->fetchColumn();
930
+    }
931
+
932
+    /**
933
+     * get the path of a file on this storage by it's file id
934
+     *
935
+     * @param int $id the file id of the file or folder to search
936
+     * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
937
+     */
938
+    public function getPathById($id) {
939
+        $query = $this->getQueryBuilder();
940
+        $query->select('path')
941
+            ->from('filecache')
942
+            ->whereStorageId()
943
+            ->whereFileId($id);
944
+
945
+        $path = $query->execute()->fetchColumn();
946
+        return $path === false ? null : $path;
947
+    }
948
+
949
+    /**
950
+     * get the storage id of the storage for a file and the internal path of the file
951
+     * unlike getPathById this does not limit the search to files on this storage and
952
+     * instead does a global search in the cache table
953
+     *
954
+     * @param int $id
955
+     * @return array first element holding the storage id, second the path
956
+     * @deprecated use getPathById() instead
957
+     */
958
+    static public function getById($id) {
959
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
960
+        $query->select('path', 'storage')
961
+            ->from('filecache')
962
+            ->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
963
+        if ($row = $query->execute()->fetch()) {
964
+            $numericId = $row['storage'];
965
+            $path = $row['path'];
966
+        } else {
967
+            return null;
968
+        }
969
+
970
+        if ($id = Storage::getStorageId($numericId)) {
971
+            return [$id, $path];
972
+        } else {
973
+            return null;
974
+        }
975
+    }
976
+
977
+    /**
978
+     * normalize the given path
979
+     *
980
+     * @param string $path
981
+     * @return string
982
+     */
983
+    public function normalize($path) {
984
+
985
+        return trim(\OC_Util::normalizeUnicode($path), '/');
986
+    }
987 987
 }
Please login to merge, or discard this patch.
lib/private/Files/Node/Folder.php 2 patches
Indentation   +483 added lines, -483 removed lines patch added patch discarded remove patch
@@ -37,487 +37,487 @@
 block discarded – undo
37 37
 use OCP\Files\Search\ISearchQuery;
38 38
 
39 39
 class Folder extends Node implements \OCP\Files\Folder {
40
-	/**
41
-	 * Creates a Folder that represents a non-existing path
42
-	 *
43
-	 * @param string $path path
44
-	 * @return string non-existing node class
45
-	 */
46
-	protected function createNonExistingNode($path) {
47
-		return new NonExistingFolder($this->root, $this->view, $path);
48
-	}
49
-
50
-	/**
51
-	 * @param string $path path relative to the folder
52
-	 * @return string
53
-	 * @throws \OCP\Files\NotPermittedException
54
-	 */
55
-	public function getFullPath($path) {
56
-		if (!$this->isValidPath($path)) {
57
-			throw new NotPermittedException('Invalid path');
58
-		}
59
-		return $this->path . $this->normalizePath($path);
60
-	}
61
-
62
-	/**
63
-	 * @param string $path
64
-	 * @return string
65
-	 */
66
-	public function getRelativePath($path) {
67
-		if ($this->path === '' or $this->path === '/') {
68
-			return $this->normalizePath($path);
69
-		}
70
-		if ($path === $this->path) {
71
-			return '/';
72
-		} else if (strpos($path, $this->path . '/') !== 0) {
73
-			return null;
74
-		} else {
75
-			$path = substr($path, strlen($this->path));
76
-			return $this->normalizePath($path);
77
-		}
78
-	}
79
-
80
-	/**
81
-	 * check if a node is a (grand-)child of the folder
82
-	 *
83
-	 * @param \OC\Files\Node\Node $node
84
-	 * @return bool
85
-	 */
86
-	public function isSubNode($node) {
87
-		return strpos($node->getPath(), $this->path . '/') === 0;
88
-	}
89
-
90
-	/**
91
-	 * get the content of this directory
92
-	 *
93
-	 * @throws \OCP\Files\NotFoundException
94
-	 * @return Node[]
95
-	 */
96
-	public function getDirectoryListing() {
97
-		$folderContent = $this->view->getDirectoryContent($this->path);
98
-
99
-		return array_map(function (FileInfo $info) {
100
-			if ($info->getMimetype() === 'httpd/unix-directory') {
101
-				return new Folder($this->root, $this->view, $info->getPath(), $info);
102
-			} else {
103
-				return new File($this->root, $this->view, $info->getPath(), $info);
104
-			}
105
-		}, $folderContent);
106
-	}
107
-
108
-	/**
109
-	 * @param string $path
110
-	 * @param FileInfo $info
111
-	 * @return File|Folder
112
-	 */
113
-	protected function createNode($path, FileInfo $info = null) {
114
-		if (is_null($info)) {
115
-			$isDir = $this->view->is_dir($path);
116
-		} else {
117
-			$isDir = $info->getType() === FileInfo::TYPE_FOLDER;
118
-		}
119
-		if ($isDir) {
120
-			return new Folder($this->root, $this->view, $path, $info);
121
-		} else {
122
-			return new File($this->root, $this->view, $path, $info);
123
-		}
124
-	}
125
-
126
-	/**
127
-	 * Get the node at $path
128
-	 *
129
-	 * @param string $path
130
-	 * @return \OC\Files\Node\Node
131
-	 * @throws \OCP\Files\NotFoundException
132
-	 */
133
-	public function get($path) {
134
-		return $this->root->get($this->getFullPath($path));
135
-	}
136
-
137
-	/**
138
-	 * @param string $path
139
-	 * @return bool
140
-	 */
141
-	public function nodeExists($path) {
142
-		try {
143
-			$this->get($path);
144
-			return true;
145
-		} catch (NotFoundException $e) {
146
-			return false;
147
-		}
148
-	}
149
-
150
-	/**
151
-	 * @param string $path
152
-	 * @return \OC\Files\Node\Folder
153
-	 * @throws \OCP\Files\NotPermittedException
154
-	 */
155
-	public function newFolder($path) {
156
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
157
-			$fullPath = $this->getFullPath($path);
158
-			$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
159
-			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
160
-			if(!$this->view->mkdir($fullPath)) {
161
-				throw new NotPermittedException('Could not create folder');
162
-			}
163
-			$node = new Folder($this->root, $this->view, $fullPath);
164
-			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
165
-			return $node;
166
-		} else {
167
-			throw new NotPermittedException('No create permission for folder');
168
-		}
169
-	}
170
-
171
-	/**
172
-	 * @param string $path
173
-	 * @return \OC\Files\Node\File
174
-	 * @throws \OCP\Files\NotPermittedException
175
-	 */
176
-	public function newFile($path) {
177
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
178
-			$fullPath = $this->getFullPath($path);
179
-			$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
180
-			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
181
-			if (!$this->view->touch($fullPath)) {
182
-				throw new NotPermittedException('Could not create path');
183
-			}
184
-			$node = new File($this->root, $this->view, $fullPath);
185
-			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
186
-			return $node;
187
-		}
188
-		throw new NotPermittedException('No create permission for path');
189
-	}
190
-
191
-	/**
192
-	 * search for files with the name matching $query
193
-	 *
194
-	 * @param string|ISearchQuery $query
195
-	 * @return \OC\Files\Node\Node[]
196
-	 */
197
-	public function search($query) {
198
-		if (is_string($query)) {
199
-			return $this->searchCommon('search', array('%' . $query . '%'));
200
-		} else {
201
-			return $this->searchCommon('searchQuery', array($query));
202
-		}
203
-	}
204
-
205
-	/**
206
-	 * search for files by mimetype
207
-	 *
208
-	 * @param string $mimetype
209
-	 * @return Node[]
210
-	 */
211
-	public function searchByMime($mimetype) {
212
-		return $this->searchCommon('searchByMime', array($mimetype));
213
-	}
214
-
215
-	/**
216
-	 * search for files by tag
217
-	 *
218
-	 * @param string|int $tag name or tag id
219
-	 * @param string $userId owner of the tags
220
-	 * @return Node[]
221
-	 */
222
-	public function searchByTag($tag, $userId) {
223
-		return $this->searchCommon('searchByTag', array($tag, $userId));
224
-	}
225
-
226
-	/**
227
-	 * @param string $method cache method
228
-	 * @param array $args call args
229
-	 * @return \OC\Files\Node\Node[]
230
-	 */
231
-	private function searchCommon($method, $args) {
232
-		$limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false;
233
-		if ($limitToHome && count(explode('/', $this->path)) !== 3) {
234
-			throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
235
-		}
236
-
237
-		$files = array();
238
-		$rootLength = strlen($this->path);
239
-		$mount = $this->root->getMount($this->path);
240
-		$storage = $mount->getStorage();
241
-		$internalPath = $mount->getInternalPath($this->path);
242
-		$internalPath = rtrim($internalPath, '/');
243
-		if ($internalPath !== '') {
244
-			$internalPath = $internalPath . '/';
245
-		}
246
-		$internalRootLength = strlen($internalPath);
247
-
248
-		$cache = $storage->getCache('');
249
-
250
-		$results = call_user_func_array(array($cache, $method), $args);
251
-		foreach ($results as $result) {
252
-			if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) {
253
-				$result['internalPath'] = $result['path'];
254
-				$result['path'] = substr($result['path'], $internalRootLength);
255
-				$result['storage'] = $storage;
256
-				$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
257
-			}
258
-		}
259
-
260
-		if (!$limitToHome) {
261
-			$mounts = $this->root->getMountsIn($this->path);
262
-			foreach ($mounts as $mount) {
263
-				$storage = $mount->getStorage();
264
-				if ($storage) {
265
-					$cache = $storage->getCache('');
266
-
267
-					$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
268
-					$results = call_user_func_array([$cache, $method], $args);
269
-					foreach ($results as $result) {
270
-						$result['internalPath'] = $result['path'];
271
-						$result['path'] = $relativeMountPoint . $result['path'];
272
-						$result['storage'] = $storage;
273
-						$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage,
274
-							$result['internalPath'], $result, $mount);
275
-					}
276
-				}
277
-			}
278
-		}
279
-
280
-		return array_map(function (FileInfo $file) {
281
-			return $this->createNode($file->getPath(), $file);
282
-		}, $files);
283
-	}
284
-
285
-	/**
286
-	 * @param int $id
287
-	 * @return \OC\Files\Node\Node[]
288
-	 */
289
-	public function getById($id) {
290
-		$mountCache = $this->root->getUserMountCache();
291
-		if (strpos($this->getPath(), '/', 1) > 0) {
292
-			list(, $user) = explode('/', $this->getPath());
293
-		} else {
294
-			$user = null;
295
-		}
296
-		$mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
297
-		$mounts = $this->root->getMountsIn($this->path);
298
-		$mounts[] = $this->root->getMount($this->path);
299
-		/** @var IMountPoint[] $folderMounts */
300
-		$folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
301
-			return $mountPoint->getMountPoint();
302
-		}, $mounts), $mounts);
303
-
304
-		/** @var ICachedMountInfo[] $mountsContainingFile */
305
-		$mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
306
-			return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
307
-		}));
308
-
309
-		if (count($mountsContainingFile) === 0) {
310
-			if ($user === $this->getAppDataDirectoryName()) {
311
-				return $this->getByIdInRootMount((int) $id);
312
-			}
313
-			return [];
314
-		}
315
-
316
-		$nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
317
-			$mount = $folderMounts[$cachedMountInfo->getMountPoint()];
318
-			$cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
319
-			if (!$cacheEntry) {
320
-				return null;
321
-			}
322
-
323
-			// cache jails will hide the "true" internal path
324
-			$internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
325
-			$pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
326
-			$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
327
-			$absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
328
-			return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
329
-				$absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
330
-				\OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
331
-			));
332
-		}, $mountsContainingFile);
333
-
334
-		$nodes = array_filter($nodes);
335
-
336
-		return array_filter($nodes, function (Node $node) {
337
-			return $this->getRelativePath($node->getPath());
338
-		});
339
-	}
340
-
341
-	protected function getAppDataDirectoryName(): string {
342
-		$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
343
-		return 'appdata_' . $instanceId;
344
-	}
345
-
346
-	/**
347
-	 * In case the path we are currently in is inside the appdata_* folder,
348
-	 * the original getById method does not work, because it can only look inside
349
-	 * the user's mount points. But the user has no mount point for the root storage.
350
-	 *
351
-	 * So in that case we directly check the mount of the root if it contains
352
-	 * the id. If it does we check if the path is inside the path we are working
353
-	 * in.
354
-	 *
355
-	 * @param int $id
356
-	 * @return array
357
-	 */
358
-	protected function getByIdInRootMount(int $id): array {
359
-		$mount = $this->root->getMount('');
360
-		$cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
361
-		if (!$cacheEntry) {
362
-			return [];
363
-		}
364
-
365
-		$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
366
-		$currentPath = rtrim($this->path, '/') . '/';
367
-
368
-		if (strpos($absolutePath, $currentPath) !== 0) {
369
-			return [];
370
-		}
371
-
372
-		return [$this->root->createNode(
373
-			$absolutePath, new \OC\Files\FileInfo(
374
-				$absolutePath,
375
-				$mount->getStorage(),
376
-				$cacheEntry->getPath(),
377
-				$cacheEntry,
378
-				$mount
379
-		))];
380
-	}
381
-
382
-	public function getFreeSpace() {
383
-		return $this->view->free_space($this->path);
384
-	}
385
-
386
-	public function delete() {
387
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
388
-			$this->sendHooks(array('preDelete'));
389
-			$fileInfo = $this->getFileInfo();
390
-			$this->view->rmdir($this->path);
391
-			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
392
-			$this->sendHooks(['postDelete'], [$nonExisting]);
393
-			$this->exists = false;
394
-		} else {
395
-			throw new NotPermittedException('No delete permission for path');
396
-		}
397
-	}
398
-
399
-	/**
400
-	 * Add a suffix to the name in case the file exists
401
-	 *
402
-	 * @param string $name
403
-	 * @return string
404
-	 * @throws NotPermittedException
405
-	 */
406
-	public function getNonExistingName($name) {
407
-		$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
408
-		return trim($this->getRelativePath($uniqueName), '/');
409
-	}
410
-
411
-	/**
412
-	 * @param int $limit
413
-	 * @param int $offset
414
-	 * @return \OCP\Files\Node[]
415
-	 */
416
-	public function getRecent($limit, $offset = 0) {
417
-		$mimetypeLoader = \OC::$server->getMimeTypeLoader();
418
-		$mounts = $this->root->getMountsIn($this->path);
419
-		$mounts[] = $this->getMountPoint();
420
-
421
-		$mounts = array_filter($mounts, function (IMountPoint $mount) {
422
-			return $mount->getStorage();
423
-		});
424
-		$storageIds = array_map(function (IMountPoint $mount) {
425
-			return $mount->getStorage()->getCache()->getNumericStorageId();
426
-		}, $mounts);
427
-		/** @var IMountPoint[] $mountMap */
428
-		$mountMap = array_combine($storageIds, $mounts);
429
-		$folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
430
-
431
-		// Search in batches of 500 entries
432
-		$searchLimit = 500;
433
-		$results = [];
434
-		$searchResultCount = 0;
435
-		$count = 0;
436
-		do {
437
-			$searchResult = $this->recentSearch($searchLimit, $offset, $storageIds, $folderMimetype);
438
-
439
-			// Exit condition if there are no more results
440
-			if (count($searchResult) === 0) {
441
-				break;
442
-			}
443
-
444
-			$searchResultCount += count($searchResult);
445
-
446
-			$parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
447
-
448
-			foreach ($parseResult as $result) {
449
-				$results[] = $result;
450
-			}
451
-
452
-			$offset += $searchLimit;
453
-			$count++;
454
-		} while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5));
455
-
456
-		return array_slice($results, 0, $limit);
457
-	}
458
-
459
-	private function recentSearch($limit, $offset, $storageIds, $folderMimetype) {
460
-		$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
461
-		$query = $builder
462
-			->select('f.*')
463
-			->from('filecache', 'f')
464
-			->andWhere($builder->expr()->in('f.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
465
-			->andWhere($builder->expr()->orX(
466
-			// handle non empty folders separate
467
-				$builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
468
-				$builder->expr()->eq('f.size', new Literal(0))
469
-			))
470
-			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
471
-			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
472
-			->orderBy('f.mtime', 'DESC')
473
-			->setMaxResults($limit)
474
-			->setFirstResult($offset);
475
-		return $query->execute()->fetchAll();
476
-	}
477
-
478
-	private function recentParse($result, $mountMap, $mimetypeLoader) {
479
-		$files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
480
-			$mount = $mountMap[$entry['storage']];
481
-			$entry['internalPath'] = $entry['path'];
482
-			$entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
483
-			$entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
484
-			$path = $this->getAbsolutePath($mount, $entry['path']);
485
-			if (is_null($path)) {
486
-				return null;
487
-			}
488
-			$fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
489
-			return $this->root->createNode($fileInfo->getPath(), $fileInfo);
490
-		}, $result));
491
-
492
-		return array_values(array_filter($files, function (Node $node) {
493
-			$cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId());
494
-			if (!$cacheEntry) {
495
-				return false;
496
-			}
497
-			$relative = $this->getRelativePath($node->getPath());
498
-			return $relative !== null && $relative !== '/'
499
-				&& ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ;
500
-		}));
501
-	}
502
-
503
-	private function getAbsolutePath(IMountPoint $mount, $path) {
504
-		$storage = $mount->getStorage();
505
-		if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
506
-			if ($storage->instanceOfStorage(SharedStorage::class)) {
507
-				$storage->getSourceStorage();
508
-			}
509
-			/** @var \OC\Files\Storage\Wrapper\Jail $storage */
510
-			$jailRoot = $storage->getUnjailedPath('');
511
-			$rootLength = strlen($jailRoot) + 1;
512
-			if ($path === $jailRoot) {
513
-				return $mount->getMountPoint();
514
-			} else if (substr($path, 0, $rootLength) === $jailRoot . '/') {
515
-				return $mount->getMountPoint() . substr($path, $rootLength);
516
-			} else {
517
-				return null;
518
-			}
519
-		} else {
520
-			return $mount->getMountPoint() . $path;
521
-		}
522
-	}
40
+    /**
41
+     * Creates a Folder that represents a non-existing path
42
+     *
43
+     * @param string $path path
44
+     * @return string non-existing node class
45
+     */
46
+    protected function createNonExistingNode($path) {
47
+        return new NonExistingFolder($this->root, $this->view, $path);
48
+    }
49
+
50
+    /**
51
+     * @param string $path path relative to the folder
52
+     * @return string
53
+     * @throws \OCP\Files\NotPermittedException
54
+     */
55
+    public function getFullPath($path) {
56
+        if (!$this->isValidPath($path)) {
57
+            throw new NotPermittedException('Invalid path');
58
+        }
59
+        return $this->path . $this->normalizePath($path);
60
+    }
61
+
62
+    /**
63
+     * @param string $path
64
+     * @return string
65
+     */
66
+    public function getRelativePath($path) {
67
+        if ($this->path === '' or $this->path === '/') {
68
+            return $this->normalizePath($path);
69
+        }
70
+        if ($path === $this->path) {
71
+            return '/';
72
+        } else if (strpos($path, $this->path . '/') !== 0) {
73
+            return null;
74
+        } else {
75
+            $path = substr($path, strlen($this->path));
76
+            return $this->normalizePath($path);
77
+        }
78
+    }
79
+
80
+    /**
81
+     * check if a node is a (grand-)child of the folder
82
+     *
83
+     * @param \OC\Files\Node\Node $node
84
+     * @return bool
85
+     */
86
+    public function isSubNode($node) {
87
+        return strpos($node->getPath(), $this->path . '/') === 0;
88
+    }
89
+
90
+    /**
91
+     * get the content of this directory
92
+     *
93
+     * @throws \OCP\Files\NotFoundException
94
+     * @return Node[]
95
+     */
96
+    public function getDirectoryListing() {
97
+        $folderContent = $this->view->getDirectoryContent($this->path);
98
+
99
+        return array_map(function (FileInfo $info) {
100
+            if ($info->getMimetype() === 'httpd/unix-directory') {
101
+                return new Folder($this->root, $this->view, $info->getPath(), $info);
102
+            } else {
103
+                return new File($this->root, $this->view, $info->getPath(), $info);
104
+            }
105
+        }, $folderContent);
106
+    }
107
+
108
+    /**
109
+     * @param string $path
110
+     * @param FileInfo $info
111
+     * @return File|Folder
112
+     */
113
+    protected function createNode($path, FileInfo $info = null) {
114
+        if (is_null($info)) {
115
+            $isDir = $this->view->is_dir($path);
116
+        } else {
117
+            $isDir = $info->getType() === FileInfo::TYPE_FOLDER;
118
+        }
119
+        if ($isDir) {
120
+            return new Folder($this->root, $this->view, $path, $info);
121
+        } else {
122
+            return new File($this->root, $this->view, $path, $info);
123
+        }
124
+    }
125
+
126
+    /**
127
+     * Get the node at $path
128
+     *
129
+     * @param string $path
130
+     * @return \OC\Files\Node\Node
131
+     * @throws \OCP\Files\NotFoundException
132
+     */
133
+    public function get($path) {
134
+        return $this->root->get($this->getFullPath($path));
135
+    }
136
+
137
+    /**
138
+     * @param string $path
139
+     * @return bool
140
+     */
141
+    public function nodeExists($path) {
142
+        try {
143
+            $this->get($path);
144
+            return true;
145
+        } catch (NotFoundException $e) {
146
+            return false;
147
+        }
148
+    }
149
+
150
+    /**
151
+     * @param string $path
152
+     * @return \OC\Files\Node\Folder
153
+     * @throws \OCP\Files\NotPermittedException
154
+     */
155
+    public function newFolder($path) {
156
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
157
+            $fullPath = $this->getFullPath($path);
158
+            $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
159
+            $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
160
+            if(!$this->view->mkdir($fullPath)) {
161
+                throw new NotPermittedException('Could not create folder');
162
+            }
163
+            $node = new Folder($this->root, $this->view, $fullPath);
164
+            $this->sendHooks(['postWrite', 'postCreate'], [$node]);
165
+            return $node;
166
+        } else {
167
+            throw new NotPermittedException('No create permission for folder');
168
+        }
169
+    }
170
+
171
+    /**
172
+     * @param string $path
173
+     * @return \OC\Files\Node\File
174
+     * @throws \OCP\Files\NotPermittedException
175
+     */
176
+    public function newFile($path) {
177
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
178
+            $fullPath = $this->getFullPath($path);
179
+            $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
180
+            $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
181
+            if (!$this->view->touch($fullPath)) {
182
+                throw new NotPermittedException('Could not create path');
183
+            }
184
+            $node = new File($this->root, $this->view, $fullPath);
185
+            $this->sendHooks(['postWrite', 'postCreate'], [$node]);
186
+            return $node;
187
+        }
188
+        throw new NotPermittedException('No create permission for path');
189
+    }
190
+
191
+    /**
192
+     * search for files with the name matching $query
193
+     *
194
+     * @param string|ISearchQuery $query
195
+     * @return \OC\Files\Node\Node[]
196
+     */
197
+    public function search($query) {
198
+        if (is_string($query)) {
199
+            return $this->searchCommon('search', array('%' . $query . '%'));
200
+        } else {
201
+            return $this->searchCommon('searchQuery', array($query));
202
+        }
203
+    }
204
+
205
+    /**
206
+     * search for files by mimetype
207
+     *
208
+     * @param string $mimetype
209
+     * @return Node[]
210
+     */
211
+    public function searchByMime($mimetype) {
212
+        return $this->searchCommon('searchByMime', array($mimetype));
213
+    }
214
+
215
+    /**
216
+     * search for files by tag
217
+     *
218
+     * @param string|int $tag name or tag id
219
+     * @param string $userId owner of the tags
220
+     * @return Node[]
221
+     */
222
+    public function searchByTag($tag, $userId) {
223
+        return $this->searchCommon('searchByTag', array($tag, $userId));
224
+    }
225
+
226
+    /**
227
+     * @param string $method cache method
228
+     * @param array $args call args
229
+     * @return \OC\Files\Node\Node[]
230
+     */
231
+    private function searchCommon($method, $args) {
232
+        $limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false;
233
+        if ($limitToHome && count(explode('/', $this->path)) !== 3) {
234
+            throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
235
+        }
236
+
237
+        $files = array();
238
+        $rootLength = strlen($this->path);
239
+        $mount = $this->root->getMount($this->path);
240
+        $storage = $mount->getStorage();
241
+        $internalPath = $mount->getInternalPath($this->path);
242
+        $internalPath = rtrim($internalPath, '/');
243
+        if ($internalPath !== '') {
244
+            $internalPath = $internalPath . '/';
245
+        }
246
+        $internalRootLength = strlen($internalPath);
247
+
248
+        $cache = $storage->getCache('');
249
+
250
+        $results = call_user_func_array(array($cache, $method), $args);
251
+        foreach ($results as $result) {
252
+            if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) {
253
+                $result['internalPath'] = $result['path'];
254
+                $result['path'] = substr($result['path'], $internalRootLength);
255
+                $result['storage'] = $storage;
256
+                $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
257
+            }
258
+        }
259
+
260
+        if (!$limitToHome) {
261
+            $mounts = $this->root->getMountsIn($this->path);
262
+            foreach ($mounts as $mount) {
263
+                $storage = $mount->getStorage();
264
+                if ($storage) {
265
+                    $cache = $storage->getCache('');
266
+
267
+                    $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
268
+                    $results = call_user_func_array([$cache, $method], $args);
269
+                    foreach ($results as $result) {
270
+                        $result['internalPath'] = $result['path'];
271
+                        $result['path'] = $relativeMountPoint . $result['path'];
272
+                        $result['storage'] = $storage;
273
+                        $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage,
274
+                            $result['internalPath'], $result, $mount);
275
+                    }
276
+                }
277
+            }
278
+        }
279
+
280
+        return array_map(function (FileInfo $file) {
281
+            return $this->createNode($file->getPath(), $file);
282
+        }, $files);
283
+    }
284
+
285
+    /**
286
+     * @param int $id
287
+     * @return \OC\Files\Node\Node[]
288
+     */
289
+    public function getById($id) {
290
+        $mountCache = $this->root->getUserMountCache();
291
+        if (strpos($this->getPath(), '/', 1) > 0) {
292
+            list(, $user) = explode('/', $this->getPath());
293
+        } else {
294
+            $user = null;
295
+        }
296
+        $mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
297
+        $mounts = $this->root->getMountsIn($this->path);
298
+        $mounts[] = $this->root->getMount($this->path);
299
+        /** @var IMountPoint[] $folderMounts */
300
+        $folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
301
+            return $mountPoint->getMountPoint();
302
+        }, $mounts), $mounts);
303
+
304
+        /** @var ICachedMountInfo[] $mountsContainingFile */
305
+        $mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
306
+            return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
307
+        }));
308
+
309
+        if (count($mountsContainingFile) === 0) {
310
+            if ($user === $this->getAppDataDirectoryName()) {
311
+                return $this->getByIdInRootMount((int) $id);
312
+            }
313
+            return [];
314
+        }
315
+
316
+        $nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
317
+            $mount = $folderMounts[$cachedMountInfo->getMountPoint()];
318
+            $cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
319
+            if (!$cacheEntry) {
320
+                return null;
321
+            }
322
+
323
+            // cache jails will hide the "true" internal path
324
+            $internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
325
+            $pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
326
+            $pathRelativeToMount = ltrim($pathRelativeToMount, '/');
327
+            $absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
328
+            return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
329
+                $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
330
+                \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
331
+            ));
332
+        }, $mountsContainingFile);
333
+
334
+        $nodes = array_filter($nodes);
335
+
336
+        return array_filter($nodes, function (Node $node) {
337
+            return $this->getRelativePath($node->getPath());
338
+        });
339
+    }
340
+
341
+    protected function getAppDataDirectoryName(): string {
342
+        $instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
343
+        return 'appdata_' . $instanceId;
344
+    }
345
+
346
+    /**
347
+     * In case the path we are currently in is inside the appdata_* folder,
348
+     * the original getById method does not work, because it can only look inside
349
+     * the user's mount points. But the user has no mount point for the root storage.
350
+     *
351
+     * So in that case we directly check the mount of the root if it contains
352
+     * the id. If it does we check if the path is inside the path we are working
353
+     * in.
354
+     *
355
+     * @param int $id
356
+     * @return array
357
+     */
358
+    protected function getByIdInRootMount(int $id): array {
359
+        $mount = $this->root->getMount('');
360
+        $cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
361
+        if (!$cacheEntry) {
362
+            return [];
363
+        }
364
+
365
+        $absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
366
+        $currentPath = rtrim($this->path, '/') . '/';
367
+
368
+        if (strpos($absolutePath, $currentPath) !== 0) {
369
+            return [];
370
+        }
371
+
372
+        return [$this->root->createNode(
373
+            $absolutePath, new \OC\Files\FileInfo(
374
+                $absolutePath,
375
+                $mount->getStorage(),
376
+                $cacheEntry->getPath(),
377
+                $cacheEntry,
378
+                $mount
379
+        ))];
380
+    }
381
+
382
+    public function getFreeSpace() {
383
+        return $this->view->free_space($this->path);
384
+    }
385
+
386
+    public function delete() {
387
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
388
+            $this->sendHooks(array('preDelete'));
389
+            $fileInfo = $this->getFileInfo();
390
+            $this->view->rmdir($this->path);
391
+            $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
392
+            $this->sendHooks(['postDelete'], [$nonExisting]);
393
+            $this->exists = false;
394
+        } else {
395
+            throw new NotPermittedException('No delete permission for path');
396
+        }
397
+    }
398
+
399
+    /**
400
+     * Add a suffix to the name in case the file exists
401
+     *
402
+     * @param string $name
403
+     * @return string
404
+     * @throws NotPermittedException
405
+     */
406
+    public function getNonExistingName($name) {
407
+        $uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
408
+        return trim($this->getRelativePath($uniqueName), '/');
409
+    }
410
+
411
+    /**
412
+     * @param int $limit
413
+     * @param int $offset
414
+     * @return \OCP\Files\Node[]
415
+     */
416
+    public function getRecent($limit, $offset = 0) {
417
+        $mimetypeLoader = \OC::$server->getMimeTypeLoader();
418
+        $mounts = $this->root->getMountsIn($this->path);
419
+        $mounts[] = $this->getMountPoint();
420
+
421
+        $mounts = array_filter($mounts, function (IMountPoint $mount) {
422
+            return $mount->getStorage();
423
+        });
424
+        $storageIds = array_map(function (IMountPoint $mount) {
425
+            return $mount->getStorage()->getCache()->getNumericStorageId();
426
+        }, $mounts);
427
+        /** @var IMountPoint[] $mountMap */
428
+        $mountMap = array_combine($storageIds, $mounts);
429
+        $folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
430
+
431
+        // Search in batches of 500 entries
432
+        $searchLimit = 500;
433
+        $results = [];
434
+        $searchResultCount = 0;
435
+        $count = 0;
436
+        do {
437
+            $searchResult = $this->recentSearch($searchLimit, $offset, $storageIds, $folderMimetype);
438
+
439
+            // Exit condition if there are no more results
440
+            if (count($searchResult) === 0) {
441
+                break;
442
+            }
443
+
444
+            $searchResultCount += count($searchResult);
445
+
446
+            $parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
447
+
448
+            foreach ($parseResult as $result) {
449
+                $results[] = $result;
450
+            }
451
+
452
+            $offset += $searchLimit;
453
+            $count++;
454
+        } while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5));
455
+
456
+        return array_slice($results, 0, $limit);
457
+    }
458
+
459
+    private function recentSearch($limit, $offset, $storageIds, $folderMimetype) {
460
+        $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
461
+        $query = $builder
462
+            ->select('f.*')
463
+            ->from('filecache', 'f')
464
+            ->andWhere($builder->expr()->in('f.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
465
+            ->andWhere($builder->expr()->orX(
466
+            // handle non empty folders separate
467
+                $builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
468
+                $builder->expr()->eq('f.size', new Literal(0))
469
+            ))
470
+            ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
471
+            ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
472
+            ->orderBy('f.mtime', 'DESC')
473
+            ->setMaxResults($limit)
474
+            ->setFirstResult($offset);
475
+        return $query->execute()->fetchAll();
476
+    }
477
+
478
+    private function recentParse($result, $mountMap, $mimetypeLoader) {
479
+        $files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
480
+            $mount = $mountMap[$entry['storage']];
481
+            $entry['internalPath'] = $entry['path'];
482
+            $entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
483
+            $entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
484
+            $path = $this->getAbsolutePath($mount, $entry['path']);
485
+            if (is_null($path)) {
486
+                return null;
487
+            }
488
+            $fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
489
+            return $this->root->createNode($fileInfo->getPath(), $fileInfo);
490
+        }, $result));
491
+
492
+        return array_values(array_filter($files, function (Node $node) {
493
+            $cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId());
494
+            if (!$cacheEntry) {
495
+                return false;
496
+            }
497
+            $relative = $this->getRelativePath($node->getPath());
498
+            return $relative !== null && $relative !== '/'
499
+                && ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ;
500
+        }));
501
+    }
502
+
503
+    private function getAbsolutePath(IMountPoint $mount, $path) {
504
+        $storage = $mount->getStorage();
505
+        if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
506
+            if ($storage->instanceOfStorage(SharedStorage::class)) {
507
+                $storage->getSourceStorage();
508
+            }
509
+            /** @var \OC\Files\Storage\Wrapper\Jail $storage */
510
+            $jailRoot = $storage->getUnjailedPath('');
511
+            $rootLength = strlen($jailRoot) + 1;
512
+            if ($path === $jailRoot) {
513
+                return $mount->getMountPoint();
514
+            } else if (substr($path, 0, $rootLength) === $jailRoot . '/') {
515
+                return $mount->getMountPoint() . substr($path, $rootLength);
516
+            } else {
517
+                return null;
518
+            }
519
+        } else {
520
+            return $mount->getMountPoint() . $path;
521
+        }
522
+    }
523 523
 }
Please login to merge, or discard this patch.
Spacing   +30 added lines, -30 removed lines patch added patch discarded remove patch
@@ -56,7 +56,7 @@  discard block
 block discarded – undo
56 56
 		if (!$this->isValidPath($path)) {
57 57
 			throw new NotPermittedException('Invalid path');
58 58
 		}
59
-		return $this->path . $this->normalizePath($path);
59
+		return $this->path.$this->normalizePath($path);
60 60
 	}
61 61
 
62 62
 	/**
@@ -69,7 +69,7 @@  discard block
 block discarded – undo
69 69
 		}
70 70
 		if ($path === $this->path) {
71 71
 			return '/';
72
-		} else if (strpos($path, $this->path . '/') !== 0) {
72
+		} else if (strpos($path, $this->path.'/') !== 0) {
73 73
 			return null;
74 74
 		} else {
75 75
 			$path = substr($path, strlen($this->path));
@@ -84,7 +84,7 @@  discard block
 block discarded – undo
84 84
 	 * @return bool
85 85
 	 */
86 86
 	public function isSubNode($node) {
87
-		return strpos($node->getPath(), $this->path . '/') === 0;
87
+		return strpos($node->getPath(), $this->path.'/') === 0;
88 88
 	}
89 89
 
90 90
 	/**
@@ -96,7 +96,7 @@  discard block
 block discarded – undo
96 96
 	public function getDirectoryListing() {
97 97
 		$folderContent = $this->view->getDirectoryContent($this->path);
98 98
 
99
-		return array_map(function (FileInfo $info) {
99
+		return array_map(function(FileInfo $info) {
100 100
 			if ($info->getMimetype() === 'httpd/unix-directory') {
101 101
 				return new Folder($this->root, $this->view, $info->getPath(), $info);
102 102
 			} else {
@@ -157,7 +157,7 @@  discard block
 block discarded – undo
157 157
 			$fullPath = $this->getFullPath($path);
158 158
 			$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
159 159
 			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
160
-			if(!$this->view->mkdir($fullPath)) {
160
+			if (!$this->view->mkdir($fullPath)) {
161 161
 				throw new NotPermittedException('Could not create folder');
162 162
 			}
163 163
 			$node = new Folder($this->root, $this->view, $fullPath);
@@ -196,7 +196,7 @@  discard block
 block discarded – undo
196 196
 	 */
197 197
 	public function search($query) {
198 198
 		if (is_string($query)) {
199
-			return $this->searchCommon('search', array('%' . $query . '%'));
199
+			return $this->searchCommon('search', array('%'.$query.'%'));
200 200
 		} else {
201 201
 			return $this->searchCommon('searchQuery', array($query));
202 202
 		}
@@ -229,7 +229,7 @@  discard block
 block discarded – undo
229 229
 	 * @return \OC\Files\Node\Node[]
230 230
 	 */
231 231
 	private function searchCommon($method, $args) {
232
-		$limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false;
232
+		$limitToHome = ($method === 'searchQuery') ? $args[0]->limitToHome() : false;
233 233
 		if ($limitToHome && count(explode('/', $this->path)) !== 3) {
234 234
 			throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
235 235
 		}
@@ -241,7 +241,7 @@  discard block
 block discarded – undo
241 241
 		$internalPath = $mount->getInternalPath($this->path);
242 242
 		$internalPath = rtrim($internalPath, '/');
243 243
 		if ($internalPath !== '') {
244
-			$internalPath = $internalPath . '/';
244
+			$internalPath = $internalPath.'/';
245 245
 		}
246 246
 		$internalRootLength = strlen($internalPath);
247 247
 
@@ -253,7 +253,7 @@  discard block
 block discarded – undo
253 253
 				$result['internalPath'] = $result['path'];
254 254
 				$result['path'] = substr($result['path'], $internalRootLength);
255 255
 				$result['storage'] = $storage;
256
-				$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
256
+				$files[] = new \OC\Files\FileInfo($this->path.'/'.$result['path'], $storage, $result['internalPath'], $result, $mount);
257 257
 			}
258 258
 		}
259 259
 
@@ -268,16 +268,16 @@  discard block
 block discarded – undo
268 268
 					$results = call_user_func_array([$cache, $method], $args);
269 269
 					foreach ($results as $result) {
270 270
 						$result['internalPath'] = $result['path'];
271
-						$result['path'] = $relativeMountPoint . $result['path'];
271
+						$result['path'] = $relativeMountPoint.$result['path'];
272 272
 						$result['storage'] = $storage;
273
-						$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage,
273
+						$files[] = new \OC\Files\FileInfo($this->path.'/'.$result['path'], $storage,
274 274
 							$result['internalPath'], $result, $mount);
275 275
 					}
276 276
 				}
277 277
 			}
278 278
 		}
279 279
 
280
-		return array_map(function (FileInfo $file) {
280
+		return array_map(function(FileInfo $file) {
281 281
 			return $this->createNode($file->getPath(), $file);
282 282
 		}, $files);
283 283
 	}
@@ -293,16 +293,16 @@  discard block
 block discarded – undo
293 293
 		} else {
294 294
 			$user = null;
295 295
 		}
296
-		$mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
296
+		$mountsContainingFile = $mountCache->getMountsForFileId((int) $id, $user);
297 297
 		$mounts = $this->root->getMountsIn($this->path);
298 298
 		$mounts[] = $this->root->getMount($this->path);
299 299
 		/** @var IMountPoint[] $folderMounts */
300
-		$folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
300
+		$folderMounts = array_combine(array_map(function(IMountPoint $mountPoint) {
301 301
 			return $mountPoint->getMountPoint();
302 302
 		}, $mounts), $mounts);
303 303
 
304 304
 		/** @var ICachedMountInfo[] $mountsContainingFile */
305
-		$mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
305
+		$mountsContainingFile = array_values(array_filter($mountsContainingFile, function(ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
306 306
 			return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
307 307
 		}));
308 308
 
@@ -313,18 +313,18 @@  discard block
 block discarded – undo
313 313
 			return [];
314 314
 		}
315 315
 
316
-		$nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
316
+		$nodes = array_map(function(ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
317 317
 			$mount = $folderMounts[$cachedMountInfo->getMountPoint()];
318
-			$cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
318
+			$cacheEntry = $mount->getStorage()->getCache()->get((int) $id);
319 319
 			if (!$cacheEntry) {
320 320
 				return null;
321 321
 			}
322 322
 
323 323
 			// cache jails will hide the "true" internal path
324
-			$internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
324
+			$internalPath = ltrim($cachedMountInfo->getRootInternalPath().'/'.$cacheEntry->getPath(), '/');
325 325
 			$pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
326 326
 			$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
327
-			$absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
327
+			$absolutePath = rtrim($cachedMountInfo->getMountPoint().$pathRelativeToMount, '/');
328 328
 			return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
329 329
 				$absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
330 330
 				\OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
@@ -333,14 +333,14 @@  discard block
 block discarded – undo
333 333
 
334 334
 		$nodes = array_filter($nodes);
335 335
 
336
-		return array_filter($nodes, function (Node $node) {
336
+		return array_filter($nodes, function(Node $node) {
337 337
 			return $this->getRelativePath($node->getPath());
338 338
 		});
339 339
 	}
340 340
 
341 341
 	protected function getAppDataDirectoryName(): string {
342 342
 		$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
343
-		return 'appdata_' . $instanceId;
343
+		return 'appdata_'.$instanceId;
344 344
 	}
345 345
 
346 346
 	/**
@@ -362,8 +362,8 @@  discard block
 block discarded – undo
362 362
 			return [];
363 363
 		}
364 364
 
365
-		$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
366
-		$currentPath = rtrim($this->path, '/') . '/';
365
+		$absolutePath = '/'.ltrim($cacheEntry->getPath(), '/');
366
+		$currentPath = rtrim($this->path, '/').'/';
367 367
 
368 368
 		if (strpos($absolutePath, $currentPath) !== 0) {
369 369
 			return [];
@@ -418,10 +418,10 @@  discard block
 block discarded – undo
418 418
 		$mounts = $this->root->getMountsIn($this->path);
419 419
 		$mounts[] = $this->getMountPoint();
420 420
 
421
-		$mounts = array_filter($mounts, function (IMountPoint $mount) {
421
+		$mounts = array_filter($mounts, function(IMountPoint $mount) {
422 422
 			return $mount->getStorage();
423 423
 		});
424
-		$storageIds = array_map(function (IMountPoint $mount) {
424
+		$storageIds = array_map(function(IMountPoint $mount) {
425 425
 			return $mount->getStorage()->getCache()->getNumericStorageId();
426 426
 		}, $mounts);
427 427
 		/** @var IMountPoint[] $mountMap */
@@ -476,7 +476,7 @@  discard block
 block discarded – undo
476 476
 	}
477 477
 
478 478
 	private function recentParse($result, $mountMap, $mimetypeLoader) {
479
-		$files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
479
+		$files = array_filter(array_map(function(array $entry) use ($mountMap, $mimetypeLoader) {
480 480
 			$mount = $mountMap[$entry['storage']];
481 481
 			$entry['internalPath'] = $entry['path'];
482 482
 			$entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
@@ -489,7 +489,7 @@  discard block
 block discarded – undo
489 489
 			return $this->root->createNode($fileInfo->getPath(), $fileInfo);
490 490
 		}, $result));
491 491
 
492
-		return array_values(array_filter($files, function (Node $node) {
492
+		return array_values(array_filter($files, function(Node $node) {
493 493
 			$cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId());
494 494
 			if (!$cacheEntry) {
495 495
 				return false;
@@ -511,13 +511,13 @@  discard block
 block discarded – undo
511 511
 			$rootLength = strlen($jailRoot) + 1;
512 512
 			if ($path === $jailRoot) {
513 513
 				return $mount->getMountPoint();
514
-			} else if (substr($path, 0, $rootLength) === $jailRoot . '/') {
515
-				return $mount->getMountPoint() . substr($path, $rootLength);
514
+			} else if (substr($path, 0, $rootLength) === $jailRoot.'/') {
515
+				return $mount->getMountPoint().substr($path, $rootLength);
516 516
 			} else {
517 517
 				return null;
518 518
 			}
519 519
 		} else {
520
-			return $mount->getMountPoint() . $path;
520
+			return $mount->getMountPoint().$path;
521 521
 		}
522 522
 	}
523 523
 }
Please login to merge, or discard this patch.
lib/private/Files/Search/SearchQuery.php 1 patch
Indentation   +69 added lines, -69 removed lines patch added patch discarded remove patch
@@ -29,80 +29,80 @@
 block discarded – undo
29 29
 use OCP\IUser;
30 30
 
31 31
 class SearchQuery implements ISearchQuery {
32
-	/** @var  ISearchOperator */
33
-	private $searchOperation;
34
-	/** @var  integer */
35
-	private $limit;
36
-	/** @var  integer */
37
-	private $offset;
38
-	/** @var  ISearchOrder[] */
39
-	private $order;
40
-	/** @var IUser */
41
-	private $user;
42
-	private $limitToHome;
32
+    /** @var  ISearchOperator */
33
+    private $searchOperation;
34
+    /** @var  integer */
35
+    private $limit;
36
+    /** @var  integer */
37
+    private $offset;
38
+    /** @var  ISearchOrder[] */
39
+    private $order;
40
+    /** @var IUser */
41
+    private $user;
42
+    private $limitToHome;
43 43
 
44
-	/**
45
-	 * SearchQuery constructor.
46
-	 *
47
-	 * @param ISearchOperator $searchOperation
48
-	 * @param int $limit
49
-	 * @param int $offset
50
-	 * @param array $order
51
-	 * @param IUser $user
52
-	 * @param bool $limitToHome
53
-	 */
54
-	public function __construct(
55
-		ISearchOperator $searchOperation,
56
-		int $limit,
57
-		int $offset,
58
-		array $order,
59
-		IUser $user,
60
-		bool $limitToHome = false
61
-	) {
62
-		$this->searchOperation = $searchOperation;
63
-		$this->limit = $limit;
64
-		$this->offset = $offset;
65
-		$this->order = $order;
66
-		$this->user = $user;
67
-		$this->limitToHome = $limitToHome;
68
-	}
44
+    /**
45
+     * SearchQuery constructor.
46
+     *
47
+     * @param ISearchOperator $searchOperation
48
+     * @param int $limit
49
+     * @param int $offset
50
+     * @param array $order
51
+     * @param IUser $user
52
+     * @param bool $limitToHome
53
+     */
54
+    public function __construct(
55
+        ISearchOperator $searchOperation,
56
+        int $limit,
57
+        int $offset,
58
+        array $order,
59
+        IUser $user,
60
+        bool $limitToHome = false
61
+    ) {
62
+        $this->searchOperation = $searchOperation;
63
+        $this->limit = $limit;
64
+        $this->offset = $offset;
65
+        $this->order = $order;
66
+        $this->user = $user;
67
+        $this->limitToHome = $limitToHome;
68
+    }
69 69
 
70
-	/**
71
-	 * @return ISearchOperator
72
-	 */
73
-	public function getSearchOperation() {
74
-		return $this->searchOperation;
75
-	}
70
+    /**
71
+     * @return ISearchOperator
72
+     */
73
+    public function getSearchOperation() {
74
+        return $this->searchOperation;
75
+    }
76 76
 
77
-	/**
78
-	 * @return int
79
-	 */
80
-	public function getLimit() {
81
-		return $this->limit;
82
-	}
77
+    /**
78
+     * @return int
79
+     */
80
+    public function getLimit() {
81
+        return $this->limit;
82
+    }
83 83
 
84
-	/**
85
-	 * @return int
86
-	 */
87
-	public function getOffset() {
88
-		return $this->offset;
89
-	}
84
+    /**
85
+     * @return int
86
+     */
87
+    public function getOffset() {
88
+        return $this->offset;
89
+    }
90 90
 
91
-	/**
92
-	 * @return ISearchOrder[]
93
-	 */
94
-	public function getOrder() {
95
-		return $this->order;
96
-	}
91
+    /**
92
+     * @return ISearchOrder[]
93
+     */
94
+    public function getOrder() {
95
+        return $this->order;
96
+    }
97 97
 
98
-	/**
99
-	 * @return IUser
100
-	 */
101
-	public function getUser() {
102
-		return $this->user;
103
-	}
98
+    /**
99
+     * @return IUser
100
+     */
101
+    public function getUser() {
102
+        return $this->user;
103
+    }
104 104
 
105
-	public function limitToHome(): bool {
106
-		return $this->limitToHome;
107
-	}
105
+    public function limitToHome(): bool {
106
+        return $this->limitToHome;
107
+    }
108 108
 }
Please login to merge, or discard this patch.
apps/dav/lib/Files/FileSearchBackend.php 2 patches
Indentation   +377 added lines, -377 removed lines patch added patch discarded remove patch
@@ -51,381 +51,381 @@
 block discarded – undo
51 51
 use SearchDAV\Query\Query;
52 52
 
53 53
 class FileSearchBackend implements ISearchBackend {
54
-	/** @var CachingTree */
55
-	private $tree;
56
-
57
-	/** @var IUser */
58
-	private $user;
59
-
60
-	/** @var IRootFolder */
61
-	private $rootFolder;
62
-
63
-	/** @var IManager */
64
-	private $shareManager;
65
-
66
-	/** @var View */
67
-	private $view;
68
-
69
-	/**
70
-	 * FileSearchBackend constructor.
71
-	 *
72
-	 * @param CachingTree $tree
73
-	 * @param IUser $user
74
-	 * @param IRootFolder $rootFolder
75
-	 * @param IManager $shareManager
76
-	 * @param View $view
77
-	 * @internal param IRootFolder $rootFolder
78
-	 */
79
-	public function __construct(CachingTree $tree, IUser $user, IRootFolder $rootFolder, IManager $shareManager, View $view) {
80
-		$this->tree = $tree;
81
-		$this->user = $user;
82
-		$this->rootFolder = $rootFolder;
83
-		$this->shareManager = $shareManager;
84
-		$this->view = $view;
85
-	}
86
-
87
-	/**
88
-	 * Search endpoint will be remote.php/dav
89
-	 *
90
-	 * @return string
91
-	 */
92
-	public function getArbiterPath() {
93
-		return '';
94
-	}
95
-
96
-	public function isValidScope($href, $depth, $path) {
97
-		// only allow scopes inside the dav server
98
-		if (is_null($path)) {
99
-			return false;
100
-		}
101
-
102
-		try {
103
-			$node = $this->tree->getNodeForPath($path);
104
-			return $node instanceof Directory;
105
-		} catch (NotFound $e) {
106
-			return false;
107
-		}
108
-	}
109
-
110
-	public function getPropertyDefinitionsForScope($href, $path) {
111
-		// all valid scopes support the same schema
112
-
113
-		//todo dynamically load all propfind properties that are supported
114
-		return [
115
-			// queryable properties
116
-			new SearchPropertyDefinition('{DAV:}displayname', true, true, true),
117
-			new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true),
118
-			new SearchPropertyDefinition('{DAV:}getlastmodified', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
119
-			new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
120
-			new SearchPropertyDefinition(TagsPlugin::FAVORITE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_BOOLEAN),
121
-			new SearchPropertyDefinition(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, true, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
122
-			new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, true, true, false),
123
-
124
-			// select only properties
125
-			new SearchPropertyDefinition('{DAV:}resourcetype', false, true, false),
126
-			new SearchPropertyDefinition('{DAV:}getcontentlength', false, true, false),
127
-			new SearchPropertyDefinition(FilesPlugin::CHECKSUMS_PROPERTYNAME, false, true, false),
128
-			new SearchPropertyDefinition(FilesPlugin::PERMISSIONS_PROPERTYNAME, false, true, false),
129
-			new SearchPropertyDefinition(FilesPlugin::GETETAG_PROPERTYNAME, false, true, false),
130
-			new SearchPropertyDefinition(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME, false, true, false),
131
-			new SearchPropertyDefinition(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME, false, true, false),
132
-			new SearchPropertyDefinition(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_BOOLEAN),
133
-			new SearchPropertyDefinition(FilesPlugin::FILEID_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
134
-		];
135
-	}
136
-
137
-	/**
138
-	 * @param Query $search
139
-	 * @return SearchResult[]
140
-	 */
141
-	public function search(Query $search) {
142
-		if (count($search->from) !== 1) {
143
-			throw new \InvalidArgumentException('Searching more than one folder is not supported');
144
-		}
145
-		$query = $this->transformQuery($search);
146
-		$scope = $search->from[0];
147
-		if ($scope->path === null) {
148
-			throw new \InvalidArgumentException('Using uri\'s as scope is not supported, please use a path relative to the search arbiter instead');
149
-		}
150
-		$node = $this->tree->getNodeForPath($scope->path);
151
-		if (!$node instanceof Directory) {
152
-			throw new \InvalidArgumentException('Search is only supported on directories');
153
-		}
154
-
155
-		$fileInfo = $node->getFileInfo();
156
-		$folder = $this->rootFolder->get($fileInfo->getPath());
157
-		/** @var Folder $folder $results */
158
-		$results = $folder->search($query);
159
-
160
-		/** @var SearchResult[] $nodes */
161
-		$nodes = array_map(function (Node $node) {
162
-			if ($node instanceof Folder) {
163
-				$davNode = new \OCA\DAV\Connector\Sabre\Directory($this->view, $node, $this->tree, $this->shareManager);
164
-			} else {
165
-				$davNode = new \OCA\DAV\Connector\Sabre\File($this->view, $node, $this->shareManager);
166
-			}
167
-			$path = $this->getHrefForNode($node);
168
-			$this->tree->cacheNode($davNode, $path);
169
-			return new SearchResult($davNode, $path);
170
-		}, $results);
171
-
172
-		if (!$query->limitToHome()) {
173
-			// Sort again, since the result from multiple storages is appended and not sorted
174
-			usort($nodes, function (SearchResult $a, SearchResult $b) use ($search) {
175
-				return $this->sort($a, $b, $search->orderBy);
176
-			});
177
-		}
178
-
179
-		// If a limit is provided use only return that number of files
180
-		if ($search->limit->maxResults !== 0) {
181
-			$nodes = \array_slice($nodes, 0, $search->limit->maxResults);
182
-		}
183
-
184
-		return $nodes;
185
-	}
186
-
187
-	private function sort(SearchResult $a, SearchResult $b, array $orders) {
188
-		/** @var Order $order */
189
-		foreach ($orders as $order) {
190
-			$v1 = $this->getSearchResultProperty($a, $order->property);
191
-			$v2 = $this->getSearchResultProperty($b, $order->property);
192
-
193
-
194
-			if ($v1 === null && $v2 === null) {
195
-				continue;
196
-			}
197
-			if ($v1 === null) {
198
-				return $order->order === Order::ASC ? 1 : -1;
199
-			}
200
-			if ($v2 === null) {
201
-				return $order->order === Order::ASC ? -1 : 1;
202
-			}
203
-
204
-			$s = $this->compareProperties($v1, $v2, $order);
205
-			if ($s === 0) {
206
-				continue;
207
-			}
208
-
209
-			if ($order->order === Order::DESC) {
210
-				$s = -$s;
211
-			}
212
-			return $s;
213
-		}
214
-
215
-		return 0;
216
-	}
217
-
218
-	private function compareProperties($a, $b, Order $order) {
219
-		switch ($order->property->dataType) {
220
-			case SearchPropertyDefinition::DATATYPE_STRING:
221
-				return strcmp($a, $b);
222
-			case SearchPropertyDefinition::DATATYPE_BOOLEAN:
223
-				if ($a === $b) {
224
-					return 0;
225
-				}
226
-				if ($a === false) {
227
-					return -1;
228
-				}
229
-				return 1;
230
-			default:
231
-				if ($a === $b) {
232
-					return 0;
233
-				}
234
-				if ($a < $b) {
235
-					return -1;
236
-				}
237
-				return 1;
238
-		}
239
-	}
240
-
241
-	private function getSearchResultProperty(SearchResult $result, SearchPropertyDefinition $property) {
242
-		/** @var \OCA\DAV\Connector\Sabre\Node $node */
243
-		$node = $result->node;
244
-
245
-		switch ($property->name) {
246
-			case '{DAV:}displayname':
247
-				return $node->getName();
248
-			case '{DAV:}getlastmodified':
249
-				return $node->getLastModified();
250
-			case FilesPlugin::SIZE_PROPERTYNAME:
251
-				return $node->getSize();
252
-			case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME:
253
-				return $node->getInternalFileId();
254
-			default:
255
-				return null;
256
-		}
257
-	}
258
-
259
-	/**
260
-	 * @param Node $node
261
-	 * @return string
262
-	 */
263
-	private function getHrefForNode(Node $node) {
264
-		$base = '/files/' . $this->user->getUID();
265
-		return $base . $this->view->getRelativePath($node->getPath());
266
-	}
267
-
268
-	/**
269
-	 * @param Query $query
270
-	 * @return ISearchQuery
271
-	 */
272
-	private function transformQuery(Query $query): ISearchQuery {
273
-		// TODO offset
274
-		$limit = $query->limit;
275
-		$orders = array_map([$this, 'mapSearchOrder'], $query->orderBy);
276
-
277
-		$limitHome = false;
278
-		$ownerProp = $this->extractWhereValue($query->where, FilesPlugin::OWNER_ID_PROPERTYNAME, Operator::OPERATION_EQUAL);
279
-		if ($ownerProp !== null) {
280
-			if ($ownerProp === $this->user->getUID()) {
281
-				$limitHome = true;
282
-			} else {
283
-				throw new \InvalidArgumentException("Invalid search value for '{http://owncloud.org/ns}owner-id', only the current user id is allowed");
284
-			}
285
-		}
286
-
287
-		return new SearchQuery(
288
-			$this->transformSearchOperation($query->where),
289
-			(int)$limit->maxResults,
290
-			0,
291
-			$orders,
292
-			$this->user,
293
-			$limitHome
294
-		);
295
-	}
296
-
297
-	/**
298
-	 * @param Order $order
299
-	 * @return ISearchOrder
300
-	 */
301
-	private function mapSearchOrder(Order $order) {
302
-		return new SearchOrder($order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING, $this->mapPropertyNameToColumn($order->property));
303
-	}
304
-
305
-	/**
306
-	 * @param Operator $operator
307
-	 * @return ISearchOperator
308
-	 */
309
-	private function transformSearchOperation(Operator $operator) {
310
-		list(, $trimmedType) = explode('}', $operator->type);
311
-		switch ($operator->type) {
312
-			case Operator::OPERATION_AND:
313
-			case Operator::OPERATION_OR:
314
-			case Operator::OPERATION_NOT:
315
-				$arguments = array_map([$this, 'transformSearchOperation'], $operator->arguments);
316
-				return new SearchBinaryOperator($trimmedType, $arguments);
317
-			case Operator::OPERATION_EQUAL:
318
-			case Operator::OPERATION_GREATER_OR_EQUAL_THAN:
319
-			case Operator::OPERATION_GREATER_THAN:
320
-			case Operator::OPERATION_LESS_OR_EQUAL_THAN:
321
-			case Operator::OPERATION_LESS_THAN:
322
-			case Operator::OPERATION_IS_LIKE:
323
-				if (count($operator->arguments) !== 2) {
324
-					throw new \InvalidArgumentException('Invalid number of arguments for ' . $trimmedType . ' operation');
325
-				}
326
-				if (!($operator->arguments[0] instanceof SearchPropertyDefinition)) {
327
-					throw new \InvalidArgumentException('Invalid argument 1 for ' . $trimmedType . ' operation, expected property');
328
-				}
329
-				if (!($operator->arguments[1] instanceof Literal)) {
330
-					throw new \InvalidArgumentException('Invalid argument 2 for ' . $trimmedType . ' operation, expected literal');
331
-				}
332
-				return new SearchComparison($trimmedType, $this->mapPropertyNameToColumn($operator->arguments[0]), $this->castValue($operator->arguments[0], $operator->arguments[1]->value));
333
-			case Operator::OPERATION_IS_COLLECTION:
334
-				return new SearchComparison('eq', 'mimetype', ICacheEntry::DIRECTORY_MIMETYPE);
335
-			default:
336
-				throw new \InvalidArgumentException('Unsupported operation ' . $trimmedType . ' (' . $operator->type . ')');
337
-		}
338
-	}
339
-
340
-	/**
341
-	 * @param SearchPropertyDefinition $property
342
-	 * @return string
343
-	 */
344
-	private function mapPropertyNameToColumn(SearchPropertyDefinition $property) {
345
-		switch ($property->name) {
346
-			case '{DAV:}displayname':
347
-				return 'name';
348
-			case '{DAV:}getcontenttype':
349
-				return 'mimetype';
350
-			case '{DAV:}getlastmodified':
351
-				return 'mtime';
352
-			case FilesPlugin::SIZE_PROPERTYNAME:
353
-				return 'size';
354
-			case TagsPlugin::FAVORITE_PROPERTYNAME:
355
-				return 'favorite';
356
-			case TagsPlugin::TAGS_PROPERTYNAME:
357
-				return 'tagname';
358
-			case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME:
359
-				return 'fileid';
360
-			default:
361
-				throw new \InvalidArgumentException('Unsupported property for search or order: ' . $property->name);
362
-		}
363
-	}
364
-
365
-	private function castValue(SearchPropertyDefinition $property, $value) {
366
-		switch ($property->dataType) {
367
-			case SearchPropertyDefinition::DATATYPE_BOOLEAN:
368
-				return $value === 'yes';
369
-			case SearchPropertyDefinition::DATATYPE_DECIMAL:
370
-			case SearchPropertyDefinition::DATATYPE_INTEGER:
371
-			case SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER:
372
-				return 0 + $value;
373
-			case SearchPropertyDefinition::DATATYPE_DATETIME:
374
-				if (is_numeric($value)) {
375
-					return max(0, 0 + $value);
376
-				}
377
-				$date = \DateTime::createFromFormat(\DateTime::ATOM, $value);
378
-				return ($date instanceof \DateTime && $date->getTimestamp() !== false) ? $date->getTimestamp() : 0;
379
-			default:
380
-				return $value;
381
-		}
382
-	}
383
-
384
-	/**
385
-	 * Get a specific property from the were clause
386
-	 */
387
-	private function extractWhereValue(Operator &$operator, string $propertyName, string $comparison, bool $acceptableLocation = true): ?string {
388
-		switch ($operator->type) {
389
-			case Operator::OPERATION_AND:
390
-			case Operator::OPERATION_OR:
391
-			case Operator::OPERATION_NOT:
392
-				foreach ($operator->arguments as &$argument) {
393
-					$value = $this->extractWhereValue($argument, $propertyName, $comparison, $acceptableLocation && $operator->type === Operator::OPERATION_AND);
394
-					if ($value !== null) {
395
-						return $value;
396
-					}
397
-				}
398
-				return null;
399
-			case Operator::OPERATION_EQUAL:
400
-			case Operator::OPERATION_GREATER_OR_EQUAL_THAN:
401
-			case Operator::OPERATION_GREATER_THAN:
402
-			case Operator::OPERATION_LESS_OR_EQUAL_THAN:
403
-			case Operator::OPERATION_LESS_THAN:
404
-			case Operator::OPERATION_IS_LIKE:
405
-				if ($operator->arguments[0]->name === $propertyName) {
406
-					if ($operator->type === $comparison) {
407
-						if ($acceptableLocation) {
408
-							if ($operator->arguments[1] instanceof Literal) {
409
-								$value = $operator->arguments[1]->value;
410
-
411
-								// to remove the comparison from the query, we replace it with an empty AND
412
-								$operator = new Operator(Operator::OPERATION_AND);
413
-
414
-								return $value;
415
-							} else {
416
-								throw new \InvalidArgumentException("searching by '$propertyName' is only allowed with a literal value");
417
-							}
418
-						} else{
419
-							throw new \InvalidArgumentException("searching by '$propertyName' is not allowed inside a '{DAV:}or' or '{DAV:}not'");
420
-						}
421
-					} else {
422
-						throw new \InvalidArgumentException("searching by '$propertyName' is only allowed inside a '$comparison'");
423
-					}
424
-				} else {
425
-					return null;
426
-				}
427
-			default:
428
-				return null;
429
-		}
430
-	}
54
+    /** @var CachingTree */
55
+    private $tree;
56
+
57
+    /** @var IUser */
58
+    private $user;
59
+
60
+    /** @var IRootFolder */
61
+    private $rootFolder;
62
+
63
+    /** @var IManager */
64
+    private $shareManager;
65
+
66
+    /** @var View */
67
+    private $view;
68
+
69
+    /**
70
+     * FileSearchBackend constructor.
71
+     *
72
+     * @param CachingTree $tree
73
+     * @param IUser $user
74
+     * @param IRootFolder $rootFolder
75
+     * @param IManager $shareManager
76
+     * @param View $view
77
+     * @internal param IRootFolder $rootFolder
78
+     */
79
+    public function __construct(CachingTree $tree, IUser $user, IRootFolder $rootFolder, IManager $shareManager, View $view) {
80
+        $this->tree = $tree;
81
+        $this->user = $user;
82
+        $this->rootFolder = $rootFolder;
83
+        $this->shareManager = $shareManager;
84
+        $this->view = $view;
85
+    }
86
+
87
+    /**
88
+     * Search endpoint will be remote.php/dav
89
+     *
90
+     * @return string
91
+     */
92
+    public function getArbiterPath() {
93
+        return '';
94
+    }
95
+
96
+    public function isValidScope($href, $depth, $path) {
97
+        // only allow scopes inside the dav server
98
+        if (is_null($path)) {
99
+            return false;
100
+        }
101
+
102
+        try {
103
+            $node = $this->tree->getNodeForPath($path);
104
+            return $node instanceof Directory;
105
+        } catch (NotFound $e) {
106
+            return false;
107
+        }
108
+    }
109
+
110
+    public function getPropertyDefinitionsForScope($href, $path) {
111
+        // all valid scopes support the same schema
112
+
113
+        //todo dynamically load all propfind properties that are supported
114
+        return [
115
+            // queryable properties
116
+            new SearchPropertyDefinition('{DAV:}displayname', true, true, true),
117
+            new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true),
118
+            new SearchPropertyDefinition('{DAV:}getlastmodified', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
119
+            new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
120
+            new SearchPropertyDefinition(TagsPlugin::FAVORITE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_BOOLEAN),
121
+            new SearchPropertyDefinition(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, true, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
122
+            new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, true, true, false),
123
+
124
+            // select only properties
125
+            new SearchPropertyDefinition('{DAV:}resourcetype', false, true, false),
126
+            new SearchPropertyDefinition('{DAV:}getcontentlength', false, true, false),
127
+            new SearchPropertyDefinition(FilesPlugin::CHECKSUMS_PROPERTYNAME, false, true, false),
128
+            new SearchPropertyDefinition(FilesPlugin::PERMISSIONS_PROPERTYNAME, false, true, false),
129
+            new SearchPropertyDefinition(FilesPlugin::GETETAG_PROPERTYNAME, false, true, false),
130
+            new SearchPropertyDefinition(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME, false, true, false),
131
+            new SearchPropertyDefinition(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME, false, true, false),
132
+            new SearchPropertyDefinition(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_BOOLEAN),
133
+            new SearchPropertyDefinition(FilesPlugin::FILEID_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
134
+        ];
135
+    }
136
+
137
+    /**
138
+     * @param Query $search
139
+     * @return SearchResult[]
140
+     */
141
+    public function search(Query $search) {
142
+        if (count($search->from) !== 1) {
143
+            throw new \InvalidArgumentException('Searching more than one folder is not supported');
144
+        }
145
+        $query = $this->transformQuery($search);
146
+        $scope = $search->from[0];
147
+        if ($scope->path === null) {
148
+            throw new \InvalidArgumentException('Using uri\'s as scope is not supported, please use a path relative to the search arbiter instead');
149
+        }
150
+        $node = $this->tree->getNodeForPath($scope->path);
151
+        if (!$node instanceof Directory) {
152
+            throw new \InvalidArgumentException('Search is only supported on directories');
153
+        }
154
+
155
+        $fileInfo = $node->getFileInfo();
156
+        $folder = $this->rootFolder->get($fileInfo->getPath());
157
+        /** @var Folder $folder $results */
158
+        $results = $folder->search($query);
159
+
160
+        /** @var SearchResult[] $nodes */
161
+        $nodes = array_map(function (Node $node) {
162
+            if ($node instanceof Folder) {
163
+                $davNode = new \OCA\DAV\Connector\Sabre\Directory($this->view, $node, $this->tree, $this->shareManager);
164
+            } else {
165
+                $davNode = new \OCA\DAV\Connector\Sabre\File($this->view, $node, $this->shareManager);
166
+            }
167
+            $path = $this->getHrefForNode($node);
168
+            $this->tree->cacheNode($davNode, $path);
169
+            return new SearchResult($davNode, $path);
170
+        }, $results);
171
+
172
+        if (!$query->limitToHome()) {
173
+            // Sort again, since the result from multiple storages is appended and not sorted
174
+            usort($nodes, function (SearchResult $a, SearchResult $b) use ($search) {
175
+                return $this->sort($a, $b, $search->orderBy);
176
+            });
177
+        }
178
+
179
+        // If a limit is provided use only return that number of files
180
+        if ($search->limit->maxResults !== 0) {
181
+            $nodes = \array_slice($nodes, 0, $search->limit->maxResults);
182
+        }
183
+
184
+        return $nodes;
185
+    }
186
+
187
+    private function sort(SearchResult $a, SearchResult $b, array $orders) {
188
+        /** @var Order $order */
189
+        foreach ($orders as $order) {
190
+            $v1 = $this->getSearchResultProperty($a, $order->property);
191
+            $v2 = $this->getSearchResultProperty($b, $order->property);
192
+
193
+
194
+            if ($v1 === null && $v2 === null) {
195
+                continue;
196
+            }
197
+            if ($v1 === null) {
198
+                return $order->order === Order::ASC ? 1 : -1;
199
+            }
200
+            if ($v2 === null) {
201
+                return $order->order === Order::ASC ? -1 : 1;
202
+            }
203
+
204
+            $s = $this->compareProperties($v1, $v2, $order);
205
+            if ($s === 0) {
206
+                continue;
207
+            }
208
+
209
+            if ($order->order === Order::DESC) {
210
+                $s = -$s;
211
+            }
212
+            return $s;
213
+        }
214
+
215
+        return 0;
216
+    }
217
+
218
+    private function compareProperties($a, $b, Order $order) {
219
+        switch ($order->property->dataType) {
220
+            case SearchPropertyDefinition::DATATYPE_STRING:
221
+                return strcmp($a, $b);
222
+            case SearchPropertyDefinition::DATATYPE_BOOLEAN:
223
+                if ($a === $b) {
224
+                    return 0;
225
+                }
226
+                if ($a === false) {
227
+                    return -1;
228
+                }
229
+                return 1;
230
+            default:
231
+                if ($a === $b) {
232
+                    return 0;
233
+                }
234
+                if ($a < $b) {
235
+                    return -1;
236
+                }
237
+                return 1;
238
+        }
239
+    }
240
+
241
+    private function getSearchResultProperty(SearchResult $result, SearchPropertyDefinition $property) {
242
+        /** @var \OCA\DAV\Connector\Sabre\Node $node */
243
+        $node = $result->node;
244
+
245
+        switch ($property->name) {
246
+            case '{DAV:}displayname':
247
+                return $node->getName();
248
+            case '{DAV:}getlastmodified':
249
+                return $node->getLastModified();
250
+            case FilesPlugin::SIZE_PROPERTYNAME:
251
+                return $node->getSize();
252
+            case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME:
253
+                return $node->getInternalFileId();
254
+            default:
255
+                return null;
256
+        }
257
+    }
258
+
259
+    /**
260
+     * @param Node $node
261
+     * @return string
262
+     */
263
+    private function getHrefForNode(Node $node) {
264
+        $base = '/files/' . $this->user->getUID();
265
+        return $base . $this->view->getRelativePath($node->getPath());
266
+    }
267
+
268
+    /**
269
+     * @param Query $query
270
+     * @return ISearchQuery
271
+     */
272
+    private function transformQuery(Query $query): ISearchQuery {
273
+        // TODO offset
274
+        $limit = $query->limit;
275
+        $orders = array_map([$this, 'mapSearchOrder'], $query->orderBy);
276
+
277
+        $limitHome = false;
278
+        $ownerProp = $this->extractWhereValue($query->where, FilesPlugin::OWNER_ID_PROPERTYNAME, Operator::OPERATION_EQUAL);
279
+        if ($ownerProp !== null) {
280
+            if ($ownerProp === $this->user->getUID()) {
281
+                $limitHome = true;
282
+            } else {
283
+                throw new \InvalidArgumentException("Invalid search value for '{http://owncloud.org/ns}owner-id', only the current user id is allowed");
284
+            }
285
+        }
286
+
287
+        return new SearchQuery(
288
+            $this->transformSearchOperation($query->where),
289
+            (int)$limit->maxResults,
290
+            0,
291
+            $orders,
292
+            $this->user,
293
+            $limitHome
294
+        );
295
+    }
296
+
297
+    /**
298
+     * @param Order $order
299
+     * @return ISearchOrder
300
+     */
301
+    private function mapSearchOrder(Order $order) {
302
+        return new SearchOrder($order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING, $this->mapPropertyNameToColumn($order->property));
303
+    }
304
+
305
+    /**
306
+     * @param Operator $operator
307
+     * @return ISearchOperator
308
+     */
309
+    private function transformSearchOperation(Operator $operator) {
310
+        list(, $trimmedType) = explode('}', $operator->type);
311
+        switch ($operator->type) {
312
+            case Operator::OPERATION_AND:
313
+            case Operator::OPERATION_OR:
314
+            case Operator::OPERATION_NOT:
315
+                $arguments = array_map([$this, 'transformSearchOperation'], $operator->arguments);
316
+                return new SearchBinaryOperator($trimmedType, $arguments);
317
+            case Operator::OPERATION_EQUAL:
318
+            case Operator::OPERATION_GREATER_OR_EQUAL_THAN:
319
+            case Operator::OPERATION_GREATER_THAN:
320
+            case Operator::OPERATION_LESS_OR_EQUAL_THAN:
321
+            case Operator::OPERATION_LESS_THAN:
322
+            case Operator::OPERATION_IS_LIKE:
323
+                if (count($operator->arguments) !== 2) {
324
+                    throw new \InvalidArgumentException('Invalid number of arguments for ' . $trimmedType . ' operation');
325
+                }
326
+                if (!($operator->arguments[0] instanceof SearchPropertyDefinition)) {
327
+                    throw new \InvalidArgumentException('Invalid argument 1 for ' . $trimmedType . ' operation, expected property');
328
+                }
329
+                if (!($operator->arguments[1] instanceof Literal)) {
330
+                    throw new \InvalidArgumentException('Invalid argument 2 for ' . $trimmedType . ' operation, expected literal');
331
+                }
332
+                return new SearchComparison($trimmedType, $this->mapPropertyNameToColumn($operator->arguments[0]), $this->castValue($operator->arguments[0], $operator->arguments[1]->value));
333
+            case Operator::OPERATION_IS_COLLECTION:
334
+                return new SearchComparison('eq', 'mimetype', ICacheEntry::DIRECTORY_MIMETYPE);
335
+            default:
336
+                throw new \InvalidArgumentException('Unsupported operation ' . $trimmedType . ' (' . $operator->type . ')');
337
+        }
338
+    }
339
+
340
+    /**
341
+     * @param SearchPropertyDefinition $property
342
+     * @return string
343
+     */
344
+    private function mapPropertyNameToColumn(SearchPropertyDefinition $property) {
345
+        switch ($property->name) {
346
+            case '{DAV:}displayname':
347
+                return 'name';
348
+            case '{DAV:}getcontenttype':
349
+                return 'mimetype';
350
+            case '{DAV:}getlastmodified':
351
+                return 'mtime';
352
+            case FilesPlugin::SIZE_PROPERTYNAME:
353
+                return 'size';
354
+            case TagsPlugin::FAVORITE_PROPERTYNAME:
355
+                return 'favorite';
356
+            case TagsPlugin::TAGS_PROPERTYNAME:
357
+                return 'tagname';
358
+            case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME:
359
+                return 'fileid';
360
+            default:
361
+                throw new \InvalidArgumentException('Unsupported property for search or order: ' . $property->name);
362
+        }
363
+    }
364
+
365
+    private function castValue(SearchPropertyDefinition $property, $value) {
366
+        switch ($property->dataType) {
367
+            case SearchPropertyDefinition::DATATYPE_BOOLEAN:
368
+                return $value === 'yes';
369
+            case SearchPropertyDefinition::DATATYPE_DECIMAL:
370
+            case SearchPropertyDefinition::DATATYPE_INTEGER:
371
+            case SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER:
372
+                return 0 + $value;
373
+            case SearchPropertyDefinition::DATATYPE_DATETIME:
374
+                if (is_numeric($value)) {
375
+                    return max(0, 0 + $value);
376
+                }
377
+                $date = \DateTime::createFromFormat(\DateTime::ATOM, $value);
378
+                return ($date instanceof \DateTime && $date->getTimestamp() !== false) ? $date->getTimestamp() : 0;
379
+            default:
380
+                return $value;
381
+        }
382
+    }
383
+
384
+    /**
385
+     * Get a specific property from the were clause
386
+     */
387
+    private function extractWhereValue(Operator &$operator, string $propertyName, string $comparison, bool $acceptableLocation = true): ?string {
388
+        switch ($operator->type) {
389
+            case Operator::OPERATION_AND:
390
+            case Operator::OPERATION_OR:
391
+            case Operator::OPERATION_NOT:
392
+                foreach ($operator->arguments as &$argument) {
393
+                    $value = $this->extractWhereValue($argument, $propertyName, $comparison, $acceptableLocation && $operator->type === Operator::OPERATION_AND);
394
+                    if ($value !== null) {
395
+                        return $value;
396
+                    }
397
+                }
398
+                return null;
399
+            case Operator::OPERATION_EQUAL:
400
+            case Operator::OPERATION_GREATER_OR_EQUAL_THAN:
401
+            case Operator::OPERATION_GREATER_THAN:
402
+            case Operator::OPERATION_LESS_OR_EQUAL_THAN:
403
+            case Operator::OPERATION_LESS_THAN:
404
+            case Operator::OPERATION_IS_LIKE:
405
+                if ($operator->arguments[0]->name === $propertyName) {
406
+                    if ($operator->type === $comparison) {
407
+                        if ($acceptableLocation) {
408
+                            if ($operator->arguments[1] instanceof Literal) {
409
+                                $value = $operator->arguments[1]->value;
410
+
411
+                                // to remove the comparison from the query, we replace it with an empty AND
412
+                                $operator = new Operator(Operator::OPERATION_AND);
413
+
414
+                                return $value;
415
+                            } else {
416
+                                throw new \InvalidArgumentException("searching by '$propertyName' is only allowed with a literal value");
417
+                            }
418
+                        } else{
419
+                            throw new \InvalidArgumentException("searching by '$propertyName' is not allowed inside a '{DAV:}or' or '{DAV:}not'");
420
+                        }
421
+                    } else {
422
+                        throw new \InvalidArgumentException("searching by '$propertyName' is only allowed inside a '$comparison'");
423
+                    }
424
+                } else {
425
+                    return null;
426
+                }
427
+            default:
428
+                return null;
429
+        }
430
+    }
431 431
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -158,7 +158,7 @@  discard block
 block discarded – undo
158 158
 		$results = $folder->search($query);
159 159
 
160 160
 		/** @var SearchResult[] $nodes */
161
-		$nodes = array_map(function (Node $node) {
161
+		$nodes = array_map(function(Node $node) {
162 162
 			if ($node instanceof Folder) {
163 163
 				$davNode = new \OCA\DAV\Connector\Sabre\Directory($this->view, $node, $this->tree, $this->shareManager);
164 164
 			} else {
@@ -171,7 +171,7 @@  discard block
 block discarded – undo
171 171
 
172 172
 		if (!$query->limitToHome()) {
173 173
 			// Sort again, since the result from multiple storages is appended and not sorted
174
-			usort($nodes, function (SearchResult $a, SearchResult $b) use ($search) {
174
+			usort($nodes, function(SearchResult $a, SearchResult $b) use ($search) {
175 175
 				return $this->sort($a, $b, $search->orderBy);
176 176
 			});
177 177
 		}
@@ -261,8 +261,8 @@  discard block
 block discarded – undo
261 261
 	 * @return string
262 262
 	 */
263 263
 	private function getHrefForNode(Node $node) {
264
-		$base = '/files/' . $this->user->getUID();
265
-		return $base . $this->view->getRelativePath($node->getPath());
264
+		$base = '/files/'.$this->user->getUID();
265
+		return $base.$this->view->getRelativePath($node->getPath());
266 266
 	}
267 267
 
268 268
 	/**
@@ -286,7 +286,7 @@  discard block
 block discarded – undo
286 286
 
287 287
 		return new SearchQuery(
288 288
 			$this->transformSearchOperation($query->where),
289
-			(int)$limit->maxResults,
289
+			(int) $limit->maxResults,
290 290
 			0,
291 291
 			$orders,
292 292
 			$this->user,
@@ -321,19 +321,19 @@  discard block
 block discarded – undo
321 321
 			case Operator::OPERATION_LESS_THAN:
322 322
 			case Operator::OPERATION_IS_LIKE:
323 323
 				if (count($operator->arguments) !== 2) {
324
-					throw new \InvalidArgumentException('Invalid number of arguments for ' . $trimmedType . ' operation');
324
+					throw new \InvalidArgumentException('Invalid number of arguments for '.$trimmedType.' operation');
325 325
 				}
326 326
 				if (!($operator->arguments[0] instanceof SearchPropertyDefinition)) {
327
-					throw new \InvalidArgumentException('Invalid argument 1 for ' . $trimmedType . ' operation, expected property');
327
+					throw new \InvalidArgumentException('Invalid argument 1 for '.$trimmedType.' operation, expected property');
328 328
 				}
329 329
 				if (!($operator->arguments[1] instanceof Literal)) {
330
-					throw new \InvalidArgumentException('Invalid argument 2 for ' . $trimmedType . ' operation, expected literal');
330
+					throw new \InvalidArgumentException('Invalid argument 2 for '.$trimmedType.' operation, expected literal');
331 331
 				}
332 332
 				return new SearchComparison($trimmedType, $this->mapPropertyNameToColumn($operator->arguments[0]), $this->castValue($operator->arguments[0], $operator->arguments[1]->value));
333 333
 			case Operator::OPERATION_IS_COLLECTION:
334 334
 				return new SearchComparison('eq', 'mimetype', ICacheEntry::DIRECTORY_MIMETYPE);
335 335
 			default:
336
-				throw new \InvalidArgumentException('Unsupported operation ' . $trimmedType . ' (' . $operator->type . ')');
336
+				throw new \InvalidArgumentException('Unsupported operation '.$trimmedType.' ('.$operator->type.')');
337 337
 		}
338 338
 	}
339 339
 
@@ -358,7 +358,7 @@  discard block
 block discarded – undo
358 358
 			case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME:
359 359
 				return 'fileid';
360 360
 			default:
361
-				throw new \InvalidArgumentException('Unsupported property for search or order: ' . $property->name);
361
+				throw new \InvalidArgumentException('Unsupported property for search or order: '.$property->name);
362 362
 		}
363 363
 	}
364 364
 
@@ -415,7 +415,7 @@  discard block
 block discarded – undo
415 415
 							} else {
416 416
 								throw new \InvalidArgumentException("searching by '$propertyName' is only allowed with a literal value");
417 417
 							}
418
-						} else{
418
+						} else {
419 419
 							throw new \InvalidArgumentException("searching by '$propertyName' is not allowed inside a '{DAV:}or' or '{DAV:}not'");
420 420
 						}
421 421
 					} else {
Please login to merge, or discard this patch.