// // This file is part of the aMule Project. // // Copyright (c) 2003-2006 aMule Team ( admin@amule.org / http://www.amule.org ) // // Any parts of this program derived from the xMule, lMule or eMule project, // or contributed by third-party developers are copyrighted by their // respective authors. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA // #include // Needed before any other wx/*.h #include // Needed for wxCommandEvent #include // Needed for wxStopWatch #include // Needed for wxArrayString #include // Needed for wxFileName::GetPathSeperator() #include #include "AddFileThread.h" // Interface declarations #include // Needed for unicode2char #include "amule.h" // Needed for theApp #include "OPCodes.h" // Needed for TM_HASHTHREADFINISHED #include "PartFile.h" // Needed for CKnownFile and CPartFile #include "CFile.h" // Needed for CFile #include "FileFunctions.h" // Needed for GetLastModificationTime() #include "AICHSyncThread.h" // Needed for CAICHSyncThread #include "Logger.h" // Needed for AddLogLine #include #include "InternalEvents.h" // Needed for CMuleInternalEvent #include #include const byte default_zero_hash[16] = { 0x31, 0xD6, 0xCF, 0xE0, 0xD1, 0x6A, 0xE9, 0x31, 0xB7, 0x3C, 0x59, 0xD7, 0xE0, 0xC0, 0x89, 0xC0 }; //! The mutex used to protect variables and datastructures used by the thread. static wxMutex s_mutex; //! Is the hasher active. Does not mean that there are any threads running. static bool s_running; // static CAddFileThread* s_thread; /** * Container for queued files. */ struct QueuedFile { //! The full path to the file. wxString m_path; //! The name of the file. wxString m_name; //! The PartFile owning this file in case of a final hashing (completing). const CPartFile* m_owner; }; //! The queue-type typedef std::list FileQueue; //! The queue of files to be hashed static FileQueue s_queue; CAddFileThread::CAddFileThread() : wxThread(wxTHREAD_DETACHED) { } bool CAddFileThread::IsRunning() { wxMutexLocker lock( s_mutex ); return s_running; } void CAddFileThread::CreateNewThread() { if ( !s_thread && !s_queue.empty() ) { // Ensure that the AICH thread isn't running if ( CAICHSyncThread::IsRunning() ) { CAICHSyncThread::Stop(); } AddLogLineM( false, _("Hasher: Creating new thread.") ); s_thread = new CAddFileThread(); wxThreadError state = s_thread->Create(); if (state != wxTHREAD_NO_ERROR) { AddDebugLogLineM(true, logHasher, wxString::Format(wxT("Error while creating file-hashing thread: %d"), state)); // Delete() must be called in this case acording to the docs. s_thread->Delete(); delete s_thread; s_thread = NULL; } // The threads shouldn't be hugging the CPU, as it will already be hugging the HD s_thread->SetPriority(WXTHREAD_MIN_PRIORITY); state = s_thread->Run(); if (state != wxTHREAD_NO_ERROR) { AddDebugLogLineM(true, logHasher, wxString::Format(wxT("Error while attempting to run file-hashing thread: %d"), state)); // Delete() must be called in this case acording to the docs. s_thread->Delete(); delete s_thread; s_thread = NULL; } } } void CAddFileThread::Start() { wxASSERT( wxThread::IsMain() ); wxMutexLocker lock( s_mutex ); if ( s_running ) { AddDebugLogLineM( true, logHasher, wxT("Warning, Start() called while already running.") ); } else { s_running = true; // No threads are running yet, so all files are non-busy. CreateNewThread(); } } void CAddFileThread::Stop() { wxASSERT( wxThread::IsMain() ); { wxMutexLocker lock(s_mutex); if ( !s_running ) { AddDebugLogLineM( true, logHasher, wxT("Warning, Attempted to stop already stopped hasher!") ); return; } s_running = false; } if ( s_thread ) { AddLogLineM( false, _("Hasher: Signaling for remaining threads to terminate.") ); // Wait for all threads to die while ( s_thread ) { // Sleep for 1/100 of a second to avoid clobbering the mutex MilliSleep(10); // Flush any pending log-entries caused by the thread. CLogger::FlushPendingEntries(); } } } int CAddFileThread::GetFileCount() { wxMutexLocker lock( s_mutex ); return s_queue.size(); } void CAddFileThread::AddFile(const wxString& path, const wxString& name, const CPartFile* part) { AddDebugLogLineM( false, logHasher, wxString( wxT("Adding file to queue: ") ) + path ); wxMutexLocker lock( s_mutex ); // Avoid duplicate files for ( FileQueue::iterator it = s_queue.begin(); it != s_queue.end(); ++it ) { if ( it->m_path == path && it->m_name == name ) { return; } } QueuedFile newfile = { path, name, part }; // If it's a partfile (part != NULL), then add it to the front so that // it gets hashed sooner, otherwise add it to the back if ( part == NULL ) { s_queue.push_back( newfile ); } else { s_queue.push_front( newfile ); } // Check if we need to start an actual thread. CreateNewThread(); } wxThread::ExitCode CAddFileThread::Entry() { // The current file being hashed. QueuedFile current; // Continue to loop until there's nothing to do, or someone kills the threads while ( IsRunning() ) { { wxMutexLocker lock(s_mutex); if ( s_queue.empty() ) { AddLogLineM( false, _("Hasher: No files on queue, stopping thread.") ); break; } current = s_queue.front(); s_queue.pop_front(); } wxString filename = JoinPaths(current.m_path, current.m_name); // The file currently getting hashed CFile file; // Attempt to open the file if ( !file.Open( filename, CFile::read ) ) { AddDebugLogLineM( true, logHasher, wxT("Warning, failed to open file, skipping: " ) + filename ); continue; } // We only support files <= 4gigs if (file.GetLength() > MAX_FILE_SIZE) { AddDebugLogLineM( true, logHasher, wxT("Warning, file is bigger than 4GB, skipping: ") + filename ); continue; } // Zero-size partfiles should be hashed, but not zero-sized shared-files. if (!file.GetLength() && !current.m_owner) { AddDebugLogLineM( true, logHasher, wxT("Warning, 0-size file, skipping: ") + filename ); continue; } // Create a CKnownFile to contain the result CKnownFile* knownfile = new CKnownFile(); // Set initial values knownfile->m_strFilePath = current.m_path; knownfile->SetFileName(current.m_name); knownfile->SetFileSize( file.GetLength() ); knownfile->date = GetLastModificationTime( filename ); knownfile->m_AvailPartFrequency.Insert(0, 0, knownfile->GetPartCount() ); // We can only create the AICH hashset if the file is a knownfile or // if the partfile is complete, since the MD4 hashset is checked first, // so that the AICH hashset only gets assigned if the MD4 hashset // matches what we expected. Due to the rareity of post-completion // corruptions, this gives us a nice speedup in almost all cases. bool needsAICH = !current.m_owner || current.m_owner->GetGapList().IsEmpty(); bool error = false; // Prepare the AICH hashset if needed if ( needsAICH ) { knownfile->GetAICHHashset()->FreeHashSet(); AddLogLineM( false, CFormat( _("Hasher: Starting to create MD4 and AICH hash for file: %s")) % current.m_name ); } else { AddLogLineM( false, CFormat( _("Hasher: Starting to create MD4 hash for file: %s")) % current.m_name ); } // This loops creates the part-hashes, loop-de-loop. while ( !error && ( file.GetPosition() < file.GetLength() ) && IsRunning() ) { error = !CreateNextPartHash( &file, knownfile, needsAICH ); } // Checking if something went wrong if ( error ) { AddDebugLogLineM( true, logHasher, wxT("Error while reading file, skipping: ") + current.m_name ); // Remove the temp-result delete knownfile; continue; } if ( IsRunning() ) { // If the file is < PARTSIZE, then the filehash is that one hash, // otherwise, the filehash is the hash of the parthashes if ( knownfile->hashlist.GetCount() == 1 ) { knownfile->m_abyFileHash = knownfile->hashlist[0]; knownfile->hashlist.Clear(); } else { unsigned int len = knownfile->hashlist.GetCount() * 16; byte data[len]; for (size_t i = 0; i < knownfile->hashlist.GetCount(); i++) { memcpy( data + 16*i, knownfile->hashlist[i].GetHash(), 16 ); } byte hash[16]; knownfile->CreateHashFromString( data, len, hash, NULL ); knownfile->m_abyFileHash.SetHash( (byte*)hash ); } // Did we create a AICH hashset? if ( needsAICH ) { CAICHHashSet* m_pAICHHashSet = knownfile->GetAICHHashset(); m_pAICHHashSet->ReCalculateHash(false); if ( m_pAICHHashSet->VerifyHashTree(true) ) { m_pAICHHashSet->SetStatus(AICH_HASHSETCOMPLETE); if ( !m_pAICHHashSet->SaveHashSet() ) { AddDebugLogLineM( true, logHasher, wxT("Warning, failed to save AICH hashset for file: ") + current.m_name ); } } } // Pass on the completion CMuleInternalEvent evt(wxEVT_CORE_FILE_HASHING_FINISHED); evt.SetClientData( knownfile ); evt.SetExtraLong( (long)current.m_owner ); AddLogLineM( false, CFormat( _("Hasher: Finished hashing file: %s")) % current.m_name ); wxPostEvent(&theApp, evt); } else { delete knownfile; } } // Notify that the thread has died AddLogLineM( false, _("Hasher: A thread has died.") ); { wxMutexLocker lock(s_mutex); s_thread = NULL; } // Notify the core. CMuleInternalEvent evt(wxEVT_CORE_FILE_HASHING_SHUTDOWN); wxPostEvent(&theApp, evt); return 0; } bool CAddFileThread::CreateNextPartHash( CFile* file, CKnownFile* owner, bool createAICH ) { // Well read a PARTSIZE bytes per cycle uint32 cur_length = PARTSIZE; if ( file->GetPosition() + PARTSIZE > file->GetLength() ) { // Less than PARTSIZE left, reduce read-length cur_length = file->GetLength() - file->GetPosition(); } // Is this file EXACTLY (n * PARTSIZE) long? bool zero_hash = ((file->GetPosition() + PARTSIZE) == file->GetLength()); // Sanity check, this shoulnd't happen due to the loop calling this function, but ... if ( cur_length == 0 ) { AddDebugLogLineM( true, logHasher, wxT("Warning, EOF in CreateNextPartHash!") ); return false; } byte* data = new byte[cur_length]; // Check for read errors try { file->Read(data, cur_length); } catch (const CIOFailureException& e) { delete[] data; AddDebugLogLineM(true, logHasher, wxT("IO failure while hashing file: ") + e.what()); return false; } // The MD4 hash byte hash[16]; // The AICH hash CAICHHashTree* pBlockAICHHashTree = NULL; // Obtain the AICH hash if wanted if ( createAICH ) { // Get the start-position of the current part uint32 position = file->GetPosition() - cur_length; pBlockAICHHashTree = owner->GetAICHHashset()->m_pHashTree.FindHash( position, cur_length); } // Create the md4 hash and perhaps a AICH hash owner->CreateHashFromString( data, cur_length, hash, pBlockAICHHashTree ); delete[] data; // Store the md4 hash owner->hashlist.Add( CMD4Hash(hash) ); // Kry This is because of the ed2k implementation for parts. A 2 * PARTSIZE // file i.e. will have 3 parts (check CKnownFile::SetFileSize for comments). // So we have to create the hash for the 0-size data, which will be the default // md4 hash for null data: 31D6CFE0D16AE931B73C59D7E0C089C0 if ( zero_hash ) { owner->hashlist.Add( CMD4Hash(default_zero_hash) ); } return true; }