source: branches/file_selection/libtransmission/makemeta.c @ 2131

Last change on this file since 2131 was 2131, checked in by charles, 15 years ago
  • if user aborts, don't physically create the torrent file. (thx BMW)
  • remove the gtk threading code that got checked in by accident in yesterday's flurry
  • slightly better error checking in makemeta
File size: 12.7 KB
Line 
1/*
2 * This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is available under the
5 * Attribution-Noncommercial-Share Alike 3.0 License.
6 * Overview:  http://creativecommons.org/licenses/by-nc-sa/3.0/
7 * Full text: http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode
8 *
9 * Loosely speaking, you're free to use and adapt this work
10 * provided that it's for noncommercial uses and that you share your
11 * changes under a compatible license.
12 */
13
14#include <sys/types.h>
15#include <sys/stat.h>
16#include <unistd.h>
17#include <libgen.h>
18#include <dirent.h>
19#include <stdio.h> /* FILE, snprintf, stderr */
20#include <stdlib.h> /* malloc, calloc */
21
22#include "transmission.h"
23#include "internal.h" /* for tr_torrent_t */
24#include "bencode.h"
25#include "makemeta.h"
26#include "platform.h" /* threads, locks */
27#include "shared.h" /* shared lock */
28#include "version.h"
29
30/****
31*****
32****/
33
34struct FileList
35{
36    struct FileList * next;
37    char filename[MAX_PATH_LENGTH];
38};
39
40static struct FileList*
41getFiles( const char        * dir,
42          const char        * base,
43          struct FileList   * list )
44{
45    int i;
46    char buf[MAX_PATH_LENGTH];
47    struct stat sb;
48    sb.st_size = 0;
49
50    snprintf( buf, sizeof(buf), "%s"TR_PATH_DELIMITER_STR"%s", dir, base );
51    i = stat( buf, &sb );
52    if( i ) {
53        tr_err("makemeta couldn't stat \"%s\"; skipping. (%s)", buf, strerror(errno));
54        return list;
55    }
56
57    if ( S_ISDIR( sb.st_mode ) )
58    {
59        DIR * odir = opendir( buf );
60        struct dirent *d;
61        for (d = readdir( odir ); d!=NULL; d=readdir( odir ) )
62            if( strcmp( d->d_name,"." ) && strcmp( d->d_name,".." ) )
63                list = getFiles( buf, d->d_name, list );
64        closedir( odir );
65    }
66    else if( S_ISREG( sb.st_mode ) )
67    {
68        struct FileList * node = malloc( sizeof( struct FileList ) );
69        snprintf( node->filename, sizeof( node->filename ), "%s", buf );
70        node->next = list;
71        list = node;
72    }
73
74    return list;
75}
76
77static void
78freeFileList( struct FileList * list )
79{
80    while( list ) {
81        struct FileList * tmp = list->next;
82        free( list );
83        list = tmp;
84    }
85}
86
87static off_t
88getFileSize ( const char * filename )
89{
90    struct stat sb;
91    sb.st_size = 0;
92    stat( filename, &sb );
93    return sb.st_size;
94}
95
96#define MiB 1048576ul
97#define GiB 1073741824ul
98
99static size_t
100bestPieceSize( size_t totalSize )
101{
102    /* almost always best to have a piee size of 512 or 256 kb.
103       common practice seems to be to bump up to 1MB pieces at
104       at total size of around 8GiB or so */
105
106    if (totalSize >= (8 * GiB) )
107        return MiB;
108
109    if (totalSize >= GiB )
110        return MiB / 2;
111
112    return MiB / 4;
113}
114
115/****
116*****
117****/
118
119static int pstrcmp( const void * va, const void * vb)
120{
121    const char * a = *(const char**) va;
122    const char * b = *(const char**) vb;
123    return strcmp( a, b );
124}
125
126tr_metainfo_builder_t*
127tr_metaInfoBuilderCreate( tr_handle_t * handle, const char * topFile )
128{
129    size_t i;
130    struct FileList * files;
131    const struct FileList * walk;
132    tr_metainfo_builder_t * ret = calloc( 1, sizeof(tr_metainfo_builder_t) );
133    ret->top = tr_strdup( topFile );
134    ret->handle = handle;
135
136    if (1) {
137        struct stat sb;
138        stat( topFile, &sb );
139        ret->isSingleFile = !S_ISDIR( sb.st_mode );
140    }
141
142    /* build a list of files containing topFile and,
143       if it's a directory, all of its children */
144    if (1) {
145        char *dir, *base;
146        char dirbuf[MAX_PATH_LENGTH];
147        char basebuf[MAX_PATH_LENGTH];
148        strlcpy( dirbuf, topFile, sizeof( dirbuf ) );
149        strlcpy( basebuf, topFile, sizeof( basebuf ) );
150        dir = dirname( dirbuf );
151        base = basename( basebuf );
152        files = getFiles( dir, base, NULL );
153    }
154
155    for( walk=files; walk!=NULL; walk=walk->next )
156        ++ret->fileCount;
157    ret->files = calloc( ret->fileCount, sizeof(char*) );
158    ret->fileLengths = calloc( ret->fileCount, sizeof(size_t) );
159
160    for( i=0, walk=files; walk!=NULL; walk=walk->next, ++i )
161        ret->files[i] = tr_strdup( walk->filename );
162
163    qsort( ret->files, ret->fileCount, sizeof(char*), pstrcmp );
164    for( i=0; i<ret->fileCount; ++i ) {
165        ret->fileLengths[i] = getFileSize( ret->files[i] );
166        ret->totalSize += ret->fileLengths[i];
167    }
168
169    freeFileList( files );
170   
171    ret->pieceSize = bestPieceSize( ret->totalSize );
172    ret->pieceCount = ret->totalSize / ret->pieceSize;
173    if( ret->totalSize % ret->pieceSize )
174        ++ret->pieceCount;
175
176    return ret;
177}
178
179void
180tr_metaInfoBuilderFree( tr_metainfo_builder_t * builder )
181{
182    if( builder != NULL )
183    {
184        size_t i;
185        for( i=0; i<builder->fileCount; ++i )
186            tr_free( builder->files[i] );
187        tr_free( builder->files );
188        tr_free( builder->fileLengths );
189        tr_free( builder->top );
190        tr_free( builder->comment );
191        tr_free( builder->announce );
192        tr_free( builder->outputFile );
193        tr_free( builder );
194    }
195}
196
197/****
198*****
199****/
200
201static uint8_t*
202getHashInfo ( tr_metainfo_builder_t * b )
203{
204    size_t off = 0;
205    size_t fileIndex = 0;
206    uint8_t *ret = (uint8_t*) malloc ( SHA_DIGEST_LENGTH * b->pieceCount );
207    uint8_t *walk = ret;
208    uint8_t *buf = malloc( b->pieceSize );
209    size_t totalRemain;
210    FILE * fp;
211
212    b->pieceIndex = 0;
213    totalRemain = b->totalSize;
214    fp = fopen( b->files[fileIndex], "rb" );
215    while ( totalRemain )
216    {
217        uint8_t *bufptr = buf;
218        const size_t thisPieceSize = MIN( b->pieceSize, totalRemain );
219        size_t pieceRemain = thisPieceSize;
220
221        assert( b->pieceIndex < b->pieceCount );
222
223        while( pieceRemain )
224        {
225            const size_t n_this_pass = MIN( (b->fileLengths[fileIndex] - off), pieceRemain );
226            fread( bufptr, 1, n_this_pass, fp );
227            bufptr += n_this_pass;
228            off += n_this_pass;
229            pieceRemain -= n_this_pass;
230            if( off == b->fileLengths[fileIndex] ) {
231                off = 0;
232                fclose( fp );
233                fp = NULL;
234                if( ++fileIndex < b->fileCount ) {
235                    fp = fopen( b->files[fileIndex], "rb" );
236                }
237            }
238        }
239
240        assert( bufptr-buf == (int)thisPieceSize );
241        assert( pieceRemain == 0 );
242        SHA1( buf, thisPieceSize, walk );
243        walk += SHA_DIGEST_LENGTH;
244
245        if( b->abortFlag ) {
246            b->failed = 1;
247            break;
248        }
249
250        totalRemain -= thisPieceSize;
251        ++b->pieceIndex;
252    }
253    assert( b->abortFlag || (walk-ret == (int)(SHA_DIGEST_LENGTH*b->pieceCount)) );
254    assert( b->abortFlag || !totalRemain );
255    assert( b->abortFlag || fp == NULL );
256
257    if( fp != NULL )
258        fclose( fp );
259
260    free( buf );
261    return ret;
262}
263
264static void
265getFileInfo( const char * topFile,
266             const char * filename,
267             benc_val_t * uninitialized_length,
268             benc_val_t * uninitialized_path )
269{
270    benc_val_t *sub;
271    const char *pch, *prev;
272    const size_t topLen = strlen(topFile) + 1; /* +1 for '/' */
273    int n;
274
275    /* get the file size */
276    tr_bencInitInt( uninitialized_length, getFileSize(filename) );
277
278    /* the path list */
279    n = 1;
280    for( pch=filename+topLen; *pch; ++pch )
281        if (*pch == TR_PATH_DELIMITER)
282            ++n;
283    tr_bencInit( uninitialized_path, TYPE_LIST );
284    tr_bencListReserve( uninitialized_path, n );
285    for( prev=pch=filename+topLen; ; ++pch )
286    {
287        char buf[MAX_PATH_LENGTH];
288
289        if (*pch && *pch!=TR_PATH_DELIMITER )
290            continue;
291
292        memcpy( buf, prev, pch-prev );
293        buf[pch-prev] = '\0';
294
295        sub = tr_bencListAdd( uninitialized_path );
296        tr_bencInitStrDup( sub, buf );
297
298        prev = pch + 1;
299        if (!*pch)
300           break;
301    }
302}
303
304static void
305makeFilesList( benc_val_t                 * list,
306               const tr_metainfo_builder_t  * builder )
307{
308    size_t i = 0;
309
310    tr_bencListReserve( list, builder->fileCount );
311
312    for( i=0; i<builder->fileCount; ++i )
313    {
314        benc_val_t * dict = tr_bencListAdd( list );
315        benc_val_t *length, *pathVal;
316
317        tr_bencInit( dict, TYPE_DICT );
318        tr_bencDictReserve( dict, 2 );
319        length = tr_bencDictAdd( dict, "length" );
320        pathVal = tr_bencDictAdd( dict, "path" );
321        getFileInfo( builder->top, builder->files[i], length, pathVal );
322    }
323}
324
325static void
326makeInfoDict ( benc_val_t             * dict,
327               tr_metainfo_builder_t  * builder )
328{
329    uint8_t * pch;
330    benc_val_t * val;
331    char base[MAX_PATH_LENGTH];
332
333    tr_bencDictReserve( dict, 5 );
334
335    val = tr_bencDictAdd( dict, "name" );
336    strlcpy( base, builder->top, sizeof( base ) );
337    tr_bencInitStrDup ( val, basename( base ) );
338
339    val = tr_bencDictAdd( dict, "piece length" );
340    tr_bencInitInt( val, builder->pieceSize );
341
342    pch = getHashInfo( builder );
343    val = tr_bencDictAdd( dict, "pieces" );
344    tr_bencInitStr( val, pch, SHA_DIGEST_LENGTH * builder->pieceCount, 0 );
345
346    if ( builder->isSingleFile )
347    {
348        val = tr_bencDictAdd( dict, "length" );
349        tr_bencInitInt( val, builder->fileLengths[0] );
350    }
351    else
352    {
353        val = tr_bencDictAdd( dict, "files" );
354        tr_bencInit( val, TYPE_LIST );
355        makeFilesList( val, builder );
356    }
357
358    val = tr_bencDictAdd( dict, "private" );
359    tr_bencInitInt( val, builder->isPrivate ? 1 : 0 );
360}
361
362static void tr_realMakeMetaInfo ( tr_metainfo_builder_t * builder )
363{
364    int n = 5;
365    benc_val_t top, *val;
366
367    tr_bencInit ( &top, TYPE_DICT );
368    if ( builder->comment && *builder->comment ) ++n;
369    tr_bencDictReserve( &top, n );
370
371        val = tr_bencDictAdd( &top, "announce" );
372        tr_bencInitStrDup( val, builder->announce );
373
374        val = tr_bencDictAdd( &top, "created by" );
375        tr_bencInitStrDup( val, TR_NAME " " VERSION_STRING );
376
377        val = tr_bencDictAdd( &top, "creation date" );
378        tr_bencInitInt( val, time(0) );
379
380        val = tr_bencDictAdd( &top, "encoding" );
381        tr_bencInitStrDup( val, "UTF-8" );
382
383        if( builder->comment && *builder->comment ) {
384            val = tr_bencDictAdd( &top, "comment" );
385            tr_bencInitStrDup( val, builder->comment );
386        }
387
388        val = tr_bencDictAdd( &top, "info" );
389        tr_bencInit( val, TYPE_DICT );
390        tr_bencDictReserve( val, 666 );
391        makeInfoDict( val, builder );
392
393    /* save the file */
394    if ( !builder->abortFlag ) {
395        size_t nmemb;
396        char * pch = tr_bencSaveMalloc( &top, &n );
397        FILE * fp = fopen( builder->outputFile, "wb+" );
398        nmemb = n;
399        if( fp == NULL )
400            builder->failed = 1;
401        else if( fwrite( pch, 1, nmemb, fp ) != nmemb )
402            builder->failed = 1;
403        free( pch );
404        fclose( fp );
405    }
406
407    /* cleanup */
408    tr_bencFree( & top );
409    builder->isDone = 1;
410    builder->failed |= builder->abortFlag;
411}
412
413/***
414****
415****  A threaded builder queue
416****
417***/
418
419static tr_metainfo_builder_t * queue = NULL;
420
421static int workerIsRunning = 0;
422
423static tr_thread_t workerThread;
424
425static tr_lock_t* getQueueLock( tr_handle_t * h )
426{
427    static tr_lock_t * lock = NULL;
428
429    tr_sharedLock( h->shared );
430    if( lock == NULL )
431    {
432        lock = calloc( 1, sizeof( tr_lock_t ) );
433        tr_lockInit( lock );
434    }
435    tr_sharedUnlock( h->shared );
436
437    return lock;
438}
439
440static void workerFunc( void * user_data )
441{
442    tr_handle_t * handle = (tr_handle_t *) user_data;
443
444    for (;;)
445    {
446        tr_metainfo_builder_t * builder = NULL;
447
448        /* find the next builder to process */
449        tr_lock_t * lock = getQueueLock ( handle );
450        tr_lockLock( lock );
451        if( queue != NULL ) {
452            builder = queue;
453            queue = queue->nextBuilder;
454        }
455        tr_lockUnlock( lock );
456
457        /* if no builders, this worker thread is done */
458        if( builder == NULL )
459          break;
460
461        tr_realMakeMetaInfo ( builder );
462    }
463
464    workerIsRunning = 0;
465}
466
467void
468tr_makeMetaInfo( tr_metainfo_builder_t  * builder,
469                 const char             * outputFile,
470                 const char             * announce,
471                 const char             * comment,
472                 int                      isPrivate )
473{
474    tr_lock_t * lock;
475    builder->announce = tr_strdup( announce );
476    builder->comment = tr_strdup( comment );
477    builder->isPrivate = isPrivate;
478    if( outputFile && *outputFile )
479        builder->outputFile = tr_strdup( outputFile );
480    else {
481        char out[MAX_PATH_LENGTH];
482        snprintf( out, sizeof(out), "%s.torrent", builder->top);
483        builder->outputFile = tr_strdup( out );
484    }
485
486    /* enqueue the builder */
487    lock = getQueueLock ( builder->handle );
488    tr_lockLock( lock );
489    builder->nextBuilder = queue;
490    queue = builder;
491    if( !workerIsRunning ) {
492        workerIsRunning = 1;
493        tr_threadCreate( &workerThread, workerFunc, builder->handle, "makeMeta" );
494    }
495    tr_lockUnlock( lock );
496}
497
Note: See TracBrowser for help on using the repository browser.