Garmin Fleet Management Controller  2.19.0
CCustomFormsDlg.cpp
Go to the documentation of this file.
1 /*********************************************************************
2 *
3 * MODULE NAME:
4 * CCustomFormsDlg.cpp
5 *
6 * Copyright 2013-2015 by Garmin Ltd. or its subsidiaries.
7 *---------------------------------------------------------------------
8 * $NoKeywords$
9 *********************************************************************/
10 #include "stdafx.h"
11 #include "CCustomFormsDlg.h"
12 #include "Event.h"
13 #include "CFileTransferDlg.h"
15 #include "FileStream.h"
16 #include "comdef.h"
17 #include "util.h"
18 #include <regex>
19 
20 #import <msxml6.dll> named_guids
21 
22 using namespace MSXML2;
23 
24 #if( FMI_SUPPORT_A612 )
25 
26 #define MULTIPART_CONTENT_HEADER "Content-Type: multipart/related; boundary=\""
27 
28 // CCustomFormsDlg dialog
29 
30 IMPLEMENT_DYNAMIC(CCustomFormsDlg, CDialog)
31 
32 BEGIN_MESSAGE_MAP(CCustomFormsDlg, CDialog)
33  ON_MESSAGE( WM_EVENT( EVENT_FMI_FORM_DELETED ), OnFormDeleted )
34  ON_MESSAGE( WM_EVENT( EVENT_FMI_FORM_POSITION_CHANGED ), OnFormPositionChanged )
35  ON_MESSAGE( WM_EVENT( EVENT_FMI_FORM_SUBMIT_RECEIVED ), OnFormSubmitReceived )
36 #if( FMI_SUPPORT_A621 )
37  ON_MESSAGE( WM_EVENT( EVENT_FMI_FORM_SHOW_FAILED ), OnFormShowFail )
38  ON_MESSAGE( WM_EVENT( EVENT_FMI_FORM_SHOW_SUCCESS ), OnFormShowSuccess )
39 #endif
40  ON_WM_SIZE()
41 END_MESSAGE_MAP()
42 
43 //----------------------------------------------------------------------
45 //----------------------------------------------------------------------
46 CCustomFormsDlg::CCustomFormsDlg(CWnd* pParent, FmiApplicationLayer & aCom)
47  : CDialog( CCustomFormsDlg::IDD, pParent ),
48  mCom( aCom )
49  {
50  m_templateTransform = new XMLTransformer( _T( "custom-forms\\preview_template.xsl" ) );
51  m_submittedTransform = new XMLTransformer( _T( "custom-forms\\preview_submitted.xsl" ) );
52  }
53 
54 //----------------------------------------------------------------------
56 //----------------------------------------------------------------------
58 {
59  delete m_templateTransform;
60  delete m_submittedTransform;
61 
62  CoUninitialize();
63 }
64 
65 //----------------------------------------------------------------------
68 //----------------------------------------------------------------------
69 void CCustomFormsDlg::DoDataExchange(CDataExchange* pDX)
70  {
71  CDialog::DoDataExchange(pDX);
72  DDX_Control( pDX, IDC_EMBEDDED_BROWSER, m_browser );
73  }
74 
75 //----------------------------------------------------------------------
78 //----------------------------------------------------------------------
80  {
81  CoInitialize(NULL);
82 
83  CDialog::OnInitDialog();
84 
85  _variant_t flags( 0L, VT_I4 );
86  _variant_t target_frame_name( "" );
87  _variant_t post_data( "" );
88  _variant_t headers( "" );
89 
90  // write catalog to file and then display in browser control
91  CFile catalog;
92  CFileException pEx;
93  if ( !catalog.Open( _T( "custom-forms\\catalog.xml" ), CFile::modeCreate | CFile::modeWrite, &pEx ) )
94  {
95  m_browser.Navigate( _T( "about:blank" ), &flags, &target_frame_name, &post_data, &headers );
96  TCHAR msg[256];
97  pEx.GetErrorMessage( msg, 256 );
98  m_browser.WriteContent( CString( msg ) );
99  }
100  else {
101  CString filePath = catalog.GetFilePath();
102  catalog.Close();
103 
104  if ( buildCatalog( filePath ) )
105  {
106  CString url( "file:///" );
107  url += filePath;
108  m_browser.Navigate( url, &flags, &target_frame_name, &post_data, &headers );
109  }
110  }
111 
112  return TRUE;
113  }
114 
115 //----------------------------------------------------------------------
129 //----------------------------------------------------------------------
131  (
132  UINT aType,
133  int aClientWidth,
134  int aClientHeight
135  )
136 {
137  CDialog::OnSize( aType, aClientWidth, aClientHeight );
138 
139  if ( NULL == m_browser.GetSafeHwnd() )
140  {
141  return;
142  }
143 
144  UpdateData();
145 
146  CRect rect;
147  GetClientRect( &rect );
148 
149  m_browser.MoveWindow( rect.left, rect.top, rect.Width(), rect.Height() );
150 }
151 //----------------------------------------------------------------------
153 //----------------------------------------------------------------------
154 LRESULT CCustomFormsDlg::OnFormDeleted( WPARAM wParam, LPARAM )
155  {
156  CString message;
157  message.Format( _T( "The device has reported that form template %d was deleted" ), (int) wParam );
158  MessageBox( message, _T( "Form Template Deleted" ), MB_OK | MB_ICONINFORMATION );
159 
160  return 0;
161  }
162 
163 //----------------------------------------------------------------------
165 //----------------------------------------------------------------------
166 LRESULT CCustomFormsDlg::OnFormPositionChanged( WPARAM wParam, LPARAM lParam)
167  {
168  CString message;
169  message.Format( _T( "The device has reported that form template %d is at position %d" ), (int) wParam, (uint8) lParam );
170  MessageBox( message, _T( "Form Template Position" ), MB_OK | MB_ICONINFORMATION );
171 
172  return 0;
173  }
174 
175 #if( FMI_SUPPORT_A621 )
176 //----------------------------------------------------------------------
178 //----------------------------------------------------------------------
179 LRESULT CCustomFormsDlg::OnFormShowFail( WPARAM wParam, LPARAM lParam)
180  {
181  CString message;
182  message.Format( _T( "Failed to Show form: Form ID Not Present on Device" ));
183  MessageBox( message, _T( "Show Form Failed" ), MB_OK | MB_ICONINFORMATION );
184 
185  return 0;
186  }
187 
188 //----------------------------------------------------------------------
190 //----------------------------------------------------------------------
191 LRESULT CCustomFormsDlg::OnFormShowSuccess ( WPARAM wParam, LPARAM lParam)
192  {
193  CString message;
194  message.Format( _T( "Form successfully displayed on device." ));
195  MessageBox( message, _T( "Show Form Success" ), MB_OK | MB_ICONINFORMATION );
196 
197  return 0;
198  }
199 #endif
200 
201 static BOOL skipTo( CFile & src, const char * boundary )
202  {
203  size_t boundaryLen = strlen( boundary );
204  char * buf = new char[boundaryLen];
205  memset( buf, 0, boundaryLen );
206  int offset = 0;
207  int rLen;
208  while( ( rLen = src.Read( ( void* ) &buf[offset++], 1 ) ) >= 0 )
209  {
210  if( 0 == strncmp( buf, boundary, min( offset, boundaryLen ) ) )
211  {
212  if( offset == boundaryLen )
213  {
214  // read CR+LF
215  src.Read( ( void* ) buf, 1 );
216  if( buf[0] == '\r' )
217  {
218  src.Read( ( void* ) buf, 1 );
219  }
220  delete buf;
221  return TRUE;
222  }
223  }
224  else
225  {
226  memset( buf, 0, boundaryLen );
227  offset = 0;
228  }
229  }
230  delete buf;
231  return FALSE;
232  }
233 
234 static BOOL readUntil( CFile & src, const char * boundary, CFile & dst )
235  {
236  size_t boundaryLen = strlen( boundary );
237  char * buf = new char[boundaryLen];
238  memset( buf, 0, boundaryLen );
239  int offset = 0;
240  int rLen;
241  while( ( rLen = src.Read( ( void* ) &buf[offset++], 1 ) ) >= 0 )
242  {
243  if( 0 == strncmp( buf, boundary, min( offset, boundaryLen ) ) )
244  {
245  if( offset == boundaryLen )
246  {
247  // read CR+LF
248  src.Read( ( void* ) buf, 1 );
249  if( buf[0] == '\r' )
250  {
251  src.Read( ( void* ) buf, 1 );
252  }
253  delete buf;
254  return TRUE;
255  }
256  }
257  else
258  {
259  dst.Write( ( void* ) buf, offset );
260  memset( buf, 0, boundaryLen );
261  offset = 0;
262  }
263  }
264  delete buf;
265  return FALSE;
266  }
267 
268 static BOOL readLine( CFile & src, std::string & dst )
269  {
270  char b[1];
271  BOOL expectLF = FALSE;
272  UINT rLen = 0;
273  while( ( rLen = src.Read( ( void* ) b, 1 ) ) == 1 )
274  {
275  switch( b[0] )
276  {
277  case '\n':
278  return TRUE;
279  case '\r':
280  expectLF = TRUE;
281  break;
282  default:
283  if( expectLF )
284  {
285  // didn't encounter a linefeed when expected, so append the CR
286  dst += '\r';
287  }
288  expectLF = FALSE;
289  dst += b[0];
290  break;
291  }
292  }
293  return FALSE;
294  }
295 
296 //----------------------------------------------------------------------
298 //----------------------------------------------------------------------
299 static void write(CFile * file, char * text)
300  {
301  file->Write( ( const void * )text, ( UINT ) strlen( text ) );
302  }
303 
304 //----------------------------------------------------------------------
306 //----------------------------------------------------------------------
307 static void write(CFile * file, std::string & text)
308  {
309  file->Write( ( const void * )text.c_str(), ( UINT ) text.length() );
310  }
311 
312 static BOOL extractMultiPart( CFile & srcFile, CString dstPath, CStringArray & extractedFiles )
313  {
314  std::string boundary;
315  readLine( srcFile, boundary );
316  // remove trailing quotation mark
317  boundary = boundary.substr( 0, boundary.length() - 1 );
318  if( !skipTo( srcFile, boundary.c_str() ) )
319  {
320  return FALSE;
321  }
322  std::string contentType, other;
323  CString contentID;
324  while( readLine( srcFile, contentType ) )
325  {
326  readLine( srcFile, other ); // Content-ID or empty
327  if( 0 == other.find( "Content-ID:" ) )
328  {
329  contentID = CString( other.substr( 11 ).c_str() );
330  contentID = contentID.Trim();
331  contentID = contentID.Mid( 1, contentID.GetLength() - 2 );
332  readLine( srcFile, other );
333  CFile dstFile;
334  if( !dstFile.Open( dstPath + _T( "\\" ) + contentID, CFile::modeCreate | CFile::modeWrite ) )
335  {
336  return FALSE;
337  }
338  readUntil( srcFile, boundary.c_str(), dstFile );
339  dstFile.Close();
340  extractedFiles.Add( contentID );
341  }
342  else if( 0 == contentType.find( "Content-Type" ) && 0 < contentType.find( "text/xml" ) )
343  {
344  contentID = _T( "main.xml" );
345  CFile dstFile;
346  if( !dstFile.Open( dstPath + _T( "\\" ) + contentID, CFile::modeCreate | CFile::modeWrite ) )
347  {
348  return FALSE;
349  }
350  // regex replace the cids with the actual relative file URIs as lines are extracted
351  std::tr1::regex pattern( "\"cid:(.*)\"" );
352  std::string replace = "\"$1\"";
353  std::string line;
354  while( readLine( srcFile, line ) )
355  {
356  if( 0 == line.compare( boundary ) )
357  {
358  break;
359  }
360  std::string changed = std::tr1::regex_replace( line, pattern, replace );
361  write( &dstFile, changed );
362  write( &dstFile, "\r\n" );
363  line.clear();
364  }
365  dstFile.Close();
366  extractedFiles.Add( contentID );
367  }
368  else
369  {
370  return FALSE;
371  }
372  contentType.clear();
373  other.clear();
374  }
375 
376  return TRUE;
377  }
378 
379 //----------------------------------------------------------------------
381 //----------------------------------------------------------------------
382 LRESULT CCustomFormsDlg::OnFormSubmitReceived( WPARAM, LPARAM )
383  {
384  // attempt to open the file and read the form id and version
385  MSXML2::IXMLDOMDocument2Ptr submitDoc( MSXML2::CLSID_DOMDocument60 );
386 
387  submitDoc->async = VARIANT_FALSE;
388  submitDoc->validateOnParse = VARIANT_FALSE;
389  submitDoc->resolveExternals = VARIANT_FALSE;
390  //unzip if necessary into another file
391  CString tempFile = unzip( SAVE_RECEIVED_FORM_SUBMIT );
392  CFile f;
393  if( !f.Open( tempFile, CFile::modeRead | CFile::typeBinary ) )
394  {
395  MessageBox( _T( "Unable to parse submitted form file" ), _T( "Form Submitted"), MB_ICONERROR | MB_OK );
396  return 0;
397  }
398 
399  const int mpchLen = strlen( MULTIPART_CONTENT_HEADER );
400  char header[sizeof( MULTIPART_CONTENT_HEADER ) + 1];
401  memset( header, 0, sizeof( header ) );
402  UINT rLen = f.Read( header, mpchLen );
403  // it seems reasonable to expect that a plain xml submitted form file would be at least as
404  // long as the multipart content header so anything less would be invalid
405  if( rLen != mpchLen )
406  {
407  f.Close();
408  MessageBox( _T( "Unable to parse submitted form file - unrecognized header" ), _T( "Form Submitted"), MB_ICONERROR | MB_OK );
409  return 0;
410  }
411  CStringArray files;
412  CString srcPath = tempFile.Left( tempFile.ReverseFind( '\\' ) );
413  COleDateTime submitTime = COleDateTime::GetCurrentTime();
414  CString submitTimeStr = submitTime.Format( _T( "%Y-%m-%d_%H.%M.%S" ) );
415  if( 0 == strncmp( header, "<?xml", strlen( "<?xml" ) ) )
416  {
417  f.Close();
418  files.Add( tempFile );
419  }
420  else if( 0 == strncmp( header, MULTIPART_CONTENT_HEADER, mpchLen ) )
421  {
422  if( srcPath.GetLength() > 0 )
423  {
424  srcPath += _T( "\\" );
425  }
426  srcPath += submitTimeStr;
427  if( ERROR_PATH_NOT_FOUND == CreateDirectory( srcPath, NULL) )
428  {
429  MessageBox( _T( "Failed to create temporary folder " ) + srcPath, _T( "Form Submitted" ), MB_OK | MB_ICONERROR );
430  return 0;
431  }
432  extractMultiPart( f, srcPath, files );
433  f.Close();
434  DeleteFile( tempFile );
435  srcPath += _T( "\\" );
436  tempFile = srcPath + files[0];
437  // all the files extracted will be moved to the final destination folder once the form ID is determined
438  }
439  else
440  {
441  f.Close();
442  MessageBox( _T( "Unable to parse submitted form file - unrecognized header" ), _T( "Form Submitted"), MB_ICONERROR | MB_OK );
443  return 0;
444  }
445  VARIANT_BOOL loaded = submitDoc->load( _variant_t( tempFile ) );
446  if ( VARIANT_TRUE != loaded )
447  {
448  if ( submitDoc->parseError )
449  {
450  CString reason = submitDoc->parseError->Getreason();
451  MessageBox( reason, _T( "Form Submitted"), MB_ICONERROR | MB_OK );
452  }
453  else
454  {
455  MessageBox( _T( "Unable to parse submitted form file - XML parse error" ), _T( "Form Submitted"), MB_ICONERROR | MB_OK );
456  }
457  return 0;
458  }
459 
460  MSXML2::IXMLDOMNodePtr formId = submitDoc->selectSingleNode( _bstr_t( "//form/@id" ) );
461  MSXML2::IXMLDOMNodePtr formVersion = submitDoc->selectSingleNode( _bstr_t( "//form/@version" ) );
462  if ( formId == NULL )
463  {
464  MessageBox( _T( "Submitted form file's form element does not contain required attribute 'id'" ), _T( "Form Submitted"), MB_ICONERROR | MB_OK );
465  return 0;
466  }
467  if ( formVersion == NULL)
468  {
469  MessageBox( _T( "Submitted form file's form element does not contain required attribute 'version'" ), _T( "Form Submitted"), MB_ICONERROR | MB_OK );
470  return 0;
471  }
472 
473  //store the file in the appropriate location - same folder as the corresponding form
474  //even if the form file does not exist in the repository
475  CStringArray folders;
476  folders.Add( _T( "custom-forms" ) );
477  folders.Add( _T( "library" ) );
478  folders.Add( formId->Gettext() );
479  folders.Add( formVersion->Gettext() );
480  folders.Add( submitTimeStr );
481  CString destPath;
482  for( int i=0; i<folders.GetCount(); i++ )
483  {
484  destPath += folders[i] + _T( "\\" );
485  if( ERROR_PATH_NOT_FOUND == CreateDirectory( destPath, NULL) )
486  {
487  MessageBox( _T( "Failed to create repository folder " ) + destPath, _T( "Form Submitted" ), MB_OK | MB_ICONERROR );
488  return 0;
489  }
490  }
491  for( int i=0; i<files.GetCount(); i++ )
492  {
493  CString destFile = destPath + files[i];
494  // rename to main.xml in destination folder submitted form was just a regular xml file
495  if( tempFile.Find( files[i] ) >= 0 )
496  {
497  destFile = destPath + _T( "main.xml" );
498  }
499  if( !MoveFile( srcPath + files[i], destFile ) )
500  {
501  MessageBox( _T( "Failed to move submitted form file to repository " ) + destPath, _T( "Form Submitted" ), MB_OK | MB_ICONERROR );
502  return 0;
503  }
504  }
505 
506  refreshCatalog();
507  return 0;
508  }
509 
510 //----------------------------------------------------------------------
512 //----------------------------------------------------------------------
514  {
515  CFile catalog;
516  CFileException pEx;
517  if ( !catalog.Open( _T( "custom-forms\\catalog.xml" ), CFile::modeCreate | CFile::modeWrite, &pEx ) )
518  {
519  return;
520  }
521 
522  CString filePath = catalog.GetFilePath();
523  catalog.Close();
524 
525  if ( buildCatalog( filePath ) )
526  {
527  CString url( "file:///" );
528  url += filePath;
529  url.Replace( '\\', '/' );
530 
531  // refresh the browser if it is currently displaying the old catalog
532  UpdateData();
533  CString browserAddress = CWebBrowser2::URLDecode( m_browser.get_LocationURL() );
534  if ( 0 == url.CompareNoCase( browserAddress ) )
535  {
536  m_browser.Refresh();
537  }
538  }
539  }
540 
541 //----------------------------------------------------------------------
543 //----------------------------------------------------------------------
544 bool CCustomFormsDlg::buildCatalog(LPCTSTR filePath)
545  {
546  MSXML2::IXMLDOMDocument2Ptr doc( MSXML2::CLSID_DOMDocument60 );
547  doc->loadXML( _T( "<catalog></catalog>" ) );
548  MSXML2::IXMLDOMElementPtr root = doc->GetdocumentElement();
549  _variant_t vtObject;
550  vtObject.vt = VT_DISPATCH;
551  vtObject.pdispVal = root;
552  vtObject.pdispVal->AddRef();
553  MSXML2::IXMLDOMProcessingInstructionPtr xmlProc = doc->createProcessingInstruction( "xml", " version='1.0' encoding='UTF-8'" );
554  doc->insertBefore( xmlProc, vtObject );
555  MSXML2::IXMLDOMProcessingInstructionPtr xslProc = doc->createProcessingInstruction( "xml-stylesheet", " type='text/xsl' href='catalog.xsl'" );
556  doc->insertBefore( xslProc, vtObject );
557 
558  CFileFind ff;
559  // search for form folders
560  CStringArray formFolders;
561  BOOL bWorking = ff.FindFile( _T( "custom-forms\\library\\*" ) );
562  while ( bWorking )
563  {
564  bWorking = ff.FindNextFileW();
565  if ( ff.IsDirectory() && !ff.IsDots() )
566  {
567  formFolders.Add( ff.GetFileName() );
568  }
569  }
570  ff.Close();
571 
572  // specify the schema for the form template validator since it isn't in same folder as loaded files (and cache it)
573  MSXML2::IXMLDOMSchemaCollection2Ptr templateSchemas( MSXML2::CLSID_XMLSchemaCache60 );
574  templateSchemas->add( _bstr_t( "" ), _bstr_t( "custom-forms\\form_template.xsd" ) );
575 
576  MSXML2::IXMLDOMDocument2Ptr templateDoc( MSXML2::CLSID_DOMDocument60 );
577  templateDoc->schemas = templateSchemas.GetInterfacePtr();;
578 
579  templateDoc->async = VARIANT_FALSE;
580  templateDoc->validateOnParse = VARIANT_TRUE;
581  templateDoc->resolveExternals = VARIANT_TRUE;
582 
583  // specify the schema for the submitted form validator since it isn't in same folder as loaded files (and cache it)
584  MSXML2::IXMLDOMSchemaCollection2Ptr submitSchemas( MSXML2::CLSID_XMLSchemaCache60 );
585  submitSchemas->add( _bstr_t( "" ), _bstr_t( "custom-forms\\form_submit.xsd" ) );
586 
587  MSXML2::IXMLDOMDocument2Ptr submitDoc( MSXML2::CLSID_DOMDocument60 );
588  submitDoc->schemas = submitSchemas.GetInterfacePtr();;
589 
590  submitDoc->async = VARIANT_FALSE;
591  submitDoc->validateOnParse = VARIANT_TRUE;
592  submitDoc->resolveExternals = VARIANT_TRUE;
593 
594  CString tmp;
595 
596  // search form version folders
597  CStringArray versionFolders;
598  for ( int i=0; i<formFolders.GetCount(); i++ )
599  {
600  MSXML2::IXMLDOMElementPtr formElem = doc->createElement( _T( "form" ) );
601  formElem->setAttribute( _bstr_t( "id" ), _variant_t( formFolders[i] ) );
602  root->appendChild( formElem );
603 
604  bWorking = ff.FindFile( _T( "custom-forms\\library\\" ) + formFolders[i] + _T( "\\*" ) );
605  while ( bWorking )
606  {
607  bWorking = ff.FindNextFileW();
608  if ( ff.IsDirectory() && !ff.IsDots() )
609  {
610  versionFolders.Add( ff.GetFileName() );
611  }
612  }
613  ff.Close();
614  for ( int j=0; j<versionFolders.GetCount(); j++ )
615  {
616  CString versionFolder = _T( "custom-forms\\library\\" ) + formFolders[i] + _T( "\\" ) + versionFolders[j];
617  MSXML2::IXMLDOMElementPtr versionElem = doc->createElement( _T( "version" ) );
618  versionElem->setAttribute( _bstr_t( "id" ), _variant_t( versionFolders[j] ) );
619  formElem->appendChild( versionElem );
620 
621  // if form file does exist, add its full path to the catalog and validate it
622  CFile formFile;
623  if ( formFile.Open( versionFolder + _T( "\\form.xml" ), CFile::modeRead ) )
624  {
625  CString formFilePath = formFile.GetFilePath();
626  formFile.Close();
627  VARIANT_BOOL valid = templateDoc->load( _variant_t( formFilePath ) );
628  MSXML2::IXMLDOMElementPtr templateElem = doc->createElement( _T( "template" ) );
629  versionElem->appendChild( templateElem );
630  if ( VARIANT_TRUE != valid )
631  {
632  MSXML2::IXMLDOMElementPtr parseErrorElem = doc->createElement( _T( "parseError" ) );
633  parseErrorElem->setAttribute( _bstr_t( "line" ), _variant_t( templateDoc->parseError->Getline() ) );
634  parseErrorElem->setAttribute( _bstr_t( "char" ), _variant_t( templateDoc->parseError->Getlinepos() ) );
635  parseErrorElem->appendChild( doc->createCDATASection( templateDoc->parseError->Getreason() ) );
636  templateElem->appendChild( parseErrorElem );
637  }
638  MSXML2::IXMLDOMElementPtr urlElem = doc->createElement( _T( "url" ) );
639  formFilePath.Replace( '\\', '/' );
640  urlElem->Puttext( _bstr_t( _T( "file:///" ) + formFilePath ) );
641  templateElem->appendChild( urlElem );
642  }
643  // validate all submitted form files while adding to catalog
644  bWorking = ff.FindFile( versionFolder + _T( "\\*_*" ) );
645  CFileStatus fileStatus;
646  while( bWorking ) {
647  bWorking = ff.FindNextFileW();
648  CString submitPath = ff.GetFilePath();
649  CString subPath = ff.GetFileName();
650  if( ff.IsDirectory() )
651  {
652  submitPath += _T( "\\main.xml" );
653  subPath += _T( "\\main.xml" );
654  if( !CFile::GetStatus( submitPath, fileStatus ) )
655  {
656  DWORD lastErr = GetLastError();
657  if( lastErr == ERROR_FILE_NOT_FOUND || lastErr == ERROR_PATH_NOT_FOUND )
658  {
659  continue;
660  }
661  }
662  }
663  else if( submitPath.Find( _T( ".xml" ) ) < 0 )
664  {
665  continue;
666  }
667  MSXML2::IXMLDOMElementPtr submitElem = doc->createElement( _T( "submit" ) );
668  versionElem->appendChild( submitElem );
669  VARIANT_BOOL valid = submitDoc->load( _variant_t( submitPath ) );
670  if ( VARIANT_TRUE != valid )
671  {
672  MSXML2::IXMLDOMElementPtr parseErrorElem = doc->createElement( _T( "parseError" ) );
673  parseErrorElem->setAttribute( _bstr_t( "line" ), _variant_t( submitDoc->parseError->Getline() ) );
674  parseErrorElem->setAttribute( _bstr_t( "char" ), _variant_t( submitDoc->parseError->Getlinepos() ) );
675  parseErrorElem->appendChild( doc->createCDATASection( submitDoc->parseError->Getreason() ) );
676  submitElem->appendChild( parseErrorElem );
677  }
678  MSXML2::IXMLDOMElementPtr urlElem = doc->createElement( _T( "url" ) );
679  subPath.Replace( '\\', '/' );
680  urlElem->Puttext( _bstr_t( subPath ) );
681  submitElem->appendChild( urlElem );
682  MSXML2::IXMLDOMElementPtr rootElem = doc->createElement( _T( "root" ) );
683  subPath = ff.GetRoot();
684  subPath.Replace( '\\', '/' );
685  rootElem->Puttext( _bstr_t( _T( "file:///" ) + subPath ) );
686  submitElem->appendChild( rootElem );
687  }
688  ff.Close();
689 
690  }
691  versionFolders.RemoveAll();
692  }
693  HRESULT hr = doc->save( _variant_t( filePath ) );
694  return SUCCEEDED( hr );
695  }
696 
697 //----------------------------------------------------------------------
699 //----------------------------------------------------------------------
700 void CCustomFormsDlg::transform( LPCTSTR src, XMLTransformer * transformer, CMapStringToString & params )
701  {
702  // link the m_browser member variable
703  UpdateData();
704  CFile tmpFile;
705  if ( !tmpFile.Open( _T( "custom-forms\\tmp.html" ), CFile::modeCreate | CFile::modeWrite ) ) {
706  m_browser.WriteContent( CString( "unable to open temporary file" ) );
707  return;
708  }
709 
710  // remember the full path to the temp file for navigation
711  CString url( "file://" );
712  url += tmpFile.GetFilePath();
713  tmpFile.Close();
714 
715  // open a FileStream to the temp file to hold the transformation output
716  FileStream *fileStream;
717  FileStream::OpenFile( _T( "custom-forms\\tmp.html" ), ( IStream** ) &fileStream, true );
718  transformer->transform( src, fileStream, params );
719  delete fileStream;
720 
721  _variant_t flags( 0L, VT_I4 );
722  _variant_t target_frame_name( "" );
723  _variant_t post_data( "" );
724  _variant_t headers( "" );
725 
726  m_browser.Navigate(url, &flags, &target_frame_name, &post_data, &headers );
727  }
728 
729 // CCustomFormsDlg message handlers
730 BEGIN_EVENTSINK_MAP(CCustomFormsDlg, CDialog)
731  ON_EVENT(CCustomFormsDlg, IDC_EMBEDDED_BROWSER, 250, CCustomFormsDlg::OnBeforeNavigate2, VTS_DISPATCH VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PBOOL)
732 END_EVENTSINK_MAP()
733 
734 static BOOL cleanupParentFolder( CString & child, int depth )
735  {
736  CString folder = child.Left( child.ReverseFind( '\\' ) );
737  CFileFind ff;
738  BOOL bHasMore = ff.FindFile( folder + _T( "\\*" ) );
739  if( !bHasMore )
740  {
741  return FALSE;
742  }
743 
744  while ( bHasMore )
745  {
746  bHasMore = ff.FindNextFileW();
747  if ( !ff.IsDots() )
748  {
749  ff.Close();
750  return FALSE;
751  }
752  }
753 
754  ff.Close();
755 
756  if( !RemoveDirectory( folder ) )
757  {
758  return FALSE;
759  }
760 
761  if( depth > 1 )
762  {
763  return cleanupParentFolder( folder, depth-1 );
764  }
765 
766  return TRUE;
767  }
768 
769 //----------------------------------------------------------------------
771 //----------------------------------------------------------------------
772 void CCustomFormsDlg::OnBeforeNavigate2(LPDISPATCH, VARIANT* URL, VARIANT*, VARIANT*, VARIANT*, VARIANT*, BOOL* Cancel)
773  {
774  if ( URL->vt != VT_BSTR )
775  {
776  return;
777  }
778 
779  CString urlStr = URL->bstrVal;
780  // manually transform form templates and submitted forms since the XSL templates do not know where the
781  // supporting resources are located
782  if ( urlStr.Find( _T( "template:" ) ) == 0)
783  {
784  // take over
785  *Cancel = TRUE;
786 
787  // override relative path param defaults in the template (which were set for debugging purposes)
788  CMapStringToString params;
789  // resources are relative to the temp file
790  params.SetAt( _T( "resources" ), _T( "" ) );
791  CString src = CWebBrowser2::URLDecode( urlStr.Right( urlStr.GetLength() - (int) strlen( "template:" ) ) );
792  transform( src, m_templateTransform, params );
793  }
794  else if ( urlStr.Find( _T( "submitted:") ) == 0 )
795  {
796  // take over
797  *Cancel = TRUE;
798 
799  CString submittedFile = CWebBrowser2::URLDecode( urlStr.Right( urlStr.GetLength() - (int) strlen( "submitted:" ) ) );
800 
801  // override relative path param defaults in the template (which were set for debugging purposes)
802  CMapStringToString params;
803  // resources are relative to the temp file
804  params.SetAt( _T( "resources" ), _T( "" ) );
805  // the form template file is in the same folder as the submitted form file
806  CString formPath = submittedFile.Left( submittedFile.ReverseFind( '?' ) );
807  params.SetAt( _T( "formPath" ), formPath );
808  submittedFile.Replace( '?', '/' );
809  CString relPath = submittedFile.Left( submittedFile.ReverseFind( '/' ) + 1 );
810  params.SetAt( _T( "relPath" ), relPath );
811  transform( submittedFile, m_submittedTransform, params );
812  }
813  else if ( urlStr.Find( _T( "app:") ) == 0 )
814  {
815  // take over
816  *Cancel = TRUE;
817 
818  CString command = urlStr.Right( urlStr.GetLength() - (int) strlen( "app:" ) );
819  CString procedure = command.Left( command.Find( '(' ) );
820  CString args = command.Mid( procedure.GetLength() + 1, command.ReverseFind( ')') - ( procedure.GetLength() + 1 ) );
821 
822  TRACE2( "app command '%s' executed with args '%s'\r\n", procedure, args );
823 
824  CStringArray arguments;
825  //split arguments separated by comma
826  int curPos = 0;
827  CString token = args.Tokenize( _T( "," ), curPos );
828  while ( token != _T( "" ) )
829  {
830  if ( 0 == token.Find( '"' ) )
831  {
832  if ( token.GetLength()-1 == token.ReverseFind( '"' ) )
833  {
834  token = token.Mid( 1, token.GetLength() - 2 );
835  token = CWebBrowser2::URLDecode( token );
836  }
837  else {
838  token += _T( "," );
839  token += args.Tokenize( _T( "," ), curPos );
840  continue;
841  }
842  }
843  arguments.Add( token );
844  token = args.Tokenize( _T( "," ), curPos );
845  }
846 
847  if ( procedure == _T( "importForm" ) )
848  {
849  importForm();
850  }
851  else if ( procedure == _T( "deleteCustom" ) )
852  {
853  deleteFromDevice( _ttoi( arguments[0] ) );
854  }
855  else if ( procedure == _T( "changePosition" ) )
856  {
857  moveOnDevice( _ttoi( arguments[0] ), (uint8) _ttoi( arguments[1] ) );
858  }
859  else if ( procedure == _T( "requestPosition" ) )
860  {
861  requestPosition( _ttoi( arguments[0] ) );
862  }
863 #if( FMI_SUPPORT_A621 )
864  else if ( procedure == _T( "showForm" ) )
865  {
866  showForm( _ttoi( arguments[0] ) );
867  }
868 #endif
869  else if ( procedure == _T( "sendForm" ) )
870  {
871  CString formFile = arguments[0];
872  formFile.Replace( '/', '\\');
873  sendToDevice( formFile.Right( formFile.GetLength() - (int) strlen( "file:///" ) ) );
874  }
875  else if ( procedure == _T( "removeForm" ) )
876  {
877  CString formFile = arguments[0];
878  formFile.Replace( '/', '\\');
879  CString filename = formFile.Right( formFile.GetLength() - (int) strlen( "file:///" ) );
880  if ( DeleteFile( filename ) )
881  {
882  cleanupParentFolder( filename, 2 ); // check parent folder (form version) and its parent folder (form id)
883  refreshCatalog();
884  }
885  else {
886  CString message;
887  message.Format( _T( "Unable to remove form. Error code: %d" ), GetLastError() );
888  MessageBox( message, _T( "Remove form" ), MB_OK | MB_ICONERROR );
889  }
890  }
891  }
892  }
893 
895  {
896  TCHAR workingDirectory[200];
897  // opening a file in another directory changes the current
898  // directory, which will cause problems because the log and data
899  // files are opened relative to the current directory. So, get the
900  // directory now so that it can be restored when the user is done
901  // picking a file.
902  DWORD returnValue = GetCurrentDirectory( 200, workingDirectory );
903  if( returnValue == 0 || returnValue > 200 )
904  {
905  MessageBox( _T( "Unable to get current directory" ), _T( "Import Form Template" ), MB_OK | MB_ICONERROR );
906  return FALSE;
907  }
908 
909  CFileDialog dlg
910  (
911  TRUE,
912  _T( "xml" ),
913  NULL,
914  OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
915  _T( "XML Files (*.xml)|*.xml||" )
916  );
917 
918  if( dlg.DoModal() == IDOK )
919  {
920  // restore working directory
921  SetCurrentDirectory( workingDirectory );
922 
923  // specify the schema for the form template validator since it isn't in same folder as loaded files (and cache it)
924  MSXML2::IXMLDOMSchemaCollection2Ptr templateSchemas( MSXML2::CLSID_XMLSchemaCache60 );
925  templateSchemas->add( _bstr_t( "" ), _bstr_t( "custom-forms\\form_template.xsd" ) );
926 
927  MSXML2::IXMLDOMDocument2Ptr templateDoc( MSXML2::CLSID_DOMDocument60 );
928  templateDoc->schemas = templateSchemas.GetInterfacePtr();;
929 
930  templateDoc->async = VARIANT_FALSE;
931  // validate on load
932  templateDoc->validateOnParse = VARIANT_TRUE;
933  templateDoc->resolveExternals = VARIANT_TRUE;
934 
935  VARIANT_BOOL valid = templateDoc->load( _variant_t( dlg.GetPathName() ) );
936  if ( VARIANT_TRUE != valid )
937  {
938  CString text = templateDoc->parseError->Getreason();
939  CString message;
940  message.Format( _T( "Form validation failed.\nLine %d, char %d: %s \nDo you want to import anyway?" ),
941  templateDoc->parseError->Getline(), templateDoc->parseError->Getlinepos(), text );
942  if (IDNO == MessageBox(
943  message,
944  _T( "Import Form Template" ),
945  MB_YESNO | MB_ICONWARNING ) )
946  {
947  return FALSE;
948  }
949  // reload without validation
950  templateDoc->validateOnParse = VARIANT_FALSE;
951  templateDoc->load( _variant_t( dlg.GetPathName() ) );
952  }
953 
954  MSXML2::IXMLDOMNodePtr formId = templateDoc->selectSingleNode( _bstr_t( "//form/@id" ) );
955  MSXML2::IXMLDOMNodePtr formVersion = templateDoc->selectSingleNode( _bstr_t( "//form/@version" ) );
956  MSXML2::IXMLDOMNodePtr formTitle = templateDoc->selectSingleNode( _bstr_t( "//form/title" ) );
957  CStringArray messages;
958  if ( formId == NULL )
959  {
960  messages.Add( _T( "form element does not contain required attribute 'id'" ) );
961  }
962  if ( formVersion == NULL)
963  {
964  messages.Add( _T( "form element does not contain required attribute 'version'" ) );
965  }
966  if ( formTitle == NULL)
967  {
968  messages.Add( _T( "form element does not contain required child element 'title'" ) );
969  }
970  if ( messages.GetCount() > 0 )
971  {
972  CString message( "Severe error importing form template:" );
973  for ( int i=0; i<messages.GetCount(); i++ )
974  {
975  message += _T( "\n" );
976  message += messages[i];
977  }
978  MessageBox( message, _T( "Import Form Template" ), MB_OK | MB_ICONERROR );
979  return FALSE;
980  }
981 
982  // create folder structure
983  CStringArray folders;
984  folders.Add( _T( "custom-forms" ) );
985  folders.Add( _T( "library" ) );
986  folders.Add( formId->Gettext() );
987  folders.Add( formVersion->Gettext() );
988  CString path;
989  for ( int i=0; i<folders.GetCount(); i++ )
990  {
991  path += folders[i] + _T( "\\" );
992  if ( ERROR_PATH_NOT_FOUND == CreateDirectory( path, NULL) )
993  {
994  MessageBox( _T( "Failed to create repository folder " ) + path, _T( "Import Form Template" ), MB_OK | MB_ICONERROR );
995  return FALSE;
996  }
997  }
998 
999  CString targetPath = path + _T( "\\form.xml" );
1000 
1001  CFileStatus existingFile;
1002  if ( CFile::GetStatus(targetPath, existingFile) )
1003  {
1004  if ( IDOK != MessageBox(
1005  _T( "A template file already exists for this form id and version\nand will be overwritten if you continue." ),
1006  _T( "Import Form Template" ),
1007  MB_OKCANCEL | MB_ICONWARNING ) )
1008  {
1009  return FALSE;
1010  }
1011  }
1012 
1013  // copy file to correct folder
1014  if ( CopyFile( dlg.GetPathName(), targetPath, FALSE ) )
1015  {
1016  refreshCatalog();
1017  return TRUE;
1018  }
1019  return FALSE;
1020  }
1021 
1022  //restore working directory
1023  SetCurrentDirectory( workingDirectory );
1024  return FALSE;
1025  }
1026 
1027 BOOL CCustomFormsDlg::sendToDevice( LPCTSTR filename )
1028  {
1029  CFileStatus fileStatus;
1030  if ( !CFile::GetStatus( filename, fileStatus ) )
1031  {
1032  CString message;
1033  message.Format( _T( "Form file %s does not exist." ), filename );
1034  MessageBox( message, _T( "Send Form Template to Device" ), MB_OK | MB_ICONERROR );
1035  return FALSE;
1036  }
1037 
1038  // validate on load
1039 
1040  // specify the schema for the form template validator since it isn't in same folder as loaded files (and cache it)
1041  MSXML2::IXMLDOMSchemaCollection2Ptr templateSchemas( MSXML2::CLSID_XMLSchemaCache60 );
1042  templateSchemas->add( _bstr_t( "" ), _bstr_t( "custom-forms\\form_template.xsd" ) );
1043 
1044  MSXML2::IXMLDOMDocument2Ptr templateDoc( MSXML2::CLSID_DOMDocument60 );
1045  templateDoc->schemas = templateSchemas.GetInterfacePtr();;
1046 
1047  templateDoc->async = VARIANT_FALSE;
1048  templateDoc->validateOnParse = VARIANT_TRUE;
1049  templateDoc->resolveExternals = VARIANT_TRUE;
1050 
1051  VARIANT_BOOL valid = templateDoc->load( _variant_t( filename ) );
1052  if ( VARIANT_TRUE != valid )
1053  {
1054  if (IDNO == MessageBox(
1055  _T( "Form validation failed.\nDo you want to send the file anyway?" ),
1056  _T( "Send Form Template to Device" ),
1057  MB_YESNO | MB_ICONWARNING ) )
1058  {
1059  return FALSE;
1060  }
1061  // reload without validation
1062  templateDoc->validateOnParse = VARIANT_FALSE;
1063  templateDoc->load( _variant_t( filename ) );
1064  }
1065 
1066  MSXML2::IXMLDOMNodePtr formVersion = templateDoc->selectSingleNode( _bstr_t( "//form/@version" ) );
1067  if ( formVersion == NULL)
1068  {
1069  MessageBox(
1070  _T( "Form template does not specify a version attribute, which is necessary for file transport." ),
1071  _T( "Send Form Template to Device" ),
1072  MB_OK | MB_ICONERROR );
1073  return FALSE;
1074  }
1075 
1076  char file[MAX_PATH+1];
1077  char versionString[35];
1078  uint8 version[16];
1079  uint8 versionLength;
1080 
1081  memset( version, 0, sizeof( version ) );
1082  WideCharToMultiByte( mCom.mClientCodepage, 0, formVersion->Gettext(), -1, versionString, 34, NULL, NULL );
1083  versionString[34] = '\0';
1084  versionLength = (uint8)minval( 16, strlen( versionString ) );
1085  memmove( version, versionString, versionLength );
1086 
1087  WideCharToMultiByte( CP_ACP, 0, filename, -1, file, MAX_PATH+1, NULL, NULL );
1088  file[MAX_PATH] = '\0';
1089 
1090  mCom.sendFile( file, versionLength, version, (uint8) FMI_FILE_TYPE_CUSTOM_FORMS, TRUE );
1091 
1092  CFileTransferProgressDlg sending_dlg( this, mCom );
1093  sending_dlg.DoModal();
1094 
1095  return TRUE;
1096  }
1097 
1099  {
1100  uint32 message = id;
1101 
1102  mCom.sendFmiPacket( FMI_CUSTOM_FORM_DEL_REQUEST, (uint8*)&message, sizeof( message ) );
1103 
1104  return TRUE;
1105  }
1106 
1107 BOOL CCustomFormsDlg::moveOnDevice(int id, uint8 newPosition)
1108  {
1109  custom_form_move_type message;
1110  memset( &message, 0, sizeof( message ) );
1111  message.form_id = id;
1112  message.new_position = newPosition;
1113 
1114  mCom.sendFmiPacket( FMI_CUSTOM_FORM_MOVE_REQUEST, (uint8*)&message, sizeof( message ) );
1115 
1116  return TRUE;
1117  }
1118 
1120  {
1121  uint32 message = id;
1122 
1123  mCom.sendFmiPacket( FMI_CUSTOM_FORM_GET_POS_REQUEST, (uint8*)&message, sizeof( message ) );
1124 
1125  return TRUE;
1126  }
1127 
1128 #if( FMI_SUPPORT_A621 )
1130  {
1131  uint32 message = id;
1132 
1133  mCom.sendFmiPacket( FMI_CUSTOM_FORM_SHOW_REQUEST, (uint8*)&message, sizeof( message ) );
1134 
1135  return TRUE;
1136  }
1137 #endif
1138 #endif
Modal dialog displaying the status of the file transfer.
BOOL deleteFromDevice(int formId)
afx_msg LRESULT OnFormShowSuccess(WPARAM, LPARAM)
This function handles form show success event from device.
static BOOL extractMultiPart(CFile &srcFile, CString dstPath, CStringArray &extractedFiles)
afx_msg void OnSize(UINT aType, int aClientWidth, int aClientHeight)
Called after the dialog is resized; repositions the contents of the display.
void refreshCatalog()
This function reconstructs catalog file and redisplays it.
#define FALSE
Definition: garmin_types.h:46
Browser-based container for listing the Custom Forms library.
BOOL showForm(int formId)
afx_msg LRESULT OnFormDeleted(WPARAM, LPARAM)
This function handles form delete ACK event from device.
#define TRUE
Definition: garmin_types.h:45
BOOL sendToDevice(LPCTSTR filename)
File-based implementation of IStream interface.
Definition: FileStream.h:18
void transform(LPCTSTR src, XMLTransformer *transformer, CMapStringToString &params)
Transform specified XML document and display in web view.
static BOOL skipTo(CFile &src, const char *boundary)
#define minval(_x, _y)
The smaller of _x and _y.
Definition: util_macros.h:95
Data type for Custom Forms Packet ID (0X1202) from client to server.
Definition: fmi.h:1923
#define IDC_EMBEDDED_BROWSER
Definition: resource.h:432
static void write(CFile *file, char *text)
This function is a helper for writing text to a file.
Serial communication controller for Garmin and FMI packets.
CString unzip(char *original)
This function will uncompress a file into a new file.
Definition: util.cpp:741
afx_msg LRESULT OnFormShowFail(WPARAM, LPARAM)
This function handles form show failure event from device.
static BOOL cleanupParentFolder(CString &child, int depth)
#define SAVE_RECEIVED_FORM_SUBMIT
File name of the saved custom form submission file.
static BOOL readUntil(CFile &src, const char *boundary, CFile &dst)
virtual void DoDataExchange(CDataExchange *pDX)
Perform dialog data exchange and validation.
Utility class for simplifying XSL transformations.
static CString URLDecode(CString sIn)
translate escape sequences into their original characters
unsigned char uint8
8-bit unsigned integer
Definition: garmin_types.h:62
#define MULTIPART_CONTENT_HEADER
static HRESULT OpenFile(LPCWSTR pName, IStream **ppStream, bool fWrite)
Definition: FileStream.cpp:32
bool buildCatalog(LPCTSTR filename)
Construct a catalog document based on the library structure.
virtual ~CCustomFormsDlg()
Destructor.
afx_msg LRESULT OnFormPositionChanged(WPARAM, LPARAM)
This function handles form position report event from device.
BOOL transform(LPCTSTR src, IStream *dst, CMapStringToString &params)
transform an XML document
static BOOL readLine(CFile &src, std::string &dst)
BOOL requestPosition(int formId)
#define WM_EVENT(_event)
Translation from an application event to the corresponding Windows message.
void OnBeforeNavigate2(LPDISPATCH pDisp, VARIANT *URL, VARIANT *Flags, VARIANT *TargetFrameName, VARIANT *PostData, VARIANT *Headers, BOOL *Cancel)
This catches navigation attempts for the purpose of handling application-specific navigation...
uint32 new_position
Definition: fmi.h:1926
unsigned long int uint32
32-bit unsigned integer
Definition: garmin_types.h:66
BOOL moveOnDevice(int formId, uint8 newPosition)
afx_msg LRESULT OnFormSubmitReceived(WPARAM, LPARAM)
This function handles completed form submit event from device.
BOOL OnInitDialog()
This function is called when the window is created.