buycatalog.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. #include "buycatalog.h"
  2. #include "productinfo.h"
  3. #include "mainwindow.h"
  4. #include <QtCore/QFile>
  5. #include <QtCore/QTextStream>
  6. #include <QtGui/QApplication>
  7. #include <QtCore/QDebug>
  8. #include <QtCore/QMetaType>
  9. #include <QtGui/QMessageBox>
  10. #include <QtCore/QDirIterator>
  11. #include <QProgressDialog>
  12. Q_DECLARE_METATYPE(IAPClient::ProductDataList) //to be able to transfer data this type with Qt::QueuedConnection
  13. #define INVALID_VALUE -1
  14. /*
  15. * BuyCatalog - a QDialog inherited class wich will display a list of available
  16. * products allowing the user to trigger the In-Application Purchase experience
  17. * at a click of a button.
  18. *
  19. * This constuctor reads a list of product IDs from the catalog.dat file and
  20. * asks for the corresponding product data (IAPClient::ProductData) which the
  21. * API will fetch from Ovi.
  22. */
  23. BuyCatalog::BuyCatalog(QWidget *parent)
  24. : QDialog(parent),
  25. productsRequested(0),
  26. current_requestId(INVALID_VALUE),
  27. infoDialog(NULL)
  28. {
  29. // required so that IAPClient::ProductData can be queued in the signal
  30. qRegisterMetaType<IAPClient::ProductDataHash>("IAPClient::ProductDataHash");
  31. qDebug() << "::. Catalog constuctor";
  32. iap_client = new IAPClient(this);
  33. ui.setupUi(this);
  34. ui.listWidget->setStyleSheet(QString::fromUtf8("QListWidget{ background-color: grey; border-style: outset; border-width: 1px; border-radius: 1px; border-color: black; selection-color: black; selection-background-color: red; }" \
  35. "QPushButton{ background-color: green; border-style: outset; border-width: 2px; border-radius: 10px; border-color: beige; font: bold 14px; min-width: 4em; max-width:5em; padding: 6px; }" \
  36. "QPushButton:pressed { background-color: rgb(224, 0, 0); border-style: inset;}"));
  37. connect(ui.listWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(moreProductInfo(QListWidgetItem*)));
  38. Q_ASSERT(parent);
  39. connect(ui.backButton, SIGNAL(clicked()), parent, SLOT(catalogClosed()));
  40. connect(ui.restoreButton, SIGNAL(clicked()), this, SLOT(restoreProducts()));
  41. ui.backButton->setEnabled(false);
  42. ui.restoreButton->setEnabled(false);
  43. QString filepath = QApplication::applicationDirPath();
  44. filepath.append("/drm/data");
  45. QFile file(filepath);
  46. file.open(QFile::ReadOnly);
  47. QDirIterator dirit(filepath, QDir::Dirs, QDirIterator::NoIteratorFlags);
  48. while(dirit.hasNext())
  49. {
  50. QString dir = dirit.next();
  51. if(dir.contains("resourceid_", Qt::CaseInsensitive))
  52. {
  53. // breakdown the !:/private/<SID>/drm/data/resourceid_<productID>/
  54. // extract the product to which this file belongs
  55. QString product;
  56. QStringList elem = dir.split("_", QString::SkipEmptyParts);
  57. elem=elem[1].split("/", QString::SkipEmptyParts);
  58. product = elem[0].trimmed();
  59. if(!product.isEmpty())
  60. {
  61. if(!isProductActivated(product))
  62. {
  63. products.append(product);
  64. qDebug() << "++ ProductID " << product << " to be puchased.";
  65. }
  66. else
  67. {
  68. qDebug() << "++ ProductID " << product << " is activated.";
  69. }
  70. }
  71. }
  72. }
  73. //qDebug() << "Products list count: " << products.count();
  74. if(products.count()>0)
  75. {
  76. // connect IAP API's signals to app's slots
  77. connect(iap_client, SIGNAL(productDataReceived( int, QString, IAPClient::ProductDataHash)), this,
  78. SLOT(productDataReceived(int, QString, IAPClient::ProductDataHash)), Qt::QueuedConnection);
  79. connect(iap_client, SIGNAL(purchaseCompleted( int , QString, QString)), this,
  80. SLOT(purchaseCompleted( int , QString, QString)),Qt::QueuedConnection);
  81. connect(iap_client, SIGNAL(purchaseFlowFinished(int)), this, SLOT(purchaseFlowFinished(int)), Qt::QueuedConnection);
  82. ui.progressBar->setRange(0, products.count());
  83. ui.progressBar->setTextVisible(false);
  84. // request product data from Ovi for the products
  85. requestNextProduct();
  86. }
  87. else
  88. ui.progressBar->setVisible(false);
  89. ui.backButton->setEnabled(true);
  90. connect(iap_client, SIGNAL(restorableProductsReceived( int, QString, IAPClient::ProductDataList)), this,
  91. SLOT(restorableProductsReceived( int, QString, IAPClient::ProductDataList)));
  92. connect(iap_client, SIGNAL(restorationCompleted(int, QString, QString)), this,
  93. SLOT(restorationCompleted(int, QString, QString)),Qt::QueuedConnection);
  94. busyIndicator = new QProgressDialog("", "", 1, 1, this);
  95. busyIndicator->setCancelButton(NULL);
  96. busyIndicator->setWindowModality(Qt::WindowModal);
  97. qDebug() << ".:: Catalog constuctor";
  98. }
  99. /*
  100. *
  101. *
  102. *
  103. */
  104. BuyCatalog::~BuyCatalog()
  105. {
  106. delete infoDialog;
  107. delete iap_client;
  108. }
  109. /*
  110. *
  111. *
  112. *
  113. */
  114. void BuyCatalog::infoDialogClosed(int result)
  115. {
  116. if(result==QDialog::Accepted)
  117. {
  118. IAPClient::ProductDataHash info = available_products[current_product_index];
  119. current_requestId = iap_client->purchaseProduct(info.value("id").toString(), IAPClient::ForcedAutomaticRestoration);
  120. }
  121. }
  122. /*
  123. *
  124. *
  125. *
  126. */
  127. void BuyCatalog::moreProductInfo(QListWidgetItem* item)
  128. {
  129. qDebug() << "::. moreProductInfo";
  130. if(infoDialog!=NULL)
  131. {
  132. disconnect(infoDialog, SIGNAL(finished(int)), this, SLOT(infoDialogClosed(int)));
  133. delete infoDialog;
  134. infoDialog = NULL;
  135. }
  136. current_product_index = ui.listWidget->row(item);
  137. IAPClient::ProductDataHash info = available_products[current_product_index];
  138. // create a dialog for showing detailed product info
  139. infoDialog = new ProductInfo(info.value("info").toString(), info.value("description").toString(), info.value("price").toString(), this);
  140. // get notified when the product info dialog is closed
  141. connect(infoDialog, SIGNAL(finished(int)), this, SLOT(infoDialogClosed(int)));
  142. // show the dialog
  143. infoDialog->showFullScreen();
  144. qDebug() << ".:: moreProductInfo";
  145. }
  146. /*
  147. * Returns true if the product files can be accessed or false if the purchase/restore is needed
  148. */
  149. bool BuyCatalog::isProductActivated(QString product)
  150. {
  151. // ToDo: construct path, find first file and attempt to open it
  152. // return false if open request fails with DRM access code
  153. return false;
  154. }
  155. /*
  156. *
  157. *
  158. *
  159. */
  160. void BuyCatalog::buyProduct()
  161. {
  162. qDebug() << "::. buyProduct";
  163. // locate the list item which holds the button that send a signal to this slot
  164. QWidget *clickedWidget = qobject_cast<QWidget *>(sender()->parent());
  165. for (int index = 0; index < ui.listWidget->count(); index++)
  166. {
  167. QListWidgetItem *runningItem = ui.listWidget->item(index);
  168. QWidget *widget = ui.listWidget->itemWidget(runningItem);
  169. if (clickedWidget == widget)
  170. {
  171. IAPClient::ProductDataHash info = available_products[index];
  172. // issue a buy request for the corresponding product
  173. // if the user has paid for this content already, restoration of the license is needed
  174. current_productId = info.value("id").toString();
  175. current_requestId = iap_client->purchaseProduct(current_productId, IAPClient::ForcedAutomaticRestoration);
  176. break;
  177. }
  178. }
  179. qDebug() << ".:: buyProduct";
  180. }
  181. /*
  182. *
  183. *
  184. *
  185. */
  186. void BuyCatalog::productDataReceived( int requestId, QString status, IAPClient::ProductDataHash productData )
  187. {
  188. qDebug() << "::. productDataReceived";
  189. //Q_ASSERT(requestId == current_requestId);
  190. if(QString::compare(status, "OK", Qt::CaseInsensitive)==0)
  191. {
  192. available_products.append(productData);
  193. // add a new item to the products catalog UI's list
  194. QListWidgetItem *item = new QListWidgetItem();
  195. ui.listWidget->addItem(item);
  196. QLabel *labelTitle = new QLabel(productData.value("info").toString());
  197. QLabel *labelPrice = new QLabel(productData.value("price").toString());
  198. QHBoxLayout *layoutTitleAndPrice= new QHBoxLayout();
  199. layoutTitleAndPrice->addWidget(labelTitle);
  200. layoutTitleAndPrice->addWidget(labelPrice);
  201. QPushButton *button = new QPushButton(tr("Buy..."));
  202. QLabel *labelShort = new QLabel(productData.value("shortdescription").toString());
  203. QHBoxLayout *layoutShortBuy= new QHBoxLayout();
  204. layoutShortBuy->addWidget(labelShort);
  205. layoutShortBuy->addWidget(button);
  206. QVBoxLayout *layoutItem = new QVBoxLayout();
  207. layoutItem->addItem(layoutTitleAndPrice);
  208. layoutItem->addItem(layoutShortBuy);
  209. QWidget *widget = new QWidget();
  210. widget->setLayout(layoutItem);
  211. item->setSizeHint(widget->sizeHint());
  212. ui.listWidget->setItemWidget(item, widget);
  213. // if multiple products are available, let's highlight the one that triggered the shopping session
  214. if( productData.value("id").toString() == defaultProduct)
  215. ui.listWidget->setCurrentItem(item);
  216. connect(button, SIGNAL(clicked()), this, SLOT(buyProduct()));
  217. }
  218. else // what are all the possible status messages?
  219. {
  220. // the product is not available for one reason or another?
  221. // probably you can ignore this
  222. // or report it to your server for analysis
  223. qDebug() << "Requested product could not be retrived: " << status;
  224. }
  225. // if we don't have any more products ...
  226. if(productsRequested == products.count())
  227. {
  228. ui.progressBar->setVisible(false);
  229. // have we got any product data?
  230. if(!available_products.count())
  231. {
  232. QMessageBox message;
  233. message.setText(tr("No products available. Please try again later!"));
  234. message.exec();
  235. // close catalog
  236. reject();
  237. }
  238. }
  239. else
  240. {
  241. // let's look for the next product's data
  242. requestNextProduct();
  243. }
  244. qDebug() << ".:: productDataReceived";
  245. }
  246. /*
  247. *
  248. *
  249. *
  250. */
  251. void BuyCatalog::requestNextProduct()
  252. {
  253. qDebug() << "::. requestNextProduct";
  254. if(productsRequested < products.count())
  255. {
  256. QString prod = products[productsRequested++];
  257. current_requestId = iap_client->getProductData(prod);
  258. qDebug() << "+ Request for product" << prod << " returned id: " << current_requestId;
  259. ui.progressBar->setValue(productsRequested-1);
  260. }
  261. else
  262. ui.progressBar->setValue(productsRequested);
  263. if(productsRequested == products.count() - 1){
  264. ui.restoreButton->setEnabled(true);
  265. }
  266. qDebug() << ".:: requestNextProduct";
  267. }
  268. /*
  269. *
  270. *
  271. *
  272. */
  273. void BuyCatalog::purchaseCompleted( int requestId, QString status, QString purchaseTicket )
  274. {
  275. qDebug() << "::. purchaseCompleted with status: " << status;
  276. if(requestId != current_requestId)
  277. return;
  278. if(QString::compare(status, "OK", Qt::CaseInsensitive)==0
  279. || QString::compare(status, "RestorableProduct", Qt::CaseInsensitive)==0)
  280. {
  281. MainWindow::saveTicket(purchaseTicket, current_productId);
  282. // In-Application Purchase API will take care of downloading the DRM license
  283. // the files covered by this license will become accessible
  284. }
  285. else
  286. {
  287. // some error
  288. // error message already displayed by In-Application Purchase UI, may also be reflected in app's UI.
  289. }
  290. qDebug() << ".:: purchaseCompleted";
  291. }
  292. /*
  293. *
  294. *
  295. *
  296. */
  297. void BuyCatalog::userAndDeviceDataReceived( int /*requestId*/, QString /*status*/,
  298. IAPClient::UserAndDeviceDataHash /*userdata*/ )
  299. {
  300. qDebug() << "::. userAndDeviceDataReceived";
  301. qDebug() << ".:: userAndDeviceDataReceived";
  302. }
  303. /*
  304. *
  305. *
  306. *
  307. */
  308. void BuyCatalog::restorableProductsReceived( int requestId, QString status,
  309. IAPClient::ProductDataList items )
  310. {
  311. qDebug() << "::. restorableProductsReceived with status: " << status;
  312. if(requestId != current_requestId){
  313. qDebug() << "::. restorableProductsReceived failed ";
  314. return;
  315. }
  316. current_requestId = INVALID_VALUE;
  317. if(!items.empty()){
  318. restorableProductItems.append(items);
  319. current_productId = restorableProductItems.takeFirst().value("id").toString();
  320. current_requestId = iap_client->restoreProduct(current_productId);
  321. }
  322. if(current_requestId == INVALID_VALUE){
  323. busyIndicator->close();
  324. ui.backButton->click();
  325. }else{
  326. ui.progressBar->setRange(0, restorableProductItems.count() + 1);
  327. ui.progressBar->setTextVisible(false);
  328. ui.progressBar->setVisible(true);
  329. }
  330. }
  331. /*
  332. *
  333. *
  334. *
  335. */
  336. void BuyCatalog::restorationFlowFinished( int requestId )
  337. {
  338. qDebug() << "::. restorationFlowFinished";
  339. qDebug() << ".:: restorationFlowFinished";
  340. }
  341. /*
  342. *
  343. *
  344. *
  345. */
  346. void BuyCatalog::restorationCompleted( int requestId, QString status, QString purchaseTicket )
  347. {
  348. qDebug() << "::. restorationCompleted with status: " << status;
  349. purchaseCompleted(requestId, status, purchaseTicket);
  350. if(restorableProductItems.empty()){
  351. busyIndicator->close();
  352. ui.backButton->click();
  353. }else{
  354. ui.progressBar->setValue(ui.progressBar->value() + 1);
  355. current_productId = restorableProductItems.takeFirst().value("id").toString();
  356. current_requestId = iap_client->restoreProduct(current_productId);
  357. }
  358. qDebug() << ".:: restorationCompleted";
  359. }
  360. /*
  361. *
  362. *
  363. *
  364. */
  365. void BuyCatalog::purchaseFlowFinished( int requestId )
  366. {
  367. qDebug() << "::. purchaseFlowFinished";
  368. // now the user is back in app's UI
  369. qDebug() << ".:: purchaseFlowFinished";
  370. }
  371. void BuyCatalog::restoreProducts()
  372. {
  373. current_requestId = iap_client->getRestorableProducts();
  374. if(current_requestId < 0)
  375. return;
  376. //lock ui
  377. busyIndicator->setLabelText("restoration in progress...");
  378. busyIndicator->show();
  379. restorableProductItems.clear();
  380. }
  381. // End of the file.