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 &parameterObject, 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 &parameterObject, 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 &parameterObject, 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 &parameterObject, 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 &parameterObject, 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 &parameterObject, 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 &parameterObject, 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