ارتباط و سینک اپلیکیشن iOS با Dropbox (قسمت اول)

اگر شما هم همیشه به این فکر بودید که اطلاعات لوکال اپلیکیشنتون که روی دیواس iOS کاربر هست رو چجوری می‌شه یه جای امن و قابل دسترس برای کاربر بکاپ گرفت، این نوشته مربوط به شماست.

این ایده بر این اساس شکل گرفت که نیاز بود دیتابیس و فایل های لوکال کاربر یه جایی به جز بکند سرور، و جایی که برای کاربر های عادی هم قابل دسترس باشه، بکاپ گرفت تا اگه کاربر دیوایسشو عوض کرد بتونه به راحتی اطلاعات دیوایس قدیمیش رو با دیوایس جدیدش سینک کنه و خب بله، اپلیکیشن ما Cloud-Base نبود و بخاطر بالا بودن حجم فایل های لوکالی کاربر، محدود بودن فضای بکند سرور و تعداد کاربر های زیادی که داشتیم امکان ذخیره فایل ها روی سرور ممکن نبود.

این دقیقا کاری هست که WhatsApp با گوگل درایو انجام میده و به دلیل خصومت شخصی بنده با گوگل، تصمیم گرفتم که برای این کار از Dropbox استفاده کنم.

مرحله اول: ساخت اپ OAuth2 برای Dropbox

اگر که اکانت دراپ باکس ندارید، همین الان می‌تونید از طریق این لینک برای خودتون یه اکانت بسازید. ساخت حساب کاربری رایگان هست و مقدار محدودی هم حجم رایگان در اختیارتون قرار میده که خب اگر قرار هست به صورت جدی برای اپلیکیشنتون ازین سیستم استفاده کنید، احتمالا باید حجم بخرید که خب قیمتش نسبتا ارزون هست. حداقل ارزون تر از افزایش حجم بکند سرور.

بعد از ورود به حساب دراپ باکستون برای ساختن یک اپ OAuth2 که در واقع پل ارتباطی هست بین اپلیکیشن iOS و حساب کاربری دراپ باکستون، به این لینک مراجعه کنید.

بعد از ورود به صفحه روی دکمه Create App کلیک کنید و فرم رو بر اساس نیاز های خودتون کامل کنید

من برای این آموزش از Dropbox API استفاده می‌کنم و دسترسی کامل به دراپ باکسم رو درخواست می‌کنم.

بعد ازین که اپلیکیشن ساخته شد، شما به صفحه تنضیمات اپ ریدایرکت می‌شید. ما برای این آموزش به ۲ کلید App Key و Access Token نیاز داریم. برای تولید کلید Access Token به روی دکمه Generated access token کلیک کنید.

مرحله دوم: نصب فریم‌ورک روی پروژه

برای نصب فریم‌ورک مربوطه دراپ باکس از CocoaPods استفاده می‌کنیم.

اگر که نمی‌دونید CocoaPods چی هست و یا تا حالا ازش استفاده نکردید پیشنهاد می‌کنم که قبل از خوندن ادامه این مطلب یه سری به این لینک بزنید.

فریمورک رسمی دراپ‌باکس رو با این پاد می‌تونید روی پروژه نصب کنید:

pod 'ObjectiveDropboxOfficial'

من توی این آموزش دارم از ورژن ۹.۰.۱۵ این فریم‌ورک استفاده می‌کنم و ممکنه کدهای به کار رفته توی این آموزش با ورژن های قبلی یا بعدی متفاوت باشه.

مرحله سوم: کانفیگ کردن پروژه

خب بعد ازین که فریم‌ورک روی پروژه نصب شد، نوبت به کانفیگ کردن پروژه می‌رسه.

اول به سراغ فایل info.plist می‌ریم. بر اساس تغییرات امنیتی که اپل توی کنفرانس WWDC 2015 معرفی کرد باید این فایل رو برای برقراری ارتباط با api دراپ باکس تغییر بدیم.

فایل info.plist رو به صورت سورس کد باز کنید و این کد رو بهش اضافه کنید:

<key>LSApplicationQueriesSchemes</key>
    <array>
        <string>dbapi-8-emm</string>
        <string>dbapi-2</string>
    </array>

این کد به SDK اجازه میده که تعیین کنه که SDK رسمی هست و قرار هست که از OAuth ورژن 2 استفاده کنه.

بعد از این نیاز هست این کد رو هم اضافه کنید تا api متوجه شه که SDK قرار هست با کدوم اپ OAuth2 کار کنه:

<key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>db-<APP_KEY></string>
            </array>
            <key>CFBundleURLName</key>
            <string></string>
        </dict>
    </array>

در اینجا <APP_KEY> رو با کدی که در مرحله اول گرفتید عوض کنید.

فایل info.plist در آخر باید به شکل زیر در بیاد:


احتمالا همینجوری که متوجه شدید این فریم ورک Objective-C هست اما نگران نباشید چون که می‌تونید از همین هم توی پروژه‌ی Swift استفاده کنید. فقط کافیه که YOUR_PROJECT-Bridging-Header.h هدر این فریم ورک رو اضافه کنید و سوئیفت بنویسید.

این فایل احتمالا به صورت خودکار به پروژه اضافه میشه اما اگه نشد نگران نباشید، یه فایل هدر خودتون بسازید و آدرس رو در تنظیمات پروژه، در تب Build Setting در Swift Compiler - General و Objective-C Bridging Header اضافه کنید.

هدر این فریم‌ورک

 #import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>

هست. اگر که از پروژه آبجکتیو سی استفاده می‌کنید این رو باید به فایل هدر اون کلاس اضافه کنید.

مرحله چهارم: شروع کد نویسی

من توی این مقاله تصمیم دارم که هم کد سوئیفت رو باهاتون به اشتراک بذارم و هم کد آبجکتیو سی رو.

خب برای این که اپلیکیشن به اپ OAuth کانکت شه ما نیاز به Access Token داریم که اون رو توی مرحله اول جنریت کردیم و حالا می‌خوایم ازون استفاده کنیم. اما توی این حالت کاربر صرفا می‌تونه فایل هاش رو روی دراپ باکس شما آپلود کنه. اگر که کاربر بخواد فایل هاش رو روی دراپ باکس خودش آپلود کنه نیاز هست که ابتدا به اپ OAuth شما اجازه دسترسی بده و Access Token خودش رو بگیره.

در iOS برای اجازه دسترسی گرفتن ۲ راه وجود داره:

  1. راه مستقیم که به این صورت عمل می‌کنه که اگر اپلیکیشن دراپ باکس روی دیوایس کاربر نصب باشه وارد اون اپلیکیشن می‌شه، مجوز دسترسی رو صادر می‌کنه و دوباره به اپ شما بر می‌گرده با دست پر! که خب منظورم همون Access Token هست.
  2. از طریق سافاری که خب برای وقتی هست که کاربر اپلیکیشن دراپ باکس رو روی دیوایسش نداره. برای این کار از SFSafariViewController استفاده می‌شه. باقی ماجرا روی سافاری اتفاق می‌وفته و باز به اپلیکیشن شما بر می‌گرده.

ولی خب من برای شروع کار توی این قسمت و برای این که این داستان خیلی پیچیده نشده، تصمیم دارم که از Access Tokenی که خودمون تولید کردیم استفاده کنم و با دراپ باکس ارتباط برقرار کنم و توی قسمت بعدی وارد مسائل گرفتن مجوز برای هر کاربر می‌شم.

در ابتدا ما باید یک کلاینت دراپ باکس بسازیم که قرار هست که به api دستور بده.

کد آبجکتیو سی:

 DBUserClient *client = [[DBUserClient alloc] initWithAccessToken:@"<YOUR_ACCESS_TOKEN>"];

کد سوئیفت:

var client = DBUserClient(accessToken: "<YOUR_ACCESS_TOKEN>")

خب حالا ما با آبجکت clientی که ساختیم می‌تونیم به api دستور بدیم. برای دست گرمی ابتدا می‌خوایم صرفا یه سری فولدر رو به صورت یک Path توی Root دراپ باکس بسازیم

کد آبجکتیو سی:

[[client.filesRoutes createFolder:@"/test/path/in/Dropbox/account"]
    setResponseBlock:^(DBFILESFolderMetadata *result, DBFILESCreateFolderError *routeError, DBRequestError *networkError) {
      if (result) {
        NSLog(@"%@\n", result);
      } else {
        NSLog(@"%@\n%@\n", routeError, networkError);
      }
    }];
    

کد سوئیفت:

client.filesRoutes.createFolder("/test/path/in/Dropbox/account").responseBlock = { result, routeError, networkError in
    if result != nil {
        if let aResult = result {
            print("\(aResult)\n")
        }
    } else {
        if let anError = routeError, let anError1 = networkError {
            print("\(anError)\n\(anError1)\n")
        }
    }
}

بعد از این که دستور ارسال شد، ریسپانس توی responseBlock میاد که خب اگه کار رو درست انجام داده باشیم Result==True هست و بعد می‌تونیم بریم ببینیم که آیا واقعا فولدرامون ساخته شده یا نه!

بله شده!
بله شده!


حالا می‌رسیم به بخش هیجان انگیز این ماجرا که آپلود هست.

در ابتدا ما باید فایل یا فایل هایی که می‌خوایم آپلود کنیم رو به NSData تبدیل کنیم. توی این آموزش من فرض رو بر این می‌گیرم که شما فایل هاژ دیتابیس کاربر رو توی یه فایل زیپ توی Document Library ذخیره کردید. برای تبدیل اون فایل به NSData از کد زیر استفاده کنید

کد آبجکتیو سی:

NSData *zipData = [NSData dataWithContentsOfFile:ZipPath];

کد سوئیفت:

var zipData = NSData(contentsOfFile: ZipPath) as Data?

حالا برای آپلود اون فایل از کد زیر استفاده می‌کنیم

کد آبجکتیو سی:

 NSString *DropboxPath = [NSString stringWithFormat:@"/Columbo/%@/%@/%@",Test,Username,ZipFileName];
  DBFILESWriteMode *mode = [[DBFILESWriteMode alloc] initWithOverwrite];
  [[[client.filesRoutes uploadData:DropboxPath
  mode:mode
  autorename:@(YES)
  clientModified:nil
  mute:@(NO)
  inputData:zipData]
  setResponseBlock:^(DBFILESFileMetadata *result, DBFILESUploadError *routeError, 
   DBRequestError *networkError) {
   if (result) {
   [self hidehud:true message:@"Images Uploaded Succesfully"];
   } else {
   [self hidehud:true message:@"Error"];
   }
   }] setProgressBlock:^(int64_t bytesUploaded, int64_t totalBytesUploaded, int64_t 
   totalBytesExpectedToUploaded) {
   NSLog(@"\n%lld\n%lld\n%lld\n", bytesUploaded, totalBytesUploaded, 
   totalBytesExpectedToUploaded);
   }];

کد سوئیفت:

var DropboxPath = "/Test/\(CompanyCode)/\(Username)/\(ZipFileName)"
var mode = DBFILESWriteMode()
client.filesRoutes.uploadData(DropboxPath, mode: mode, autorename: true, clientModified: nil, mute: false, inputData: zipData).setResponseBlock({ result, routeError, networkError in
    if result != nil {
        self.hidehud(true, message: "Images Uploaded Succesfully")
    } else {
        self.hidehud(true, message: "Error")
    }
}).progressBlock = { bytesUploaded, totalBytesUploaded, totalBytesExpectedToUploaded in
    print(String(format: "\n%lld\n%lld\n%lld\n", bytesUploaded, totalBytesUploaded, totalBytesExpectedToUploaded))
}

در اینجا مسیری که می‌خواهیم فایل در دراپ باکس آپلود شود را به صورت String می‌نویسیم. این مسیر نیازی نیست که از قبل در دراپ باکس ساخته شده باشد. اگر وجود نداشته باشد، دراپ باکس آن را می‌سازد.

بعد از این که دستور ارسال شد، ریسپانس توی responseBlock میاد که خب اگه کار رو درست انجام داده باشیم Result==True هست.

همچنین شما می‌تونید روند آپلود رو توی progressBlock دریافت کنید که اون رو به صورت یک Progress Bar به کاربر نمایش بدید.

به همین راحتی ما فایلمون رو توی دراپ باکس آپلود کردیم.

در قسمت بعدی می‌ریم سراغ موارد یکمی پیچیده تر مثل آپلود چندین فایل به صورت همزمان، دانلود فایل روی دیوایس کاربر و گرفتن مجوز دسترسی به دراپ باکس کاربر.