Merge pull request #4676 from jmarshallnz/dont_set_scraper_on_tvshow_on_nfo
[vuplus_xbmc] / xbmc / utils / FileOperationJob.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "FileOperationJob.h"
22 #include "filesystem/File.h"
23 #include "filesystem/Directory.h"
24 #include "filesystem/ZipManager.h"
25 #include "filesystem/FileDirectoryFactory.h"
26 #include "filesystem/MultiPathDirectory.h"
27 #include "filesystem/SpecialProtocol.h"
28 #include "log.h"
29 #include "Util.h"
30 #include "URIUtils.h"
31 #include "utils/StringUtils.h"
32 #include "URL.h"
33 #include "guilib/LocalizeStrings.h"
34 #include "guilib/GUIWindowManager.h"
35 #include "dialogs/GUIDialogExtendedProgressBar.h"
36
37 #ifdef HAS_FILESYSTEM_RAR
38 #include "filesystem/RarManager.h"
39 #endif
40
41 using namespace std;
42 using namespace XFILE;
43
44 CFileOperationJob::CFileOperationJob()
45 {
46   m_handle = NULL;
47   m_displayProgress = false;
48 }
49
50 CFileOperationJob::CFileOperationJob(FileAction action, CFileItemList & items,
51                                     const CStdString& strDestFile,
52                                     bool displayProgress,
53                                     int heading, int line)
54 {
55   m_handle = NULL;
56   m_displayProgress = displayProgress;
57   m_heading = heading;
58   m_line = line;
59   SetFileOperation(action, items, strDestFile);
60 }
61
62 void CFileOperationJob::SetFileOperation(FileAction action, CFileItemList &items, const CStdString &strDestFile)
63 {
64   m_action = action;
65   m_strDestFile = strDestFile;
66
67   m_items.Clear();
68   for (int i = 0; i < items.Size(); i++)
69     m_items.Add(CFileItemPtr(new CFileItem(*items[i])));
70 }
71
72 bool CFileOperationJob::DoWork()
73 {
74   FileOperationList ops;
75   double totalTime = 0.0;
76
77   if (m_displayProgress)
78   {
79     CGUIDialogExtendedProgressBar* dialog =
80       (CGUIDialogExtendedProgressBar*)g_windowManager.GetWindow(WINDOW_DIALOG_EXT_PROGRESS);
81     m_handle = dialog->GetHandle(GetActionString(m_action));
82   }
83
84   bool success = DoProcess(m_action, m_items, m_strDestFile, ops, totalTime);
85
86   unsigned int size = ops.size();
87
88   double opWeight = 100.0 / totalTime;
89   double current = 0.0;
90
91   for (unsigned int i = 0; i < size && success; i++)
92     success &= ops[i].ExecuteOperation(this, current, opWeight);
93
94   if (m_handle)
95     m_handle->MarkFinished();
96
97   return success;
98 }
99
100 bool CFileOperationJob::DoProcessFile(FileAction action, const CStdString& strFileA, const CStdString& strFileB, FileOperationList &fileOperations, double &totalTime)
101 {
102   int64_t time = 1;
103
104   if (action == ActionCopy || action == ActionReplace || (action == ActionMove && !CanBeRenamed(strFileA, strFileB)))
105   {
106     struct __stat64 data;
107     if(CFile::Stat(strFileA, &data) == 0)
108       time += data.st_size;
109   }
110
111   fileOperations.push_back(CFileOperation(action, strFileA, strFileB, time));
112
113   totalTime += time;
114
115   return true;
116 }
117
118 bool CFileOperationJob::DoProcessFolder(FileAction action, const CStdString& strPath, const CStdString& strDestFile, FileOperationList &fileOperations, double &totalTime)
119 {
120   // check whether this folder is a filedirectory - if so, we don't process it's contents
121   CFileItem item(strPath, false);
122   IFileDirectory *file = CFileDirectoryFactory::Create(strPath, &item);
123   if (file)
124   {
125     delete file;
126     return true;
127   }
128   CLog::Log(LOGDEBUG,"FileManager, processing folder: %s",strPath.c_str());
129   CFileItemList items;
130   //m_rootDir.GetDirectory(strPath, items);
131   CDirectory::GetDirectory(strPath, items, "", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_GET_HIDDEN);
132   for (int i = 0; i < items.Size(); i++)
133   {
134     CFileItemPtr pItem = items[i];
135     pItem->Select(true);
136     CLog::Log(LOGDEBUG,"  -- %s",pItem->GetPath().c_str());
137   }
138
139   if (!DoProcess(action, items, strDestFile, fileOperations, totalTime)) return false;
140
141   if (action == ActionMove)
142   {
143     fileOperations.push_back(CFileOperation(ActionDeleteFolder, strPath, "", 1));
144     totalTime += 1.0;
145   }
146
147   return true;
148 }
149
150 bool CFileOperationJob::DoProcess(FileAction action, CFileItemList & items, const CStdString& strDestFile, FileOperationList &fileOperations, double &totalTime)
151 {
152   for (int iItem = 0; iItem < items.Size(); ++iItem)
153   {
154     CFileItemPtr pItem = items[iItem];
155     if (pItem->IsSelected())
156     {
157       CStdString strNoSlash = pItem->GetPath();
158       URIUtils::RemoveSlashAtEnd(strNoSlash);
159       CStdString strFileName = URIUtils::GetFileName(strNoSlash);
160
161       // special case for upnp
162       if (URIUtils::IsUPnP(items.GetPath()) || URIUtils::IsUPnP(pItem->GetPath()))
163       {
164         // get filename from label instead of path
165         strFileName = pItem->GetLabel();
166
167         if(!pItem->m_bIsFolder && !URIUtils::HasExtension(strFileName))
168         {
169           // FIXME: for now we only work well if the url has the extension
170           // we should map the content type to the extension otherwise
171           strFileName += URIUtils::GetExtension(pItem->GetPath());
172         }
173
174         strFileName = CUtil::MakeLegalFileName(strFileName);
175       }
176
177       CStdString strnewDestFile;
178       if(!strDestFile.empty()) // only do this if we have a destination
179         strnewDestFile = URIUtils::ChangeBasePath(pItem->GetPath(), strFileName, strDestFile); // Convert (URL) encoding + slashes (if source / target differ)
180
181       if (pItem->m_bIsFolder)
182       {
183         // in ActionReplace mode all subdirectories will be removed by the below
184         // DoProcessFolder(ActionDelete) call as well, so ActionCopy is enough when
185         // processing those
186         FileAction subdirAction = (action == ActionReplace) ? ActionCopy : action;
187         // create folder on dest. drive
188         if (action != ActionDelete && action != ActionDeleteFolder)
189           DoProcessFile(ActionCreateFolder, strnewDestFile, "", fileOperations, totalTime);
190         if (action == ActionReplace && CDirectory::Exists(strnewDestFile))
191           DoProcessFolder(ActionDelete, strnewDestFile, "", fileOperations, totalTime);
192         if (!DoProcessFolder(subdirAction, pItem->GetPath(), strnewDestFile, fileOperations, totalTime))
193           return false;
194         if (action == ActionDelete || action == ActionDeleteFolder)
195           DoProcessFile(ActionDeleteFolder, pItem->GetPath(), "", fileOperations, totalTime);
196       }
197       else
198         DoProcessFile(action, pItem->GetPath(), strnewDestFile, fileOperations, totalTime);
199     }
200   }
201   return true;
202 }
203
204 CFileOperationJob::CFileOperation::CFileOperation(FileAction action, const CStdString &strFileA, const CStdString &strFileB, int64_t time) : m_action(action), m_strFileA(strFileA), m_strFileB(strFileB), m_time(time)
205 {
206 }
207
208 struct DataHolder
209 {
210   CFileOperationJob *base;
211   double current;
212   double opWeight;
213 };
214
215 CStdString CFileOperationJob::GetActionString(FileAction action)
216 {
217   CStdString result;
218   switch (action)
219   {
220     case ActionCopy:
221     case ActionReplace:
222       result = g_localizeStrings.Get(115);
223       break;
224     case ActionMove:
225       result = g_localizeStrings.Get(116);
226       break;
227     case ActionDelete:
228     case ActionDeleteFolder:
229       result = g_localizeStrings.Get(117);
230       break;
231     case ActionCreateFolder:
232       result = g_localizeStrings.Get(119);
233       break;
234     default:
235       break;
236   }
237
238   return result;
239 }
240
241 bool CFileOperationJob::CFileOperation::ExecuteOperation(CFileOperationJob *base, double &current, double opWeight)
242 {
243   bool bResult = true;
244
245   base->m_currentFile = CURL(m_strFileA).GetFileNameWithoutPath();
246   base->m_currentOperation = GetActionString(m_action);
247
248   if (base->ShouldCancel((unsigned)current, 100))
249     return false;
250
251   if (base->m_handle)
252   {
253     base->m_handle->SetText(base->GetCurrentFile());
254     base->m_handle->SetPercentage((float)current);
255   }
256
257   DataHolder data = {base, current, opWeight};
258
259   switch (m_action)
260   {
261     case ActionCopy:
262     case ActionReplace:
263     {
264       CLog::Log(LOGDEBUG,"FileManager: copy %s -> %s\n", m_strFileA.c_str(), m_strFileB.c_str());
265
266       bResult = CFile::Cache(m_strFileA, m_strFileB, this, &data);
267     }
268     break;
269     case ActionMove:
270     {
271       CLog::Log(LOGDEBUG,"FileManager: move %s -> %s\n", m_strFileA.c_str(), m_strFileB.c_str());
272
273       if (CanBeRenamed(m_strFileA, m_strFileB))
274         bResult = CFile::Rename(m_strFileA, m_strFileB);
275       else if (CFile::Cache(m_strFileA, m_strFileB, this, &data))
276         bResult = CFile::Delete(m_strFileA);
277       else
278         bResult = false;
279     }
280     break;
281     case ActionDelete:
282     {
283       CLog::Log(LOGDEBUG,"FileManager: delete %s\n", m_strFileA.c_str());
284
285       bResult = CFile::Delete(m_strFileA);
286     }
287     break;
288     case ActionDeleteFolder:
289     {
290       CLog::Log(LOGDEBUG,"FileManager: delete folder %s\n", m_strFileA.c_str());
291
292       bResult = CDirectory::Remove(m_strFileA);
293     }
294     break;
295     case ActionCreateFolder:
296     {
297       CLog::Log(LOGDEBUG,"FileManager: create folder %s\n", m_strFileA.c_str());
298
299       bResult = CDirectory::Create(m_strFileA);
300     }
301     break;
302   }
303
304   current += (double)m_time * opWeight;
305
306   return bResult;
307 }
308
309 inline bool CFileOperationJob::CanBeRenamed(const CStdString &strFileA, const CStdString &strFileB)
310 {
311 #ifndef TARGET_POSIX
312   if (strFileA[1] == ':' && strFileA[0] == strFileB[0])
313     return true;
314 #else
315   if (URIUtils::IsHD(strFileA) && URIUtils::IsHD(strFileB))
316     return true;
317 #endif
318   return false;
319 }
320
321 void CFileOperationJob::CFileOperation::Debug()
322 {
323   printf("%i | %s > %s\n", m_action, m_strFileA.c_str(), m_strFileB.c_str());
324 }
325
326 bool CFileOperationJob::CFileOperation::OnFileCallback(void* pContext, int ipercent, float avgSpeed)
327 {
328   DataHolder *data = (DataHolder *)pContext;
329   double current = data->current + ((double)ipercent * data->opWeight * (double)m_time)/ 100.0;
330
331   if (avgSpeed > 1000000.0f)
332     data->base->m_avgSpeed = StringUtils::Format("%.1f MB/s", avgSpeed / 1000000.0f);
333   else
334     data->base->m_avgSpeed = StringUtils::Format("%.1f KB/s", avgSpeed / 1000.0f);
335
336   if (data->base->m_handle)
337   {
338     CStdString line;
339     line = StringUtils::Format("%s (%s)",
340                                data->base->GetCurrentFile().c_str(),
341                                data->base->GetAverageSpeed().c_str());
342     data->base->m_handle->SetText(line);
343     data->base->m_handle->SetPercentage((float)current);
344   }
345
346   return !data->base->ShouldCancel((unsigned)current, 100);
347 }
348
349 bool CFileOperationJob::operator==(const CJob* job) const
350 {
351   if (strcmp(job->GetType(),GetType()) == 0)
352   {
353     const CFileOperationJob* rjob = dynamic_cast<const CFileOperationJob*>(job);
354     if (rjob)
355     {
356       if (GetAction() == rjob->GetAction() &&
357           m_strDestFile == rjob->m_strDestFile &&
358           m_items.Size() == rjob->m_items.Size())
359       {
360         for (int i=0;i<m_items.Size();++i)
361         {
362           if (m_items[i]->GetPath() != rjob->m_items[i]->GetPath() ||
363               m_items[i]->IsSelected() != rjob->m_items[i]->IsSelected())
364             return false;
365         }
366         return true;
367       }
368     }
369   }
370   return false;
371 }