1// 2// This file is part of the aMule Project. 3// 4// Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org ) 5// Copyright (c) 2002-2011 Merkur ( devs@emule-project.net / http://www.emule-project.net ) 6// 7// Any parts of this program derived from the xMule, lMule or eMule project, 8// or contributed by third-party developers are copyrighted by their 9// respective authors. 10// 11// This program is free software; you can redistribute it and/or modify 12// it under the terms of the GNU General Public License as published by 13// the Free Software Foundation; either version 2 of the License, or 14// (at your option) any later version. 15// 16// This program is distributed in the hope that it will be useful, 17// but WITHOUT ANY WARRANTY; without even the implied warranty of 18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19// GNU General Public License for more details. 20// 21// You should have received a copy of the GNU General Public License 22// along with this program; if not, write to the Free Software 23// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 24// 25 26#include <wx/app.h> 27 28#include "PartFileConvert.h" 29 30#include "amule.h" 31#include "DownloadQueue.h" 32#include <common/Format.h> 33#include "Logger.h" 34#include "PartFile.h" 35#include "Preferences.h" 36#include "SharedFileList.h" 37#include <common/FileFunctions.h> 38#include "OtherFunctions.h" 39#include "GuiEvents.h" 40#include "DataToText.h" 41 42static unsigned s_nextJobId = 0; 43 44 45struct ConvertJob { 46 unsigned id; 47 CPath folder; 48 CPath filename; 49 wxString filehash; 50 int format; 51 ConvStatus state; 52 uint32_t size; 53 uint32_t spaceneeded; 54 uint8 partmettype; 55 bool removeSource; 56 ConvertJob() { id=s_nextJobId++; size=0; spaceneeded=0; partmettype=PMT_UNKNOWN; removeSource=true; } 57}; 58 59ConvertInfo::ConvertInfo(ConvertJob *job) 60 : id(job->id), 61 folder(job->folder), filename(job->filename), filehash(job->filehash), 62 state(job->state), size(job->size), spaceneeded(job->spaceneeded) 63{} 64 65 66wxThread* CPartFileConvert::s_convertPfThread = NULL; 67std::list<ConvertJob*> CPartFileConvert::s_jobs; 68ConvertJob* CPartFileConvert::s_pfconverting = NULL; 69wxMutex CPartFileConvert::s_mutex; 70 71 72int CPartFileConvert::ScanFolderToAdd(const CPath& folder, bool deletesource) 73{ 74 int count = 0; 75 CDirIterator finder(folder); 76 77 CPath file = finder.GetFirstFile(CDirIterator::File, wxT("*.part.met")); 78 while (file.IsOk()) { 79 ConvertToeMule(folder.JoinPaths(file), deletesource); 80 file = finder.GetNextFile(); 81 count++; 82 } 83 /* Shareaza 84 file = finder.GetFirstFile(CDirIterator::File, wxT("*.sd")); 85 while (!file.IsEmpty()) { 86 ConvertToeMule(file, deletesource); 87 file = finder.GetNextFile(); 88 count++; 89 } 90 */ 91 92 file = finder.GetFirstFile(CDirIterator::Dir, wxT("*.*")); 93 while (file.IsOk()) { 94 ScanFolderToAdd(folder.JoinPaths(file), deletesource); 95 96 file = finder.GetNextFile(); 97 } 98 99 return count; 100} 101 102void CPartFileConvert::ConvertToeMule(const CPath& file, bool deletesource) 103{ 104 if (!file.FileExists()) { 105 return; 106 } 107 108 ConvertJob* newjob = new ConvertJob(); 109 newjob->folder = file; 110 newjob->removeSource = deletesource; 111 newjob->state = CONV_QUEUE; 112 113 wxMutexLocker lock(s_mutex); 114 115 s_jobs.push_back(newjob); 116 117 Notify_ConvertUpdateJobInfo(newjob); 118 119 StartThread(); 120} 121 122void CPartFileConvert::StartThread() 123{ 124 if (!s_convertPfThread) { 125 s_convertPfThread = new CPartFileConvert(); 126 127 switch ( s_convertPfThread->Create() ) { 128 case wxTHREAD_NO_ERROR: 129 AddDebugLogLineN( logPfConvert, wxT("A new thread has been created.") ); 130 break; 131 case wxTHREAD_RUNNING: 132 AddDebugLogLineC( logPfConvert, wxT("Error, attempt to create an already running thread!") ); 133 break; 134 case wxTHREAD_NO_RESOURCE: 135 AddDebugLogLineC( logPfConvert, wxT("Error, attempt to create a thread without resources!") ); 136 break; 137 default: 138 AddDebugLogLineC( logPfConvert, wxT("Error, unknown error attempting to create a thread!") ); 139 } 140 141 // The thread shouldn't hog the CPU, as it will already be hogging the HD 142 s_convertPfThread->SetPriority(WXTHREAD_MIN_PRIORITY); 143 144 s_convertPfThread->Run(); 145 } 146} 147 148void CPartFileConvert::StopThread() 149{ 150 if (s_convertPfThread) { 151 s_convertPfThread->Delete(); 152 } else { 153 return; 154 } 155 156 AddLogLineNS(_("Waiting for partfile convert thread to die...")); 157 while (s_convertPfThread) { 158 wxSleep(1); 159 } 160} 161 162wxThread::ExitCode CPartFileConvert::Entry() 163{ 164 int imported = 0; 165 166 for (;;) 167 { 168 // search next queued job and start it 169 { 170 wxMutexLocker lock(s_mutex); 171 s_pfconverting = NULL; 172 for (std::list<ConvertJob*>::iterator it = s_jobs.begin(); it != s_jobs.end(); ++it) { 173 if ((*it)->state == CONV_QUEUE) { 174 s_pfconverting = *it; 175 break; 176 } 177 } 178 } 179 180 if (s_pfconverting) { 181 { 182 wxMutexLocker lock(s_mutex); 183 s_pfconverting->state = CONV_INPROGRESS; 184 } 185 186 Notify_ConvertUpdateJobInfo(s_pfconverting); 187 188 ConvStatus convertResult = performConvertToeMule(s_pfconverting->folder); 189 { 190 wxMutexLocker lock(s_mutex); 191 s_pfconverting->state = convertResult; 192 } 193 194 if (s_pfconverting->state == CONV_OK) { 195 ++imported; 196 } 197 198 Notify_ConvertUpdateJobInfo(s_pfconverting); 199 200 AddLogLineC(CFormat(_("Importing %s: %s")) % s_pfconverting->folder % GetConversionState(s_pfconverting->state)); 201 202 if (TestDestroy()) { 203 wxMutexLocker lock(s_mutex); 204 DeleteContents(s_jobs); 205 break; 206 } 207 } else { 208 break; // nothing more to do now 209 } 210 } 211 212 // clean up 213 Notify_ConvertClearInfos(); 214 215 if (imported) { 216 theApp->sharedfiles->PublishNextTurn(); 217 } 218 219 AddDebugLogLineN(logPfConvert, wxT("No more jobs on queue, exiting from thread.")); 220 221 s_convertPfThread = NULL; 222 223 return NULL; 224} 225 226ConvStatus CPartFileConvert::performConvertToeMule(const CPath& fileName) 227{ 228 wxString filepartindex, buffer; 229 unsigned fileindex; 230 231 CPath folder = fileName.GetPath(); 232 CPath partfile = fileName.GetFullName(); 233 CPath newfilename; 234 235 CDirIterator finder(folder); 236 237 Notify_ConvertUpdateProgressFull(0, _("Reading temp folder"), s_pfconverting->folder.GetPrintable()); 238 239 filepartindex = partfile.RemoveAllExt().GetRaw(); 240 241 Notify_ConvertUpdateProgress(4, _("Retrieving basic information from download info file")); 242 243 CPartFile* file = new CPartFile(); 244 s_pfconverting->partmettype = file->LoadPartFile(folder, partfile, false, true); 245 246 switch (s_pfconverting->partmettype) { 247 case PMT_UNKNOWN: 248 case PMT_BADFORMAT: 249 delete file; 250 return CONV_BADFORMAT; 251 } 252 253 CPath oldfile = folder.JoinPaths(partfile.RemoveExt()); 254 255 { 256 wxMutexLocker lock(s_mutex); 257 s_pfconverting->size = file->GetFileSize(); 258 s_pfconverting->filename = file->GetFileName(); 259 s_pfconverting->filehash = file->GetFileHash().Encode(); 260 } 261 262 Notify_ConvertUpdateJobInfo(s_pfconverting); 263 264 if (theApp->downloadqueue->GetFileByID(file->GetFileHash())) { 265 delete file; 266 return CONV_ALREADYEXISTS; 267 } 268 269 if (s_pfconverting->partmettype == PMT_SPLITTED) { 270 char *ba = new char [PARTSIZE]; 271 272 try { 273 CFile inputfile; 274 275 // just count 276 unsigned maxindex = 0; 277 unsigned partfilecount = 0; 278 CPath filePath = finder.GetFirstFile(CDirIterator::File, filepartindex + wxT(".*.part")); 279 while (filePath.IsOk()) { 280 long l; 281 ++partfilecount; 282 filePath.GetFullName().RemoveExt().GetExt().ToLong(&l); 283 fileindex = (unsigned)l; 284 filePath = finder.GetNextFile(); 285 if (fileindex > maxindex) maxindex = fileindex; 286 } 287 float stepperpart; 288 { 289 wxMutexLocker lock(s_mutex); 290 if (partfilecount > 0) { 291 stepperpart = (80.0f / partfilecount); 292 if (maxindex * PARTSIZE <= s_pfconverting->size) { 293 s_pfconverting->spaceneeded = maxindex * PARTSIZE; 294 } else { 295 s_pfconverting->spaceneeded = s_pfconverting->size; 296 } 297 } else { 298 stepperpart = 80.0f; 299 s_pfconverting->spaceneeded = 0; 300 } 301 } 302 303 Notify_ConvertUpdateJobInfo(s_pfconverting); 304 305 sint64 freespace = CPath::GetFreeSpaceAt(thePrefs::GetTempDir()); 306 if (freespace != wxInvalidOffset) { 307 if (static_cast<uint64>(freespace) < maxindex * PARTSIZE) { 308 delete file; 309 delete [] ba; 310 return CONV_OUTOFDISKSPACE; 311 } 312 } 313 314 // create new partmetfile, and remember the new name 315 file->CreatePartFile(); 316 newfilename = file->GetFullName(); 317 318 Notify_ConvertUpdateProgress(8, _("Creating destination file")); 319 320 file->m_hpartfile.SetLength( s_pfconverting->spaceneeded ); 321 322 unsigned curindex = 0; 323 CPath filename = finder.GetFirstFile(CDirIterator::File, filepartindex + wxT(".*.part")); 324 while (filename.IsOk()) { 325 // stats 326 ++curindex; 327 buffer = CFormat(_("Loading data from old download file (%u of %u)")) % curindex % partfilecount; 328 329 Notify_ConvertUpdateProgress(10 + (curindex * stepperpart), buffer); 330 331 long l; 332 filename.GetFullName().RemoveExt().GetExt().ToLong(&l); 333 fileindex = (unsigned)l; 334 if (fileindex == 0) { 335 filename = finder.GetNextFile(); 336 continue; 337 } 338 339 uint32 chunkstart = (fileindex - 1) * PARTSIZE; 340 341 // open, read data of the part-part-file into buffer, close file 342 inputfile.Open(filename, CFile::read); 343 uint64 toReadWrite = std::min<uint64>(PARTSIZE, inputfile.GetLength()); 344 inputfile.Read(ba, toReadWrite); 345 inputfile.Close(); 346 347 buffer = CFormat(_("Saving data block into new single download file (%u of %u)")) % curindex % partfilecount; 348 349 Notify_ConvertUpdateProgress(10 + (curindex * stepperpart), buffer); 350 351 // write the buffered data 352 file->m_hpartfile.WriteAt(ba, chunkstart, toReadWrite); 353 354 filename = finder.GetNextFile(); 355 } 356 delete[] ba; 357 } catch (const CSafeIOException& e) { 358 AddDebugLogLineC(logPfConvert, wxT("IO error while converting partfiles: ") + e.what()); 359 360 delete[] ba; 361 file->Delete(); 362 return CONV_IOERROR; 363 } 364 365 file->m_hpartfile.Close(); 366 } 367 // import an external common format partdownload 368 else //if (pfconverting->partmettype==PMT_DEFAULTOLD || pfconverting->partmettype==PMT_NEWOLD || Shareaza ) 369 { 370 if (!s_pfconverting->removeSource) { 371 wxMutexLocker lock(s_mutex); 372 s_pfconverting->spaceneeded = oldfile.GetFileSize(); 373 } 374 375 Notify_ConvertUpdateJobInfo(s_pfconverting); 376 377 sint64 freespace = CPath::GetFreeSpaceAt(thePrefs::GetTempDir()); 378 if (freespace == wxInvalidOffset) { 379 delete file; 380 return CONV_IOERROR; 381 } else if (freespace < s_pfconverting->spaceneeded) { 382 delete file; 383 return CONV_OUTOFDISKSPACE; 384 } 385 386 file->CreatePartFile(); 387 newfilename = file->GetFullName(); 388 389 file->m_hpartfile.Close(); 390 391 bool ret = false; 392 393 Notify_ConvertUpdateProgress(92, _("Copy")); 394 395 CPath::RemoveFile(newfilename.RemoveExt()); 396 if (!oldfile.FileExists()) { 397 // data file does not exist. well, then create a 0 byte big one 398 CFile datafile; 399 ret = datafile.Create(newfilename.RemoveExt()); 400 } else if (s_pfconverting->removeSource) { 401 ret = CPath::RenameFile(oldfile, newfilename.RemoveExt()); 402 } else { 403 ret = CPath::CloneFile(oldfile, newfilename.RemoveExt(), false); 404 } 405 if (!ret) { 406 file->Delete(); 407 //delete file; 408 return CONV_FAILED; 409 } 410 411 } 412 413 Notify_ConvertUpdateProgress(94, _("Retrieving source downloadfile information")); 414 415 CPath::RemoveFile(newfilename); 416 if (s_pfconverting->removeSource) { 417 CPath::RenameFile(folder.JoinPaths(partfile), newfilename); 418 } else { 419 CPath::CloneFile(folder.JoinPaths(partfile), newfilename, false); 420 } 421 422 file->m_hashlist.clear(); 423 424 if (!file->LoadPartFile(thePrefs::GetTempDir(), file->GetPartMetFileName(), false)) { 425 //delete file; 426 file->Delete(); 427 return CONV_BADFORMAT; 428 } 429 430 if (s_pfconverting->partmettype == PMT_NEWOLD || s_pfconverting->partmettype == PMT_SPLITTED) { 431 file->SetCompletedSize(file->transferred); 432 file->m_iGainDueToCompression = 0; 433 file->m_iLostDueToCorruption = 0; 434 } 435 436 Notify_ConvertUpdateProgress(100, _("Adding download and saving new partfile")); 437 438 theApp->downloadqueue->AddDownload(file, thePrefs::AddNewFilesPaused(), 0); 439 file->SavePartFile(); 440 441 if (file->GetStatus(true) == PS_READY) { 442 theApp->sharedfiles->SafeAddKFile(file); // part files are always shared files 443 } 444 445 if (s_pfconverting->removeSource) { 446 CPath oldFile = finder.GetFirstFile(CDirIterator::File, filepartindex + wxT(".*")); 447 while (oldFile.IsOk()) { 448 CPath::RemoveFile(folder.JoinPaths(oldFile)); 449 oldFile = finder.GetNextFile(); 450 } 451 452 if (s_pfconverting->partmettype == PMT_SPLITTED) { 453 CPath::RemoveDir(folder); 454 } 455 } 456 457 return CONV_OK; 458} 459 460// Notification handlers 461 462void CPartFileConvert::RemoveJob(unsigned id) 463{ 464 wxMutexLocker lock(s_mutex); 465 for (std::list<ConvertJob*>::iterator it = s_jobs.begin(); it != s_jobs.end(); ++it) { 466 if ((*it)->id == id && (*it)->state != CONV_INPROGRESS) { 467 ConvertJob *job = *it; 468 s_jobs.erase(it); 469 Notify_ConvertRemoveJobInfo(id); 470 delete job; 471 break; 472 } 473 } 474} 475 476void CPartFileConvert::RetryJob(unsigned id) 477{ 478 wxMutexLocker lock(s_mutex); 479 for (std::list<ConvertJob*>::iterator it = s_jobs.begin(); it != s_jobs.end(); ++it) { 480 if ((*it)->id == id && (*it)->state != CONV_INPROGRESS && (*it)->state != CONV_OK) { 481 (*it)->state = CONV_QUEUE; 482 Notify_ConvertUpdateJobInfo(*it); 483 StartThread(); 484 break; 485 } 486 } 487} 488 489void CPartFileConvert::ReaddAllJobs() 490{ 491 wxMutexLocker lock(s_mutex); 492 for (std::list<ConvertJob*>::iterator it = s_jobs.begin(); it != s_jobs.end(); ++it) { 493 Notify_ConvertUpdateJobInfo(*it); 494 } 495} 496