2010-02-24 12 views
7

Quello che mi serve è essere in grado di estrarre i file in un file .rar negli stream. Sto creando un caso di prova per avere un'idea di come usare unrar source. Ho cercato e armeggiato per un po ', ma non riesco a capire come usare la libreria. Sono sorpreso che non riesca nemmeno a trovare la documentazione o un tutorial per questo, considerando quanto sono comuni gli archivi .rar.Uso della libreria unrar: estrazione di file in un buffer filestream

Ho fatto un po 'di progressi da solo, ma non sempre funziona. Alcuni file vengono estratti correttamente. Altri file sono mescolati per qualche motivo (ma non completamente dati binari "garbage"). Tutto quello che sappiamo finora è, di solito (ma non sempre):

file
  • non lavorano hanno fileInfo.Method = 48. Essi sembrano essere i file che hanno un rapporto di compressione di 100% - vale a dire senza compressione

  • file di lavoro hanno fileInfo.Method = 49, 50, 51, 52 o 53, che corrispondono alle velocità di compressione, più veloce, Veloce, Normale, Buono , Best

Ma non ho idea del perché sia ​​così. Ancora non riesci a trovare documentazione o un esempio funzionante.

Di seguito è la fonte del test case che ho finora e un example rar archive che, quando viene estratto con questo programma, ha sia file funzionanti che non funzionanti.

/* put in the same directory as the unrar source files 
* compiling with: 
* make clean 
* make lib 
* g++ rartest.cpp -o rartest libunrar.so -lboost_filesystem 
*/ 

#include <cstring> 
#include <iostream> 
#include <fstream> 

#include <boost/filesystem.hpp> 

#define _UNIX 
#define RARDLL 
#include "dll.hpp" 

using namespace std; 
namespace fs = boost::filesystem; 

//char fileName[100] = "testout0.jpg\0"; 
// 
//// doens't work 
//int PASCAL ProcessDataProc(unsigned char* buffer, int buffLen) { 
// cout << "writing..." << endl; 
// ofstream outFile(fileName); 
// cout << buffLen << endl; 
// cout << outFile.write((const char*)buffer, buffLen) << endl; 
// cout << "done writing..." << endl; 
// fileName[7]++; 
//} 

int CALLBACK CallbackProc(unsigned int msg, long myBuffer, long rarBuffer, long bufferLen) { 
    switch(msg) { 
    case UCM_CHANGEVOLUME: 
     break; 
    case UCM_PROCESSDATA: 
     memcpy((char*)myBuffer, (char*)rarBuffer, bufferLen); 
     break; 
    case UCM_NEEDPASSWORD: 
     break; 
    } 
    return 1; 
} 

int main(int argc, char* argv[]) { 
    if (argc != 2) 
    return 0; 
    ifstream archiveStream(argv[1]); 
    if (!archiveStream.is_open()) 
    cout << "fstream couldn't open file\n"; 

    // declare and set parameters 
    HANDLE rarFile; 
    RARHeaderDataEx fileInfo; 
    RAROpenArchiveDataEx archiveInfo; 
    memset(&archiveInfo, 0, sizeof(archiveInfo)); 
    archiveInfo.CmtBuf = NULL; 
    //archiveInfo.OpenMode = RAR_OM_LIST; 
    archiveInfo.OpenMode = RAR_OM_EXTRACT; 
    archiveInfo.ArcName = argv[1]; 

    // Open file 
    rarFile = RAROpenArchiveEx(&archiveInfo); 
    if (archiveInfo.OpenResult != 0) { 
    RARCloseArchive(rarFile); 
    cout << "unrar couldn't open" << endl; 
    exit(1); 
    } 
    fileInfo.CmtBuf = NULL; 

    cout << archiveInfo.Flags << endl; 

    // loop through archive 
    int numFiles = 0; 
    int fileSize; 
    int RHCode; 
    int PFCode; 
    while(true) { 
    RHCode = RARReadHeaderEx(rarFile, &fileInfo); 
    if (RHCode != 0) break; 

    numFiles++; 
    fs::path path(fileInfo.FileName); 
    fileSize = fileInfo.UnpSize; 

    cout << fileInfo.Method << " " << fileInfo.FileName << " (" << fileInfo.UnpSize << ")" << endl; 

    char fileBuffer[fileInfo.UnpSize]; 

    // not sure what this does 
    //RARSetProcessDataProc(rarFile, ProcessDataProc); 

    // works for some files, but not for others 
    RARSetCallback(rarFile, CallbackProc, (long) &fileBuffer); 
    PFCode = RARProcessFile(rarFile, RAR_TEST, NULL, NULL); 

    // properly extracts to a directory... but I need a stream 
    // and I don't want to write to disk, read it, and delete from disk 
    //PFCode = RARProcessFile(rarFile, RAR_EXTRACT, ".", fileInfo.FileName); 

    // just skips 
    //PFCode = RARProcessFile(rarFile, RAR_SKIP, NULL, NULL); 

    if (PFCode != 0) { 
     RARCloseArchive(rarFile); 
     cout << "error processing this file\n" << endl; 
     exit(1); 
    } 
    ofstream outFile(path.filename().c_str()); 
    outFile.write(fileBuffer, fileSize); 
    } 
    if (RHCode != ERAR_END_ARCHIVE) 
    cout << "error traversing through archive: " << RHCode << endl; 
    RARCloseArchive(rarFile); 

    cout << "num files: " << numFiles << endl; 

} 

aggiornamento:

ho trovato un file che sembra essere (dice di essere?) Il documentation, ma secondo il file, non sto facendo niente di male. Penso che potrei essere costretto a ricorrere a CRC controllando i buffer e implementando una soluzione alternativa se fallisce.

fonte soluzione (grazie, Denis Krjuchkov!):

/* put in the same directory as the unrar source files 
* compiling with: 
* make clean 
* make lib 
* g++ rartest.cpp -o rartest libunrar.so -lboost_filesystem 
*/ 

#include <cstring> 
#include <iostream> 
#include <fstream> 

#include <boost/filesystem.hpp> 
#include <boost/crc.hpp> 

#define _UNIX 
#define RARDLL 
#include "dll.hpp" 

using namespace std; 
namespace fs = boost::filesystem; 

//char fileName[100] = "testout0.jpg\0"; 
// 
//// doens't work 
//int PASCAL ProcessDataProc(unsigned char* buffer, int buffLen) { 
// cout << "writing..." << endl; 
// ofstream outFile(fileName); 
// cout << buffLen << endl; 
// cout << outFile.write((const char*)buffer, buffLen) << endl; 
// cout << "done writing..." << endl; 
// fileName[7]++; 
//} 

int CALLBACK CallbackProc(unsigned int msg, long myBufferPtr, long rarBuffer, long bytesProcessed) { 
    switch(msg) { 
    case UCM_CHANGEVOLUME: 
     return -1; 
     break; 
    case UCM_PROCESSDATA: 
     memcpy(*(char**)myBufferPtr, (char*)rarBuffer, bytesProcessed); 
     *(char**)myBufferPtr += bytesProcessed; 
     return 1; 
     break; 
    case UCM_NEEDPASSWORD: 
     return -1; 
     break; 
    } 
} 

int main(int argc, char* argv[]) { 
    if (argc != 2) 
    return 0; 
    ifstream archiveStream(argv[1]); 
    if (!archiveStream.is_open()) 
    cout << "fstream couldn't open file\n"; 

    // declare and set parameters 
    RARHANDLE rarFile; // I renamed this macro in dll.hpp for my own purposes 
    RARHANDLE rarFile2; 
    RARHeaderDataEx fileInfo; 
    RAROpenArchiveDataEx archiveInfo; 
    memset(&archiveInfo, 0, sizeof(archiveInfo)); 
    archiveInfo.CmtBuf = NULL; 
    //archiveInfo.OpenMode = RAR_OM_LIST; 
    archiveInfo.OpenMode = RAR_OM_EXTRACT; 
    archiveInfo.ArcName = argv[1]; 

    // Open file 
    rarFile = RAROpenArchiveEx(&archiveInfo); 
    rarFile2 = RAROpenArchiveEx(&archiveInfo); 
    if (archiveInfo.OpenResult != 0) { 
    RARCloseArchive(rarFile); 
    cout << "unrar couldn't open" << endl; 
    exit(1); 
    } 
    fileInfo.CmtBuf = NULL; 

// cout << archiveInfo.Flags << endl; 

    // loop through archive 
    int numFiles = 0; 
    int fileSize; 
    int RHCode; 
    int PFCode; 
    int crcVal; 
    bool workaroundUsed = false; 
    char currDir[2] = "."; 
    char tmpFile[11] = "buffer.tmp"; 
    while(true) { 
    RHCode = RARReadHeaderEx(rarFile, &fileInfo); 
    if (RHCode != 0) break; 
    RARReadHeaderEx(rarFile2, &fileInfo); 

    numFiles++; 
    fs::path path(fileInfo.FileName); 
    fileSize = fileInfo.UnpSize; 
    crcVal = fileInfo.FileCRC; 

    cout << dec << fileInfo.Method << " " << fileInfo.FileName << " (" << fileInfo.UnpSize << ")" << endl; 
    cout << " " << hex << uppercase << crcVal << endl; 

    char fileBuffer[fileSize]; 
    char* bufferPtr = fileBuffer; 

    // not sure what this does 
    //RARSetProcessDataProc(rarFile, ProcessDataProc); 

    // works for some files, but not for others 
    RARSetCallback(rarFile, CallbackProc, (long) &bufferPtr); 
    PFCode = RARProcessFile(rarFile, RAR_TEST, NULL, NULL); 

    // properly extracts to a directory... but I need a stream 
    // and I don't want to write to disk, read it, and delete from disk 
// PFCode = RARProcessFile(rarFile, RAR_EXTRACT, currDir, fileInfo.FileName); 

    // just skips 
    //PFCode = RARProcessFile(rarFile, RAR_SKIP, NULL, NULL); 

    if (PFCode != 0) { 
     RARCloseArchive(rarFile); 
     cout << "error processing this file\n" << endl; 
     exit(1); 
    } 

    // crc check 
    boost::crc_32_type crc32result; 
    crc32result.process_bytes(&fileBuffer, fileSize); 
    cout << " " << hex << uppercase << crc32result.checksum() << endl; 

    // old workaround - crc check always succeeds now! 
    if (crcVal == crc32result.checksum()) { 
     RARProcessFile(rarFile2, RAR_SKIP, NULL, NULL); 
    } 
    else { 
     workaroundUsed = true; 
     RARProcessFile(rarFile2, RAR_EXTRACT, currDir, tmpFile); 
     ifstream inFile(tmpFile); 
     inFile.read(fileBuffer, fileSize); 
    } 

    ofstream outFile(path.filename().c_str()); 
    outFile.write(fileBuffer, fileSize); 
    } 
    if (workaroundUsed) remove(tmpFile); 
    if (RHCode != ERAR_END_ARCHIVE) 
    cout << "error traversing through archive: " << RHCode << endl; 
    RARCloseArchive(rarFile); 

    cout << dec << "num files: " << numFiles << endl; 

} 
+0

Forse c'è un problema con i caratteri EOL (archivio fatto su Windows ma estratto su Unix), ma non sono così sicuro .. –

+0

Mi sto assicurando di usare il corretto 'buffLen' o' fileSize' durante la lettura/scrivendo ai buffer però. A questo punto, sono quasi pronto a dare la colpa alla libreria Unrar. – Kache

risposta

6

Non ho familiarità con unrar, dopo aver letto rapidamente una documentazione, penso che si stia assumendo che CallbackProc venga chiamato esattamente una volta per file. Tuttavia, penso che unrar possa chiamarlo più volte. Spacchetta alcuni dati quindi chiama CallbackProc, quindi decomprime il prossimo blocco dati e di nuovo chiama CallbackProc, il processo viene iterato finché tutti i dati non vengono elaborati. Dovresti ricordare quanti byte vengono effettivamente scritti nel buffer e aggiungere nuovi dati all'offset corrispondente.

+0

Spiega sicuramente perché i file estratti falliti sono stati confusi ma non tutti i dati inutili. Rileggo la documentazione e non mi dava l'impressione che unrar potesse eseguire la richiamata più volte per file. Come ci hai pensato? Avere un callback eseguito periodicamente nel bel mezzo dell'estrazione non mi sembra molto intuitivo. – Kache

+0

Suppongo che il motivo sia che i file nell'archivio potrebbero essere sufficientemente grandi e non adatti alla memoria disponibile. Disimballare tali file interamente in un buffer sarebbe impossibile o almeno inefficiente. –

0

ti sembra di avere pubblicato alcune di codice sorgente, ma nessuna domanda reale.

Avete preso in considerazione guardando Rarlabs Feedback Page (che punta a loro forums

Inoltre, si veda: This Article

+0

Non riesco a ottenere (anche un 'main()') vuoto da compilare. Sto cercando di decomprimere un file in un archivio rar in un buffer, per fare ciò che desidero. Ho guardato attraverso i tuoi link. Rarlabs non ha supporto per la loro libreria unrar di cui sono a conoscenza - solo una libreria unrar open source senza documentazione. – Kache

3

non riesco a trovare nessuna documentazione online sia, ma ci sono esempi che è possibile utilizzare:

Vai a http://www.krugle.com e, nell'angolo in basso a sinistra della pagina, inserisci una parola chiave come RAROpenArchiveEx. Vedrai i file di intestazione e di origine da vari proiezioni open source ts che fanno uso della libreria unrar.

Questo dovrebbe farti andare.

+0

Grazie! Daremo un'occhiata ad alcuni di questi. Si spera che almeno uno di questi estratti direttamente a un buffer, e posso capire come fare la stessa cosa. – Kache