1 /* 2 * Copyright 2011-2013, Oliver Tappe <zooey@hirschkaefer.de> 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <dirent.h> 8 #include <errno.h> 9 #include <getopt.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <string.h> 13 14 #include <map> 15 16 #include <Entry.h> 17 #include <ObjectList.h> 18 #include <Path.h> 19 #include <String.h> 20 21 #include <package/hpkg/HPKGDefs.h> 22 #include <package/hpkg/PackageInfoAttributeValue.h> 23 #include <package/hpkg/RepositoryContentHandler.h> 24 #include <package/hpkg/RepositoryReader.h> 25 #include <package/hpkg/RepositoryWriter.h> 26 #include <package/hpkg/StandardErrorOutput.h> 27 #include <package/PackageInfo.h> 28 #include <package/PackageInfoContentHandler.h> 29 #include <package/RepositoryInfo.h> 30 31 #include "package_repo.h" 32 33 34 using BPackageKit::BHPKG::BRepositoryWriterListener; 35 using BPackageKit::BHPKG::BRepositoryWriter; 36 using namespace BPackageKit::BHPKG; 37 using namespace BPackageKit; 38 39 40 bool operator< (const BPackageInfo & left, const BPackageInfo & right) 41 { 42 if (left.Name() != right.Name()) 43 return left.Name() < right.Name(); 44 return left.Version().Compare(right.Version()) < 0; 45 } 46 47 48 namespace 49 { 50 51 52 typedef std::map<BPackageInfo, bool> PackageInfos; 53 54 55 status_t 56 parsePackageListFile(const char* packageListFileName, 57 BObjectList<BString>* packageFileNames) 58 { 59 FILE* packageListFile = fopen(packageListFileName, "r"); 60 if (packageListFile == NULL) { 61 printf("Error: Unable to open %s\n", packageListFileName); 62 return B_ENTRY_NOT_FOUND; 63 } 64 char buffer[128]; 65 while (fgets(buffer, sizeof(buffer), packageListFile) != NULL) { 66 BString* packageFileName = new(std::nothrow) BString(buffer); 67 if (packageFileName == NULL) { 68 printf("Error: Out of memory when reading from %s\n", 69 packageListFileName); 70 fclose(packageListFile); 71 return B_NO_MEMORY; 72 } 73 packageFileName->Trim(); 74 packageFileNames->AddItem(packageFileName); 75 } 76 fclose(packageListFile); 77 return B_OK; 78 } 79 80 81 struct PackageInfosCollector : BRepositoryContentHandler { 82 PackageInfosCollector(PackageInfos& packageInfos, 83 BHPKG::BErrorOutput* errorOutput) 84 : 85 fPackageInfos(packageInfos), 86 fErrorOutput(errorOutput), 87 fRepositoryInfo(), 88 fPackageInfo(), 89 fPackageInfoContentHandler(fPackageInfo, fErrorOutput) 90 { 91 } 92 93 virtual status_t HandlePackage(const char* packageName) 94 { 95 fPackageInfo.Clear(); 96 return B_OK; 97 } 98 99 virtual status_t HandlePackageAttribute( 100 const BPackageInfoAttributeValue& value) 101 { 102 return fPackageInfoContentHandler.HandlePackageAttribute(value); 103 } 104 105 virtual status_t HandlePackageDone(const char* packageName) 106 { 107 if (fPackageInfo.InitCheck() != B_OK) { 108 const BString& name = fPackageInfo.Name(); 109 printf("Error: package-info in repository for '%s' is incomplete\n", 110 name.IsEmpty() ? "<unknown-package>" : name.String()); 111 return B_BAD_DATA; 112 } 113 fPackageInfos[fPackageInfo] = false; 114 return B_OK; 115 } 116 117 virtual status_t HandleRepositoryInfo(const BRepositoryInfo& repositoryInfo) 118 { 119 fRepositoryInfo = repositoryInfo; 120 return B_OK; 121 } 122 123 virtual void HandleErrorOccurred() 124 { 125 } 126 127 const BRepositoryInfo& RepositoryInfo() const 128 { 129 return fRepositoryInfo; 130 } 131 132 private: 133 PackageInfos& fPackageInfos; 134 BHPKG::BErrorOutput* fErrorOutput; 135 BRepositoryInfo fRepositoryInfo; 136 BPackageInfo fPackageInfo; 137 BPackageInfoContentHandler fPackageInfoContentHandler; 138 }; 139 140 141 class RepositoryWriterListener : public BRepositoryWriterListener { 142 public: 143 RepositoryWriterListener(bool verbose, bool quiet) 144 : fVerbose(verbose), fQuiet(quiet) 145 { 146 } 147 148 virtual void PrintErrorVarArgs(const char* format, va_list args) 149 { 150 vfprintf(stderr, format, args); 151 } 152 153 virtual void OnPackageAdded(const BPackageInfo& packageInfo) 154 { 155 } 156 157 virtual void OnRepositoryInfoSectionDone(uint32 uncompressedSize) 158 { 159 if (fQuiet || !fVerbose) 160 return; 161 162 printf("----- Repository Info Section --------------------\n"); 163 printf("repository info size: %10" B_PRIu32 " (uncompressed)\n", 164 uncompressedSize); 165 } 166 167 virtual void OnPackageAttributesSectionDone(uint32 stringCount, 168 uint32 uncompressedSize) 169 { 170 if (fQuiet || !fVerbose) 171 return; 172 173 printf("----- Package Attribute Section -------------------\n"); 174 printf("string count: %10" B_PRIu32 "\n", stringCount); 175 printf("package attributes size: %10" B_PRIu32 " (uncompressed)\n", 176 uncompressedSize); 177 } 178 179 virtual void OnRepositoryDone(uint32 headerSize, uint32 repositoryInfoSize, 180 uint32 licenseCount, uint32 packageCount, uint32 packageAttributesSize, 181 uint64 totalSize) 182 { 183 if (fQuiet || !fVerbose) 184 return; 185 186 printf("----- Package Repository Info -----\n"); 187 if (fVerbose) 188 printf("embedded license count %10" B_PRIu32 "\n", licenseCount); 189 printf("package count %10" B_PRIu32 "\n", packageCount); 190 printf("-----------------------------------\n"); 191 printf("header size: %10" B_PRIu32 "\n", headerSize); 192 printf("repository header size: %10" B_PRIu32 "\n", 193 repositoryInfoSize); 194 printf("package attributes size: %10" B_PRIu32 "\n", 195 packageAttributesSize); 196 printf("total size: %10" B_PRIu64 "\n", totalSize); 197 printf("-----------------------------------\n"); 198 } 199 200 private: 201 bool fVerbose; 202 bool fQuiet; 203 }; 204 205 206 } // anonymous namespace 207 208 209 int 210 command_update(int argc, const char* const* argv) 211 { 212 const char* changeToDirectory = NULL; 213 bool quiet = false; 214 bool verbose = false; 215 216 while (true) { 217 static struct option sLongOptions[] = { 218 { "help", no_argument, 0, 'h' }, 219 { "quiet", no_argument, 0, 'q' }, 220 { "verbose", no_argument, 0, 'v' }, 221 { 0, 0, 0, 0 } 222 }; 223 224 opterr = 0; // don't print errors 225 int c = getopt_long(argc, (char**)argv, "+C:hqv", sLongOptions, NULL); 226 if (c == -1) 227 break; 228 229 switch (c) { 230 case 'C': 231 changeToDirectory = optarg; 232 break; 233 234 case 'h': 235 print_usage_and_exit(false); 236 break; 237 238 case 'q': 239 quiet = true; 240 break; 241 242 case 'v': 243 verbose = true; 244 break; 245 246 default: 247 print_usage_and_exit(true); 248 break; 249 } 250 } 251 252 // The remaining three arguments are the source and target repository file 253 // plus the package list file. 254 if (optind + 3 != argc) 255 print_usage_and_exit(true); 256 257 const char* sourceRepositoryFileName = argv[optind++]; 258 const char* targetRepositoryFileName = argv[optind++]; 259 const char* packageListFileName = argv[optind++]; 260 261 BStandardErrorOutput errorOutput; 262 RepositoryWriterListener listener(verbose, quiet); 263 264 BEntry sourceRepositoryEntry(sourceRepositoryFileName); 265 if (!sourceRepositoryEntry.Exists()) { 266 listener.PrintError( 267 "Error: given source repository file '%s' doesn't exist!\n", 268 sourceRepositoryFileName); 269 return 1; 270 } 271 // determine path for 'repo.info' file from given new repository file 272 BString repositoryInfoFileName(targetRepositoryFileName); 273 repositoryInfoFileName.Append(".info"); 274 BEntry repositoryInfoEntry(repositoryInfoFileName.String()); 275 BRepositoryInfo repositoryInfo(repositoryInfoEntry); 276 status_t result = repositoryInfo.InitCheck(); 277 if (result != B_OK) { 278 listener.PrintError( 279 "Error: can't parse/read repository-info file %s : %s\n", 280 repositoryInfoFileName.String(), strerror(result)); 281 return 1; 282 } 283 284 // open source repository 285 BRepositoryReader repositoryReader(&errorOutput); 286 result = repositoryReader.Init(sourceRepositoryFileName); 287 if (result != B_OK) { 288 listener.PrintError( 289 "Error: can't read from old repository file : %s\n", 290 strerror(result)); 291 return 1; 292 } 293 294 // collect package infos from source repository 295 PackageInfos packageInfos; 296 PackageInfosCollector packageInfosCollector(packageInfos, &errorOutput); 297 result = repositoryReader.ParseContent(&packageInfosCollector); 298 if (result != B_OK) { 299 listener.PrintError( 300 "Error: couldn't fetch package infos from old repository : %s\n", 301 strerror(result)); 302 return 1; 303 } 304 305 // create new repository 306 BRepositoryWriter repositoryWriter(&listener, &repositoryInfo); 307 BString tempRepositoryFileName(targetRepositoryFileName); 308 tempRepositoryFileName += ".___new___"; 309 if ((result = repositoryWriter.Init(tempRepositoryFileName.String())) 310 != B_OK) { 311 listener.PrintError("Error: can't initialize repository-writer : %s\n", 312 strerror(result)); 313 return 1; 314 } 315 316 BEntry tempRepositoryFile(tempRepositoryFileName.String()); 317 BPath targetRepositoryFilePath(targetRepositoryFileName); 318 319 BObjectList<BString> packageNames(100, true); 320 if ((result = parsePackageListFile(packageListFileName, &packageNames)) 321 != B_OK) { 322 listener.PrintError( 323 "Error: Failed to read package-list-file \"%s\": %s\n", 324 packageListFileName, strerror(result)); 325 return 1; 326 } 327 328 // change directory, if requested 329 if (changeToDirectory != NULL) { 330 if (chdir(changeToDirectory) != 0) { 331 listener.PrintError( 332 "Error: Failed to change the current working directory to " 333 "\"%s\": %s\n", changeToDirectory, strerror(errno)); 334 return 1; 335 } 336 } 337 338 // add all given package files 339 for (int i = 0; i < packageNames.CountItems(); ++i) { 340 BPackageInfo packageInfo; 341 if ((result = packageInfo.ReadFromPackageFile( 342 packageNames.ItemAt(i)->String())) != B_OK) { 343 listener.PrintError( 344 "Error: Failed to read package-info from \"%s\": %s\n", 345 packageNames.ItemAt(i)->String(), strerror(result)); 346 return 1; 347 } 348 PackageInfos::iterator infoIter = packageInfos.find(packageInfo); 349 if (infoIter != packageInfos.end()) { 350 infoIter->second = true; 351 if ((result = repositoryWriter.AddPackageInfo(infoIter->first)) 352 != B_OK) 353 return 1; 354 if (verbose) { 355 printf("keeping '%s-%s'\n", infoIter->first.Name().String(), 356 infoIter->first.Version().ToString().String()); 357 } 358 } else { 359 BEntry entry(packageNames.ItemAt(i)->String()); 360 if ((result = repositoryWriter.AddPackage(entry)) != B_OK) 361 return 1; 362 if (!quiet) { 363 printf("added '%s' ...\n", 364 packageNames.ItemAt(i)->String()); 365 } 366 } 367 } 368 369 // tell about packages dropped from repository 370 PackageInfos::const_iterator infoIter; 371 for (infoIter = packageInfos.begin(); infoIter != packageInfos.end(); 372 ++infoIter) { 373 if (!infoIter->second) { 374 printf("dropped '%s-%s'\n", infoIter->first.Name().String(), 375 infoIter->first.Version().ToString().String()); 376 } 377 } 378 379 380 // write the repository 381 result = repositoryWriter.Finish(); 382 if (result != B_OK) 383 return 1; 384 385 result = tempRepositoryFile.Rename(targetRepositoryFilePath.Leaf(), true); 386 if (result != B_OK) { 387 printf("Error: unable to rename repository %s to %s - %s\n", 388 tempRepositoryFileName.String(), targetRepositoryFileName, 389 strerror(result)); 390 return 1; 391 } 392 393 if (verbose) { 394 printf("\nsuccessfully created repository '%s'\n", 395 targetRepositoryFileName); 396 } 397 398 return 0; 399 } 400