UWP + C++/CX 로 OneDrive앱 개발하기

그저 내가 개발중인 게임과 툴에서 OneDrive에 억세스 하고 싶었다. C++에서 C#코드를 부를 생각은 없었다. 네이티브 C++로 하고 싶었다.
예전에 Casablanca(C++ Rest SDK)를 사용해서 OneDrivePlayerW81이란 앱을 만든 적이 있다. 그때 Casablanca 쓰면서 엄청 짜증났던 기억이 있다. 그래서 이번엔 Casablanca를 쓰지 않고 UWP API와 C++/CX로 구현할 생각이다.
일단 UWP 앱이지만 데스크탑앱에서도 UWP의 HttpClient를 사용할 수 있으므로 추후 데스크탑 포팅도 가능하지 않을까 기대해본다.(확실히 가능한지는 아직 모른다).

하여간 몇 일전 시작했고 현재까지 진행상황이다.
api.onedrive.com/v1.0으로 시작해서 어느 정도 뼈대를 잡았는데 자동 로그인 처리를 하려니 문제가 많아서 포기.

그래서 apis.live.net/v5.0 로 돌아왔다. 몇일 삽질 해둔 덕에 REST get,post체계는 만들어놔서 비교적 쉽게 바꿨다.

co_await를 적극 사용해보고 싶었지만 이게 아직 실험적인 기능이라 HttpClient의 GetAsync()따위의 메소드에는 사용 불가. 결국 빌어먹을 create_task().then().then()의 task 체인 지옥을 피해갈 수 없었다.

api.onedrive.com/v1.0에 대한 토큰을 REST 호출로 받아오려니 이게 좀 문제가 있다. 자동 로그인을 처리할 방법이 막막하다. refresh_token을 사용해서 처리해보려고 별 짓을 다 해봤는데 내가 뭘 잘못했는지 refresh_token처리에 계속 실패하고 있다. 이게 제대로 작동을 해도 웹에서나 어울리지 앱에서 사용하기엔 영 불편해서 결국 포기했다.

apis.live.net/v5.0 에 Windows::Security::Authentication::OnlineId::OnlineIdAuthenticator를 사용하면 토큰 받아오는 절차가 매우 간편할 뿐더러 자동로그인도 그야말로 자동으로 된다. 로그인 팝업을 띄울 필요가 없다.

로그인 코드는 다음과 같다. 토큰을 받아오자마자 루트 폴더의 내용을 가져온다.

String^ scope = L"wl.signin wl.basic wl.photos wl.skydrive_update";

auto request = ref new OnlineIdServiceTicketRequest(scope, "DELEGATION");
m_Authenticator = ref new Windows::Security::Authentication::OnlineId::OnlineIdAuthenticator();

auto login_task = create_task(m_Authenticator->AuthenticateUserAsync(request));
login_task.then([this](task<Windows::Security::Authentication::OnlineId::UserIdentity^> ident_task)
{
	bool	success = false;
	UserIdentity^ identity = nullptr;
	try
	{
		identity = ident_task.get();
		if (identity)
		{
			if (identity->Tickets->Size)
			{
				success = true;
			}
		}
	}
	catch (Exception^ e)
	{
		const WCHAR* errMsg = e->Message->Data();
		OutputDebugString(errMsg);
	}

	if (!success)
		return;

	auto ticket = identity->Tickets->GetAt(0);
	String^ token = ticket->Value;

	HttpClient^	httpClient = ref new HttpClient();

	String^ cmd = L"https://apis.live.net/v5.0/me?access_token=" + token;
	Uri^	uri = ref new Uri(cmd);

	auto task_folder = create_task(httpClient->GetStringAsync(uri));
	task_folder.then([this,token](task<String^> task_json)
	{
		String^ json = nullptr;

		try
		{
			json = task_json.get();
		}
		catch (Exception^ e)
		{
			const WCHAR* err = e->Message->Data();
			OutputDebugString(err);
		}

		if (!json)
		{
			return;
		}
		String^ UserName = GetUserName(json);
	});
});

String^ token = ticket->Value;에서 받아온 토큰은 저장해둔다. 이후에는 Access token붙여서 GET,POST 호출하면 된다.
탐색페이지에서 폴더를 클릭했을때 폴더의 내용을 가져오는데, 특정폴더의 내용은 다음과 같이 가져온다

HttpClient^	httpClient = ref new HttpClient();

auto headerAuth = ref new Windows::Web::Http::Headers::HttpCredentialsHeaderValue(ref new String(BEARER), m_AccessToken);
httpClient->DefaultRequestHeaders->Authorization = headerAuth;

String^ cmd = nullptr;

if (folder)
{
	cmd = ref new String(ROOT_URL) + L"/" + folder->ID + L"/files";
}
else
{
	cmd = ref new String(ROOT_URL) + L"/me/skydrive/files";
}

auto uri = ref new Uri(cmd);

auto task_folder = create_task(httpClient->GetStringAsync(uri));
task_folder.then([this,folder](task<String^> task_json)
{
	String^ json = nullptr;

	try
	{
		json = task_json.get();
	}
	catch (Exception^ e)
	{
		const WCHAR* err = e->Message->Data();
		OutputDebugString(err);
	}

	if (!json)
	{
		return;
	}

	Vector<FileItem^>^ items = CreateFileItemsInfoFromJson(json,folder);

	m_CurFolder = folder;
	m_CurItems = items;
});

그리고 JSON파싱을 해야되는데 아마 이것 때문에 Casablanca를 쓰는것 같다. 그런데 UWP에는 JSON파싱 API가 이미 있다. 굳이 Casablanca를 사용할 필요가 없다.
위 코드에서 폴더안의 파일과 서브폴더들 목록을 받아와서 CreateFileItemsInfoFromJson()이란 함수를 호출하는데, 물론 따로 만든 함수이고 아래처럼 구현했다.

Vector<FileItem^>^ OneDriveService::CreateFileItemsInfoFromJson(String^ jsonStr,FileItem^ ParentFolder)
{
	Vector<FileItem^>^	files = ref new Vector<FileItem^>();
	
	Windows::Data::Json::JsonObject^ tokenResponse = ref new JsonObject();
	
	if (!JsonObject::TryParse(jsonStr, &tokenResponse))
		return nullptr;
	
	auto map = tokenResponse->GetView();

	
	IJsonValue^ value = map->Lookup("data");
	String^ s = value->Stringify();

	JsonArray^ mapValue = ref new JsonArray();
	if (JsonArray::TryParse(s, &mapValue))
	{
		auto vec = mapValue->GetView();

		for each(auto item in vec)
		{
			auto vtype = item->ValueType;
			switch (vtype)
			{
			case JsonValueType::Object:
				{
					JsonObject^	obj = item->GetObject();
					FileItem^	fileItem = CreateFileItem(obj);
					fileItem->SetParentFolder(ParentFolder);		
					files->Append(fileItem);
				}
				break;
			default:
				__debugbreak();
				int a = 0;
			}
		}
	}
	
	return files;
}
FileItem^ OneDriveService::CreateFileItem(Windows::Data::Json::JsonObject^ obj)
{
	auto view = obj->GetView();

	FileItem^	fileItem = nullptr;
	String^ Name = nullptr;
	String^ Title = nullptr;
	String^ ID = nullptr;
	String^ ParentID = nullptr;

	SKY_FILE_TYPE	type = SKY_FILE_TYPE_ETC;

	for each (auto item in view)
	{
		String^ key = item->Key;

		if (key == L"name")
		{
			Name = item->Value->GetString();
		}
		if (key == L"id")
		{
			ID = item->Value->GetString();
		}
		if (key == L"type")
		{
			String^ value = item->Value->GetString();
			if (L"folder" == value || L"album" == value)
			{
				type = SKY_FILE_TYPE_FOLDER;
			}
			else if (L"photo" == value)
			{
				type = SKY_FILE_TYPE_PHOTO;
			}
			else if (L"audio" == value)
			{
				type = SKY_FILE_TYPE_AUDIO;
			}
			else
			{
				type = SKY_FILE_TYPE_ETC;
			}
		}
		if (key == L"title")
		{
			auto value_type = item->Value->ValueType;
			if (value_type == JsonValueType::String)
			{			
				Title = item->Value->GetString();
			}
		}
		if (key == L"parent_id")
		{
			ParentID = item->Value->GetString();
		}

	}
	if (Name && ID)
	{
		fileItem = ref new FileItem;
		fileItem->FileName = Name;
		fileItem->ID = ID;
		fileItem->ParentID = ParentID;
		fileItem->Title = Title;
		fileItem->SetType(type);
		
	}
	return fileItem;
}

UWP에서 JSON파싱은 별도 라이브러리 없이 간단히 할 수 있다. 게다가 Visual Studio 디버거는 JSon타입의 String을 JSON포맷으로 깔끔하게 보여준다.

json_visualizer

현재 폴더 탐색기능, 파일 다운로드 기능까진 구현해놨다.

onedriveaccess_2016_1104

아 그리고 진짜 C++하는 사람들이 다 말라죽은건지, 그 사람들 중에 OneDrive쓸려는 사람은 한명도 없는건지…
Casablanca 안쓰고 OneDrive억세스하는 C++ 샘플을 한개도 못찾았는데 내가 검색을 못해서 그런거라고 누가 말해줬으면 좋겠네.
아니 Casablanca쓰고 OneDrive억세스하는 샘플도 사실 없다. 해당 문서도 사라졌다. 몇 년전엔 MS에서 작성해서 올려준 live_connect.h라는 파일 한개짜리 클래스가 있었다. 하지만 얼마 후 삭제되었고 더 이상 올라오지 않는다.

조금 더 다듬어서 소스코드 공개할 예정이다. 내가 쪽팔려서 소스코드 공개는 잘 안하는데 이건 너무 없어서 안할 수가 없다.
나처럼 UWP로 DirectX게임과 툴을 개발하고 그 안에서 OneDrive를 쓰고 싶은 사람이 있다면 도움이 되겠지.


답글 남기기

댓글을 게시하려면 다음의 방법 중 하나를 사용하여 로그인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중