source: trunk/libtransmission/makemeta.c @ 5585

Last change on this file since 5585 was 5585, checked in by charles, 14 years ago

tidy up some libtransmission filenames.

  • Property svn:keywords set to Date Rev Author Id
File size: 13.5 KB
Line 
1/*
2 * This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: makemeta.c 5585 2008-04-11 17:01:13Z charles $
11 */
12
13#include <assert.h>
14#include <errno.h>
15#include <stdio.h> /* FILE, snprintf, stderr */
16#include <stdlib.h> /* qsort */
17
18#include <sys/types.h>
19#include <sys/stat.h>
20#include <unistd.h>
21#include <libgen.h> /* dirname, basename */
22#include <dirent.h>
23
24#include "crypto.h" /* tr_sha1 */
25#include "trcompat.h" /* strlcpy */
26#include "transmission.h"
27#include "bencode.h"
28#include "makemeta.h"
29#include "platform.h" /* threads, locks */
30#include "utils.h" /* buildpath */
31#include "version.h"
32
33/****
34*****
35****/
36
37struct FileList
38{
39    struct FileList * next;
40    uint64_t size;
41    char * filename;
42};
43
44static struct FileList*
45getFiles( const char        * dir,
46          const char        * base,
47          struct FileList   * list )
48{
49    int i;
50    char buf[MAX_PATH_LENGTH];
51    struct stat sb;
52    DIR * odir = NULL;
53    sb.st_size = 0;
54
55    tr_buildPath( buf, sizeof(buf), dir, base, NULL );
56    i = stat( buf, &sb );
57    if( i ) {
58        tr_err( _( "Torrent Creator is skipping file \"%s\": %s" ), buf, tr_strerror(errno));
59        return list;
60    }
61
62    if ( S_ISDIR( sb.st_mode ) && (( odir = opendir ( buf ) )) )
63    {
64        struct dirent *d;
65        for (d = readdir( odir ); d!=NULL; d=readdir( odir ) )
66            if( d->d_name && d->d_name[0]!='.' ) /* skip dotfiles, ., and .. */
67                list = getFiles( buf, d->d_name, list );
68        closedir( odir );
69    }
70    else if( S_ISREG( sb.st_mode ) )
71    {
72        struct FileList * node = tr_new( struct FileList, 1 );
73        node->size = sb.st_size;
74        if( ( buf[0]=='.' ) && ( buf[1]=='/' ) )
75            node->filename = tr_strdup( buf + 2 );
76        else
77            node->filename = tr_strdup( buf );
78        node->next = list;
79        list = node;
80    }
81
82    return list;
83}
84
85static int
86bestPieceSize( uint64_t totalSize )
87{
88    static const uint64_t GiB = 1073741824;
89    static const uint64_t MiB = 1048576;
90    static const uint64_t KiB = 1024;
91
92    if( totalSize >=   (1*GiB) ) return MiB;
93    if( totalSize >= (512*MiB) ) return (512*KiB);
94    if( totalSize >= (350*MiB) ) return (256*KiB);
95    if( totalSize >= (150*MiB) ) return (128*KiB);
96    if( totalSize >=  (50*MiB) ) return (64*KiB);
97    return (32*KiB); /* less than 50 meg */
98}
99
100static int
101builderFileCompare ( const void * va, const void * vb)
102{
103    const tr_metainfo_builder_file * a = va;
104    const tr_metainfo_builder_file * b = vb;
105    return strcmp( a->filename, b->filename );
106}
107
108tr_metainfo_builder*
109tr_metaInfoBuilderCreate( tr_handle * handle, const char * topFile )
110{
111    int i;
112    struct FileList * files;
113    struct FileList * walk;
114    tr_metainfo_builder * ret = tr_new0( tr_metainfo_builder, 1 );
115    ret->top = tr_strdup( topFile );
116    ret->handle = handle; 
117    if (1) {
118        struct stat sb;
119        stat( topFile, &sb );
120        ret->isSingleFile = !S_ISDIR( sb.st_mode );
121    }
122
123    /* build a list of files containing topFile and,
124       if it's a directory, all of its children */
125    if (1) {
126        char *dir, *base;
127        char dirbuf[MAX_PATH_LENGTH];
128        char basebuf[MAX_PATH_LENGTH];
129        strlcpy( dirbuf, topFile, sizeof( dirbuf ) );
130        strlcpy( basebuf, topFile, sizeof( basebuf ) );
131        dir = dirname( dirbuf );
132        base = basename( basebuf );
133        files = getFiles( dir, base, NULL );
134    }
135
136    for( walk=files; walk!=NULL; walk=walk->next )
137        ++ret->fileCount;
138
139    ret->files = tr_new0( tr_metainfo_builder_file, ret->fileCount );
140
141    for( i=0, walk=files; walk!=NULL; ++i )
142    {
143        struct FileList * tmp = walk;
144        tr_metainfo_builder_file * file = &ret->files[i];
145        walk = walk->next;
146        file->filename = tmp->filename;
147        file->size = tmp->size;
148        ret->totalSize += tmp->size;
149        tr_free( tmp );
150    }
151
152    qsort( ret->files,
153           ret->fileCount,
154           sizeof(tr_metainfo_builder_file),
155           builderFileCompare );
156
157    ret->pieceSize = bestPieceSize( ret->totalSize );
158    ret->pieceCount = ret->pieceSize
159        ? (int)( ret->totalSize / ret->pieceSize)
160        : 0;
161    if( ret->totalSize % ret->pieceSize )
162        ++ret->pieceCount;
163
164    return ret;
165}
166
167void
168tr_metaInfoBuilderFree( tr_metainfo_builder * builder )
169{
170    if( builder != NULL )
171    {
172        uint32_t i;
173        for( i=0; i<builder->fileCount; ++i )
174            tr_free( builder->files[i].filename );
175        tr_free( builder->files );
176        tr_free( builder->top );
177        tr_free( builder->comment );
178        tr_free( builder->announce );
179        tr_free( builder->outputFile );
180        tr_free( builder );
181    }
182}
183
184/****
185*****
186****/
187
188static uint8_t*
189getHashInfo ( tr_metainfo_builder * b )
190{
191    uint32_t fileIndex = 0;
192    uint8_t *ret = tr_new0( uint8_t, SHA_DIGEST_LENGTH * b->pieceCount );
193    uint8_t *walk = ret;
194    uint8_t *buf;
195    uint64_t totalRemain;
196    uint64_t off = 0;
197    FILE * fp;
198
199    if( !b->totalSize )
200        return ret;
201
202    buf = tr_new( uint8_t, b->pieceSize );
203    b->pieceIndex = 0;
204    totalRemain = b->totalSize;
205    fp = fopen( b->files[fileIndex].filename, "rb" );
206    if( !fp ) {
207        b->my_errno = errno;
208        snprintf( b->errfile, sizeof( b->errfile ), b->files[fileIndex].filename );
209        b->result = TR_MAKEMETA_IO_READ;
210        tr_free( ret );
211        return NULL;
212    }
213    while ( totalRemain )
214    {
215        uint8_t *bufptr = buf;
216        const uint64_t thisPieceSize =
217            MIN( (uint32_t)b->pieceSize, totalRemain );
218        uint64_t pieceRemain = thisPieceSize;
219
220        assert( b->pieceIndex < b->pieceCount );
221
222        while( pieceRemain )
223        {
224            const uint64_t n_this_pass =
225                MIN( (b->files[fileIndex].size - 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->files[fileIndex].size ) {
231                off = 0;
232                fclose( fp );
233                fp = NULL;
234                if( ++fileIndex < b->fileCount ) {
235                    fp = fopen( b->files[fileIndex].filename, "rb" );
236                    if( !fp ) {
237                        b->my_errno = errno;
238                        snprintf( b->errfile, sizeof( b->errfile ), b->files[fileIndex].filename );
239                        b->result = TR_MAKEMETA_IO_READ;
240                        tr_free( ret );
241                        return NULL;
242                    }
243                }
244            }
245        }
246
247        assert( bufptr-buf == (int)thisPieceSize );
248        assert( pieceRemain == 0 );
249        tr_sha1( walk, buf, thisPieceSize, NULL );
250        walk += SHA_DIGEST_LENGTH;
251
252        if( b->abortFlag ) {
253            b->result = TR_MAKEMETA_CANCELLED;
254            break;
255        }
256
257        totalRemain -= thisPieceSize;
258        ++b->pieceIndex;
259    }
260    assert( b->abortFlag || (walk-ret == (int)(SHA_DIGEST_LENGTH*b->pieceCount)) );
261    assert( b->abortFlag || !totalRemain );
262    assert( b->abortFlag || fp == NULL );
263
264    if( fp != NULL )
265        fclose( fp );
266
267    tr_free( buf );
268    return ret;
269}
270
271static void
272getFileInfo( const char                      * topFile,
273             const tr_metainfo_builder_file  * file,
274             tr_benc                         * uninitialized_length,
275             tr_benc                         * uninitialized_path )
276{
277    tr_benc *sub;
278    const char *pch, *prev;
279    const size_t topLen = strlen(topFile) + 1; /* +1 for '/' */
280    int n;
281
282    /* get the file size */
283    tr_bencInitInt( uninitialized_length, file->size );
284
285    /* the path list */
286    n = 1;
287    for( pch=file->filename+topLen; *pch; ++pch )
288        if (*pch == TR_PATH_DELIMITER)
289            ++n;
290    tr_bencInitList( uninitialized_path, n );
291    for( prev=pch=file->filename+topLen; ; ++pch )
292    {
293        char buf[MAX_PATH_LENGTH];
294
295        if (*pch && *pch!=TR_PATH_DELIMITER )
296            continue;
297
298        memcpy( buf, prev, pch-prev );
299        buf[pch-prev] = '\0';
300
301        sub = tr_bencListAdd( uninitialized_path );
302        tr_bencInitStrDup( sub, buf );
303
304        prev = pch + 1;
305        if (!*pch)
306           break;
307    }
308}
309
310static void
311makeFilesList( tr_benc                    * list,
312               const tr_metainfo_builder  * builder )
313{
314    uint32_t i = 0;
315
316    tr_bencListReserve( list, builder->fileCount );
317
318    for( i=0; i<builder->fileCount; ++i )
319    {
320        tr_benc * dict = tr_bencListAdd( list );
321        tr_benc *length, *pathVal;
322
323        tr_bencInitDict( dict, 2 );
324        length = tr_bencDictAdd( dict, "length" );
325        pathVal = tr_bencDictAdd( dict, "path" );
326        getFileInfo( builder->top, &builder->files[i], length, pathVal );
327    }
328}
329
330static void
331makeInfoDict ( tr_benc              * dict,
332               tr_metainfo_builder  * builder )
333{
334    uint8_t * pch;
335    tr_benc * val;
336    char base[MAX_PATH_LENGTH];
337
338    tr_bencDictReserve( dict, 5 );
339   
340    if ( builder->isSingleFile )
341    {
342        val = tr_bencDictAdd( dict, "length" );
343        tr_bencInitInt( val, builder->files[0].size );
344    }
345    else
346    {
347        val = tr_bencDictAdd( dict, "files" );
348        tr_bencInit( val, TYPE_LIST );
349        makeFilesList( val, builder );
350    }
351
352    val = tr_bencDictAdd( dict, "name" );
353    strlcpy( base, builder->top, sizeof( base ) );
354    tr_bencInitStrDup ( val, basename( base ) );
355
356    val = tr_bencDictAdd( dict, "piece length" );
357    tr_bencInitInt( val, builder->pieceSize );
358
359    if( ( pch = getHashInfo( builder ) ) ) {
360        val = tr_bencDictAdd( dict, "pieces" );
361        tr_bencInitStr( val, pch, SHA_DIGEST_LENGTH * builder->pieceCount, 0 );
362    }
363
364    val = tr_bencDictAdd( dict, "private" );
365    tr_bencInitInt( val, builder->isPrivate ? 1 : 0 );
366}
367
368static void
369tr_realMakeMetaInfo ( tr_metainfo_builder * builder )
370{
371    int n = 5;
372    tr_benc top, *val;
373
374    if ( builder->comment && *builder->comment ) ++n;
375    tr_bencInitDict( &top, n );
376
377    val = tr_bencDictAdd( &top, "announce" );
378    tr_bencInitStrDup( val, builder->announce );
379    if( tr_httpParseURL( builder->announce, -1, NULL, NULL, NULL ) )
380    {
381        builder->result = TR_MAKEMETA_URL;
382    }
383   
384    if( !builder->result && !builder->abortFlag )
385    {
386        if( builder->comment && *builder->comment ) {
387            val = tr_bencDictAdd( &top, "comment" );
388            tr_bencInitStrDup( val, builder->comment );
389        }
390
391        val = tr_bencDictAdd( &top, "created by" );
392        tr_bencInitStrDup( val, TR_NAME "/" LONG_VERSION_STRING );
393
394        val = tr_bencDictAdd( &top, "creation date" );
395        tr_bencInitInt( val, time(0) );
396
397        val = tr_bencDictAdd( &top, "encoding" );
398        tr_bencInitStrDup( val, "UTF-8" );
399
400        val = tr_bencDictAdd( &top, "info" );
401        tr_bencInitDict( val, 666 );
402        makeInfoDict( val, builder );
403    }
404
405    /* save the file */
406    if ( !builder->result && !builder->abortFlag ) {
407        char * pch = tr_bencSave( &top, &n );
408        FILE * fp = fopen( builder->outputFile, "wb+" );
409        size_t nmemb = n;
410        if( !fp || ( fwrite( pch, 1, nmemb, fp ) != nmemb ) ) {
411            builder->my_errno = errno;
412            strlcpy( builder->errfile, builder->outputFile, sizeof( builder->errfile ) );
413            builder->result = TR_MAKEMETA_IO_WRITE;
414        }
415        if( fp )
416            fclose( fp );
417        tr_free( pch );
418    }
419
420    /* cleanup */
421    tr_bencFree( & top );
422    if( builder->abortFlag )
423        builder->result = TR_MAKEMETA_CANCELLED;
424    builder->isDone = 1;
425}
426
427/***
428****
429****  A threaded builder queue
430****
431***/
432
433static tr_metainfo_builder * queue = NULL;
434
435static tr_thread * workerThread = NULL;
436
437static tr_lock* getQueueLock( tr_handle * h )
438{
439    static tr_lock * lock = NULL;
440
441    tr_globalLock( h );
442    if( !lock )
443         lock = tr_lockNew( );
444    tr_globalUnlock( h );
445
446    return lock;
447}
448
449static void workerFunc( void * user_data )
450{
451    tr_handle * handle = (tr_handle *) user_data;
452
453    for (;;)
454    {
455        tr_metainfo_builder * builder = NULL;
456
457        /* find the next builder to process */
458        tr_lock * lock = getQueueLock ( handle );
459        tr_lockLock( lock );
460        if( queue != NULL ) {
461            builder = queue;
462            queue = queue->nextBuilder;
463        }
464        tr_lockUnlock( lock );
465
466        /* if no builders, this worker thread is done */
467        if( builder == NULL )
468          break;
469
470        tr_realMakeMetaInfo ( builder );
471    }
472
473    workerThread = NULL;
474}
475
476void
477tr_makeMetaInfo( tr_metainfo_builder  * builder,
478                 const char           * outputFile,
479                 const char           * announce,
480                 const char           * comment,
481                 int                    isPrivate )
482{
483    tr_lock * lock;
484
485    builder->abortFlag = 0;
486    builder->isDone = 0;
487    builder->announce = tr_strdup( announce );
488    builder->comment = tr_strdup( comment );
489    builder->isPrivate = isPrivate;
490    if( outputFile && *outputFile )
491        builder->outputFile = tr_strdup( outputFile );
492    else {
493        char out[MAX_PATH_LENGTH];
494        snprintf( out, sizeof(out), "%s.torrent", builder->top);
495        builder->outputFile = tr_strdup( out );
496    }
497
498    /* enqueue the builder */
499    lock = getQueueLock ( builder->handle );
500    tr_lockLock( lock );
501    builder->nextBuilder = queue;
502    queue = builder;
503    if( !workerThread )
504         workerThread = tr_threadNew( workerFunc, builder->handle, "makeMeta" );
505    tr_lockUnlock( lock );
506}
507
Note: See TracBrowser for help on using the repository browser.