00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "StdAfx.h"
00023 #include "Shareaza.h"
00024 #include "Settings.h"
00025 #include "Network.h"
00026 #include "Library.h"
00027 #include "SharedFile.h"
00028 #include "Transfers.h"
00029 #include "Downloads.h"
00030 #include "Download.h"
00031 #include "ShareazaURL.h"
00032 #include "HttpRequest.h"
00033 #include "DlgTorrentSeed.h"
00034 #include "WndMain.h"
00035 #include "WndDownloads.h"
00036 #include "DlgHelp.h"
00037
00038 #include "LibraryHistory.h"
00039
00040 IMPLEMENT_DYNAMIC(CTorrentSeedDlg, CSkinDialog)
00041
00042 BEGIN_MESSAGE_MAP(CTorrentSeedDlg, CSkinDialog)
00043 ON_BN_CLICKED(IDC_DOWNLOAD, OnDownload)
00044 ON_BN_CLICKED(IDC_SEED, OnSeed)
00045 ON_WM_DESTROY()
00046 ON_WM_TIMER()
00047 END_MESSAGE_MAP()
00048
00049 #define BUFFER_SIZE (2*1024*1024)
00050
00051
00053
00054
00055 CTorrentSeedDlg::CTorrentSeedDlg(LPCTSTR pszTorrent, BOOL bForceSeed, CWnd* pParent) : CSkinDialog( CTorrentSeedDlg::IDD, pParent )
00056 {
00057 m_hThread = NULL;
00058 m_sTorrent = pszTorrent;
00059 m_bForceSeed = bForceSeed;
00060 }
00061
00062 CTorrentSeedDlg::~CTorrentSeedDlg()
00063 {
00064 ASSERT( m_hThread == NULL );
00065 }
00066
00067 void CTorrentSeedDlg::DoDataExchange(CDataExchange* pDX)
00068 {
00069 CDialog::DoDataExchange( pDX );
00070 DDX_Control( pDX, IDC_DOWNLOAD, m_wndDownload );
00071 DDX_Control( pDX, IDC_SEED, m_wndSeed );
00072 DDX_Control( pDX, IDC_PROGRESS, m_wndProgress );
00073 }
00074
00076
00077
00078 BOOL CTorrentSeedDlg::OnInitDialog()
00079 {
00080 CSkinDialog::OnInitDialog();
00081
00082 SkinMe( NULL, IDR_MAINFRAME );
00083
00084 if ( theApp.m_bRTL ) m_wndProgress.ModifyStyleEx( WS_EX_LAYOUTRTL, 0, 0 );
00085 m_wndProgress.SetRange( 0, 1000 );
00086 m_wndProgress.SetPos( 0 );
00087
00088 if ( m_bForceSeed )
00089 {
00090 m_wndDownload.EnableWindow( FALSE );
00091 if ( Settings.BitTorrent.AutoSeed ) PostMessage( WM_TIMER, 4 );
00092
00093 }
00094
00095
00096 return TRUE;
00097 }
00098
00099 void CTorrentSeedDlg::OnDownload()
00100 {
00101 CWnd* pWnd = AfxGetMainWnd();
00102 CBTInfo* pTorrent = new CBTInfo();
00103
00104 if ( pTorrent->LoadTorrentFile( m_sTorrent ) )
00105 {
00106 if ( pTorrent->HasEncodingError() )
00107 {
00108 CHelpDlg::Show( _T("GeneralHelp.BadTorrentEncoding") );
00109 }
00110
00111 CShareazaURL* pURL = new CShareazaURL( pTorrent );
00112
00113 CLibraryFile* pFile;
00114
00115
00116 CSingleLock oLibraryLock( &Library.m_pSection, TRUE );
00117 if ( ( pURL->m_bSHA1 && ( pFile = LibraryMaps.LookupFileBySHA1( &pURL->m_pSHA1 ) ) ) ||
00118 ( pURL->m_bED2K && ( pFile = LibraryMaps.LookupFileByED2K( &pURL->m_pED2K ) ) ) )
00119 {
00120 CString strFormat, strMessage;
00121 LoadString( strFormat, IDS_URL_ALREADY_HAVE );
00122 strMessage.Format( strFormat, (LPCTSTR)pFile->m_sName );
00123 oLibraryLock.Unlock();
00124
00125 if ( AfxMessageBox( strMessage, MB_ICONINFORMATION|MB_YESNOCANCEL|MB_DEFBUTTON2 ) == IDNO )
00126 {
00127 delete pURL;
00128 EndDialog( IDOK );
00129 return;
00130 }
00131 }
00132 else
00133 {
00134 oLibraryLock.Unlock();
00135 }
00136
00137 CDownload* pDownload = Downloads.Add( pURL );
00138
00139
00140 delete pURL;
00141
00142 if ( pDownload == NULL )
00143 {
00144 EndDialog( IDOK );
00145 return;
00146 }
00147
00148 if ( ( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) == 0 )
00149 {
00150 if ( ! Network.IsWellConnected() ) Network.Connect( TRUE );
00151 }
00152
00153 CMainWnd* pMainWnd = (CMainWnd*)AfxGetMainWnd();
00154 pMainWnd->m_pWindows.Open( RUNTIME_CLASS(CDownloadsWnd) );
00155
00156 if ( Settings.Downloads.ShowMonitorURLs )
00157 {
00158 CSingleLock pTransfersLock( &Transfers.m_pSection, TRUE );
00159 if ( Downloads.Check( pDownload ) ) pDownload->ShowMonitor( &pTransfersLock );
00160 }
00161
00162 EndDialog( IDOK );
00163 return;
00164 }
00165
00166 delete pTorrent;
00167 theApp.Message( MSG_ERROR, IDS_BT_PREFETCH_ERROR, (LPCTSTR)m_sTorrent );
00168 EndDialog( IDOK );
00169 }
00170
00171 void CTorrentSeedDlg::OnSeed()
00172 {
00173 m_wndDownload.EnableWindow( FALSE );
00174 m_wndSeed.EnableWindow( FALSE );
00175 m_bCancel = FALSE;
00176
00177 if ( m_pInfo.LoadTorrentFile( m_sTorrent ) )
00178 {
00179 if ( m_pInfo.HasEncodingError() )
00180 {
00181 CHelpDlg::Show( _T("GeneralHelp.BadTorrentEncoding") );
00182 if ( ! Settings.BitTorrent.TorrentIgnoreErrors )
00183 {
00184 m_bCancel = TRUE;
00185 return;
00186 }
00187 }
00188
00189 if ( Downloads.FindByBTH( &m_pInfo.m_pInfoSHA1 ) == NULL )
00190 {
00191
00192 if ( ! Network.IsConnected() ) Network.Connect();
00193
00194
00195 CSingleLock pLock( &Library.m_pSection );
00196 if ( pLock.Lock( 250 ) )
00197 {
00198 LibraryHistory.LastSeededTorrent.m_sName = m_pInfo.m_sName.Left( 40 );
00199 LibraryHistory.LastSeededTorrent.m_sPath = m_sTorrent;
00200 LibraryHistory.LastSeededTorrent.m_tLastSeeded = time( NULL );
00201
00202
00203 if ( LibraryHistory.LastSeededTorrent.m_pBTH != m_pInfo.m_pInfoSHA1 )
00204 {
00205 LibraryHistory.LastSeededTorrent.m_nUploaded = 0;
00206 LibraryHistory.LastSeededTorrent.m_nDownloaded = 0;
00207 LibraryHistory.LastSeededTorrent.m_pBTH = m_pInfo.m_pInfoSHA1;
00208 }
00209 }
00210
00211
00212 m_hThread = AfxBeginThread( ThreadStart, this,
00213 THREAD_PRIORITY_NORMAL )->m_hThread;
00214 }
00215 else
00216 {
00217 CString strFormat, strMessage;
00218 LoadString(strFormat, IDS_BT_SEED_ALREADY );
00219 strMessage.Format( strFormat, (LPCTSTR)m_pInfo.m_sName );
00220 AfxMessageBox( strMessage, MB_ICONEXCLAMATION );
00221 EndDialog( IDOK );
00222 }
00223 }
00224 else
00225 {
00226
00227 CString strFormat, strMessage;
00228 LoadString(strFormat, IDS_BT_SEED_PARSE_ERROR );
00229 strMessage.Format( strFormat, (LPCTSTR)m_sTorrent );
00230 AfxMessageBox( strMessage, MB_ICONEXCLAMATION );
00231 EndDialog( IDOK );
00232 }
00233 }
00234
00235 void CTorrentSeedDlg::OnCancel()
00236 {
00237 if ( m_wndDownload.IsWindowEnabled() || m_bCancel )
00238 {
00239 CSkinDialog::OnCancel();
00240 }
00241 else
00242 {
00243 m_bCancel = TRUE;
00244 }
00245 }
00246
00247 void CTorrentSeedDlg::OnDestroy()
00248 {
00249 if ( m_hThread != NULL )
00250 {
00251 m_bCancel = TRUE;
00252 CHttpRequest::CloseThread( &m_hThread, _T("CTorrentSeedDlg") );
00253 ASSERT( m_hThread == NULL );
00254 }
00255
00256 CSkinDialog::OnDestroy();
00257 }
00258
00259 void CTorrentSeedDlg::OnTimer(UINT nIDEvent)
00260 {
00261 CSkinDialog::OnTimer( nIDEvent );
00262
00263 if ( nIDEvent == 1 )
00264 {
00265 EndDialog( IDOK );
00266 }
00267 else if ( nIDEvent == 2 )
00268 {
00269 if ( m_bCancel == FALSE ) AfxMessageBox( m_sMessage, MB_ICONEXCLAMATION );
00270 EndDialog( IDCANCEL );
00271 }
00272 else if ( nIDEvent == 3 )
00273 {
00274 if ( m_nScaled != m_nOldScaled )
00275 {
00276 m_nOldScaled = m_nScaled;
00277 m_wndProgress.SetPos( m_nScaled );
00278 }
00279 }
00280 else if ( nIDEvent == 4 )
00281 {
00282 OnSeed();
00283 }
00284 }
00285
00287
00288
00289 UINT CTorrentSeedDlg::ThreadStart(LPVOID pParam)
00290 {
00291 ((CTorrentSeedDlg*)pParam)->OnRun();
00292 return 0;
00293 }
00294
00295 void CTorrentSeedDlg::OnRun()
00296 {
00297 if ( m_pInfo.m_nFiles == 1 )
00298 {
00299 RunSingleFile();
00300 }
00301 else
00302 {
00303 RunMultiFile();
00304 }
00305 }
00306
00307 void CTorrentSeedDlg::RunSingleFile()
00308 {
00309 m_sTarget = FindFile( &m_pInfo.m_pFiles[0] );
00310
00311 if ( m_sTarget.IsEmpty() || GetFileAttributes( m_sTarget ) == 0xFFFFFFFF )
00312 {
00313 CString strFormat;
00314 LoadString(strFormat, IDS_BT_SEED_SOURCE_LOST );
00315 m_sMessage.Format( strFormat, (LPCTSTR)m_pInfo.m_pFiles[0].m_sPath );
00316 PostMessage( WM_TIMER, 2 );
00317 return;
00318 }
00319
00320 if ( VerifySingle() && CreateDownload() )
00321 {
00322 PostMessage( WM_TIMER, 1 );
00323 }
00324 else
00325 {
00326 PostMessage( WM_TIMER, 2 );
00327 }
00328 }
00329
00330 void CTorrentSeedDlg::RunMultiFile()
00331 {
00332 HANDLE hTarget = CreateTarget();
00333
00334 if ( hTarget != INVALID_HANDLE_VALUE )
00335 {
00336 BOOL bBuild = BuildFiles( hTarget );
00337 CloseHandle( hTarget );
00338
00339 if ( bBuild && CreateDownload() )
00340 {
00341 PostMessage( WM_TIMER, 1 );
00342 }
00343 else
00344 {
00345 DeleteFile( m_sTarget );
00346 PostMessage( WM_TIMER, 2 );
00347 }
00348 }
00349 else
00350 {
00351 PostMessage( WM_TIMER, 2 );
00352 }
00353 }
00354
00355 CString CTorrentSeedDlg::FindFile(LPVOID pVoid)
00356 {
00357 CBTInfo::CBTFile* pFile = reinterpret_cast<CBTInfo::CBTFile*>(pVoid);
00358 CString strFile;
00359
00360 CString strPath = m_sTorrent;
00361 int nSlash = strPath.ReverseFind( '\\' );
00362 if ( nSlash >= 0 ) strPath = strPath.Left( nSlash + 1 );
00363
00364 if ( pFile->m_bSHA1 )
00365 {
00366 CSingleLock oLibraryLock( &Library.m_pSection, TRUE );
00367 if ( CLibraryFile* pShared = LibraryMaps.LookupFileBySHA1( &pFile->m_pSHA1, FALSE, TRUE ) )
00368 {
00369 strFile = pShared->GetPath();
00370 oLibraryLock.Unlock();
00371 if ( GetFileAttributes( strFile ) != 0xFFFFFFFF ) return strFile;
00372 }
00373 }
00374
00375 strFile = Settings.Downloads.CompletePath + "\\" + pFile->m_sPath;
00376 if ( GetFileAttributes( strFile ) != 0xFFFFFFFF ) return strFile;
00377
00378 strFile = strPath + pFile->m_sPath;
00379 if ( GetFileAttributes( strFile ) != 0xFFFFFFFF ) return strFile;
00380
00381
00382 LPCTSTR pszName = _tcsrchr( pFile->m_sPath, '\\' );
00383 if ( pszName == NULL ) pszName = pFile->m_sPath; else pszName ++;
00384
00385 strFile = Settings.Downloads.CompletePath + "\\" + pszName;
00386 if ( GetFileAttributes( strFile ) != 0xFFFFFFFF ) return strFile;
00387
00388 strFile = strPath + pszName;
00389 if ( GetFileAttributes( strFile ) != 0xFFFFFFFF ) return strFile;
00390
00391 strFile.Empty();
00392 return strFile;
00393 }
00394
00395 BOOL CTorrentSeedDlg::VerifySingle()
00396 {
00397 HANDLE hTarget = CreateFile( m_sTarget, GENERIC_READ, FILE_SHARE_READ, NULL,
00398 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
00399
00400 if ( hTarget == INVALID_HANDLE_VALUE )
00401 {
00402 CString strFormat;
00403 LoadString(strFormat, IDS_BT_SEED_SOURCE_LOST );
00404 m_sMessage.Format( strFormat, (LPCTSTR)m_sTarget );
00405 return FALSE;
00406 }
00407
00408 DWORD nSizeHigh = 0;
00409 DWORD nSizeLow = GetFileSize( hTarget, &nSizeHigh );
00410 m_nTotal = (QWORD)nSizeLow | ( (QWORD)nSizeHigh << 32 );
00411
00412 m_pInfo.BeginBlockTest();
00413 m_nBlockNumber = 0;
00414 m_nBlockLength = m_pInfo.m_nBlockSize;
00415
00416 BYTE* pBuffer = new BYTE[ BUFFER_SIZE ];
00417
00418 for ( m_nVolume = 0 ; m_nVolume < m_nTotal ; )
00419 {
00420 DWORD nBuffer = (DWORD)min( ( m_nTotal - m_nVolume ), QWORD(BUFFER_SIZE) );
00421 DWORD tStart = GetTickCount();
00422
00423 ReadFile( hTarget, pBuffer, nBuffer, &nBuffer, NULL );
00424 if ( ! VerifyData( pBuffer, nBuffer, m_sTarget ) ) break;
00425
00426 m_nVolume += nBuffer;
00427 m_nScaled = (int)( (double)m_nVolume / (double)m_nTotal * 1000.0f );
00428 if ( m_nScaled != m_nOldScaled ) PostMessage( WM_TIMER, 3 );
00429
00430 if ( m_bCancel ) break;
00431 tStart = ( GetTickCount() - tStart ) / 2;
00432 Sleep( min( tStart, DWORD(50) ) );
00433 if ( m_bCancel ) break;
00434 }
00435
00436 delete [] pBuffer;
00437 CloseHandle( hTarget );
00438
00439 return ( m_nVolume >= m_nTotal ) && VerifyData( NULL, 0, m_sTarget );
00440 }
00441
00442 HANDLE CTorrentSeedDlg::CreateTarget()
00443 {
00444 m_sTarget = Settings.Downloads.IncompletePath + '\\';
00445 m_sTarget += CSHA::HashToHexString( &m_pInfo.m_pInfoSHA1, FALSE );
00446
00447 HANDLE hTarget = CreateFile( m_sTarget, GENERIC_WRITE, 0, NULL, CREATE_NEW,
00448 FILE_ATTRIBUTE_NORMAL, NULL );
00449
00450 if ( hTarget == INVALID_HANDLE_VALUE )
00451 {
00452 CString strFormat;
00453 LoadString(strFormat, IDS_BT_SEED_CREATE_FAIL );
00454 m_sMessage.Format( strFormat, (LPCTSTR)m_sTarget );
00455 }
00456
00457 return hTarget;
00458 }
00459
00460 BOOL CTorrentSeedDlg::BuildFiles(HANDLE hTarget)
00461 {
00462 m_nVolume = m_nTotal = 0;
00463 m_nScaled = m_nOldScaled = 0;
00464
00465 for ( int nFile = 0 ; nFile < m_pInfo.m_nFiles ; nFile++ )
00466 {
00467 CBTInfo::CBTFile* pFile = &m_pInfo.m_pFiles[ nFile ];
00468 m_nTotal += pFile->m_nSize;
00469 }
00470
00471 m_pInfo.BeginBlockTest();
00472 m_nBlockNumber = 0;
00473 m_nBlockLength = m_pInfo.m_nBlockSize;
00474
00475 for ( int nFile = 0 ; nFile < m_pInfo.m_nFiles ; nFile++ )
00476 {
00477 CBTInfo::CBTFile* pFile = &m_pInfo.m_pFiles[ nFile ];
00478 CString strSource = FindFile( pFile );
00479 HANDLE hSource = INVALID_HANDLE_VALUE;
00480
00481 if ( strSource.GetLength() > 0 )
00482 {
00483 hSource = CreateFile( strSource, GENERIC_READ,
00484 FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
00485 }
00486
00487 if ( hSource == INVALID_HANDLE_VALUE )
00488 {
00489 CString strFormat;
00490 LoadString(strFormat, IDS_BT_SEED_SOURCE_LOST );
00491 m_sMessage.Format( strFormat, (LPCTSTR)pFile->m_sPath );
00492 return FALSE;
00493 }
00494
00495 DWORD nSizeHigh = 0;
00496 DWORD nSizeLow = GetFileSize( hSource, &nSizeHigh );
00497 QWORD nSize = (QWORD)nSizeLow + ( (QWORD)nSizeHigh << 32 );
00498
00499 if ( nSize != pFile->m_nSize )
00500 {
00501 CloseHandle( hSource );
00502 CString strFormat;
00503 LoadString(strFormat, IDS_BT_SEED_SOURCE_SIZE );
00504 m_sMessage.Format( strFormat, (LPCTSTR)pFile->m_sPath,
00505 (LPCTSTR)Settings.SmartVolume( pFile->m_nSize, FALSE ),
00506 (LPCTSTR)Settings.SmartVolume( nSize, FALSE ) );
00507 return FALSE;
00508 }
00509
00510 BOOL bSuccess = CopyFile( hTarget, hSource, pFile->m_nSize, pFile->m_sPath );
00511
00512 CloseHandle( hSource );
00513
00514 if ( m_bCancel || ! bSuccess ) return FALSE;
00515 }
00516
00517 return VerifyData( NULL, 0, m_pInfo.m_pFiles[ m_pInfo.m_nFiles - 1 ].m_sPath );
00518 }
00519
00520 BOOL CTorrentSeedDlg::CopyFile(HANDLE hTarget, HANDLE hSource, QWORD nLength, LPCTSTR pszPath)
00521 {
00522 BYTE* pBuffer = new BYTE[ BUFFER_SIZE ];
00523
00524 while ( nLength )
00525 {
00526 DWORD nBuffer = (DWORD)min( nLength, QWORD(BUFFER_SIZE) );
00527 DWORD nSuccess = 0;
00528 DWORD tStart = GetTickCount();
00529
00530 ReadFile( hSource, pBuffer, nBuffer, &nBuffer, NULL );
00531
00532 if ( ! VerifyData( pBuffer, nBuffer, pszPath ) )
00533 {
00534 delete [] pBuffer;
00535 return FALSE;
00536 }
00537
00538 WriteFile( hTarget, pBuffer, nBuffer, &nSuccess, NULL );
00539
00540 if ( nSuccess == nBuffer )
00541 {
00542 nLength -= nBuffer;
00543 }
00544 else
00545 {
00546 break;
00547 }
00548
00549 m_nVolume += nBuffer;
00550 m_nScaled = (int)( (double)m_nVolume / (double)m_nTotal * 1000.0f );
00551 if ( m_nScaled != m_nOldScaled ) PostMessage( WM_TIMER, 3 );
00552
00553 if ( m_bCancel ) break;
00554 tStart = ( GetTickCount() - tStart ) / 2;
00555 Sleep( min( tStart, DWORD(50) ) );
00556 if ( m_bCancel ) break;
00557 }
00558
00559 delete [] pBuffer;
00560
00561 if ( nLength == 0 )
00562 {
00563 return TRUE;
00564 }
00565 else
00566 {
00567 CString strFormat;
00568 LoadString(strFormat, IDS_BT_SEED_COPY_FAIL );
00569 m_sMessage.Format( strFormat, (LPCTSTR)pszPath );
00570 return FALSE;
00571 }
00572 }
00573
00574 BOOL CTorrentSeedDlg::VerifyData(BYTE* pBuffer, DWORD nLength, LPCTSTR pszPath)
00575 {
00576 if ( pBuffer == NULL )
00577 {
00578 if ( m_nBlockNumber >= m_pInfo.m_nBlockCount ) return TRUE;
00579 if ( m_pInfo.FinishBlockTest( m_nBlockNumber++ ) ) return TRUE;
00580
00581 CString strFormat;
00582 LoadString(strFormat, IDS_BT_SEED_VERIFY_FAIL );
00583 m_sMessage.Format( strFormat, (LPCTSTR)pszPath );
00584 return FALSE;
00585 }
00586
00587 while ( nLength > 0 )
00588 {
00589 DWORD nBlock = min( nLength, m_nBlockLength );
00590
00591 m_pInfo.AddToTest( pBuffer, nBlock );
00592
00593 pBuffer += nBlock;
00594 nLength -= nBlock;
00595 m_nBlockLength -= nBlock;
00596
00597 if ( m_nBlockLength == 0 )
00598 {
00599 if ( ! m_pInfo.FinishBlockTest( m_nBlockNumber++ ) )
00600 {
00601 CString strFormat;
00602 LoadString(strFormat, IDS_BT_SEED_VERIFY_FAIL );
00603 m_sMessage.Format( strFormat, (LPCTSTR)pszPath );
00604 return FALSE;
00605 }
00606
00607 if ( m_nBlockNumber < m_pInfo.m_nBlockCount )
00608 {
00609 m_nBlockLength = m_pInfo.m_nBlockSize;
00610 m_pInfo.BeginBlockTest();
00611 }
00612 }
00613 }
00614
00615 return TRUE;
00616 }
00617
00618 BOOL CTorrentSeedDlg::CreateDownload()
00619 {
00620 CSingleLock pTransfersLock( &Transfers.m_pSection );
00621 if ( ! pTransfersLock.Lock( 2000 ) ) return FALSE;
00622
00623 if ( Downloads.FindByBTH( &m_pInfo.m_pInfoSHA1 ) != NULL )
00624 {
00625 CString strFormat;
00626 LoadString(strFormat, IDS_BT_SEED_ALREADY );
00627 m_sMessage.Format( strFormat, (LPCTSTR)m_pInfo.m_sName );
00628 return FALSE;
00629 }
00630
00631 CBTInfo* pInfo = new CBTInfo();
00632 pInfo->Copy( &m_pInfo );
00633 CShareazaURL pURL( pInfo );
00634 CDownload* pDownload = Downloads.Add( &pURL );
00635
00636 if ( pDownload != NULL && pDownload->SeedTorrent( m_sTarget ) )
00637 {
00638 return TRUE;
00639 }
00640 else
00641 {
00642 CString strFormat;
00643 LoadString(strFormat, IDS_BT_SEED_ERROR );
00644 m_sMessage.Format( strFormat, (LPCTSTR)m_pInfo.m_sName );
00645 return FALSE;
00646 }
00647 }