mirror of
https://github.com/nzbget/nzbget.git
synced 2026-04-20 04:46:53 -04:00
344 lines
9.9 KiB
C++
344 lines
9.9 KiB
C++
// This file is part of par2cmdline (a PAR 2.0 compatible file verification and
|
|
// repair tool). See http://parchive.sourceforge.net for details of PAR 2.0.
|
|
//
|
|
// Copyright (c) 2003 Peter Brian Clements
|
|
//
|
|
// par2cmdline 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.
|
|
//
|
|
// par2cmdline 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
|
#include "nzbget.h"
|
|
#include "par2cmdline.h"
|
|
|
|
#ifdef _MSC_VER
|
|
#ifdef _DEBUG
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[]=__FILE__;
|
|
#define new DEBUG_NEW
|
|
#endif
|
|
#endif
|
|
|
|
Par2CreatorSourceFile::Par2CreatorSourceFile(void)
|
|
{
|
|
descriptionpacket = 0;
|
|
verificationpacket = 0;
|
|
diskfile = 0;
|
|
blockcount = 0;
|
|
//diskfilename;
|
|
//parfilename;
|
|
contextfull = 0;
|
|
}
|
|
|
|
Par2CreatorSourceFile::~Par2CreatorSourceFile(void)
|
|
{
|
|
delete descriptionpacket;
|
|
delete verificationpacket;
|
|
delete diskfile;
|
|
delete contextfull;
|
|
}
|
|
|
|
// Open the source file, compute the MD5 Hash of the whole file and the first
|
|
// 16k of the file, and then compute the FileId and store the results
|
|
// in a file description packet and a file verification packet.
|
|
|
|
bool Par2CreatorSourceFile::Open(CommandLine::NoiseLevel noiselevel, const CommandLine::ExtraFile &extrafile, u64 blocksize, bool deferhashcomputation)
|
|
{
|
|
// Get the filename and filesize
|
|
diskfilename = extrafile.FileName();
|
|
filesize = extrafile.FileSize();
|
|
|
|
// Work out how many blocks the file will be sliced into
|
|
blockcount = (u32)((filesize + blocksize-1) / blocksize);
|
|
|
|
// Determine what filename to record in the PAR2 files
|
|
string::size_type where;
|
|
if (string::npos != (where = diskfilename.find_last_of('\\')) ||
|
|
string::npos != (where = diskfilename.find_last_of('/')))
|
|
{
|
|
parfilename = diskfilename.substr(where+1);
|
|
}
|
|
else
|
|
{
|
|
parfilename = diskfilename;
|
|
}
|
|
|
|
// Create the Description and Verification packets
|
|
descriptionpacket = new DescriptionPacket;
|
|
descriptionpacket->Create(parfilename, filesize);
|
|
|
|
verificationpacket = new VerificationPacket;
|
|
verificationpacket->Create(blockcount);
|
|
|
|
// Create the diskfile object
|
|
diskfile = new DiskFile;
|
|
|
|
// Open the source file
|
|
if (!diskfile->Open(diskfilename, filesize))
|
|
return false;
|
|
|
|
// Do we want to defer the computation of the full file hash, and
|
|
// the block crc and hashes. This is only permitted if there
|
|
// is sufficient memory available to create all recovery blocks
|
|
// in one pass of the source files (i.e. chunksize == blocksize)
|
|
if (deferhashcomputation)
|
|
{
|
|
// Initialise a buffer to read the first 16k of the source file
|
|
size_t buffersize = 16 * 1024;
|
|
if (buffersize > filesize)
|
|
buffersize = (size_t)filesize;
|
|
char *buffer = new char[buffersize];
|
|
|
|
// Read the data from the file
|
|
if (!diskfile->Read(0, buffer, buffersize))
|
|
{
|
|
diskfile->Close();
|
|
delete [] buffer;
|
|
return false;
|
|
}
|
|
|
|
// Compute the hash of the data read from the file
|
|
MD5Context context;
|
|
context.Update(buffer, buffersize);
|
|
delete [] buffer;
|
|
MD5Hash hash;
|
|
context.Final(hash);
|
|
|
|
// Store the hash in the descriptionpacket and compute the file id
|
|
descriptionpacket->Hash16k(hash);
|
|
|
|
// Compute the fileid and store it in the verification packet.
|
|
descriptionpacket->ComputeFileId();
|
|
verificationpacket->FileId(descriptionpacket->FileId());
|
|
|
|
// Allocate an MD5 context for computing the file hash
|
|
// during the recovery data generation phase
|
|
contextfull = new MD5Context;
|
|
}
|
|
else
|
|
{
|
|
// Initialise a buffer to read the source file
|
|
size_t buffersize = 1024*1024;
|
|
if (buffersize > min(blocksize,filesize))
|
|
buffersize = (size_t)min(blocksize,filesize);
|
|
char *buffer = new char[buffersize];
|
|
|
|
// Get ready to start reading source file to compute the hashes and crcs
|
|
u64 offset = 0;
|
|
u32 blocknumber = 0;
|
|
u64 need = blocksize;
|
|
|
|
MD5Context filecontext;
|
|
MD5Context blockcontext;
|
|
u32 blockcrc = 0;
|
|
|
|
// Whilst we have not reached the end of the file
|
|
while (offset < filesize)
|
|
{
|
|
// Work out how much we can read
|
|
size_t want = (size_t)min(filesize-offset, (u64)buffersize);
|
|
|
|
// Read some data from the file into the buffer
|
|
if (!diskfile->Read(offset, buffer, want))
|
|
{
|
|
diskfile->Close();
|
|
delete [] buffer;
|
|
return false;
|
|
}
|
|
|
|
// If the new data passes the 16k boundary, compute the 16k hash for the file
|
|
if (offset < 16384 && offset + want >= 16384)
|
|
{
|
|
filecontext.Update(buffer, (size_t)(16384-offset));
|
|
|
|
MD5Context temp = filecontext;
|
|
MD5Hash hash;
|
|
temp.Final(hash);
|
|
|
|
// Store the 16k hash in the file description packet
|
|
descriptionpacket->Hash16k(hash);
|
|
|
|
if (offset + want > 16384)
|
|
{
|
|
filecontext.Update(&buffer[16384-offset], (size_t)(offset+want)-16384);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
filecontext.Update(buffer, want);
|
|
}
|
|
|
|
// Get ready to update block hashes and crcs
|
|
u32 used = 0;
|
|
|
|
// Whilst we have not used all of the data we just read
|
|
while (used < want)
|
|
{
|
|
// How much of it can we use for the current block
|
|
u32 use = (u32)min(need, (u64)(want-used));
|
|
|
|
blockcrc = ~0 ^ CRCUpdateBlock(~0 ^ blockcrc, use, &buffer[used]);
|
|
blockcontext.Update(&buffer[used], use);
|
|
|
|
used += use;
|
|
need -= use;
|
|
|
|
// Have we finished the current block
|
|
if (need == 0)
|
|
{
|
|
MD5Hash blockhash;
|
|
blockcontext.Final(blockhash);
|
|
|
|
// Store the block hash and block crc in the file verification packet.
|
|
verificationpacket->SetBlockHashAndCRC(blocknumber, blockhash, blockcrc);
|
|
|
|
blocknumber++;
|
|
|
|
// More blocks
|
|
if (blocknumber < blockcount)
|
|
{
|
|
need = blocksize;
|
|
|
|
blockcontext.Reset();
|
|
blockcrc = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (noiselevel > CommandLine::nlQuiet)
|
|
{
|
|
// Display progress
|
|
u32 oldfraction = (u32)(1000 * offset / filesize);
|
|
offset += want;
|
|
u32 newfraction = (u32)(1000 * offset / filesize);
|
|
if (oldfraction != newfraction)
|
|
{
|
|
cout << newfraction/10 << '.' << newfraction%10 << "%\r" << flush;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Did we finish the last block
|
|
if (need > 0)
|
|
{
|
|
blockcrc = ~0 ^ CRCUpdateBlock(~0 ^ blockcrc, (size_t)need);
|
|
blockcontext.Update((size_t)need);
|
|
|
|
MD5Hash blockhash;
|
|
blockcontext.Final(blockhash);
|
|
|
|
// Store the block hash and block crc in the file verification packet.
|
|
verificationpacket->SetBlockHashAndCRC(blocknumber, blockhash, blockcrc);
|
|
|
|
blocknumber++;
|
|
|
|
need = 0;
|
|
}
|
|
|
|
// Finish computing the file hash.
|
|
MD5Hash filehash;
|
|
filecontext.Final(filehash);
|
|
|
|
// Store the file hash in the file description packet.
|
|
descriptionpacket->HashFull(filehash);
|
|
|
|
// Did we compute the 16k hash.
|
|
if (offset < 16384)
|
|
{
|
|
// Store the 16k hash in the file description packet.
|
|
descriptionpacket->Hash16k(filehash);
|
|
}
|
|
|
|
delete [] buffer;
|
|
|
|
// Compute the fileid and store it in the verification packet.
|
|
descriptionpacket->ComputeFileId();
|
|
verificationpacket->FileId(descriptionpacket->FileId());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Par2CreatorSourceFile::Close(void)
|
|
{
|
|
diskfile->Close();
|
|
}
|
|
|
|
|
|
void Par2CreatorSourceFile::RecordCriticalPackets(list<CriticalPacket*> &criticalpackets)
|
|
{
|
|
// Add the file description packet and file verification packet to
|
|
// the critical packet list.
|
|
criticalpackets.push_back(descriptionpacket);
|
|
criticalpackets.push_back(verificationpacket);
|
|
}
|
|
|
|
bool Par2CreatorSourceFile::CompareLess(const Par2CreatorSourceFile* const &left, const Par2CreatorSourceFile* const &right)
|
|
{
|
|
// Sort source files based on fileid
|
|
return left->descriptionpacket->FileId() < right->descriptionpacket->FileId();
|
|
}
|
|
|
|
const MD5Hash& Par2CreatorSourceFile::FileId(void) const
|
|
{
|
|
// Get the file id hash
|
|
return descriptionpacket->FileId();
|
|
}
|
|
|
|
void Par2CreatorSourceFile::InitialiseSourceBlocks(vector<DataBlock>::iterator &sourceblock, u64 blocksize)
|
|
{
|
|
for (u32 blocknum=0; blocknum<blockcount; blocknum++)
|
|
{
|
|
// Configure each source block to an appropriate offset and length within the source file.
|
|
sourceblock->SetLocation(diskfile, // file
|
|
blocknum * blocksize); // offset
|
|
sourceblock->SetLength(min(blocksize, filesize - (u64)blocknum * blocksize)); // length
|
|
sourceblock++;
|
|
}
|
|
}
|
|
|
|
void Par2CreatorSourceFile::UpdateHashes(u32 blocknumber, const void *buffer, size_t length)
|
|
{
|
|
// Compute the crc and hash of the data
|
|
u32 blockcrc = ~0 ^ CRCUpdateBlock(~0, length, buffer);
|
|
MD5Context blockcontext;
|
|
blockcontext.Update(buffer, length);
|
|
MD5Hash blockhash;
|
|
blockcontext.Final(blockhash);
|
|
|
|
// Store the results in the verification packet
|
|
verificationpacket->SetBlockHashAndCRC(blocknumber, blockhash, blockcrc);
|
|
|
|
|
|
// Update the full file hash, but don't go beyond the end of the file
|
|
if (length > filesize - blocknumber * length)
|
|
{
|
|
length = (size_t)(filesize - blocknumber * (u64)length);
|
|
}
|
|
|
|
assert(contextfull != 0);
|
|
|
|
contextfull->Update(buffer, length);
|
|
}
|
|
|
|
void Par2CreatorSourceFile::FinishHashes(void)
|
|
{
|
|
assert(contextfull != 0);
|
|
|
|
// Finish computation of the full file hash
|
|
MD5Hash hash;
|
|
contextfull->Final(hash);
|
|
|
|
// Store it in the description packet
|
|
descriptionpacket->HashFull(hash);
|
|
}
|