Libgourou

Libgourou Git Source Tree

Root/utils/adept_loan_mgt.cpp

1/*
2 Copyright (c) 2022, Grégory Soutadé
3
4 All rights reserved.
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8 * Redistributions of source code must retain the above copyright
9 notice, this list of conditions and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
13 * Neither the name of the copyright holder nor the
14 names of its contributors may be used to endorse or promote products
15 derived from this software without specific prior written permission.
16
17 THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
18 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
21 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27*/
28
29#include <getopt.h>
30
31#include <iostream>
32#include <algorithm>
33
34#define _XOPEN_SOURCE 700
35#include <stdio.h>
36#include <sys/types.h>
37#include <dirent.h>
38#include <libgen.h>
39#include <time.h>
40
41#include <libgourou.h>
42#include <libgourou_common.h>
43#include "drmprocessorclientimpl.h"
44#include "utils_common.h"
45
46#define MAX_SIZE_BOOK_NAME 30
47
48static char* adeptDir = 0;
49static const char* deviceFile = "device.xml";
50static const char* activationFile = "activation.xml";
51static const char* devicekeyFile = "devicesalt";
52static bool list = false;
53static const char* returnID = 0;
54static const char* deleteID = 0;
55static bool notify = true;
56
57struct Loan
58{
59 std::string id;
60 std::string operatorURL;
61 std::string validity;
62 std::string bookName;
63
64 std::string path;
65};
66
67class LoanMGT
68{
69public:
70 ~LoanMGT()
71 {
72for (const auto& kv : loanedBooks)
73 delete kv.second;
74 }
75
76 int run()
77 {
78int ret = 0;
79try
80{
81 DRMProcessorClientImpl client;
82 gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
83
84 loadLoanedBooks();
85
86 if (list)
87displayLoanList();
88 else if (returnID)
89returnBook(processor);
90 else if (deleteID)
91deleteLoan();
92} catch(std::exception& e)
93{
94 std::cout << e.what() << std::endl;
95 ret = 1;
96}
97
98return ret;
99 }
100
101private:
102 void loadLoanedBooks()
103 {
104DIR *dp;
105struct dirent *ep;
106int entryLen;
107struct Loan* loan;
108char * res;
109
110std::string loanDir = std::string(adeptDir) + std::string("/") + LOANS_DIR;
111
112if (!fileExists(loanDir.c_str()))
113 return;
114
115dp = opendir (loanDir.c_str());
116
117if(!dp)
118 EXCEPTION(gourou::USER_INVALID_INPUT, "Cannot read directory " << loanDir);
119
120while ((ep = readdir (dp)))
121{
122 if (ep->d_type != DT_LNK &&
123ep->d_type != DT_REG)
124continue;
125
126 entryLen = strlen(ep->d_name);
127
128 if (entryLen <= 4 ||
129ep->d_name[entryLen-4] != '.' ||
130ep->d_name[entryLen-3] != 'x' ||
131ep->d_name[entryLen-2] != 'm' ||
132ep->d_name[entryLen-1] != 'l')
133continue;
134
135 std::string id = std::string(ep->d_name, entryLen-4);
136
137 loan = new Loan;
138 loan->path = loanDir + std::string("/") + ep->d_name;
139
140 pugi::xml_document xmlDoc;
141 pugi::xml_node node;
142
143 if (!xmlDoc.load_file(loan->path.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8))
144 {
145std::cout << "Invalid loan entry " << loan->path << std::endl;
146goto error;
147 }
148
149 // id
150 node = xmlDoc.select_node("//id").node();
151 if (!node)
152 {
153std::cout << "Invalid loan entry " << ep->d_name << ", no id element" << std::endl;
154goto error;
155 }
156 loan->id = node.first_child().value();
157
158 // operatorURL
159 node = xmlDoc.select_node("//operatorURL").node();
160 if (!node)
161 {
162std::cout << "Invalid loan entry " << ep->d_name << ", no operatorURL element" << std::endl;
163goto error;
164 }
165 loan->operatorURL = node.first_child().value();
166
167 // validity
168 node = xmlDoc.select_node("//validity").node();
169 if (!node)
170 {
171std::cout << "Invalid loan entry " << ep->d_name << ", no validity element" << std::endl;
172goto error;
173 }
174 loan->validity = node.first_child().value();
175
176 // bookName
177 node = xmlDoc.select_node("//name").node();
178 if (!node)
179 {
180std::cout << "Invalid loan entry " << ep->d_name << ", no name element" << std::endl;
181goto error;
182 }
183 loan->bookName = node.first_child().value();
184
185 struct tm tm;
186 res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm);
187 if (*res == 0)
188 {
189if (mktime(&tm) <= time(NULL))
190 loan->validity = " (Expired)";
191 }
192 else
193 {
194std::cout << "Unable to parse validity timestamp :" << loan->validity << std::endl;
195loan->validity = " (Unknown)";
196 }
197
198 loanedBooks[id] = loan;
199 continue;
200
201error:
202 if (loan)
203delete loan;
204}
205
206closedir (dp);
207 }
208
209 void displayLoanList()
210 {
211if (!loanedBooks.size())
212{
213 std::cout << "No books loaned" << std::endl;
214 return;
215}
216
217struct Loan* loan;
218unsigned int maxSizeBookName=0;
219// Compute max size
220for (const auto& kv : loanedBooks)
221{
222 loan = kv.second;
223 if (loan->bookName.size() > maxSizeBookName)
224maxSizeBookName = loan->bookName.size();
225}
226
227if (maxSizeBookName > MAX_SIZE_BOOK_NAME)
228 maxSizeBookName = MAX_SIZE_BOOK_NAME;
229else if ((maxSizeBookName % 2))
230 maxSizeBookName++;
231
232// std::cout << " ID Book Expiration" << std::endl;
233// std::cout << "------------------------------" << std::endl;
234
235int fillID, fillBookName, fillExpiration=(20 - 10)/2;
236
237fillID = (ID_HASH_SIZE - 2) / 2;
238fillBookName = (maxSizeBookName - 4) / 2;
239
240std::cout.width (fillID);
241std::cout << "";
242std::cout << "ID" ;
243std::cout.width (fillID);
244std::cout << "";
245std::cout << " " ;
246
247std::cout.width (fillBookName);
248std::cout << "";
249std::cout << "Book" ;
250std::cout.width (fillBookName);
251std::cout << "";
252std::cout << " " ;
253
254std::cout.width (fillExpiration);
255std::cout << "";
256std::cout << "Expiration";
257std::cout.width (fillExpiration);
258std::cout << "" << std::endl;
259
260std::cout.fill ('-');
261std::cout.width (ID_HASH_SIZE + 4 + maxSizeBookName + 4 + 20);
262std::cout << "" << std::endl;
263std::cout.fill (' ');
264
265std::string bookName;
266
267for (const auto& kv : loanedBooks)
268{
269 loan = kv.second;
270
271 std::cout << kv.first;
272 std::cout << " ";
273
274 if (loan->bookName.size() > MAX_SIZE_BOOK_NAME)
275bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME);
276 else
277bookName = loan->bookName;
278
279 std::cout << bookName;
280 std::cout.width (maxSizeBookName - bookName.size());
281 std::cout << "";
282 std::cout << " ";
283
284 std::cout << loan->validity << std::endl;
285}
286
287std::cout << std::endl;
288 }
289
290 void returnBook(gourou::DRMProcessor& processor)
291 {
292struct Loan* loan = loanedBooks[std::string(returnID)];
293
294if (!loan)
295{
296 std::cout << "Error : Loan " << returnID << " doesn't exists" << std::endl;
297 return;
298}
299
300processor.returnLoan(loan->id, loan->operatorURL, notify);
301
302deleteID = returnID;
303if (deleteLoan(false))
304{
305 std::cout << "Loan " << returnID << " successfully returned" << std::endl;
306}
307 }
308
309 bool deleteLoan(bool displayResult=true)
310 {
311struct Loan* loan = loanedBooks[std::string(deleteID)];
312
313if (!loan)
314{
315 std::cout << "Error : Loan " << deleteID << " doesn't exists" << std::endl;
316 return false;
317}
318
319if (unlink(loan->path.c_str()))
320{
321 std::cout << "Error : Cannot delete " << loan->path << std::endl;
322 return false;
323}
324else if (displayResult)
325{
326 std::cout << "Loan " << deleteID << " deleted" << std::endl;
327}
328
329return true;
330 }
331
332 std::map<std::string, struct Loan*> loanedBooks;
333};
334
335
336static void usage(const char* cmd)
337{
338 std::cout << basename((char*)cmd) << " manage loaned books" << std::endl << std::endl;
339
340 std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS]" << std::endl << std::endl;
341
342 std::cout << "Global Options:" << std::endl;
343 std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl;
344 std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl;
345 std::cout << " " << "-d|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl;
346 std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << std::endl;
347 std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
348 std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
349 std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
350
351 std::cout << "ADEPT Options:" << std::endl;
352 std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl;
353 std::cout << std::endl;
354
355 std::cout << "Environment:" << std::endl;
356 std::cout << "ADEPT directory is optional. If not set, it's looked into :" << std::endl;
357 std::cout << " * $ADEPT_DIR environment variable" << std::endl;
358 std::cout << " * /home/<user>/.config/adept" << std::endl;
359 std::cout << " * Current directory" << std::endl;
360 std::cout << " * .adept" << std::endl;
361 std::cout << " * adobe-digital-editions directory" << std::endl;
362 std::cout << " * .adobe-digital-editions directory" << std::endl;
363}
364
365int main(int argc, char** argv)
366{
367 int c, ret = -1;
368
369 const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
370 int verbose = gourou::DRMProcessor::getLogLevel();
371 int actions = 0;
372
373 while (1) {
374int option_index = 0;
375static struct option long_options[] = {
376 {"adept-directory", required_argument, 0, 'D' },
377 {"list", no_argument, 0, 'l' },
378 {"return", no_argument, 0, 'r' },
379 {"delete", no_argument, 0, 'd' },
380 {"no-notify", no_argument, 0, 'N' },
381 {"verbose", no_argument, 0, 'v' },
382 {"version", no_argument, 0, 'V' },
383 {"help", no_argument, 0, 'h' },
384 {0, 0, 0, 0 }
385};
386
387c = getopt_long(argc, argv, "d:lr:D:vVh",
388 long_options, &option_index);
389if (c == -1)
390 break;
391
392switch (c) {
393case 'D':
394 adeptDir = optarg;
395 break;
396case 'l':
397 list = true;
398 actions++;
399 break;
400case 'r':
401 returnID = optarg;
402 actions++;
403 break;
404case 'd':
405 deleteID = optarg;
406 actions++;
407 break;
408case 'N':
409 notify = false;
410 break;
411case 'v':
412 verbose++;
413 break;
414case 'V':
415 version();
416 return 0;
417case 'h':
418 usage(argv[0]);
419 return 0;
420default:
421 usage(argv[0]);
422 return -1;
423}
424 }
425
426 gourou::DRMProcessor::setLogLevel(verbose);
427
428 // By default, simply list books loaned
429 if (actions == 0)
430list = true;
431 else if (actions != 1)
432 {
433usage(argv[0]);
434return -1;
435 }
436
437 LoanMGT loanMGT;
438
439 int i;
440 bool hasErrors = false;
441 const char* orig;
442 char *filename;
443 for (i=0; i<(int)ARRAY_SIZE(files); i++)
444 {
445orig = *files[i];
446
447if (adeptDir)
448{
449 std::string path = std::string(adeptDir) + std::string("/") + orig;
450 filename = strdup(path.c_str());
451}
452else
453 filename = strdup(orig);
454*files[i] = findFile(filename);
455free(filename);
456if (!*files[i])
457{
458 std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
459 hasErrors = true;
460}
461 }
462
463 if (hasErrors)
464 {
465// In case of adept dir was provided by user
466adeptDir = 0;
467goto end;
468 }
469
470 if (adeptDir)
471adeptDir = strdup(adeptDir); // For below free
472 else
473 {
474adeptDir = strdup(deviceFile);
475adeptDir = dirname(adeptDir);
476 }
477
478 ret = loanMGT.run();
479
480end:
481 for (i=0; i<(int)ARRAY_SIZE(files); i++)
482 {
483if (*files[i])
484 free((void*)*files[i]);
485 }
486
487 if (adeptDir)
488free(adeptDir);
489
490 return ret;
491}

Archive Download this file