Wow
[ntk/apt.git] / apt-pkg / acquire-item.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: acquire-item.cc,v 1.12 1998/11/13 07:08:48 jgg Exp $
4 /* ######################################################################
5
6 Acquire Item - Item to acquire
7
8 Each item can download to exactly one file at a time. This means you
9 cannot create an item that fetches two uri's to two files at the same
10 time. The pkgAcqIndex class creates a second class upon instantiation
11 to fetch the other index files because of this.
12
13 ##################################################################### */
14 /*}}}*/
15 // Include Files /*{{{*/
16 #ifdef __GNUG__
17 #pragma implementation "apt-pkg/acquire-item.h"
18 #endif
19 #include <apt-pkg/acquire-item.h>
20 #include <apt-pkg/configuration.h>
21 #include <apt-pkg/error.h>
22 #include <strutl.h>
23
24 #include <sys/stat.h>
25 #include <unistd.h>
26 #include <errno.h>
27 #include <string.h>
28 #include <stdio.h>
29 /*}}}*/
30
31 // Acquire::Item::Item - Constructor /*{{{*/
32 // ---------------------------------------------------------------------
33 /* */
34 pkgAcquire::Item::Item(pkgAcquire *Owner) : Owner(Owner), FileSize(0),
35 Mode(0), ID(0), Complete(false), Local(false),
36 QueueCounter(0)
37 {
38 Owner->Add(this);
39 Status = StatIdle;
40 }
41 /*}}}*/
42 // Acquire::Item::~Item - Destructor /*{{{*/
43 // ---------------------------------------------------------------------
44 /* */
45 pkgAcquire::Item::~Item()
46 {
47 Owner->Remove(this);
48 }
49 /*}}}*/
50 // Acquire::Item::Failed - Item failed to download /*{{{*/
51 // ---------------------------------------------------------------------
52 /* We return to an idle state if there are still other queues that could
53 fetch this object */
54 void pkgAcquire::Item::Failed(string Message)
55 {
56 Status = StatIdle;
57 if (QueueCounter <= 1)
58 {
59 ErrorText = LookupTag(Message,"Message");
60 Status = StatError;
61 Owner->Dequeue(this);
62 }
63 }
64 /*}}}*/
65 // Acquire::Item::Start - Item has begun to download /*{{{*/
66 // ---------------------------------------------------------------------
67 /* */
68 void pkgAcquire::Item::Start(string Message,unsigned long Size)
69 {
70 Status = StatFetching;
71 if (FileSize == 0 && Complete == false)
72 FileSize = Size;
73 }
74 /*}}}*/
75 // Acquire::Item::Done - Item downloaded OK /*{{{*/
76 // ---------------------------------------------------------------------
77 /* */
78 void pkgAcquire::Item::Done(string Message,unsigned long Size,string)
79 {
80 // We just downloaded something..
81 string FileName = LookupTag(Message,"Filename");
82 if (Complete == false && FileName == DestFile)
83 {
84 if (Owner->Log != 0)
85 Owner->Log->Fetched(Size,atoi(LookupTag(Message,"Resume-Point","0").c_str()));
86 }
87
88 Status = StatDone;
89 ErrorText = string();
90 Owner->Dequeue(this);
91 }
92 /*}}}*/
93 // Acquire::Item::Rename - Rename a file /*{{{*/
94 // ---------------------------------------------------------------------
95 /* This helper function is used by alot of item methods as thier final
96 step */
97 void pkgAcquire::Item::Rename(string From,string To)
98 {
99 if (rename(From.c_str(),To.c_str()) != 0)
100 {
101 char S[300];
102 sprintf(S,"rename failed, %s (%s -> %s).",strerror(errno),
103 From.c_str(),To.c_str());
104 Status = StatError;
105 ErrorText = S;
106 }
107 }
108 /*}}}*/
109
110 // AcqIndex::AcqIndex - Constructor /*{{{*/
111 // ---------------------------------------------------------------------
112 /* The package file is added to the queue and a second class is
113 instantiated to fetch the revision file */
114 pkgAcqIndex::pkgAcqIndex(pkgAcquire *Owner,const pkgSourceList::Item *Location) :
115 Item(Owner), Location(Location)
116 {
117 Decompression = false;
118 Erase = false;
119
120 DestFile = _config->FindDir("Dir::State::lists") + "partial/";
121 DestFile += URItoFileName(Location->PackagesURI());
122
123 // Create the item
124 Desc.URI = Location->PackagesURI() + ".gz";
125 Desc.Description = Location->PackagesInfo();
126 Desc.Owner = this;
127
128 // Set the short description to the archive component
129 if (Location->Dist[Location->Dist.size() - 1] == '/')
130 Desc.ShortDesc = Location->Dist;
131 else
132 Desc.ShortDesc = Location->Dist + '/' + Location->Section;
133
134 QueueURI(Desc);
135
136 // Create the Release fetch class
137 new pkgAcqIndexRel(Owner,Location);
138 }
139 /*}}}*/
140 // AcqIndex::Custom600Headers - Insert custom request headers /*{{{*/
141 // ---------------------------------------------------------------------
142 /* The only header we use is the last-modified header. */
143 string pkgAcqIndex::Custom600Headers()
144 {
145 string Final = _config->FindDir("Dir::State::lists");
146 Final += URItoFileName(Location->PackagesURI());
147
148 struct stat Buf;
149 if (stat(Final.c_str(),&Buf) != 0)
150 return string();
151
152 return "\nLast-Modified: " + TimeRFC1123(Buf.st_mtime);
153 }
154 /*}}}*/
155 // AcqIndex::Done - Finished a fetch /*{{{*/
156 // ---------------------------------------------------------------------
157 /* This goes through a number of states.. On the initial fetch the
158 method could possibly return an alternate filename which points
159 to the uncompressed version of the file. If this is so the file
160 is copied into the partial directory. In all other cases the file
161 is decompressed with a gzip uri. */
162 void pkgAcqIndex::Done(string Message,unsigned long Size,string MD5)
163 {
164 Item::Done(Message,Size,MD5);
165
166 if (Decompression == true)
167 {
168 // Done, move it into position
169 string FinalFile = _config->FindDir("Dir::State::lists");
170 FinalFile += URItoFileName(Location->PackagesURI());
171 Rename(DestFile,FinalFile);
172
173 /* We restore the original name to DestFile so that the clean operation
174 will work OK */
175 DestFile = _config->FindDir("Dir::State::lists") + "partial/";
176 DestFile += URItoFileName(Location->PackagesURI());
177
178 // Remove the compressed version.
179 if (Erase == true)
180 unlink(DestFile.c_str());
181 return;
182 }
183
184 Erase = false;
185 Complete = true;
186
187 // Handle the unzipd case
188 string FileName = LookupTag(Message,"Alt-Filename");
189 if (FileName.empty() == false)
190 {
191 // The files timestamp matches
192 if (StringToBool(LookupTag(Message,"Alt-IMS-Hit"),false) == true)
193 return;
194
195 Decompression = true;
196 Local = true;
197 DestFile += ".decomp";
198 Desc.URI = "copy:" + FileName;
199 QueueURI(Desc);
200 Mode = "copy";
201 return;
202 }
203
204 FileName = LookupTag(Message,"Filename");
205 if (FileName.empty() == true)
206 {
207 Status = StatError;
208 ErrorText = "Method gave a blank filename";
209 }
210
211 // The files timestamp matches
212 if (StringToBool(LookupTag(Message,"IMS-Hit"),false) == true)
213 return;
214
215 if (FileName == DestFile)
216 Erase = true;
217 else
218 Local = true;
219
220 Decompression = true;
221 DestFile += ".decomp";
222 Desc.URI = "gzip:" + FileName,Location->PackagesInfo();
223 QueueURI(Desc);
224 Mode = "gzip";
225 }
226 /*}}}*/
227
228 // AcqIndexRel::pkgAcqIndexRel - Constructor /*{{{*/
229 // ---------------------------------------------------------------------
230 /* The Release file is added to the queue */
231 pkgAcqIndexRel::pkgAcqIndexRel(pkgAcquire *Owner,
232 const pkgSourceList::Item *Location) :
233 Item(Owner), Location(Location)
234 {
235 DestFile = _config->FindDir("Dir::State::lists") + "partial/";
236 DestFile += URItoFileName(Location->ReleaseURI());
237
238 // Create the item
239 Desc.URI = Location->ReleaseURI();
240 Desc.Description = Location->ReleaseInfo();
241 Desc.Owner = this;
242
243 // Set the short description to the archive component
244 if (Location->Dist[Location->Dist.size() - 1] == '/')
245 Desc.ShortDesc = Location->Dist;
246 else
247 Desc.ShortDesc = Location->Dist + '/' + Location->Section;
248
249 QueueURI(Desc);
250 }
251 /*}}}*/
252 // AcqIndexRel::Custom600Headers - Insert custom request headers /*{{{*/
253 // ---------------------------------------------------------------------
254 /* The only header we use is the last-modified header. */
255 string pkgAcqIndexRel::Custom600Headers()
256 {
257 string Final = _config->FindDir("Dir::State::lists");
258 Final += URItoFileName(Location->ReleaseURI());
259
260 struct stat Buf;
261 if (stat(Final.c_str(),&Buf) != 0)
262 return string();
263
264 return "\nLast-Modified: " + TimeRFC1123(Buf.st_mtime);
265 }
266 /*}}}*/
267 // AcqIndexRel::Done - Item downloaded OK /*{{{*/
268 // ---------------------------------------------------------------------
269 /* The release file was not placed into the download directory then
270 a copy URI is generated and it is copied there otherwise the file
271 in the partial directory is moved into .. and the URI is finished. */
272 void pkgAcqIndexRel::Done(string Message,unsigned long Size,string MD5)
273 {
274 Item::Done(Message,Size,MD5);
275
276 string FileName = LookupTag(Message,"Filename");
277 if (FileName.empty() == true)
278 {
279 Status = StatError;
280 ErrorText = "Method gave a blank filename";
281 return;
282 }
283
284 Complete = true;
285
286 // The files timestamp matches
287 if (StringToBool(LookupTag(Message,"IMS-Hit"),false) == true)
288 return;
289
290 // We have to copy it into place
291 if (FileName != DestFile)
292 {
293 Local = true;
294 Desc.URI = "copy:" + FileName;
295 QueueURI(Desc);
296 return;
297 }
298
299 // Done, move it into position
300 string FinalFile = _config->FindDir("Dir::State::lists");
301 FinalFile += URItoFileName(Location->ReleaseURI());
302 Rename(DestFile,FinalFile);
303 }
304 /*}}}*/
305
306 // AcqArchive::AcqArchive - Constructor /*{{{*/
307 // ---------------------------------------------------------------------
308 /* */
309 pkgAcqArchive::pkgAcqArchive(pkgAcquire *Owner,pkgSourceList *Sources,
310 pkgRecords *Recs,pkgCache::VerIterator const &Version) :
311 Item(Owner), Version(Version), Sources(Sources), Recs(Recs)
312 {
313 // Select a source
314 pkgCache::VerFileIterator Vf = Version.FileList();
315 for (; Vf.end() == false; Vf++)
316 {
317 // Ignore not source sources
318 if ((Vf.File()->Flags & pkgCache::Flag::NotSource) != 0)
319 continue;
320
321 // Try to cross match against the source list
322 string PkgFile = flNotDir(Vf.File().FileName());
323 pkgSourceList::const_iterator Location;
324 for (Location = Sources->begin(); Location != Sources->end(); Location++)
325 if (PkgFile == URItoFileName(Location->PackagesURI()))
326 break;
327
328 if (Location == Sources->end())
329 continue;
330
331 // Grab the text package record
332 pkgRecords::Parser &Parse = Recs->Lookup(Vf);
333 if (_error->PendingError() == true)
334 return;
335
336 PkgFile = Parse.FileName();
337 MD5 = Parse.MD5Hash();
338 if (PkgFile.empty() == true)
339 {
340 _error->Error("Unable to locate a file name for package %s, "
341 "perhaps the package files are corrupted.",
342 Version.ParentPkg().Name());
343 return;
344 }
345
346 // See if we already have the file.
347 FileSize = Version->Size;
348 string FinalFile = _config->FindDir("Dir::Cache::Archives") + flNotDir(PkgFile);
349 struct stat Buf;
350 if (stat(FinalFile.c_str(),&Buf) == 0)
351 {
352 // Make sure the size matches
353 if ((unsigned)Buf.st_size == Version->Size)
354 {
355 Complete = true;
356 Local = true;
357 Status = StatDone;
358 DestFile = FinalFile;
359 return;
360 }
361
362 /* Hmm, we have a file and its size does not match, this shouldnt
363 happen.. */
364 unlink(FinalFile.c_str());
365 }
366
367 DestFile = _config->FindDir("Dir::Cache::Archives") + "partial/" + flNotDir(PkgFile);
368
369 // Create the item
370 Desc.URI = Location->ArchiveURI(PkgFile);
371 Desc.Description = Location->ArchiveInfo(Version);
372 Desc.Owner = this;
373 Desc.ShortDesc = Version.ParentPkg().Name();
374 QueueURI(Desc);
375
376 return;
377 }
378
379 _error->Error("I wasn't able to locate file for the %s package. "
380 "This probably means you need to rerun update.",
381 Version.ParentPkg().Name());
382 }
383 /*}}}*/
384 // AcqArchive::Done - Finished fetching /*{{{*/
385 // ---------------------------------------------------------------------
386 /* */
387 void pkgAcqArchive::Done(string Message,unsigned long Size,string Md5Hash)
388 {
389 Item::Done(Message,Size,MD5);
390
391 // Check the size
392 if (Size != Version->Size)
393 {
394 _error->Error("Size mismatch for package %s",Version.ParentPkg().Name());
395 return;
396 }
397
398 // Check the md5
399 if (Md5Hash.empty() == false && MD5.empty() == false)
400 {
401 if (Md5Hash != MD5)
402 {
403 _error->Error("MD5Sum mismatch for package %s",Version.ParentPkg().Name());
404 return;
405 }
406 }
407
408 // Grab the output filename
409 string FileName = LookupTag(Message,"Filename");
410 if (FileName.empty() == true)
411 {
412 Status = StatError;
413 ErrorText = "Method gave a blank filename";
414 return;
415 }
416
417 Complete = true;
418
419 // We have to copy it into place
420 if (FileName != DestFile)
421 {
422 DestFile = FileName;
423 Local = true;
424 return;
425 }
426
427 // Done, move it into position
428 string FinalFile = _config->FindDir("Dir::Cache::Archives");
429 FinalFile += flNotDir(DestFile);
430 Rename(DestFile,FinalFile);
431
432 DestFile = FinalFile;
433 Complete = true;
434 }
435 /*}}}*/