. FTP 대화상자

FTP 열기 대화상자는 많은 일을 한다. arFtp 배열에 등록되어 있는 FTP 서버의 목록을 보여주고 사용자가 이 중 하나를 선택하여 연결 버튼을 클릭하면 미리 작성된 접속정보를 참조하여 FTP 서버와 통신을 시작한다. 서버의 현재 폴더에 있는 파일과 폴더의 목록을 읽어 리스트 뷰에 출력하며 폴더를 선택하면 안으로 들어가 폴더의 내용도 보여준다.

목록에서 파일을 선택하면 선택된 파일명을 리턴한다. 파일명을 리턴할 뿐이지 직접 파일을 읽어주는 것은 아니다. 이때 리턴되는 파일명에는 이 파일을 다시 읽기 위한 모든 정보들이 포함된다. FTP 열기 대화상자의 기능은 거의 작은 FTP 클라이언트 프로그램 수준이다. 최종적으로 파일을 선택할 때까지는 FTP 서버와 접속을 유지해야 하므로 접속 핸들은 전역으로 선언해야 한다. 다음 두 변수를 선언하도록 하자.

 

HINTERNET hInternet, hFtp;

 

인터넷에 대한 접속핸들과 FTP 서버에 대한 접속 핸들이며 이 두 변수값은 FTP 열기 대화상자가 떠 있는 동안 유지되어야 한다. 다음은 마이크로소프트의 공개 FTP 서버에 접속한 모양이다.

접속 대상 콤보박스에 등록된 서버의 목록을 보여주고 연결되면 이 서버의 파일, 폴더 목록을 중앙의 리스트 뷰 컨트롤에 보여준다. 파일 목록을 보여주어야 하기 때문에 대화상자의 크기가 적당히 커야 하는데 아예 크기 조정이 가능한 스타일로 작성하는 것도 괜찮을 것 같다. 대화상자 프로시저는 다음과 같이 작성한다.

 

BOOL CALLBACK DGFtpProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

     int idx;

     int iItem;

     TCHAR Mes[256];

     LVCOLUMN COL;

     LVITEM LI;

     HWND hList;

     static HIMAGELIST hImgFtp;

     TCHAR Path[MAX_PATH];

     static TCHAR *FtpPath;

     static TCHAR Dir[MAX_PATH];

 

     switch(iMessage)

     {

     case WM_INITDIALOG:

          MoveToParentCenter(hDlg);

          if (hImgFtp==NULL) {

              hImgFtp=ImageList_LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_FTP),

                   16, 1, RGB(255,255,255));

          }

          hList=GetDlgItem(hDlg,IDC_FTPFILE);

          ListView_SetImageList(hList, hImgFtp, LVSIL_SMALL);

          ListView_SetExtendedListViewStyle(hList, LVS_EX_FULLROWSELECT);

 

          COL.mask=LVCF_FMT | LVCF_WIDTH | LVCF_TEXT |LVCF_SUBITEM;

          COL.fmt=LVCFMT_LEFT;

          COL.cx=250;

          COL.pszText="이름";

          COL.iSubItem=0;

          ListView_InsertColumn(hList,0,&COL);

 

          COL.pszText="크기";

          COL.cx=80;

          COL.iSubItem=1;

          ListView_InsertColumn(hList,1,&COL);

 

          COL.cx=150;

          COL.pszText="날짜";

          COL.iSubItem=2;

          ListView_InsertColumn(hList,2,&COL);

 

          LoadFtpServerList(hDlg);

          FtpPath=(TCHAR *)lParam;

          return TRUE;

     case WM_COMMAND:

          switch (LOWORD(wParam))

          {

          case IDC_BTNFTPADD:

              if (SendDlgItemMessage(hDlg,IDC_FTPSERVER,CB_GETCOUNT,0,0)

                   == sizeof(Option.arFtp)/sizeof(Option.arFtp[0])-1) {

                   MessageBox(hDlg,"FTP 서버를 더 등록할 수 없습니다.","알림",MB_OK);

                   return TRUE;

              }

              if (DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_FTPCONFIG),

                   hDlg, DGFtpConfigProc,(LPARAM)-1)==IDOK) {

                   LoadFtpServerList(hDlg);

              }

              return TRUE;

          case IDC_BTNFTPEDIT:

              idx=SendDlgItemMessage(hDlg,IDC_FTPSERVER,CB_GETCURSEL,0,0);

              if (DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_FTPCONFIG),

                   hDlg, DGFtpConfigProc,(LPARAM)idx)==IDOK) {

                   LoadFtpServerList(hDlg);

              }

              return TRUE;

          case IDC_BTNFTPDELETE:

              idx=SendDlgItemMessage(hDlg,IDC_FTPSERVER,CB_GETCURSEL,0,0);

              wsprintf(Mes,"%s FTP 서버의 정보를 삭제하시겠습니까?",Option.arFtp[idx].Name);

              if (MessageBox(hDlg,Mes,"질문",MB_YESNO)==IDYES) {

                   for (;;idx++) {

                        Option.arFtp[idx]=Option.arFtp[idx+1];

                        if (lstrlen(Option.arFtp[idx].Name)==0) {

                            break;

                        }

                   }

                   LoadFtpServerList(hDlg);

              }

              return TRUE;

          case IDC_BTNFTPCONNECT:

              if (hFtp==NULL) {

                   idx=SendDlgItemMessage(hDlg,IDC_FTPSERVER,CB_GETCURSEL,0,0);

                   if (DgFtpConnect(Option.arFtp[idx].Addr,Option.arFtp[idx].User,

                        Option.arFtp[idx].Pass,Option.arFtp[idx].Port)==FALSE) {

                        MessageBox(hDlg,"FTP 서버에 접속할 수 없습니다","알림",MB_OK);

                   } else {

                        SetWindowText(GetDlgItem(hDlg,IDC_BTNFTPCONNECT),"연결 해제");

                        DgFtpEnumFiles(hDlg);

                        lstrcpy(Dir,"");

                        wsprintf(Mes, "현재 위치 : %s", Dir);

                        SetDlgItemText(hDlg,IDC_STFTPSTATUS,Mes);

                   }

              } else {

                   DgFtpUnConnect();

                   SetWindowText(GetDlgItem(hDlg,IDC_BTNFTPCONNECT),"연결");

                   SetDlgItemText(hDlg,IDC_STFTPSTATUS,"현재 상태 : 연결 안되었음");

                   ListView_DeleteAllItems(GetDlgItem(hDlg,IDC_FTPFILE));

              }

              return TRUE;

          case IDCANCEL:

              if (hFtp) {

                   DgFtpUnConnect();

                   hImgFtp=NULL;

              }

              EndDialog(hDlg,0);

              return TRUE;

          case IDC_BTNFTPOPEN:

              if (hFtp == NULL) {

                   return TRUE;

              }

              hList=GetDlgItem(hDlg,IDC_FTPFILE);

              iItem=ListView_GetNextItem(hList,-1,LVNI_ALL | LVNI_SELECTED);

              if (iItem == -1) {

                   return TRUE;

              }

              if (iItem == 0) {

                   if (FtpSetCurrentDirectory(hFtp,"..")==TRUE) {

                        DgFtpEnumFiles(hDlg);

                        if (lstrlen(Dir)!=0) {

                            *(strrchr(Dir, ‘/’))=0;

                        }

                        wsprintf(Mes, "현재 위치 : %s", Dir);

                        SetDlgItemText(hDlg,IDC_STFTPSTATUS,Mes);

                   }

                   return TRUE;

              }

 

              LI.iItem=iItem;

              LI.iSubItem=0;

              LI.pszText=Path;

              LI.cchTextMax=255;

              LI.mask=LVIF_IMAGE | LVIF_TEXT;

              ListView_GetItem(hList,&LI);

 

              if (LI.iImage == 0) {

                   if (FtpSetCurrentDirectory(hFtp,Path)==TRUE) {

                        DgFtpEnumFiles(hDlg);

                        lstrcat(Dir,"/");

                        lstrcat(Dir,Path);

                        wsprintf(Mes, "현재 위치 : %s", Dir);

                        SetDlgItemText(hDlg,IDC_STFTPSTATUS,Mes);

                   }

              } else {

                   idx=SendDlgItemMessage(hDlg,IDC_FTPSERVER,CB_GETCURSEL,0,0);

                   wsprintf(FtpPath,"ftp://%s:%s:%s:%d%s/%s",Option.arFtp[idx].User,

                        Option.arFtp[idx].Pass,Option.arFtp[idx].Addr,

                        Option.arFtp[idx].Port,Dir,Path);

 

                   DgFtpUnConnect();

                   EndDialog(hDlg,IDOK);

                   hImgFtp=NULL;

                   UpdateWindow(g_hFrameWnd);

              }

              return TRUE;

          }

          return FALSE;

     case WM_NOTIFY:

          LPNMHDR hdr;

          LPNMITEMACTIVATE nia;

          hdr=(LPNMHDR)lParam;

          nia=(LPNMITEMACTIVATE)lParam;

          hList=GetDlgItem(hDlg,IDC_FTPFILE);

          if (hdr->hwndFrom == hList) {

              switch (hdr->code) {

              case NM_DBLCLK:

                   SendMessage(hDlg,WM_COMMAND,MAKEWPARAM(IDC_BTNFTPOPEN,BN_CLICKED),0);

                   break;

              }

          }

          break;

     }

     return FALSE;

}

 

서버의 등록, 편집 버튼은 앞서 작성해놓은 IDD_FTPCONFIG 대화상자를 호출한다. 편집할 때는 편집 대상 서버의 첨자를 파라미터로 넘겨 주고 새로 등록할 때는 -1을 전달하면 된다. 서버 정보의 추가, 편집은 서버 관리 대화상자가 알아서 하므로 이 대화상자는 변경된 서버 목록을 다시 읽어 오기만 한다. 삭제시는 arFtp 배열에서 해당 서버의 요소를 지우는데 이 작업은 FTP 열기 대화상자가 직접 한다. 다음은 이 대화상자에서 호출하는 보조 함수들이다.

 

BOOL DgFtpConnect(TCHAR *Server, TCHAR *User, TCHAR *Pass, int Port);

void DgFtpUnConnect();

void DgFtpAddFile(HWND hDlg,WIN32_FIND_DATA wfd);

void DgFtpEnumFiles(HWND hDlg);

void LoadFtpServerList(HWND hDlg);

 

FTP연결 및 해제,  등록된 서버의 목록 가져오기, 파일 목록 관리 등의 기능을 제공하며 FTP 열기 대화상자에서 이 함수들을 호출한다. 코드는 다음과 같다.

 

BOOL DgFtpConnect(TCHAR *Server, TCHAR *User, TCHAR *Pass, int Port)

{

     SetCursor(LoadCursor(NULL,IDC_WAIT));

     hInternet=InternetOpen("Dangeun", INTERNET_OPEN_TYPE_PRECONFIG,

          NULL, NULL, 0);

     if (hInternet == NULL) {

          return FALSE;

     }

 

     hFtp=InternetConnect(hInternet,Server,Port,User,Pass,INTERNET_SERVICE_FTP,0,0);

     if (hFtp==NULL) {

          InternetCloseHandle(hInternet);

          return FALSE;

     }

     return TRUE;

}

 

void DgFtpUnConnect()

{

     InternetCloseHandle(hFtp);

     InternetCloseHandle(hInternet);

     hFtp=NULL;

     hInternet=NULL;

}

 

void DgFtpAddFile(HWND hDlg,WIN32_FIND_DATA wfd)

{

     HWND hList;

     LVITEM LI,LI2;

     int idx;

     TCHAR szTmp[50];

     SYSTEMTIME st;

     TCHAR Text[MAX_PATH];

 

     hList=GetDlgItem(hDlg,IDC_FTPFILE);

     LI.mask=LVIF_TEXT | LVIF_IMAGE;

 

     if (strcmp(wfd.cFileName,"상위 폴더로 이동")!=0) {

          if (wfd.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)

              LI.iImage=0;

          else

              LI.iImage=1;

     } else {

          LI.iImage=2;

     }

     LI.iSubItem=0;

     LI.pszText=wfd.cFileName;

 

     if (LI.iImage == 2) {

          idx=0;

     } else {

          idx=1;

          if (LI.iImage == 1) {

              for (;;idx++) {

                   LI2.mask=LVIF_IMAGE;

                   LI2.iItem=idx;

                   LI2.iSubItem=0;

                   if (ListView_GetItem(hList,&LI2)==FALSE) {

                        break;

                   }

                   if (LI2.iImage == 1) {

                        break;

                   }

              }

          }

 

          for (;;idx++) {

              LI2.mask=LVIF_TEXT | LVIF_IMAGE;

              LI2.iItem=idx;

              LI2.iSubItem=0;

              LI2.pszText=Text;

              LI2.cchTextMax=MAX_PATH;

              if (ListView_GetItem(hList,&LI2)==FALSE) {

                   break;

              }

              if (LI.iImage == 0 && LI2.iImage == 1) {

                   break;

              }

              if (lstrcmpi(wfd.cFileName, Text) < 0) {

                   break;

              }

          }

     }

 

     LI.iItem=idx;

     ListView_InsertItem(hList,&LI);

 

     if (strcmp(wfd.cFileName,"상위 폴더로 이동")!=0) {

          if (wfd.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY) {

              lstrcpy(szTmp,"<폴더>");

          } else {

              itoa(wfd.nFileSizeLow,szTmp,10);

          }

          ListView_SetItemText(hList,idx,1,szTmp);

          FileTimeToSystemTime(&wfd.ftLastWriteTime,&st);

          wsprintf(szTmp,"%d %d %d %d %d",st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute);

          ListView_SetItemText(hList,idx,2,szTmp);

     }

     UpdateWindow(hList);

}

 

void DgFtpEnumFiles(HWND hDlg)

{

     WIN32_FIND_DATA wfd;

     BOOL bResult=TRUE;

     HINTERNET hFind;

 

     if (hFtp==NULL)

          return;

 

     SetCursor(LoadCursor(NULL,IDC_WAIT));

     ListView_DeleteAllItems(GetDlgItem(hDlg,IDC_FTPFILE));

     lstrcpy(wfd.cFileName,"상위 폴더로 이동");

     DgFtpAddFile(hDlg,wfd);

 

     hFind=FtpFindFirstFile(hFtp,"*.*",&wfd,0,0);

     if (hFind!=NULL) {

          DgFtpAddFile(hDlg,wfd);

          while (bResult) {

              bResult=InternetFindNextFile(hFind,&wfd);

              if (bResult==TRUE) {

                   DgFtpAddFile(hDlg,wfd);

              }

          }

          InternetCloseHandle(hFind);

     }

}

 

void LoadFtpServerList(HWND hDlg)

{

     int idx;

     BOOL bEnable;

 

     SendDlgItemMessage(hDlg,IDC_FTPSERVER,CB_RESETCONTENT,0,0);

     for (idx=0;;idx++) {

          if (lstrlen(Option.arFtp[idx].Name)==0) {

              break;

          }

          SendDlgItemMessage(hDlg,IDC_FTPSERVER,CB_ADDSTRING,0,(LPARAM)Option.arFtp[idx].Name);

     }

     SendDlgItemMessage(hDlg,IDC_FTPSERVER,CB_SETCURSEL,0,0);

 

     if (SendDlgItemMessage(hDlg,IDC_FTPSERVER,CB_GETCOUNT,0,0) == 0) {

          bEnable=FALSE;

     } else {

          bEnable=TRUE;

     }

     EnableWindow(GetDlgItem(hDlg,IDC_BTNFTPCONNECT),bEnable);

     EnableWindow(GetDlgItem(hDlg,IDC_BTNFTPEDIT),bEnable);

     EnableWindow(GetDlgItem(hDlg,IDC_BTNFTPDELETE),bEnable);

}

 

FTP 서버로의 접속 및 해제, 파일 목록 읽기 등에 대해서는 WinInet 라이브러리 관련 문서를 참조하기 바란다. WinInet을 모르고서는 이 코드를 이해할 수 없으므로 반드시 따로 공부한 후에 분석해보기 바란다. WinInet에 대한 경험이 있다면 이 함수의 내용은 어렵지 않게 파악할 수 있으므로 따로 상세한 분석은 하지 않는다.

폴더의 목록을 보여주는 방법은 파일창이 로컬 드라이브의 파일 목록을 보여주는 방법과 거의 동일하다. 알파벳순으로 정렬해서 보여주되 폴더를 우선적으로 보여주고 폴더 다음에 파일의 목록을 보여준다.