1 /*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "FileOperations.h"
10
11 #include "AudioLibrary.h"
12 #include "FileItem.h"
13 #include "MediaSource.h"
14 #include "ServiceBroker.h"
15 #include "URL.h"
16 #include "Util.h"
17 #include "VideoLibrary.h"
18 #include "filesystem/Directory.h"
19 #include "filesystem/File.h"
20 #include "media/MediaLockState.h"
21 #include "settings/AdvancedSettings.h"
22 #include "settings/MediaSourceSettings.h"
23 #include "settings/SettingsComponent.h"
24 #include "utils/FileExtensionProvider.h"
25 #include "utils/FileUtils.h"
26 #include "utils/URIUtils.h"
27 #include "utils/Variant.h"
28 #include "video/VideoDatabase.h"
29
30 using namespace XFILE;
31 using namespace JSONRPC;
32
GetRootDirectory(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)33 JSONRPC_STATUS CFileOperations::GetRootDirectory(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
34 {
35 std::string media = parameterObject["media"].asString();
36 StringUtils::ToLower(media);
37
38 VECSOURCES *sources = CMediaSourceSettings::GetInstance().GetSources(media);
39 if (sources)
40 {
41 CFileItemList items;
42 for (unsigned int i = 0; i < (unsigned int)sources->size(); i++)
43 {
44 // Do not show sources which are locked
45 if (sources->at(i).m_iHasLock == LOCK_STATE_LOCKED)
46 continue;
47
48 items.Add(CFileItemPtr(new CFileItem(sources->at(i))));
49 }
50
51 for (unsigned int i = 0; i < (unsigned int)items.Size(); i++)
52 {
53 if (items[i]->IsSmb())
54 {
55 CURL url(items[i]->GetPath());
56 items[i]->SetPath(url.GetWithoutUserDetails());
57 }
58 }
59
60 CVariant param = parameterObject;
61 param["properties"] = CVariant(CVariant::VariantTypeArray);
62 param["properties"].append("file");
63
64 HandleFileItemList(NULL, true, "sources", items, param, result);
65 }
66
67 return OK;
68 }
69
GetDirectory(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)70 JSONRPC_STATUS CFileOperations::GetDirectory(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
71 {
72 std::string media = parameterObject["media"].asString();
73 StringUtils::ToLower(media);
74
75 CFileItemList items;
76 std::string strPath = parameterObject["directory"].asString();
77
78 if (!CFileUtils::RemoteAccessAllowed(strPath))
79 return InvalidParams;
80
81 std::vector<std::string> regexps;
82 std::string extensions;
83 if (media == "video")
84 {
85 regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoExcludeFromListingRegExps;
86 extensions = CServiceBroker::GetFileExtensionProvider().GetVideoExtensions();
87 }
88 else if (media == "music")
89 {
90 regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioExcludeFromListingRegExps;
91 extensions = CServiceBroker::GetFileExtensionProvider().GetMusicExtensions();
92 }
93 else if (media == "pictures")
94 {
95 regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pictureExcludeFromListingRegExps;
96 extensions = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions();
97 }
98
99 if (CDirectory::GetDirectory(strPath, items, extensions, DIR_FLAG_DEFAULTS))
100 {
101 // we might need to get additional information for music items
102 if (media == "music")
103 {
104 JSONRPC_STATUS status = CAudioLibrary::GetAdditionalDetails(parameterObject, items);
105 if (status != OK)
106 return status;
107 }
108
109 CFileItemList filteredFiles;
110 for (unsigned int i = 0; i < (unsigned int)items.Size(); i++)
111 {
112 if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps))
113 continue;
114
115 if (items[i]->IsSmb())
116 {
117 CURL url(items[i]->GetPath());
118 items[i]->SetPath(url.GetWithoutUserDetails());
119 }
120
121 if ((media == "video" && items[i]->HasVideoInfoTag()) ||
122 (media == "music" && items[i]->HasMusicInfoTag()) ||
123 (media == "picture" && items[i]->HasPictureInfoTag()) ||
124 media == "files" ||
125 URIUtils::IsUPnP(items.GetPath()))
126 filteredFiles.Add(items[i]);
127 else
128 {
129 CFileItemPtr fileItem(new CFileItem());
130 if (FillFileItem(items[i], fileItem, media, parameterObject))
131 filteredFiles.Add(fileItem);
132 else
133 filteredFiles.Add(items[i]);
134 }
135 }
136
137 // Check if the "properties" list exists
138 // and make sure it contains the "file" and "filetype"
139 // fields
140 CVariant param = parameterObject;
141 if (!param.isMember("properties"))
142 param["properties"] = CVariant(CVariant::VariantTypeArray);
143
144 bool hasFileField = false;
145 for (CVariant::const_iterator_array itr = param["properties"].begin_array(); itr != param["properties"].end_array(); itr++)
146 {
147 if (itr->asString().compare("file") == 0)
148 {
149 hasFileField = true;
150 break;
151 }
152 }
153
154 if (!hasFileField)
155 param["properties"].append("file");
156 param["properties"].append("filetype");
157
158 HandleFileItemList("id", true, "files", filteredFiles, param, result);
159
160 return OK;
161 }
162
163 return InvalidParams;
164 }
165
GetFileDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)166 JSONRPC_STATUS CFileOperations::GetFileDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
167 {
168 std::string file = parameterObject["file"].asString();
169 if (!CFile::Exists(file))
170 return InvalidParams;
171
172 if (!CFileUtils::RemoteAccessAllowed(file))
173 return InvalidParams;
174
175 std::string path = URIUtils::GetDirectory(file);
176
177 CFileItemList items;
178 if (path.empty())
179 return InvalidParams;
180
181 CFileItemPtr item;
182 if (CDirectory::GetDirectory(path, items, "", DIR_FLAG_DEFAULTS) && items.Contains(file))
183 item = items.Get(file);
184 else
185 item = CFileItemPtr(new CFileItem(file, false));
186
187 if (!URIUtils::IsUPnP(file))
188 FillFileItem(item, item, parameterObject["media"].asString(), parameterObject);
189
190 // Check if the "properties" list exists
191 // and make sure it contains the "file"
192 // field
193 CVariant param = parameterObject;
194 if (!param.isMember("properties"))
195 param["properties"] = CVariant(CVariant::VariantTypeArray);
196
197 bool hasFileField = false;
198 for (CVariant::const_iterator_array itr = param["properties"].begin_array(); itr != param["properties"].end_array(); itr++)
199 {
200 if (itr->asString().compare("file") == 0)
201 {
202 hasFileField = true;
203 break;
204 }
205 }
206
207 if (!hasFileField)
208 param["properties"].append("file");
209 param["properties"].append("filetype");
210
211 HandleFileItem("id", true, "filedetails", item, parameterObject, param["properties"], result, false);
212 return OK;
213 }
214
SetFileDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)215 JSONRPC_STATUS CFileOperations::SetFileDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
216 {
217 std::string media = parameterObject["media"].asString();
218 StringUtils::ToLower(media);
219
220 if (media.compare("video") != 0)
221 return InvalidParams;
222
223 std::string file = parameterObject["file"].asString();
224 if (!CFile::Exists(file))
225 return InvalidParams;
226
227 if (!CFileUtils::RemoteAccessAllowed(file))
228 return InvalidParams;
229
230 CVideoDatabase videodatabase;
231 if (!videodatabase.Open())
232 return InternalError;
233
234 int fileId = videodatabase.AddFile(file);
235
236 CVideoInfoTag infos;
237 if (!videodatabase.GetFileInfo("", infos, fileId))
238 return InvalidParams;
239
240 CDateTime lastPlayed = infos.m_lastPlayed;
241 int playcount = infos.GetPlayCount();
242 if (!parameterObject["lastplayed"].isNull())
243 {
244 lastPlayed.Reset();
245 SetFromDBDateTime(parameterObject["lastplayed"], lastPlayed);
246 playcount = lastPlayed.IsValid() ? std::max(1, playcount) : 0;
247 }
248 if (!parameterObject["playcount"].isNull())
249 playcount = parameterObject["playcount"].asInteger();
250 if (playcount != infos.GetPlayCount() || lastPlayed != infos.m_lastPlayed)
251 videodatabase.SetPlayCount(CFileItem(infos), playcount, lastPlayed);
252
253 CVideoLibrary::UpdateResumePoint(parameterObject, infos, videodatabase);
254
255 videodatabase.GetFileInfo("", infos, fileId);
256 CJSONRPCUtils::NotifyItemUpdated(infos, std::map<std::string, std::string>{});
257 return ACK;
258 }
259
PrepareDownload(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)260 JSONRPC_STATUS CFileOperations::PrepareDownload(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
261 {
262 std::string protocol;
263 if (transport->PrepareDownload(parameterObject["path"].asString().c_str(), result["details"], protocol))
264 {
265 result["protocol"] = protocol;
266
267 if ((transport->GetCapabilities() & FileDownloadDirect) == FileDownloadDirect)
268 result["mode"] = "direct";
269 else
270 result["mode"] = "redirect";
271
272 return OK;
273 }
274
275 return InvalidParams;
276 }
277
Download(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)278 JSONRPC_STATUS CFileOperations::Download(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
279 {
280 return transport->Download(parameterObject["path"].asString().c_str(), result) ? OK : InvalidParams;
281 }
282
FillFileItem(const CFileItemPtr & originalItem,CFileItemPtr & item,const std::string & media,const CVariant & parameterObject)283 bool CFileOperations::FillFileItem(
284 const CFileItemPtr& originalItem,
285 CFileItemPtr& item,
286 const std::string& media /* = "" */,
287 const CVariant& parameterObject /* = CVariant(CVariant::VariantTypeArray) */)
288 {
289 if (originalItem.get() == NULL)
290 return false;
291
292 // copy all the available details
293 *item = *originalItem;
294
295 bool status = false;
296 std::string strFilename = originalItem->GetPath();
297 if (!strFilename.empty() && (CDirectory::Exists(strFilename) || CFile::Exists(strFilename)))
298 {
299 if (media == "video")
300 status = CVideoLibrary::FillFileItem(strFilename, item, parameterObject);
301 else if (media == "music")
302 status = CAudioLibrary::FillFileItem(strFilename, item, parameterObject);
303
304 if (status && item->GetLabel().empty())
305 {
306 std::string label = originalItem->GetLabel();
307 if (label.empty())
308 {
309 bool isDir = CDirectory::Exists(strFilename);
310 label = CUtil::GetTitleFromPath(strFilename, isDir);
311 if (label.empty())
312 label = URIUtils::GetFileName(strFilename);
313 }
314
315 item->SetLabel(label);
316 }
317 else if (!status)
318 {
319 if (originalItem->GetLabel().empty())
320 {
321 bool isDir = CDirectory::Exists(strFilename);
322 std::string label = CUtil::GetTitleFromPath(strFilename, isDir);
323 if (label.empty())
324 return false;
325
326 item->SetLabel(label);
327 item->SetPath(strFilename);
328 item->m_bIsFolder = isDir;
329 }
330 else
331 *item = *originalItem;
332
333 status = true;
334 }
335 }
336
337 return status;
338 }
339
FillFileItemList(const CVariant & parameterObject,CFileItemList & list)340 bool CFileOperations::FillFileItemList(const CVariant ¶meterObject, CFileItemList &list)
341 {
342 if (parameterObject.isMember("directory"))
343 {
344 std::string media = parameterObject["media"].asString();
345 StringUtils::ToLower(media);
346
347 std::string strPath = parameterObject["directory"].asString();
348 if (!strPath.empty())
349 {
350 CFileItemList items;
351 std::string extensions;
352 std::vector<std::string> regexps;
353
354 if (media == "video")
355 {
356 regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoExcludeFromListingRegExps;
357 extensions = CServiceBroker::GetFileExtensionProvider().GetVideoExtensions();
358 }
359 else if (media == "music")
360 {
361 regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioExcludeFromListingRegExps;
362 extensions = CServiceBroker::GetFileExtensionProvider().GetMusicExtensions();
363 }
364 else if (media == "pictures")
365 {
366 regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pictureExcludeFromListingRegExps;
367 extensions = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions();
368 }
369
370 CDirectory directory;
371 if (directory.GetDirectory(strPath, items, extensions, DIR_FLAG_DEFAULTS))
372 {
373 // Sort folders and files by filename to avoid reverse item order bug on some platforms,
374 // but leave items from a playlist, smartplaylist or upnp container in order supplied
375 if (!items.IsPlayList() && !items.IsSmartPlayList() && !URIUtils::IsUPnP(items.GetPath()))
376 items.Sort(SortByFile, SortOrderAscending);
377
378 CFileItemList filteredDirectories;
379 for (unsigned int i = 0; i < (unsigned int)items.Size(); i++)
380 {
381 if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps))
382 continue;
383
384 if (items[i]->m_bIsFolder)
385 filteredDirectories.Add(items[i]);
386 else if ((media == "video" && items[i]->HasVideoInfoTag()) ||
387 (media == "music" && items[i]->HasMusicInfoTag()))
388 list.Add(items[i]);
389 else
390 {
391 CFileItemPtr fileItem(new CFileItem());
392 if (FillFileItem(items[i], fileItem, media, parameterObject))
393 list.Add(fileItem);
394 else if (media == "files")
395 list.Add(items[i]);
396 }
397 }
398
399 if (parameterObject.isMember("recursive") && parameterObject["recursive"].isBoolean())
400 {
401 for (int i = 0; i < filteredDirectories.Size(); i++)
402 {
403 CVariant val = parameterObject;
404 val["directory"] = filteredDirectories[i]->GetPath();
405 FillFileItemList(val, list);
406 }
407 }
408
409 return true;
410 }
411 }
412 }
413
414 return false;
415 }
416