1 // clone_repo_dialog.cpp : implementation file
2 //
3 
4 #include "studiox_includes.h"
5 #include "clone_repo_dialog.h"
6 
7 #ifdef _DEBUG
8 #define new DEBUG_NEW
9 #endif
10 
11 char guix_repo_url[] = "https://github.com/eclipse-threadx/guix.git";
12 
13 BEGIN_MESSAGE_MAP(clone_repo_dialog, express_dialog)
14     ON_WM_CLOSE()
15     ON_BN_CLICKED(IDB_EXIT_CLONE_DIALOG, OnCancel)
16     ON_BN_CLICKED(IDB_CLONE_REPO, OnCloneRepo)
17     ON_BN_CLICKED(IDB_SELECT_DIRECTORY, OnSetDirectory)
18     ON_MESSAGE(USR_MSG_REPO_CLONE_MSG_UPDATE, OnRepoCloneMsgUpdate)
19 END_MESSAGE_MAP()
20 
21 
22 IMPLEMENT_DYNAMIC(clone_repo_dialog, express_dialog)
23 
24 #define GIT_CLONE_MSG_UPDATE_PERIOD 100 /* ms */
25 #define MSG_TYPE_CLONE    0
26 #define MSG_TYPE_COMPLETE 1
27 
28 struct git_clone_info {
29     char msg[MAX_PATH];
30     int  msg_type;
31     BOOL msg_updated;
32     git_indexer_progress stats;
33     BOOL stats_updated;
34     int checkout_completed;
35     int checkout_total;
36     BOOL checkout_updated;
37 };
38 
39 extern INI_INFO StudioXIni;
40 extern CString target_class_name;
41 
42 static git_clone_info CloneInfo;
43 
44 ///////////////////////////////////////////////////////////////////////////////
WriteCloneInfoToPipe(HANDLE writePipe,git_clone_info * info)45 static BOOL WriteCloneInfoToPipe(HANDLE writePipe, git_clone_info *info)
46 {
47     if (writePipe)
48     {
49         DWORD writtenSize;
50         BOOL result;
51 
52         result = WriteFile(writePipe, info, sizeof(git_clone_info), &writtenSize, NULL);
53         if (writtenSize == sizeof(git_clone_info) && result)
54         {
55             info->checkout_updated = FALSE;
56             info->msg_updated = FALSE;
57             info->stats_updated = FALSE;
58             return TRUE;
59         }
60     }
61 
62     return FALSE;
63 }
64 
65 ///////////////////////////////////////////////////////////////////////////////
ReadCloneInfoFromPipe(HANDLE readPipe,git_clone_info * info)66 static BOOL ReadCloneInfoFromPipe(HANDLE readPipe, git_clone_info *info)
67 {
68     DWORD readSize;
69 
70     if (readPipe)
71     {
72         BOOL result;
73         result = ReadFile(readPipe, info, sizeof(git_clone_info), &readSize, NULL);
74 
75         if (readSize == sizeof(git_clone_info) && result)
76         {
77             return TRUE;
78         }
79     }
80 
81     return FALSE;
82 }
83 
84 ///////////////////////////////////////////////////////////////////////////////
clone_repo_dialog(CWnd * parent)85 clone_repo_dialog::clone_repo_dialog(CWnd *parent)
86     : express_dialog(clone_repo_dialog::IDD, parent)
87 {
88     IconId = IDB_WARNING;
89     SetTitleText(CString("Clone GUIX Repository"));
90 
91     ZeroMemory(&mProcessInfo, sizeof(PROCESS_INFORMATION));
92     mpProgressScreen = NULL;
93     mWritePipeHandle = 0;
94     mReadPipeHandle = 0;
95 }
96 
97 ///////////////////////////////////////////////////////////////////////////////
OnInitDialog()98 BOOL clone_repo_dialog::OnInitDialog()
99 {
100     express_dialog::OnInitDialog();
101     AddCancelButton();
102     return TRUE; // return TRUE unless you set the focus to a control
103 }
104 
105 ///////////////////////////////////////////////////////////////////////////////
OnClose()106 void clone_repo_dialog::OnClose()
107 {
108     CDialog::OnClose();
109 }
110 
111 ///////////////////////////////////////////////////////////////////////////////
OnCancel()112 void clone_repo_dialog::OnCancel()
113 {
114     if (mpProgressScreen)
115     {
116         CompleteGUIXRepoClone(L"GUIX repository clone aborted!");
117     }
118     else
119     {
120         express_dialog::OnCancel();
121     }
122 }
123 
124 ///////////////////////////////////////////////////////////////////////////////
guix_transport_message_cb(const char * str,int len,void * payload)125 int guix_transport_message_cb(const char *str, int len, void *payload)
126 {
127     if (len >= MAX_PATH)
128     {
129         len = MAX_PATH - 1;
130     }
131 
132     memcpy_s(CloneInfo.msg, MAX_PATH, str, len);
133     CloneInfo.msg[len] = 0;
134 
135     CloneInfo.msg_type = MSG_TYPE_CLONE;
136     CloneInfo.msg_updated = TRUE;
137 
138     return 0;
139 }
140 
141 ///////////////////////////////////////////////////////////////////////////////
guix_indexer_progress_cb(const git_indexer_progress * stats,void * payload)142 int guix_indexer_progress_cb(const git_indexer_progress *stats, void *payload)
143 {
144     CloneInfo.stats = *stats;
145     CloneInfo.stats_updated = TRUE;
146 
147     return 0;
148 }
149 
150 ///////////////////////////////////////////////////////////////////////////////
guix_checkout_progress_cb(const char * path,size_t completed_steps,size_t total_steps,void * payload)151 void guix_checkout_progress_cb(const char *path, size_t completed_steps, size_t total_steps, void *payload)
152 {
153     CloneInfo.checkout_completed = completed_steps;
154     CloneInfo.checkout_total = total_steps;
155     CloneInfo.checkout_updated = TRUE;
156 }
157 
158 ///////////////////////////////////////////////////////////////////////////////
OnWriteCloneInfoTimer(void * lpParametar,BOOLEAN TimerOrWaitFired)159 void CALLBACK OnWriteCloneInfoTimer(void* lpParametar, BOOLEAN TimerOrWaitFired)
160 {
161     HANDLE write_pipe_handle = (HANDLE)lpParametar;
162 
163     if (CloneInfo.msg_updated ||
164         CloneInfo.checkout_updated ||
165         CloneInfo.stats_updated)
166     {
167         WriteCloneInfoToPipe(write_pipe_handle, &CloneInfo);
168     }
169 }
170 
171 ///////////////////////////////////////////////////////////////////////////////
CloneGUIXRepo(char * local_path,HANDLE write_pipe_handle)172 void CloneGUIXRepo(char *local_path, HANDLE write_pipe_handle)
173 {
174     BOOL clone_success = FALSE;
175     git_repository* clone_out;
176     git_clone_options options;
177     git_remote_callbacks remote_callbacks;
178     git_checkout_options checkout_opts;
179     git_fetch_options fetch_options = GIT_FETCH_OPTIONS_INIT;
180 
181     git_libgit2_init();
182     git_clone_options_init(&options, GIT_CLONE_OPTIONS_VERSION);
183 
184     remote_callbacks = fetch_options.callbacks;
185     remote_callbacks.sideband_progress = guix_transport_message_cb;
186     remote_callbacks.transfer_progress = guix_indexer_progress_cb;
187     fetch_options.callbacks = remote_callbacks;
188     options.fetch_opts = fetch_options;
189 
190     checkout_opts = options.checkout_opts;
191     checkout_opts.progress_cb = guix_checkout_progress_cb;
192     options.checkout_opts = checkout_opts;
193 
194     CloneInfo.checkout_updated = FALSE;
195     CloneInfo.msg_updated = FALSE;
196     CloneInfo.stats_updated = FALSE;
197 
198     // Create a timer to write git clone information to named pipe memory.
199     HANDLE hTimer;
200     BOOL success = ::CreateTimerQueueTimer(
201         &hTimer,
202         NULL,
203         OnWriteCloneInfoTimer,
204         (PVOID)write_pipe_handle,
205         GIT_CLONE_MSG_UPDATE_PERIOD,
206         GIT_CLONE_MSG_UPDATE_PERIOD,
207         WT_EXECUTEINTIMERTHREAD);
208 
209 
210     int clone_status = git_clone(&clone_out, guix_repo_url, local_path, &options);
211 
212     // Delete timer
213     DeleteTimerQueueTimer(NULL, hTimer, NULL);
214 
215     const git_error* error = NULL;
216     if (clone_status)
217     {
218         error = git_error_last();
219     }
220     else
221     {
222         git_repository_free(clone_out);
223     }
224 
225     if(error)
226     {
227         memcpy_s(CloneInfo.msg, MAX_PATH, error->message, strnlen(error->message, MAX_PATH - 1) + 1);
228     }
229     else
230     {
231         CloneInfo.msg[0] = '\0';
232     }
233 
234     git_libgit2_shutdown();
235 
236     // Nofity the calling process that git clone is completed.
237     CloneInfo.msg_type = MSG_TYPE_COMPLETE;
238     CloneInfo.msg_updated = TRUE;
239     WriteCloneInfoToPipe(write_pipe_handle , &CloneInfo);
240 }
241 
242 ///////////////////////////////////////////////////////////////////////////////
ReadCloneInfoThreadEntry(LPVOID thread_input)243 static DWORD WINAPI ReadCloneInfoThreadEntry(LPVOID thread_input)
244 {
245 
246     clone_repo_dialog *parent = (clone_repo_dialog *)thread_input;
247     HANDLE read_pipe_handle = parent->GetReadPipeHandle();
248 
249     git_clone_info info;
250 
251     while (1)
252     {
253         if (ReadCloneInfoFromPipe(read_pipe_handle, &info))
254         {
255             parent->SendMessage(USR_MSG_REPO_CLONE_MSG_UPDATE, (WPARAM)&info, NULL);
256         }
257     }
258 }
259 
260 ///////////////////////////////////////////////////////////////////////////////
OnCloneRepo()261 void clone_repo_dialog::OnCloneRepo()
262 {
263     TCHAR folder_path[MAX_PATH];
264     char *repo_url = guix_repo_url;
265 
266     if (BrowseForFolder(this->GetSafeHwnd(), _T("Select root for GUIX git repository clone..."), _T("C:\\Eclipse_ThreadX"), folder_path))
267     {
268         //"git clone --depth 1 aka.ms/azrtos-guix-repo --branch master --single-branch"
269 
270         CString CloneMessage(_T("Cloning from source URL "));
271         CloneMessage += repo_url;
272         CloneMessage += " to\n";
273         CloneMessage += "local folder ";
274         CloneMessage += folder_path;
275         CloneMessage += "\\guix";
276 
277         mpProgressScreen = new git_progress_screen(this, CloneMessage);
278         mpProgressScreen->Create(IDD_GIT_PROGRESS, this);
279         mpProgressScreen->ShowWindow(SW_SHOW);
280 
281         ShowHideChildren(SW_HIDE);
282 
283         mLocalPath.Format(L"%s\\guix", folder_path);
284 
285         // Run a subprocess to execute guix repo clone
286         TCHAR exename[MAX_PATH];
287 
288         GetModuleFileName(NULL, exename, MAX_PATH);
289 
290         SECURITY_ATTRIBUTES security_attri;
291 
292         security_attri.nLength = sizeof(SECURITY_ATTRIBUTES);
293         security_attri.lpSecurityDescriptor = NULL;
294         security_attri.bInheritHandle = TRUE;
295 
296         if(!CreatePipe(&mReadPipeHandle, &mWritePipeHandle, &security_attri, 0))
297         {
298             ErrorMsg("Create git clone named pipe memory failed!", this);
299             return;
300         }
301 
302         // Start a work thread to read git clone message from pipe.
303         mReadCloneInfoThreadHandle = CreateThread(NULL, GX_WIN32_STACK_SIZE, (LPTHREAD_START_ROUTINE)ReadCloneInfoThreadEntry, (LPVOID)this, 0, 0);
304 
305         if (mReadCloneInfoThreadHandle == INVALID_HANDLE_VALUE)
306         {
307             ErrorMsg("Create thread failed!", this);
308             mReadCloneInfoThreadHandle = 0;
309             return;
310         }
311 
312         CString szCmdLine;
313         szCmdLine.Format(L"%s -n --clone_guix_repo \"%s\" --write_pipe_handle %d", exename, mLocalPath, mWritePipeHandle);
314 
315         STARTUPINFO siStartInfo;
316 
317         // Set up members of the PROCESS_INFORMATION structure.
318         ZeroMemory(&mProcessInfo, sizeof(PROCESS_INFORMATION));
319 
320         // Set up members of the STARTUPINFO structure.
321         ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
322         siStartInfo.cb = sizeof(STARTUPINFO);
323 
324         // Create the child process.
325         if(!CreateProcess(NULL,
326             szCmdLine.GetBuffer(),     // command line
327             NULL,          // process security attributes
328             NULL,          // primary thread security attributes
329             TRUE,          // handles are inherited
330             0,             // creation flags
331             NULL,          // use parent's environment
332             NULL,          // use parent's current directory
333             &siStartInfo,  // STARTUPINFO pointer
334             &mProcessInfo  // receives PROCESS_INFORMATION
335             ))
336         {
337             ErrorMsg("Create git clone process failed!", this);
338             return;
339         }
340     }
341 }
342 
343 ///////////////////////////////////////////////////////////////////////////////
CompleteGUIXRepoClone(CString msg)344 void clone_repo_dialog::CompleteGUIXRepoClone(CString msg)
345 {
346     // Terminate git clone process
347     TerminateProcess(mProcessInfo.hProcess, 0);
348 
349     // Close handles to the child process and its primary thread.
350     CloseHandle(mProcessInfo.hProcess);
351     CloseHandle(mProcessInfo.hThread);
352     ZeroMemory(&mProcessInfo, sizeof(PROCESS_INFORMATION));
353 
354     // Close read and write pipe handles.
355     if (mReadPipeHandle)
356     {
357         CloseHandle(mReadPipeHandle);
358         mReadPipeHandle = 0;
359     }
360 
361     if (mWritePipeHandle)
362     {
363         CloseHandle(mWritePipeHandle);
364         mWritePipeHandle = 0;
365     }
366 
367     // Terminate clone information read thread.
368     if (mReadCloneInfoThreadHandle)
369     {
370         TerminateThread(mReadCloneInfoThreadHandle, 0);
371         CloseHandle(mReadCloneInfoThreadHandle);
372         mReadCloneInfoThreadHandle = 0;
373     }
374 
375     BOOL clone_succeed = FALSE;
376 
377     if (!msg.IsEmpty())
378     {
379         ErrorMsg(CW2A(msg), this);
380     }
381     else
382     {
383         StudioXIni.samples_dir = mLocalPath;
384         WriteIniInfo();
385         clone_repo_dialog::PopulateRecentProjects(StudioXIni.samples_dir);
386         Notify("The GUIX sample projects are now installed.", this);
387         clone_succeed = TRUE;
388     }
389 
390     mpProgressScreen->DestroyWindow();
391     delete mpProgressScreen;
392     mpProgressScreen = NULL;
393 
394     if (clone_succeed)
395     {
396         express_dialog::OnOK();
397     }
398     else
399     {
400         ShowHideChildren(SW_SHOW);
401     }
402 }
403 
404 ///////////////////////////////////////////////////////////////////////////////
PopulateRecentProjects(CString clone_dir)405 void clone_repo_dialog::PopulateRecentProjects(CString clone_dir)
406 {
407     if (!clone_dir.IsEmpty())
408     {
409         clone_dir.MakeLower();
410         StudioXIni.recent_project_paths[0] = clone_dir + _T("\\samples\\demo_guix_washing_machine\\demo_guix_washing_machine.gxp");
411         StudioXIni.recent_project_paths[1] = clone_dir + _T("\\samples\\demo_guix_home_automation\\demo_guix_home_automation.gxp");
412         StudioXIni.recent_project_paths[2] = clone_dir + _T("\\samples\\demo_guix_car_infotainment\\demo_guix_car_infotainment.gxp");
413         StudioXIni.recent_project_paths[3] = clone_dir + _T("\\samples\\demo_guix_shapes\\guix_shapes.gxp");
414         StudioXIni.recent_project_paths[4] = clone_dir + _T("\\samples\\demo_guix_widget_types\\guix_widget_types.gxp");
415 
416         // update recent projects menu
417         CMainFrame* pMain = (CMainFrame*)AfxGetMainWnd();
418         pMain->UpdateRecentProjectsMenu();
419 
420         target_view *target = GetTargetView();
421         if (target)
422         {
423             recent_project_win *dlg = (recent_project_win *) target->GetRecentDialog();
424             if (dlg)
425             {
426                 dlg->UpdateRecentList();
427             }
428         }
429     }
430 }
431 
432 ///////////////////////////////////////////////////////////////////////////////
OnSetDirectory()433 void clone_repo_dialog::OnSetDirectory()
434 {
435     TCHAR path[MAX_PATH];
436     char ascii_path[MAX_PATH];
437     CString test_project;
438     git_repository *repo;
439     size_t converted_chars;
440     BOOL ValidRepo = FALSE;
441 
442     git_libgit2_init();
443 
444     if (BrowseForFolder(this->GetSafeHwnd(), _T("Select local GUIX root directory.."), _T("C:\\"), path))
445     {
446         // Test to see if this is a git repository:
447 
448         wcstombs_s(&converted_chars, ascii_path, (wcslen(path) + 1) * 2, path, MAX_PATH);
449 
450         if (git_repository_open(&repo, ascii_path) != 0)
451         {
452             ErrorMsg("The selected directory does not appear to be a valid GUIX repository.", this);
453         }
454         else
455         {
456             git_repository_free(repo);
457 
458             test_project = path;
459             test_project += "\\samples\\demo_guix_calculator\\guix_calculator.gxp";
460 
461             StudioXIni.samples_dir = path;
462             WriteIniInfo();
463 
464             if (FileExists(PATH_TYPE_ABSOLUTE, test_project))
465             {
466                 PopulateRecentProjects(path);
467                 ValidRepo = TRUE;
468             }
469             else
470             {
471                 ErrorMsg("The selected directory doesn't contain the expected GUIX Studio sample projects. Please update your local GUIX repository.", this);
472             }
473         }
474     }
475     git_libgit2_shutdown();
476 
477     if (ValidRepo)
478     {
479        CDialog::OnOK();
480     }
481 
482 }
483 
484 ///////////////////////////////////////////////////////////////////////////////
OnRepoCloneMsgUpdate(WPARAM wParam,LPARAM lParam)485 LRESULT clone_repo_dialog::OnRepoCloneMsgUpdate(WPARAM wParam, LPARAM lParam)
486 {
487     git_clone_info *info = (git_clone_info *)wParam;
488 
489     if (info->stats_updated)
490     {
491         mpProgressScreen->UpdateIndexerProgress(&info->stats);
492     }
493 
494     if (info->checkout_updated)
495     {
496         mpProgressScreen->UpdateCheckoutProgress(info->checkout_completed, info->checkout_total);
497     }
498 
499     if (info->msg_updated)
500     {
501         if (info->msg_type == MSG_TYPE_CLONE)
502         {
503             mpProgressScreen->UpdateGitMessage(info->msg);
504         }
505         else if (info->msg_type == MSG_TYPE_COMPLETE)
506         {
507             CString msg("");
508 
509             if (strnlen(info->msg, sizeof(info->msg)))
510             {
511                 msg = _T("Unable to clone the GUIX repository to your local directory. The git reported error is: ");
512                 msg += info->msg;
513             }
514             CompleteGUIXRepoClone(msg);
515         }
516     }
517 
518     return 0;
519 }
520 
521 ///////////////////////////////////////////////////////////////////////////////
ShowHideChildren(BOOL show)522 void clone_repo_dialog::ShowHideChildren(BOOL show)
523 {
524     GetDlgItem(IDC_CLONE_PROMPT)->ShowWindow(show);
525     GetDlgItem(IDB_CLONE_REPO)->ShowWindow(show);
526     GetDlgItem(IDB_SELECT_DIRECTORY)->ShowWindow(show);
527     GetDlgItem(IDB_EXIT_CLONE_DIALOG)->ShowWindow(show);
528 }
529 
BEGIN_MESSAGE_MAP(git_progress_screen,CDialog)530 BEGIN_MESSAGE_MAP(git_progress_screen, CDialog)
531     ON_WM_CREATE()
532 END_MESSAGE_MAP()
533 
534 ///////////////////////////////////////////////////////////////////////////////
535 git_progress_screen::git_progress_screen(CWnd *parent, CString clone_message):
536     CDialog(git_progress_screen::IDD, parent)
537 {
538     mCloneMessage = clone_message;
539 }
540 
541 
542 ///////////////////////////////////////////////////////////////////////////////
OnInitDialog()543 BOOL git_progress_screen::OnInitDialog()
544 {
545     CDialog::OnInitDialog();
546     CenterWindow();
547 
548     CStatic *CloneMessage = (CStatic *) GetDlgItem(IDC_CLONE_OPERATION);
549     if (CloneMessage)
550     {
551         CloneMessage->SetWindowText(mCloneMessage);
552     }
553 
554     CStatic* prog_field = (CStatic*)GetDlgItem(IDC_GIT_MESSAGE);
555     if (prog_field)
556     {
557         prog_field->SetWindowText(NULL);
558     }
559 
560     prog_field = (CStatic *) GetDlgItem(IDC_OBJECT_COUNT);
561     if (prog_field)
562     {
563         prog_field->SetWindowText(NULL);
564     }
565     prog_field = (CStatic *) GetDlgItem(IDC_GIT_BYTES);
566     if (prog_field)
567     {
568         prog_field->SetWindowText(NULL);
569     }
570     prog_field = (CStatic *) GetDlgItem(IDC_GIT_ERROR);
571     if (prog_field)
572     {
573         prog_field->SetWindowText(NULL);
574     }
575     return TRUE;
576 }
577 
578 ///////////////////////////////////////////////////////////////////////////////
UpdateGitMessage(const char * str)579 void git_progress_screen::UpdateGitMessage(const char *str)
580 {
581     CStatic *git_message = (CStatic *) GetDlgItem(IDC_GIT_MESSAGE);
582     if (git_message)
583     {
584         CString msg(str);
585         git_message->SetWindowText(msg);
586     }
587 }
588 
589 ///////////////////////////////////////////////////////////////////////////////
UpdateIndexerProgress(const git_indexer_progress * stats)590 void git_progress_screen::UpdateIndexerProgress(const git_indexer_progress *stats)
591 {
592     CString stat_message;
593     CStatic *stat_field;
594 
595     stat_field = (CStatic *) GetDlgItem(IDC_OBJECT_COUNT);
596 
597     if (stat_field)
598     {
599         if (stats->received_objects != stats->total_objects)
600         {
601             if (stats->received_objects)
602             {
603                 stat_message.Format(_T("Received %d of %d objects"), stats->received_objects, stats->total_objects);
604             }
605             else
606             {
607                 stat_message.Format(_T("Indexed %d of %d objects"), stats->indexed_objects, stats->total_objects);
608             }
609         }
610         else
611         {
612             stat_message.Format(_T("Indexed %d of %d deltas"), stats->indexed_deltas, stats->total_deltas);
613         }
614         stat_field->SetWindowText(stat_message);
615     }
616 
617     stat_field = (CStatic *) GetDlgItem(IDC_GIT_BYTES);
618     if (stat_field)
619     {
620         stat_message.Format(_T("%d Total Bytes Received"), stats->received_bytes);
621         stat_field->SetWindowTextW(stat_message);
622     }
623 }
624 
625 ///////////////////////////////////////////////////////////////////////////////
UpdateCheckoutProgress(size_t completed,size_t total)626 void git_progress_screen::UpdateCheckoutProgress(size_t completed, size_t total)
627 {
628     CString stat_message;
629     CStatic *stat_field;
630 
631     stat_field = (CStatic *)GetDlgItem(IDC_GIT_ERROR);
632     if (stat_field)
633     {
634         stat_message.Format(_T("Checking out %d of %d files"), completed, total);
635         stat_field->SetWindowTextW(stat_message);
636     }
637 }