1 ////////////////////////////////////////////////////////////////////////
2 // FILE: repository.cpp
3 // AUTHOR: Johannes Winkelmann, jw@tks6.net
4 // COPYRIGHT: (c) 2002 by Johannes Winkelmann
5 // ---------------------------------------------------------------------
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
10 ////////////////////////////////////////////////////////////////////////
11
12 #include <cstdio>
13 #include <cstring>
14 #include <iostream>
15 #include <algorithm>
16 #include <vector>
17 using namespace std;
18
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <dirent.h>
23 #include <unistd.h>
24 #include <fnmatch.h>
25
26 #include "datafileparser.h"
27 #include "repository.h"
28 #include "stringhelper.h"
29 #include "pg_regex.h"
30 using namespace StringHelper;
31
32
33 const string Repository::CACHE_VERSION = "V5";
34
35 /*!
36 Create a repository
37 */
38 Repository::Repository(bool useRegex)
39 : m_useRegex(useRegex)
40 {
41 }
42
43 /*!
44 Destroy a repository
45 */
46 Repository::~Repository()
47 {
48 map<string, Package*>::const_iterator it = m_packageMap.begin();
49 for ( ; it != m_packageMap.end(); ++it ) {
50 delete it->second;
51 }
52 }
53
54
55 /*!
56 \return a map of available packages
57 */
58 const map<string, Package*>& Repository::packages() const
59 {
60 return m_packageMap;
61 }
62
63
64 /*!
65 Returns a sorted list of duplicate packages in the repository.
66 In the pairs \a first is the shadowed port and
67 \a second is the port which preceeds over \a first
68 \return a list of duplicate packages in the repository
69 */
70 const list< pair<Package*, Package*> >& Repository::shadowedPackages() const
71 {
72 return m_shadowedPackages;
73 }
74
75
76 /*!
77 \param name the package name to be returned
78 \return a Package pointer for a package name or 0 if not found
79 */
80 const Package* Repository::getPackage( const string& name ) const
81 {
82 map<string, Package*>::const_iterator it = m_packageMap.find( name );
83 if ( it == m_packageMap.end() ) {
84 return 0;
85 }
86 return it->second;
87 }
88
89
90 /*!
91 Search packages for a match of \a pattern in name, and description of
92 \a searchDesc is true.
93 \note Name searches can often done without opening the Pkgfiles, but not
94 description search. Therefore, the later is much slower
95
96 \param pattern the pattern to be found
97 \param searchDesc whether descriptions should be searched as well
98 \return a list of matching packages
99 */
100
101 void Repository::searchMatchingPackages( const string& pattern,
102 list<Package*>& target,
103 bool searchDesc ) const
104 // note: searchDesc true will read _every_ Pkgfile
105 {
106 map<string, Package*>::const_iterator it = m_packageMap.begin();
107 if (m_useRegex) {
108 RegEx re(pattern);
109 for ( ; it != m_packageMap.end(); ++it ) {
110 if (re.match(it->first)) {
111 target.push_back( it->second );
112 } else if ( searchDesc ) {
113 if ( re.match(it->second->description())) {
114 target.push_back( it->second );
115 }
116 }
117 }
118 } else {
119 for ( ; it != m_packageMap.end(); ++it ) {
120 if ( it->first.find( pattern ) != string::npos ) {
121 target.push_back( it->second );
122 } else if (searchDesc ) {
123 string s = toLowerCase( it->second->description() );
124 if ( s.find( toLowerCase( pattern ) ) != string::npos ) {
125 target.push_back( it->second );
126 }
127 }
128 }
129 }
130 }
131
132 int Repository::compareShadowPair(pair<Package*, Package*>& p1,
133 pair<Package*, Package*>& p2)
134 {
135 return p1.second->name() < p2.second->name();
136 }
137
138
139 /*!
140 init repository by reading the directories passed. Doesn't search
141 recursively, so if you want /dir and /dir/subdir checked, you have to
142 specify both
143
144 \param rootList a list of directories to look for ports in
145 \param listDuplicate whether duplicates should registered (slower)
146 */
147 void Repository::initFromFS( const list< pair<string, string> >& rootList,
148 bool listDuplicate )
149 {
150 list< pair<string, string> >::const_iterator it = rootList.begin();
151 DIR* d;
152 struct dirent* de;
153 string name;
154
155 std::map<string, bool> alreadyChecked;
156
157
158 for ( ; it != rootList.end(); ++it ) {
159
160 string path = it->first;
161 string pkgInput = stripWhiteSpace( it->second );
162
163 if ( alreadyChecked[path] ) {
164 continue;
165 }
166
167 bool filter = false;
168 if ( pkgInput.length() > 0 ) {
169 filter = true;
170 // create a proper input string
171 while ( pkgInput.find( " " ) != string::npos ) {
172 pkgInput = pkgInput.replace( pkgInput.find(" "), 1, "," );
173 }
174 while ( pkgInput.find( "\t" ) != string::npos ) {
175 pkgInput = pkgInput.replace( pkgInput.find("\t"), 1, "," );
176 }
177 while ( pkgInput.find( ",," ) != string::npos ) {
178 pkgInput = pkgInput.replace( pkgInput.find(",,"), 2, "," );
179 }
180 }
181
182 if (!filter) {
183 alreadyChecked[path] = true;
184 }
185
186 list<string> packages;
187 split( pkgInput, ',', packages );
188
189
190
191 // TODO: think about whether it would be faster (more
192 // efficient) to put all packages into a map, and the iterate
193 // over the list of allowed packages and copy them
194 // over. depending in the efficiency of find(), this might be
195 // faster
196 d = opendir( path.c_str() );
197 while ( ( de = readdir( d ) ) != NULL ) {
198 name = de->d_name;
199
200 // TODO: review this
201 struct stat buf;
202 if ( stat( (path + "/" + name + "/Pkgfile").c_str(), &buf )
203 != 0 ) {
204 // no Pkgfile -> no port
205 continue;
206 }
207
208 if ( filter && find( packages.begin(),
209 packages.end(), name ) == packages.end() ) {
210 // not found -> ignore this port
211 continue;
212 }
213
214 if ( name != "." && name != ".." ) {
215
216 map<string, Package*>::iterator hidden;
217 hidden = m_packageMap.find( name );
218 Package* p = new Package( name, path );
219 if ( p ) {
220 if ( hidden == m_packageMap.end() ) {
221 // no such package found, add
222 m_packageMap[name] = p;
223 } else if ( listDuplicate ) {
224 m_shadowedPackages.push_back(
225 make_pair( p, hidden->second ));
226 } else {
227 delete p;
228 }
229 }
230 }
231 }
232 closedir( d );
233 }
234
235 m_shadowedPackages.sort(compareShadowPair);
236 }
237
238 /*!
239 Init from a cache file
240 \param cacheFile the name of the cache file to be parser
241 \return true on success, false indicates file opening problems
242 */
243 Repository::CacheReadResult
244 Repository::initFromCache( const string& cacheFile )
245 {
246 FILE* fp = fopen( cacheFile.c_str(), "r" );
247 if ( !fp ) {
248 return ACCESS_ERR;
249 }
250
251 const int length = BUFSIZ;
252 char input[length];
253 string line;
254
255 // read version
256 if ( fgets( input, length, fp ) ) {
257 line = stripWhiteSpace( input );
258 if ( line != CACHE_VERSION ) {
259 close( fp );
260 return FORMAT_ERR;
261 }
262 }
263
264 // FIELDS:
265 // name, path, version, release,
266 // description, dependencies, url,
267 // packager, maintainer, hasReadme;
268 // hasPreInstall, hasPostInstall
269 const int fieldCount = 12;
270 string fields[fieldCount];
271 int fieldPos = 0;
272
273 while ( fgets( input, length, fp ) ) {
274 line = StringHelper::stripWhiteSpace( input );
275
276 fields[fieldPos] = line;
277 ++fieldPos;
278 if ( fieldPos == fieldCount ) {
279 fieldPos = 0;
280 Package* p = new Package( fields[0], fields[1],
281 fields[2], fields[3],
282 fields[4], fields[5], fields[6],
283 fields[7], fields[8], fields[9],
284 fields[10], fields[11]);
285 m_packageMap[p->name()] = p;
286 fgets( input, length, fp ); // read empty line
287 }
288 }
289 fclose( fp );
290
291 return READ_OK;
292 }
293
294 /*!
295 Store repository data in a cache file
296 \param cacheFile the file where the data is stored
297 \return whether the operation was successfully
298 */
299 Repository::WriteResult Repository::writeCache( const string& cacheFile )
300 {
301 string path = cacheFile;
302 string::size_type pos = cacheFile.rfind( '/' );
303 if ( pos != string::npos ) {
304 path = path.erase( pos );
305 }
306 if ( !createOutputDir( path ) ) {
307 return DIR_ERR;
308 }
309
310 FILE* fp = fopen( cacheFile.c_str(), "w" );
311 if ( !fp ) {
312 return FILE_ERR;
313 }
314
315 map<string, Package*>::const_iterator it = m_packageMap.begin();
316
317 char yesStr[] = "yes";
318 char noStr[] = "no";
319 char* hasReadme;
320 char* hasPreInstall;
321 char* hasPostInstall;
322
323 // write version
324 fprintf( fp, "%s\n", CACHE_VERSION.c_str() );
325
326 for ( ; it != m_packageMap.end(); ++it ) {
327 const Package* p = it->second;
328
329 // TODO: encode
330 hasReadme = noStr;
331 if ( p->hasReadme() ) {
332 hasReadme = yesStr;
333 }
334
335 hasPreInstall = noStr;
336 if ( p->hasPreInstall() ) {
337 hasPreInstall = yesStr;
338 }
339
340 hasPostInstall = noStr;
341 if ( p->hasPostInstall() ) {
342 hasPostInstall = yesStr;
343 }
344
345 fprintf( fp, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n\n",
346 p->name().c_str(),
347 p->path().c_str(),
348 p->version().c_str(),
349
350 p->release().c_str(),
351 p->description().c_str(),
352 p->dependencies().c_str(),
353 p->url().c_str(),
354 p->packager().c_str(),
355 p->maintainer().c_str(),
356 hasReadme, hasPreInstall, hasPostInstall );
357 }
358
359 fclose( fp );
360 return SUCCESS;
361 }
362
363 /*!
364 create all components of \path which don't exist
365 \param path the path to be created
366 \return true on success. false indicates permission problems
367 */
368 bool Repository::createOutputDir( const string& path )
369 {
370 list<string> dirs;
371 split( path, '/', dirs, 1 );
372 string tmpPath;
373
374 for ( list<string>::iterator it = dirs.begin(); it != dirs.end(); ++it ) {
375
376 tmpPath += *it + "/";
377 DIR* d;
378 if ( ( d = opendir( tmpPath.c_str() ) ) == NULL ) {
379 // doesn't exist
380 if ( mkdir( tmpPath.c_str(), 0755 ) == -1 ) {
381 cout << "- can't create output directory " << tmpPath
382 << endl;
383 return false;
384 }
385 } else {
386 closedir( d );
387 }
388
389 }
390 return true;
391 }
392
393
394 /*!
395 Search packages for a match of \a pattern in name. The name can
396 contain shell wildcards.
397
398 \param pattern the pattern to be found
399 \return a list of matching packages
400 */
401
402 void Repository::getMatchingPackages( const string& pattern,
403 list<Package*>& target ) const
404 {
405 map<string, Package*>::const_iterator it = m_packageMap.begin();
406 RegEx re(pattern);
407
408 if (m_useRegex) {
409 for ( ; it != m_packageMap.end(); ++it ) {
410 if (re.match(it->first)) {
411 target.push_back( it->second );
412 }
413 }
414 } else {
415 for ( ; it != m_packageMap.end(); ++it ) {
416 // I assume fnmatch will be quite fast for "match all" (*), so
417 // I didn't add a boolean to check for this explicitely
418 if ( fnmatch( pattern.c_str(), it->first.c_str(), 0 ) == 0 ) {
419 target.push_back( it->second );
420 }
421 }
422 }
423 }
424
425 void Repository::addDependencies( std::map<string, string>& deps )
426 {
427 map<string, string>::iterator it = deps.begin();
428 for ( ; it != deps.end(); ++it ) {
429 map<string, Package*>::const_iterator pit =
430 m_packageMap.find( it->first );
431 if ( pit != m_packageMap.end() ) {
432 Package* p = pit->second;
433 if (p->dependencies().length() == 0) {
434 // only use if no dependencies in Pkgfile
435 p->setDependencies(it->second);
436 }
437 }
438 }
439 }
|