summaryrefslogtreecommitdiffstats
path: root/amarok/src/metabundlesaver.cpp
blob: cd6e8cc0b07b929f870a9420b92121fd61dcdd6d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
// Jeff Mitchell <kde-dev@emailgoeshere.com>, (C) 2006
// License: GNU General Public License V2


#define DEBUG_PREFIX "MetaBundleSaver"

#include <cstdio>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>

#include "amarok.h"
#include "amarokconfig.h"
#include "collectiondb.h"
#include "debug.h"
#include "metabundlesaver.h"
#include "scancontroller.h"
#include <tdeapplication.h>
#include <tdefilemetainfo.h>
#include <tdeio/global.h>
#include <tdeio/job.h>
#include <tdeio/jobclasses.h>
#include <tdeio/netaccess.h>
#include <kmdcodec.h>
#include <kurl.h>
#include <tqfile.h> //decodePath()
#include <tqcstring.h>
#include <tqstring.h>
#include <taglib/fileref.h>

#include <config.h>

#include "metabundle.h"

MetaBundleSaver::MetaBundleSaver( MetaBundle *bundle )
    : TQObject()
    , m_bundle( bundle )
    , m_tempSavePath( TQString() )
    , m_origRenamedSavePath( TQString() )
    , m_tempSaveDigest( 0 )
    , m_saveFileref( 0 )
    , m_maxlen( 8192 )
    , m_cleanupNeeded( false )

{
    DEBUG_BLOCK
}

MetaBundleSaver::~MetaBundleSaver()
{
    DEBUG_BLOCK
    if( m_cleanupNeeded )
        cleanupSave();
}

TagLib::FileRef *
MetaBundleSaver::prepareToSave()
{
    DEBUG_BLOCK

    m_cleanupNeeded = true;
    KMD5 md5sum( 0, 0 );
    const KURL origPath = m_bundle->url();
    char hostbuf[32];
    hostbuf[0] = '\0';
    int hostname = gethostname( hostbuf, 32 );
    hostbuf[31] = '\0';
    if( hostname != 0 )
    {
        debug() << "Could not determine hostname!" << endl;
        return 0;
    }
    TQString pid;
    TQString randomString = m_bundle->getRandomString( 8, true );
    m_tempSavePath = origPath.path() + ".amaroktemp.host-" + TQString( hostbuf ) +
                        ".pid-" + pid.setNum( getpid() ) + ".random-" + randomString + '.' + m_bundle->type();
    m_origRenamedSavePath = origPath.path() + ".amarokoriginal.host-" + TQString( hostbuf ) +
                        ".pid-" + pid.setNum( getpid() ) + ".random-" + randomString + '.' + m_bundle->type();


    //The next long step is to copy the file over.  We can't use TDEIO because it's not thread save,
    //and std and TQFile only have provisions for renaming and removing, so manual it is
    //doing it block-by-block results it not needing a huge amount of memory overhead

    debug() << "Copying original file to copy and caluclating MD5" << endl;

    if( TQFile::exists( m_tempSavePath ) )
    {
        debug() << "Temp file already exists!" << endl;
        return 0;
    }

    TQFile orig( m_bundle->url().path() );
    TQFile copy( m_tempSavePath );

    if( !orig.open( IO_Raw | IO_ReadOnly ) )
    {
        debug() << "Could not open original file!" << endl;
        return 0;
    }

    //Do this separately so as not to create a zero-length file if you can't read from input
    if( !copy.open( IO_Raw | IO_WriteOnly | IO_Truncate ) )
    {
        debug() << "Could not create file copy" << endl;
        return 0;
    }

    TQ_LONG actualreadlen, actualwritelen;

    while( ( actualreadlen = orig.readBlock( m_databuf, m_maxlen ) ) > 0 )
    {
        md5sum.update( m_databuf, actualreadlen );
        if( ( actualwritelen = copy.writeBlock( m_databuf, actualreadlen ) ) != actualreadlen )
        {
            debug() << "Error during copying of original file data to copy!" << endl;
            return 0;
        }
    }

    if( actualreadlen == -1 )
    {
        debug() << "Error during reading original file!" << endl;
        return 0;
    }

    m_tempSaveDigest = md5sum.hexDigest();

    //By this point, we have the following:
    //The original file is copied at path m_tempSavePath
    //We have generated what will be the filename to rename the original to in m_origRenamedSavePath
    //We have successfully copied the original file to the temp location
    //We've calculated the md5sum of the original file

    debug() << "MD5 sum of temp file: " << m_tempSaveDigest.data() << endl;

    //Now, we have a MD5 sum of the original file at the time of copying saved in m_tempSaveDigest
    //Create a fileref on the copied file, for modification

    m_saveFileref = new TagLib::FileRef( TQFile::encodeName( m_tempSavePath ), false );

    if( m_saveFileref && !m_saveFileref->isNull() )
        return m_saveFileref;

    debug() << "Error creating temp file's fileref!" << endl;
    return 0;
}

bool
MetaBundleSaver::doSave()
{
    //TODO: much commenting needed.  For now this pretty much follows algorithm laid out in bug 131353,
    //but isn't useable since I need to find a good way to switch the file path with taglib, or a good way
    //to get all the metadata copied over.

    DEBUG_BLOCK
    m_cleanupNeeded = true;
    bool revert = false;

    TQFile origRenamedFile( m_origRenamedSavePath );
    KMD5 md5sum( 0, 0 );
    TQ_LONG actualreadlen;

    int errcode;

    TQCString origRenamedDigest;

    if( !m_saveFileref || m_tempSavePath.isEmpty() || m_tempSaveDigest.isEmpty() || m_origRenamedSavePath.isEmpty() )
    {
        debug() << "You must run prepareToSave() and it must return successfully before calling doSave()!" << endl;
        return false;
    }

    debug() << "Saving tag changes to the temporary file..." << endl;

    //We've made our changes to the fileref; save it first, then do the logic to move the correct file back
    if( !m_saveFileref->save() )
    {
        debug() << "Could not save the new file!" << endl;
        goto fail_remove_copy;
    }

    debug() << "Renaming original file to temporary name " << m_origRenamedSavePath << endl;

    errcode = std::rename( TQFile::encodeName( m_bundle->url().path() ).data(),
                               TQFile::encodeName( m_origRenamedSavePath ).data() );
    if( errcode != 0 )
    {
        debug() << "Could not move original!" << endl;
        perror( "Could not move original!" );
        goto fail_remove_copy;
    }

    revert = true;

    debug() << "Calculating MD5 of " << m_origRenamedSavePath << endl;

    if( !origRenamedFile.open( IO_Raw | IO_ReadOnly ) )
    {
        debug() << "Could not open temporary file!" << endl;
        goto fail_remove_copy;
    }

    while( ( actualreadlen = origRenamedFile.readBlock( m_databuf, m_maxlen ) ) > 0 )
        md5sum.update( m_databuf, actualreadlen );

    if( actualreadlen == -1 )
    {
        debug() << "Error during checksumming temp file!" << endl;
        goto fail_remove_copy;
    }

    origRenamedDigest = md5sum.hexDigest();

    debug() << "md5sum of original file: " << origRenamedDigest.data() << endl;

    if( origRenamedDigest != m_tempSaveDigest )
    {
        debug() << "Original checksum did not match current checksum!" << endl;
        goto fail_remove_copy;
    }

    debug() << "Renaming temp file to original's filename" << endl;

    errcode = std::rename( TQFile::encodeName( m_tempSavePath ).data(),
                                TQFile::encodeName( m_bundle->url().path() ).data() );
    if( errcode != 0 )
    {
        debug() << "Could not rename newly-tagged file to original!" << endl;
        perror( "Could not rename newly-tagged file to original!" );
        goto fail_remove_copy;
    }

    debug() << "Deleting original" << endl;

    errcode = std::remove( TQFile::encodeName( m_origRenamedSavePath ) );
    if( errcode != 0 )
    {
        debug() << "Could not delete the original file!" << endl;
        perror( "Could not delete the original file!" );
        return false;
    }

    debug() << "Save done, returning true!" << endl;

    return true;

    fail_remove_copy:

        debug() << "Deleting temporary file..." << endl;
        errcode = std::remove( TQFile::encodeName( m_tempSavePath ).data() );
        if( errcode != 0 )
        {
            debug() << "Could not delete the temporary file!" << endl;
            perror( "Could not delete the temporary file!" );
        }

        if( !revert )
            return false;

        debug() << "Reverting original file to original filename!" << endl;
        errcode = std::rename( TQFile::encodeName( m_origRenamedSavePath ).data(),
                                TQFile::encodeName( m_bundle->url().path() ).data() );
        if( errcode != 0 )
        {
            debug() << "Could not revert file to original filename!" << endl;
            perror( "Could not revert file to original filename!" );
        }

        return false;
}

bool
MetaBundleSaver::cleanupSave()
{
    DEBUG_BLOCK

    bool dirty = false;

    if( !m_tempSavePath.isEmpty() && TQFile::exists( m_tempSavePath ) )
    {
        int errcode;
        errcode = std::remove( TQFile::encodeName( m_tempSavePath ).data() );
        if( errcode != 0 )
        {
            dirty = true;
            debug() << "Could not delete the temporary file!" << endl;
        }
    }

    m_tempSavePath = TQString();
    m_origRenamedSavePath = TQString();
    m_tempSaveDigest = TQCString( 0 );
    if( m_saveFileref )
    {
        delete m_saveFileref;
        m_saveFileref = 0;
    }

    m_cleanupNeeded = false;
    return !dirty;
}

#include "metabundlesaver.moc"