mirror of
https://github.com/transmission/transmission
synced 2024-12-26 09:37:56 +00:00
(trunk libT) #4377 "Incomplete Folder removed when sub-folder of Default Location" -- possible fix.
This commit is contained in:
parent
154f2f96af
commit
d9541de52d
1 changed files with 125 additions and 150 deletions
|
@ -2682,199 +2682,174 @@ tr_torrentGetBytesLeftToAllocate( const tr_torrent * tor )
|
||||||
***** Removing the torrent's local data
|
***** Removing the torrent's local data
|
||||||
****/
|
****/
|
||||||
|
|
||||||
static int
|
|
||||||
vstrcmp( const void * a, const void * b )
|
|
||||||
{
|
|
||||||
return strcmp( a, b );
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
compareLongestFirst( const void * a, const void * b )
|
|
||||||
{
|
|
||||||
const size_t alen = strlen( a );
|
|
||||||
const size_t blen = strlen( b );
|
|
||||||
|
|
||||||
if( alen != blen )
|
|
||||||
return alen > blen ? -1 : 1;
|
|
||||||
|
|
||||||
return vstrcmp( a, b );
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
addDirtyFile( const char * root,
|
|
||||||
const char * filename,
|
|
||||||
tr_ptrArray * dirtyFolders )
|
|
||||||
{
|
|
||||||
char * dir = tr_dirname( filename );
|
|
||||||
|
|
||||||
/* add the parent folders to dirtyFolders until we reach the root or a known-dirty */
|
|
||||||
while ( ( dir != NULL )
|
|
||||||
&& ( strlen( root ) <= strlen( dir ) )
|
|
||||||
&& ( tr_ptrArrayFindSorted( dirtyFolders, dir, vstrcmp ) == NULL ) )
|
|
||||||
{
|
|
||||||
char * tmp;
|
|
||||||
tr_ptrArrayInsertSorted( dirtyFolders, tr_strdup( dir ), vstrcmp );
|
|
||||||
|
|
||||||
tmp = tr_dirname( dir );
|
|
||||||
tr_free( dir );
|
|
||||||
dir = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr_free( dir );
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
isSystemFile( const char * base )
|
isJunkFile( const char * base )
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
static const char * stockFiles[] = { ".DS_Store", "desktop.ini", "Thumbs.db" };
|
static const char * files[] = { ".DS_Store", "desktop.ini", "Thumbs.db" };
|
||||||
static const int stockFileCount = sizeof(stockFiles) / sizeof(stockFiles[0]);
|
static const int file_count = sizeof(files) / sizeof(files[0]);
|
||||||
|
|
||||||
for( i=0; i<stockFileCount; ++i )
|
for( i=0; i<file_count; ++i )
|
||||||
if( !strcmp( base, stockFiles[i] ) )
|
if( !strcmp( base, files[i] ) )
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
#ifdef SYS_DARWIN
|
||||||
/* check for resource forks. <http://support.apple.com/kb/TA20578> */
|
/* check for resource forks. <http://support.apple.com/kb/TA20578> */
|
||||||
if( !memcmp( base, "._", 2 ) )
|
if( !memcmp( base, "._", 2 ) )
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
deleteLocalFile( const tr_torrent * tor, const char * filename, tr_fileFunc fileFunc )
|
removeEmptyFoldersAndJunkFiles( const char * folder )
|
||||||
{
|
{
|
||||||
struct stat sb;
|
DIR * odir;
|
||||||
|
if(( odir = opendir( folder ))) {
|
||||||
/* add safeguards for (1) making sure the file exists and
|
struct dirent * d;
|
||||||
(2) that we haven't somehow walked up past the torrent's top directory... */
|
while(( d = readdir( odir ))) {
|
||||||
if( !stat( filename, &sb ) )
|
if( strcmp( d->d_name, "." ) && strcmp( d->d_name, ".." ) ) {
|
||||||
if( !tr_is_same_file( tor->currentDir, filename ) )
|
struct stat sb;
|
||||||
fileFunc( filename );
|
char * filename = tr_buildPath( folder, d->d_name, NULL );
|
||||||
}
|
if( !stat( filename, &sb ) ) {
|
||||||
|
if( S_ISDIR( sb.st_mode ) )
|
||||||
static void
|
removeEmptyFoldersAndJunkFiles( filename );
|
||||||
walkLocalData( const tr_torrent * tor,
|
else if( isJunkFile( d->d_name ) )
|
||||||
const char * root,
|
remove( filename );
|
||||||
const char * dir,
|
}
|
||||||
const char * base,
|
tr_free( filename );
|
||||||
tr_ptrArray * torrentFiles,
|
|
||||||
tr_ptrArray * folders,
|
|
||||||
tr_ptrArray * dirtyFolders,
|
|
||||||
tr_fileFunc fileFunc )
|
|
||||||
{
|
|
||||||
struct stat sb;
|
|
||||||
char * buf = tr_buildPath( dir, base, NULL );
|
|
||||||
int i = stat( buf, &sb );
|
|
||||||
|
|
||||||
if( !i )
|
|
||||||
{
|
|
||||||
DIR * odir = NULL;
|
|
||||||
|
|
||||||
if( S_ISDIR( sb.st_mode ) && ( ( odir = opendir ( buf ) ) ) )
|
|
||||||
{
|
|
||||||
struct dirent *d;
|
|
||||||
tr_ptrArrayInsertSorted( folders, tr_strdup( buf ), vstrcmp );
|
|
||||||
for( d = readdir( odir ); d != NULL; d = readdir( odir ) )
|
|
||||||
if( d->d_name && strcmp( d->d_name, "." ) && strcmp( d->d_name, ".." ) )
|
|
||||||
walkLocalData( tor, root, buf, d->d_name, torrentFiles, folders, dirtyFolders, fileFunc );
|
|
||||||
closedir( odir );
|
|
||||||
}
|
|
||||||
else if( S_ISREG( sb.st_mode ) && ( sb.st_size > 0 ) )
|
|
||||||
{
|
|
||||||
if( isSystemFile( base ) )
|
|
||||||
deleteLocalFile( tor, buf, fileFunc );
|
|
||||||
else {
|
|
||||||
const char * sub = buf + strlen( tor->currentDir ) + strlen( TR_PATH_DELIMITER_STR );
|
|
||||||
const bool isTorrentFile = tr_ptrArrayFindSorted( torrentFiles, sub, vstrcmp ) != NULL;
|
|
||||||
if( !isTorrentFile )
|
|
||||||
addDirtyFile( root, buf, dirtyFolders );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
remove( folder );
|
||||||
|
closedir( odir );
|
||||||
}
|
}
|
||||||
|
|
||||||
tr_free( buf );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool fileExists( const char * filename, time_t * optional_mtime );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This convoluted code does something (seemingly) simple:
|
||||||
|
* remove the torrent's local files.
|
||||||
|
*
|
||||||
|
* Fun complications:
|
||||||
|
* 1. Try to preserve the directory hierarchy in the recycle bin.
|
||||||
|
* 2. If there are nontorrent files, don't delete them...
|
||||||
|
* 3. ...unless the other files are "junk", such as .DS_Store
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
deleteLocalData( tr_torrent * tor, tr_fileFunc fileFunc )
|
deleteLocalData( tr_torrent * tor, tr_fileFunc func )
|
||||||
{
|
{
|
||||||
int i, n;
|
int i, n;
|
||||||
char * tmp;
|
|
||||||
char * root;
|
|
||||||
char ** s;
|
|
||||||
tr_file_index_t f;
|
tr_file_index_t f;
|
||||||
const char * cpch;
|
char * base;
|
||||||
const char * firstFile;
|
DIR * odir;
|
||||||
tr_ptrArray torrentFiles = TR_PTR_ARRAY_INIT;
|
char * tmpdir = NULL;
|
||||||
tr_ptrArray folders = TR_PTR_ARRAY_INIT;
|
tr_ptrArray files = TR_PTR_ARRAY_INIT;
|
||||||
tr_ptrArray dirtyFolders = TR_PTR_ARRAY_INIT; /* dirty == contains non-torrent files */
|
tr_ptrArray folders = TR_PTR_ARRAY_INIT;
|
||||||
|
const void * const vstrcmp = strcmp;
|
||||||
|
const char * const top = tor->currentDir;
|
||||||
|
|
||||||
if( !tr_torrentHasMetadata( tor ) )
|
/***
|
||||||
return;
|
**** Move the local data to a new tmpdir
|
||||||
|
***/
|
||||||
|
|
||||||
firstFile = tor->info.files[0].name;
|
base = tr_strdup_printf( "%s__XXXXXX", tr_torrentName( tor ) );
|
||||||
cpch = strchr( firstFile, TR_PATH_DELIMITER );
|
tmpdir = tr_buildPath( top, base, NULL );
|
||||||
tmp = cpch ? tr_strndup( firstFile, cpch - firstFile ) : NULL;
|
mkdtemp( tmpdir );
|
||||||
root = tr_buildPath( tor->currentDir, tmp, NULL );
|
tr_free( base );
|
||||||
|
|
||||||
for( f=0; f<tor->info.fileCount; ++f ) {
|
for( f=0; f<tor->info.fileCount; ++f )
|
||||||
tr_ptrArrayInsertSorted( &torrentFiles, tr_strdup( tor->info.files[f].name ), vstrcmp );
|
|
||||||
tr_ptrArrayInsertSorted( &torrentFiles, tr_torrentBuildPartial( tor, f ), vstrcmp );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* build the set of folders and dirtyFolders */
|
|
||||||
walkLocalData( tor, root, root, NULL, &torrentFiles, &folders, &dirtyFolders, fileFunc );
|
|
||||||
|
|
||||||
/* try to remove entire folders first, so that the recycle bin will be tidy */
|
|
||||||
s = (char**) tr_ptrArrayPeek( &folders, &n );
|
|
||||||
for( i=0; i<n; ++i )
|
|
||||||
if( tr_ptrArrayFindSorted( &dirtyFolders, s[i], vstrcmp ) == NULL )
|
|
||||||
deleteLocalFile( tor, s[i], fileFunc );
|
|
||||||
|
|
||||||
/* now blow away any remaining torrent files, such as torrent files in dirty folders */
|
|
||||||
for( i=0, n=tr_ptrArraySize( &torrentFiles ); i<n; ++i ) {
|
|
||||||
char * path = tr_buildPath( tor->currentDir, tr_ptrArrayNth( &torrentFiles, i ), NULL );
|
|
||||||
deleteLocalFile( tor, path, fileFunc );
|
|
||||||
tr_free( path );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Now clean out the directories left empty from the previous step.
|
|
||||||
* Work from deepest to shallowest s.t. lower folders
|
|
||||||
* won't prevent the upper folders from being deleted */
|
|
||||||
{
|
{
|
||||||
tr_ptrArray cleanFolders = TR_PTR_ARRAY_INIT;
|
char * subpath;
|
||||||
s = (char**) tr_ptrArrayPeek( &folders, &n );
|
const char * base;
|
||||||
for( i=0; i<n; ++i )
|
|
||||||
if( tr_ptrArrayFindSorted( &dirtyFolders, s[i], vstrcmp ) == NULL )
|
if( tr_torrentFindFile2( tor, f, &base, &subpath, NULL ) )
|
||||||
tr_ptrArrayInsertSorted( &cleanFolders, s[i], compareLongestFirst );
|
{
|
||||||
s = (char**) tr_ptrArrayPeek( &cleanFolders, &n );
|
char * source = tr_buildPath( base, subpath, NULL );
|
||||||
for( i=0; i<n; ++i )
|
char * target = tr_buildPath( tmpdir, subpath, NULL );
|
||||||
deleteLocalFile( tor, s[i], fileFunc );
|
char * target_dir = tr_dirname( target );
|
||||||
tr_ptrArrayDestruct( &cleanFolders, NULL );
|
tr_mkdirp( target_dir, 0777 );
|
||||||
|
rename( source, target );
|
||||||
|
tr_free( target_dir );
|
||||||
|
tr_free( target );
|
||||||
|
tr_free( source );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
**** Remove tmpdir.
|
||||||
|
****
|
||||||
|
**** Try deleting the top-level files & folders to preserve
|
||||||
|
**** the directory hierarchy in the recycle bin.
|
||||||
|
**** If case that fails -- for example, rmdir() doesn't
|
||||||
|
**** delete nonempty folders -- go from the bottom up too.
|
||||||
|
***/
|
||||||
|
|
||||||
|
/* try deleting the local data's top-level files & folders */
|
||||||
|
if(( odir = opendir( tmpdir )))
|
||||||
|
{
|
||||||
|
struct dirent * d;
|
||||||
|
while(( d = readdir( odir )))
|
||||||
|
{
|
||||||
|
if( strcmp( d->d_name, "." ) && strcmp( d->d_name, ".." ) )
|
||||||
|
{
|
||||||
|
char * file = tr_buildPath( tmpdir, d->d_name, NULL );
|
||||||
|
tr_ptrArrayInsertSorted( &folders, tr_strdup( d->d_name ), vstrcmp );
|
||||||
|
func( file );
|
||||||
|
tr_free( file );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir( odir );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* go from the bottom up */
|
||||||
|
for( i=0, n=tr_ptrArraySize(&files); i<n; ++i )
|
||||||
|
{
|
||||||
|
char * walk = tr_strdup( tr_ptrArrayNth( &files, i ) );
|
||||||
|
while( fileExists( walk, NULL ) && !tr_is_same_file( tmpdir, walk ) )
|
||||||
|
{
|
||||||
|
char * tmp = tr_dirname( walk );
|
||||||
|
func( walk );
|
||||||
|
tr_free( walk );
|
||||||
|
walk = tmp;
|
||||||
|
}
|
||||||
|
tr_free( walk );
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
**** The local data has been removed.
|
||||||
|
**** What's left in top are empty folders, junk, and user-generated files.
|
||||||
|
**** Remove the first two categories and leave the third.
|
||||||
|
***/
|
||||||
|
|
||||||
|
for( i=0, n=tr_ptrArraySize(&folders); i<n; ++i )
|
||||||
|
{
|
||||||
|
char * folder = tr_buildPath( top, tr_ptrArrayNth(&folders,i), NULL );
|
||||||
|
removeEmptyFoldersAndJunkFiles( folder );
|
||||||
|
tr_free( folder );
|
||||||
}
|
}
|
||||||
|
|
||||||
/* cleanup */
|
/* cleanup */
|
||||||
tr_ptrArrayDestruct( &dirtyFolders, tr_free );
|
rmdir( tmpdir );
|
||||||
|
tr_free( tmpdir );
|
||||||
|
tr_ptrArrayDestruct( &files, tr_free );
|
||||||
tr_ptrArrayDestruct( &folders, tr_free );
|
tr_ptrArrayDestruct( &folders, tr_free );
|
||||||
tr_ptrArrayDestruct( &torrentFiles, tr_free );
|
|
||||||
tr_free( root );
|
|
||||||
tr_free( tmp );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
tr_torrentDeleteLocalData( tr_torrent * tor, tr_fileFunc fileFunc )
|
tr_torrentDeleteLocalData( tr_torrent * tor, tr_fileFunc func )
|
||||||
{
|
{
|
||||||
assert( tr_isTorrent( tor ) );
|
assert( tr_isTorrent( tor ) );
|
||||||
|
|
||||||
if( fileFunc == NULL )
|
if( func == NULL )
|
||||||
fileFunc = remove;
|
func = remove;
|
||||||
|
|
||||||
/* close all the files because we're about to delete them */
|
/* close all the files because we're about to delete them */
|
||||||
tr_cacheFlushTorrent( tor->session->cache, tor );
|
tr_cacheFlushTorrent( tor->session->cache, tor );
|
||||||
tr_fdTorrentClose( tor->session, tor->uniqueId );
|
tr_fdTorrentClose( tor->session, tor->uniqueId );
|
||||||
|
|
||||||
deleteLocalData( tor, fileFunc );
|
deleteLocalData( tor, func );
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
|
|
Loading…
Reference in a new issue