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 return FORMAT_ERR;
260 }
261 }
262
263 // FIELDS:
264 // name, path, version, release,
265 // description, dependencies, url,
266 // packager, maintainer, hasReadme;
267 // hasPreInstall, hasPostInstall
268 const int fieldCount = 12;
269 string fields[fieldCount];
270 int fieldPos = 0;
271
272 while ( fgets( input, length, fp ) ) {
273 line = StringHelper::stripWhiteSpace( input );
274
275 fields[fieldPos] = line;
276 ++fieldPos;
277 if ( fieldPos == fieldCount ) {
278 fieldPos = 0;
279 Package* p = new Package( fields[0], fields[1],
280 fields[2], fields[3],
281 fields[4], fields[5], fields[6],
282 fields[7], fields[8], fields[9],
283 fields[10], fields[11]);
284 m_packageMap[p->name()] = p;
285 fgets( input, length, fp ); // read empty line
286 }
287 }
288 fclose( fp );
289
290 return READ_OK;
291 }
292
293 /*!
294 Store repository data in a cache file
295 \param cacheFile the file where the data is stored
296 \return whether the operation was successfully
297 */
298 Repository::WriteResult Repository::writeCache( const string& cacheFile )
299 {
300 string path = cacheFile;
301 string::size_type pos = cacheFile.rfind( '/' );
302 if ( pos != string::npos ) {
303 path = path.erase( pos );
304 }
305 if ( !createOutputDir( path ) ) {
306 return DIR_ERR;
307 }
308
309 FILE* fp = fopen( cacheFile.c_str(), "w" );
310 if ( !fp ) {
311 return FILE_ERR;
312 }
313
314 map<string, Package*>::const_iterator it = m_packageMap.begin();
315
316 char yesStr[] = "yes";
317 char noStr[] = "no";
318 char* hasReadme;
319 char* hasPreInstall;
320 char* hasPostInstall;
321
322 // write version
323 fprintf( fp, "%s\n", CACHE_VERSION.c_str() );
324
325 for ( ; it != m_packageMap.end(); ++it ) {
326 const Package* p = it->second;
327
328 // TODO: encode
329 hasReadme = noStr;
330 if ( p->hasReadme() ) {
331 hasReadme = yesStr;
332 }
333
334 hasPreInstall = noStr;
335 if ( p->hasPreInstall() ) {
336 hasPreInstall = yesStr;
337 }
338
339 hasPostInstall = noStr;
340 if ( p->hasPostInstall() ) {
341 hasPostInstall = yesStr;
342 }
343
344 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",
345 p->name().c_str(),
346 p->path().c_str(),
347 p->version().c_str(),
348
349 p->release().c_str(),
350 p->description().c_str(),
351 p->dependencies().c_str(),
352 p->url().c_str(),
353 p->packager().c_str(),
354 p->maintainer().c_str(),
355 hasReadme, hasPreInstall, hasPostInstall );
356 }
357
358 fclose( fp );
359 return SUCCESS;
360 }
361
362 /*!
363 create all components of \path which don't exist
364 \param path the path to be created
365 \return true on success. false indicates permission problems
366 */
367 bool Repository::createOutputDir( const string& path )
368 {
369 list<string> dirs;
370 split( path, '/', dirs, 1 );
371 string tmpPath;
372
373 for ( list<string>::iterator it = dirs.begin(); it != dirs.end(); ++it ) {
374
375 tmpPath += *it + "/";
376 DIR* d;
377 if ( ( d = opendir( tmpPath.c_str() ) ) == NULL ) {
378 // doesn't exist
379 if ( mkdir( tmpPath.c_str(), 0755 ) == -1 ) {
380 cout << "- can't create output directory " << tmpPath
381 << endl;
382 return false;
383 }
384 } else {
385 closedir( d );
386 }
387
388 }
389 return true;
390 }
391
392
393 /*!
394 Search packages for a match of \a pattern in name. The name can
395 contain shell wildcards.
396
397 \param pattern the pattern to be found
398 \return a list of matching packages
399 */
400
401 void Repository::getMatchingPackages( const string& pattern,
402 list<Package*>& target ) const
403 {
404 map<string, Package*>::const_iterator it = m_packageMap.begin();
405 RegEx re(pattern);
406
407 if (m_useRegex) {
408 for ( ; it != m_packageMap.end(); ++it ) {
409 if (re.match(it->first)) {
410 target.push_back( it->second );
411 }
412 }
413 } else {
414 for ( ; it != m_packageMap.end(); ++it ) {
415 // I assume fnmatch will be quite fast for "match all" (*), so
416 // I didn't add a boolean to check for this explicitely
417 if ( fnmatch( pattern.c_str(), it->first.c_str(), 0 ) == 0 ) {
418 target.push_back( it->second );
419 }
420 }
421 }
422 }
423
424 void Repository::addDependencies( std::map<string, string>& deps )
425 {
426 map<string, string>::iterator it = deps.begin();
427 for ( ; it != deps.end(); ++it ) {
428 map<string, Package*>::const_iterator pit =
429 m_packageMap.find( it->first );
430 if ( pit != m_packageMap.end() ) {
431 Package* p = pit->second;
432 if (p->dependencies().length() == 0) {
433 // only use if no dependencies in Pkgfile
434 p->setDependencies(it->second);
435 }
436 }
437 }
438 }
|