рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╕реНрдЯреЛрд░ рд╕реЗ рдбрд┐рдмрдЧрд┐рдВрдЧ рдЕрдиреБрдкреНрд░рдпреЛрдЧреЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдХреБрдЫ рд╢рдмреНрдж

рдЗрд╕ рддрдереНрдп рдХреЗ рдмрд╛рд╡рдЬреВрдж рдХрд┐ рдорд╛рд░реНрдорд▓реЗрдб рдЙрдкрдХрд░рдг рдкрд░реНрдпрд╛рд╡рд░рдг рдХреЗ рд╣рд┐рд╕реНрд╕реЗ рдХреЗ рд░реВрдк рдореЗрдВ рдЖрдкреВрд░реНрддрд┐ рдХреА рдЧрдИ рдПрдореБрд▓реЗрдЯрд░ рдХрд╛рдлреА рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рд╣реИ рдФрд░ рдЖрдкрдХреЛ рд╕рднреА рдХрд▓реНрдкрдиреАрдп рдФрд░ рд╕рдордЭ рд╕реЗ рдмрд╛рд╣рд░ рдХреА рд╕реНрдерд┐рддрд┐рдпреЛрдВ рдХреЛ рдбреАрдмрдЧ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИ, рдРрд╕реЗ рдХрдИ рдХрд╛рд░реНрдп рд╣реИрдВ рдЬреЛ рдЗрд╕реЗ рд╕рдВрднрд╛рд▓ рдирд╣реАрдВ рд╕рдХрддреЗ рд╣реИрдВред рдЗрд╕ рддрд░рд╣ рдХреЗ рдХрд╛рд░реНрдпреЛрдВ рдореЗрдВ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдПрдХ рд╡рд┐рдХрд╕рд┐рдд рдПрдкреНрд▓рд┐рдХреЗрд╢рди (рдРрдк рд╕реНрдЯреЛрд░ рдпрд╛ рдПрдВрдбреНрд░реЙрдЗрдб рдорд╛рд░реНрдХреЗрдЯ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ) рд╕реЗ рдЙрддреНрдкрд╛рджреЛрдВ рдХреА рдЦрд░реАрдж рдХреЛ рдбреАрдмрдЧ рдХрд░рдирд╛ рд╢рд╛рдорд┐рд▓ рд╣реИред рдЦрд░реАрдж рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рдирд╛ рдЖрд╡рд╢реНрдпрдХ рд░реВрдк рд╕реЗ рдбрд┐рд╡рд╛рдЗрд╕ рдкрд░ рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП, рд▓реЗрдХрд┐рди рдЕрдХреНрд╕рд░ рдРрд╕рд╛ рд╣реЛрддрд╛ рд╣реИ рдХрд┐ рдЖрд╡реЗрджрди рддрд░реНрдХ рдЦрд░реАрдж рд╕реЗ рдЬреБрдбрд╝рд╛ рд╣реЛрддрд╛ рд╣реИ, рдЬреЛ рдПрдореБрд▓реЗрдЯрд░ рдХреЗ рдиреАрдЪреЗ рдбрд┐рдмрдЧ рдХрд░рдирд╛ рдЕрдЪреНрдЫрд╛ рд╣реЛрдЧрд╛ред рдпрд╣ рдХреИрд╕реЗ рдХрд░рдирд╛ рд╣реИ, рдиреАрдЪреЗ:


рдПрдХ рдЙрджрд╛рд╣рд░рдг рдХреЗ рд░реВрдк рдореЗрдВ, s3e / s3eIOSAppStoreBilling рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░реЗрдВ рдЬреЛ рдорд╛рд░реНрдорд▓реЗрдб рдЙрджрд╛рд╣рд░рдгреЛрдВ рдХреЗ рд╕рд╛рде рдЬрд╣рд╛рдЬ рдХрд░рддрд╛ рд╣реИред рдпрджрд┐ рдЖрдк рдЗрд╕реЗ рдЪрд▓рд╛рддреЗ рд╣реИрдВ, рддреЛ рд╣рдо рд╕рднреА рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ рдПрдХ рдореЛрдбрд▓ рд╡рд┐рдВрдбреЛ рд╣реИ, рдЬрд┐рд╕рдореЗрдВ рддреНрд░реБрдЯрд┐ рд╕рдВрджреЗрд╢ "рдпрд╣ рдЙрджрд╛рд╣рд░рдг рдХреЗрд╡рд▓ рдЗрди-рдРрдк рдЦрд░реАрджрд╛рд░реА рдХреЗ рд▓рд┐рдП рд╕реНрдерд╛рдкрд┐рдд рдПрдХ рдкрд░реАрдХреНрд╖рдг рд╕реНрдЯреЛрд░ рдХреЗ рд╕рд╛рде iPhone рдкрд░ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ"ред рдпрд╣ рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ рд╕рд╣реА рд╣реИ, рд▓реЗрдХрд┐рди рд╣рдо рдЗрд╕реЗ iPhone рдкрд░ рдкреЛрд╕реНрдЯ рдХрд░рдиреЗ рд╕реЗ рдкрд╣рд▓реЗ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдбреАрдмрдЧ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВред рдРрд╕рд╛ рдХрд░рдирд╛ рдмрд┐рд▓реНрдХреБрд▓ рднреА рдореБрд╢реНрдХрд┐рд▓ рдирд╣реАрдВ рд╣реИред рд╣рдо S3E iOS рдРрдк рд╕реНрдЯреЛрд░ рдмрд┐рд▓рд┐рдВрдЧ рдХреЗ рд╡реНрдпрд╡рд╣рд╛рд░ рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдПрдХ рд╕реНрдЯрдм рд╡рд┐рдХрд╕рд┐рдд рдХрд░реЗрдВрдЧреЗ рдЬреЛ (рдбрд┐рдмрдЧ рдмрд┐рд▓реНрдб рдореЗрдВ) рдирдХрд▓ рдХрд░рддрд╛ рд╣реИред рдЪрд▓рд┐рдП mkb рдлрд╛рдЗрд▓ рдореЗрдВ рдЕрдкрдиреА рдлрд╛рдЗрд▓ рдЬреЛрдбрд╝рдХрд░ рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВ:

s3eIOSAppStoreBilling.mkb
#!/usr/bin/env mkb files { s3eIOSAppStoreBilling.cpp AppStoreStub.h AppStoreStub.cpp } ... 


рд╕реНрдЯрдм рдХреЛрдб рд╣реА рдЖрджрд┐рдо рд╣реИред рдРрдк рд╕реНрдЯреЛрд░ рдмрд┐рд▓рд┐рдВрдЧ рдХреЗ рдкреНрд░рддреНрдпреЗрдХ рдлрд╝рдВрдХреНрд╢рди рдХреЗ рд▓рд┐рдП рдПрдЪ-рдлрд╛рдЗрд▓ рдореЗрдВ рд╣рдо рдкреНрд░рддреНрдпрдп "рд╕реНрдЯрдм" рдХреЗ рд╕рд╛рде рдПрдХ рд╕реНрдЯрдм рдХреЛ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рдпрджрд┐ рдЕрд╕реЗрдВрдмрд▓реА рдбрд┐рдмрдЧрд┐рдВрдЧ рдХрд░ рд░рд╣реА рд╣реИ, рддреЛ рд╣рдо рдХреЙрд▓ рдХреЛ #define рдирд┐рд░реНрджреЗрд╢ рдХреЗ рд╕рд╛рде рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрд┐рдд рдХрд░рддреЗ рд╣реИрдВ:

AppStoreStub.h
 #ifndef _APPSTORESTUB_H_ #define _APPSTORESTUB_H_ #include <string.h> #include "s3eTypes.h" #include "s3eIOSAppStoreBilling.h" #if defined IW_DEBUG #define s3eIOSAppStoreBillingAvailable \ s3eIOSAppStoreBillingAvailableStub #define s3eIOSAppStoreBillingGetInt \ s3eIOSAppStoreBillingGetIntStub #define s3eIOSAppStoreBillingInit \ s3eIOSAppStoreBillingInitStub #define s3eIOSAppStoreBillingStart \ s3eIOSAppStoreBillingInitStub #define s3eIOSAppStoreBillingTerminate \ s3eIOSAppStoreBillingTerminateStub #define s3eIOSAppStoreBillingStop \ s3eIOSAppStoreBillingTerminateStub #define s3eIOSAppStoreBillingRequestProductInformation \ s3eIOSAppStoreBillingRequestProductInformationStub #define s3eIOSAppStoreBillingCancelProductInformationRequests \ s3eIOSAppStoreBillingCancelProductInformationRequestsStub #define s3eIOSAppStoreBillingRequestPayment \ s3eIOSAppStoreBillingRequestPaymentStub #define s3eIOSAppStoreBillingCompleteTransaction \ s3eIOSAppStoreBillingCompleteTransactionStub #define s3eIOSAppStoreBillingRestoreCompletedTransactions \ s3eIOSAppStoreBillingRestoreCompletedTransactionsStub #endif s3eBool s3eIOSAppStoreBillingAvailableStub(); int32 s3eIOSAppStoreBillingGetIntStub(s3eIOSAppStoreBillingProperty property); s3eResult s3eIOSAppStoreBillingInitStub(s3eProductInformationCallbackFn infoCallback, s3ePaymentTransactionUpdateCallbackFn updateCallback, void* userData); void s3eIOSAppStoreBillingTerminateStub(); s3eResult s3eIOSAppStoreBillingRequestProductInformationStub(const char** productIdentifiers, uint32 numProductIdentifiers); void s3eIOSAppStoreBillingCancelProductInformationRequestsStub(); s3eResult s3eIOSAppStoreBillingRequestPaymentStub(s3ePaymentRequest* paymentRequest); s3eResult s3eIOSAppStoreBillingCompleteTransactionStub(s3ePaymentTransaction* transaction, s3eBool finalise); s3eResult s3eIOSAppStoreBillingRestoreCompletedTransactionsStub(); void appStoreStubUpdate(); #endif // _APPSTORESTUB_H_ 


рдЗрди рдХрд╛рд░реНрдпреЛрдВ рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдореЗрдВ рдХреБрдЫ рднреА рдЕрд▓реМрдХрд┐рдХ рдирд╣реАрдВ рд╣реЛрддрд╛ рд╣реИ:

AppStoreStub.cpp
 #include "AppStoreStub.h" #define PRODUCT_INFO_REQUESTED 0x0001 #define TRANSACTION_REQUESTED 0x0002 s3eProductInformationCallbackFn productInfoCallback = NULL; s3ePaymentTransactionUpdateCallbackFn transactionCallback = NULL; void* billingData = NULL; s3eProductInformation productInformation; s3ePaymentTransaction transaction; s3eTransactionReceipt receipt; int eventMask = 0; s3eBool s3eIOSAppStoreBillingAvailableStub() { return S3E_TRUE; } int32 s3eIOSAppStoreBillingGetIntStub(s3eIOSAppStoreBillingProperty property) { switch (property) { case S3E_IOSAPPSTOREBILLING_CAN_MAKE_PAYMENTS: return 1; default: return 0; } } s3eResult s3eIOSAppStoreBillingInitStub(s3eProductInformationCallbackFn infoCallback, s3ePaymentTransactionUpdateCallbackFn updateCallback, void* userData) { productInfoCallback = infoCallback; transactionCallback = updateCallback; billingData = userData; return S3E_RESULT_SUCCESS; } void s3eIOSAppStoreBillingTerminateStub() {} s3eResult s3eIOSAppStoreBillingRequestProductInformationStub(const char** productIdentifiers, uint32 numProductIdentifiers) { if (numProductIdentifiers != 1) return S3E_RESULT_ERROR; memset(&productInformation, 0, sizeof(productInformation)); strcpy(productInformation.m_ProductID, *productIdentifiers); strcpy(productInformation.m_LocalisedTitle, *productIdentifiers); strcpy(productInformation.m_LocalisedDescription, *productIdentifiers); strcpy(productInformation.m_FormattedPrice, "0.00"); strcpy(productInformation.m_PriceLocale, "0.00"); productInformation.m_Price = 0; productInformation.m_ProductStoreStatus = S3E_PRODUCT_STORE_STATUS_VALID; eventMask |= PRODUCT_INFO_REQUESTED; return S3E_RESULT_SUCCESS; } void s3eIOSAppStoreBillingCancelProductInformationRequestsStub() {} s3eResult s3eIOSAppStoreBillingRequestPaymentStub(s3ePaymentRequest* paymentRequest) { memset(&transaction, 0, sizeof(transaction)); transaction.m_TransactionStatus = S3E_PAYMENT_STATUS_PURCHASED; transaction.m_Request = paymentRequest; transaction.m_Retain = S3E_FALSE; transaction.m_TransactionReceipt = &receipt; strcpy(transaction.m_TransactionID, "1"); strcpy(transaction.m_OriginalTransactionID, "1"); memset(&receipt, 0, sizeof(receipt)); receipt.m_ReceiptSize = 0; eventMask |= TRANSACTION_REQUESTED; return S3E_RESULT_ERROR; // ??? } s3eResult s3eIOSAppStoreBillingCompleteTransactionStub(s3ePaymentTransaction* transaction, s3eBool finalise) { return S3E_RESULT_SUCCESS; } s3eResult s3eIOSAppStoreBillingRestoreCompletedTransactionsStub() { return S3E_RESULT_SUCCESS; } void appStoreStubUpdate() { #if defined IW_DEBUG if (eventMask != 0) { if (eventMask & PRODUCT_INFO_REQUESTED) { productInfoCallback(&productInformation, billingData); } if (eventMask & TRANSACTION_REQUESTED) { transactionCallback(&transaction, billingData); } eventMask = 0; } #endif } 


AppStoreStubUpdate рдлрд╝рдВрдХреНрд╢рди рдХрд╛ рдЙрд▓реНрд▓реЗрдЦ рдпрд╣рд╛рдВ рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред рддрдереНрдп рдпрд╣ рд╣реИ рдХрд┐ рдРрдк рд╕реНрдЯреЛрд░ рдмрд┐рд▓рд┐рдВрдЧ рдПрдкреАрдЖрдИ рд╕реНрдЯреЛрд░ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рдХреЙрд▓ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИред рдЗрд╕рдХрд╛ рдорддрд▓рдм рд╣реИ рдХрд┐ рдЙрддреНрдкрд╛рдж рдЬрд╛рдирдХрд╛рд░реА s3eIOSAppStoreBillingRequestProductInformation рдХрд╛ рдЕрдиреБрд░реЛрдз рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдлрд╝рдВрдХреНрд╢рди рдХреЙрд▓рдмреИрдХ рдХреЗ рд╕рдВрджрд░реНрдн рдореЗрдВ рдлрд╝рдВрдХреНрд╢рди рдХреЙрд▓рдмреИрдХ рдХреЙрд▓ рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП рдФрд░ рдЦрд░реАрджрд╛рд░реА s3eIOSAppStoreBillingRequestPendment рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред рд╣рдо рдЗрди рдХреЙрд▓ рдХреЛ AppStoreStubUpdate рдореЗрдВ рдХрд░ рджреЗрдВрдЧреЗ, рдорд╛рд░рд╛рдорд▓реЗрдб рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЗ рдЕрдкрдбреЗрдЯ рдлрд╝рдВрдХреНрд╢рди рдХреЗ рдЕрдВрддрд┐рдо рдХреЛ рдХреЙрд▓ рдХрд░рддреЗ рд╣реИрдВред

рд╕рд╛рде рд╣реА, рд░рд┐рдЯрд░реНрди рдХреЛрдб s3eIOSAppStoreBillingRequestPaymentStub рдкрд░ рд╕рд╡рд╛рд▓ рдЦрдбрд╝реЗ рд╣реЛ рд╕рдХрддреЗ рд╣реИрдВред рдИрдорд╛рдирджрд╛рд░реА рд╕реЗ рдХрд╣реВрдВ рддреЛ рдореИрдВ рдЦреБрдж рдЗрд╕ рдкрд▓ рдХреЛ рдирд╣реАрдВ рд╕рдордЭрддрд╛ рд╣реВрдВред рдЪреАрдЬреЛрдВ рдХреЗ рддрд░реНрдХ рдХреЗ рдЕрдиреБрд╕рд╛рд░, рдпрджрд┐ рдпрд╣ рд╕рдлрд▓ рд╣реЛрддрд╛ рд╣реИ, рддреЛ рдЙрд╕реЗ S3E_RESULT_SUCCESS (0 рдХреЗ рдмрд░рд╛рдмрд░) рд╡рд╛рдкрд╕ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП, рд▓реЗрдХрд┐рди рдЙрджрд╛рд╣рд░рдг рдЕрдиреБрд░реЛрдз рдХреА рд╢реБрджреНрдзрддрд╛ рдХреЗ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд╕рддреНрдпрд╛рдкрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ:

s3eIOSAppStoreBilling.cpp
  if (s3eIOSAppStoreBillingRequestPayment(&g_PaymentRequest)) SetStatus("Purchasing %s...", g_ProductID2); else SetStatus("Purchasing %s FAILED", g_ProductID2); 


рдпрд╣реА рд╣реИ, рдПрдХ рдиреЙрдирдЬрд╝рд░реЛ рдореВрд▓реНрдп рдЕрдкреЗрдХреНрд╖рд┐рдд рд╣реИред рдЬрд╛рд╣рд┐рд░ рд╣реИ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рдбреЗрд╡рд▓рдкрд░реНрд╕ рдиреЗ рдЧрд▓рддреА рдХреА, рд▓реЗрдХрд┐рди рдЗрд╕ рдмрд┐рдВрджреБ рдкрд░ рдпрд╣ рдзреНрдпрд╛рди рджреЗрдиреЗ рдпреЛрдЧреНрдп рд╣реИ рдЬрдм iPhone рдкрд░ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдбреАрдмрдЧ рдХрд░рдирд╛ред

рдЕрдВрддрд┐рдо рдмрд╛рдд рдЪреЗрдХ рдХрд╛ рд╣реИред S3eIOSAppStoreBillingRequestPaymentStub рдореЗрдВ рд╣рдо рдПрдХ рдЦрд╛рд▓реА рдЪреЗрдХ рдмрдирд╛рддреЗ рд╣реИрдВ:

AppStoreStub.cpp
  ... memset(&receipt, 0, sizeof(receipt)); receipt.m_ReceiptSize = 0; ... 


рд▓реЗрдХрд┐рди рдпрд╣, рд╢рд╛рдпрдж, рдкреВрд░реА рддрд░рд╣ рд╕реЗ рд╕рд╣реА рдирд╣реАрдВ рд╣реИ рдпрджрд┐ рдЖрд╡реЗрджрди рдХреЛ рдЬрд╛рдВрдЪ рдХреЛ рд╕рддреНрдпрд╛рдкрд┐рдд рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП рдпрд╛ рдЗрд╕рдореЗрдВ рдирд┐рд╣рд┐рдд рдХреБрдЫ рдЬрд╛рдирдХрд╛рд░реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЦрд░реАрдж рдХреА рддрд╛рд░реАрдЦ)ред рдЖрдк рдЗрд╕ рдмрд╛рд░реЗ рдореЗрдВ рдкрдврд╝ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рдХреИрд╕реЗ рд╕рд╣реА рдЪреЗрдХ рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ ред рдпрд╣ рдмрддрд╛рддрд╛ рд╣реИ рдХрд┐ рдЪреЗрдХ рдХреЛ рдХреИрд╕реЗ рд╕рддреНрдпрд╛рдкрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдПред

рдмрд╣реБрдд рдХрдо рдмрдЪрд╛ рд╣реИред рдЙрджрд╛рд╣рд░рдг рдХреЛрдб рдореЗрдВ, рд╣рдо рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рдХреЙрд▓ рдХрд╛ рдЕрдиреБрдХрд░рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, #include AppStoreStub.h (рдпрд╣ рдЕрдВрддрд┐рдо рдПрдХ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП) рдФрд░ appStoreStubUpdate рдореЗрдВ рдПрдХ рдХреЙрд▓ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ:

s3eIOSAppStoreBilling.cpp
 bool ExampleUpdate() { appStoreStubUpdate(); ... 


рдЙрджрд╛рд╣рд░рдг рд╕рдВрдХрд▓рд┐рдд рдХрд░рдиреЗ рдФрд░ рдбреАрдмрдЧрд░ рдХреЗ рддрд╣рдд рдЪрд▓рд╛рдиреЗ рдХреЗ рдмрд╛рдж, рд╣рдо рдпрд╣ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рдЕрдм рд╣рдо рдЙрддреНрдкрд╛рдж рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдХрд╛ рдЕрдиреБрд░реЛрдз рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рдРрдк рд╕реНрдЯреЛрд░ рдХреЛ рдкрд░реЗрд╢рд╛рди рдХрд┐рдП рдмрд┐рдирд╛ рдЦрд░реАрджрд╛рд░реА рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред

Source: https://habr.com/ru/post/In157677/


All Articles