//---------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation 1993-1994
//
// File: update.c
//
// This files contains code for the Update UI and dialog.
//
// History:
//  08-17-93 ScottH     Created.
//
//---------------------------------------------------------------------------


#include "brfprv.h"     // common headers

#include "res.h"
#include "recact.h"
#include <help.h>


// This structure contains all the important counts
// that determine the specific course of action when
// the user wants to update something.
typedef struct
{
    // These are 1 to 1
    UINT    cFiles;
    UINT    cOrphans;
    UINT    cSubfolders;
    
    // These are 1 to 1
    UINT    cUnavailable;
    UINT    cDoSomething;
    UINT    cConflict;
    UINT    cTombstone;
} UPDCOUNT;

// This is the structure passed to the dialog at WM_INITDIALOG
typedef struct
{
    PRECLIST lprl;              // Supplied reclist
    CBS *    pcbs;
    UINT     uFlags;            // UF_ Flags
    HDPA     hdpa;              // List of RA_ITEMs
    UINT     cDoSomething;
} XUPDSTRUCT,  * LPXUPDSTRUCT;


typedef struct tagUPD
{
    HWND hwnd;              // dialog handle
    
    LPXUPDSTRUCT pxupd;
    
} UPD, * PUPD;

#define Upd_Prl(this)           ((this)->pxupd->lprl)
#define Upd_AtomBrf(this)       ((this)->pxupd->pcbs->atomBrf)
#define Upd_GetBrfPtr(this)     Atom_GetName(Upd_AtomBrf(this))

#define Upd_GetPtr(hwnd)        (PUPD)GetWindowLongPtr(hwnd, DWLP_USER)
#define Upd_SetPtr(hwnd, lp)    (PUPD)SetWindowLongPtr(hwnd, DWLP_USER, (LRESULT)(lp))

// These flags are used for DoUpdateMsg
#define DUM_ALL             0x0001
#define DUM_SELECTION       0x0002
#define DUM_ORPHAN          0x0004
#define DUM_UPTODATE        0x0008
#define DUM_UNAVAILABLE     0x0010
#define DUM_SUBFOLDER_TWIN  0x0020

// These flags are returned by PassedSpecialCases
#define PSC_SHOWDIALOG      0x0001
#define PSC_POSTMSGBOX      0x0002


//---------------------------------------------------------------------------
// Some comments
//---------------------------------------------------------------------------

// There are several special cases and conditions which must be 
// handled.  Lets break them down now.  The case numbers on on the far
// right, and are referenced in comments thru out this file.
//
// There are two user actions: Update All and Update Selection.
//
// Update All:
// 
//               Case                                     What to do
//         -----------------                            --------------
//  A1.  * No files in the briefcase                --->  MB (messagebox)
//  A2.  * All files are orphans                    --->  MB
//       * Some files are twins and...
//          * they are all available and...
//  A3.        * they are all up-to-date            --->  MB
//  A4.        * some of them need updating         --->  Update dialog
//          * some are unavailable and...
//  A5.        * the available ones are up-to-date  --->  MB then Update 
//  A6.        * some need updating                 --->  MB then Update 
// 
//
// Update Selection:
//
//               Case                                     What to do
//         -----------------                            --------------
//       * Single selection and...
//  S1.     * is an orphan                          --->  MB
//          * is available and...
//  S2.        * up-to-date                         --->  MB
//  S3.        * needs updating                     --->  Update 
//  S4.     * is unavailable                        --->  MB then Update 
//       * Multi selection and...
//  S5.     * all are orphans                       --->  MB
//          * some (non-orphans) are unavailable and...
//             * some need updating and...
//  S6.           * none are orphans                --->  MB then Update 
//  S7.           * some are orphans                --->  MB then Update then MB
//             * the available ones are up-to-date and...
//  S8.           * none are orphans                --->  MB then Update 
//  S9.           * some are orphans                --->  MB then Update then MB
//          * all (non-orphans) are available and...
//             * some need updating and...
// S10.           * none are orphans                --->  Update 
// S11.           * some are orphans                --->  Update then MB
//             * all up-to-date and...
// S12.           * none are orphans                --->  MB
// S13.           * some are orphans                --->  MB



//---------------------------------------------------------------------------
// Dialog code
//---------------------------------------------------------------------------

/*----------------------------------------------------------
Purpose: Fill the reconciliation action listbox
Returns: TRUE on success
Cond:    --
*/
BOOL PRIVATE Upd_FillList(
                          PUPD this)
{
    HWND hwndCtl = GetDlgItem(this->hwnd, IDC_UPDATEACTIONS);
    HDPA hdpa = this->pxupd->hdpa;
    int cItems;
    int i;
    
    cItems = DPA_GetPtrCount(hdpa);
    for (i = 0; i < cItems; i++)
    {
        RA_ITEM * pitem = DPA_FastGetPtr(hdpa, i);
        RecAct_InsertItem(hwndCtl, pitem);
        RAI_Free(pitem);
    }
    
    return TRUE;
}


/*----------------------------------------------------------
Purpose: Sets the Update and Cancel buttons according to the
bDisableUpdate parameter.

  Returns: --
  Cond:    --
*/
void PRIVATE Upd_SetExitButtons(
                                PUPD this,
                                BOOL bDisableUpdate)
{
    HWND hwndOK = GetDlgItem(this->hwnd, IDOK);
    
    // Disable the update button?
    if (bDisableUpdate)
    {
        // Yes
        if (GetFocus() == hwndOK)
        {
            SetFocus(GetDlgItem(this->hwnd, IDCANCEL));
        }
        Button_Enable(hwndOK, FALSE);
    }
    else
    {
        // No
        Button_Enable(hwndOK, TRUE);
    }
}


/*----------------------------------------------------------
Purpose: WM_INITDIALOG Handler
Returns: 
Cond:    --
*/
BOOL PRIVATE Upd_OnInitDialog(
                              PUPD this,
                              HWND hwndFocus,
                              LPARAM lParam)
{
    HWND hwnd = this->hwnd;
    TCHAR szFmt[MAXBUFLEN];
    TCHAR sz[MAXMSGLEN];
    
    ASSERT(lParam != 0L);
    
    this->pxupd = (LPXUPDSTRUCT)lParam;
    
    if (Upd_FillList(this))
    {
        // Set the title caption
        wsprintf(sz, SzFromIDS(IDS_CAP_UpdateFmt, szFmt, ARRAYSIZE(szFmt)),
            PathFindFileName(Upd_GetBrfPtr(this)));
        SetWindowText(hwnd, sz);
        
        // Do any files need updating?
        if (0 == this->pxupd->cDoSomething)
        {
            // No
            Upd_SetExitButtons(this, TRUE);
        }
    }
    else
    {
        // Failed
        EndDialog(hwnd, -1);
    }
    return(TRUE);
}


/*----------------------------------------------------------
Purpose: Handle RN_ITEMCHANGED
Returns: --
Cond:    --
*/
void PRIVATE Upd_HandleItemChange(
                                  PUPD this,
                                  NM_RECACT  * lpnm)
{
    PRECITEM lpri;
    
    ASSERT((lpnm->mask & RAIF_LPARAM) != 0);
    
    lpri = (PRECITEM)lpnm->lParam;
    
    // The action has changed, update the recnode accordingly
    if (lpnm->mask & RAIF_ACTION)
    {
        LPCTSTR pszDir = Upd_GetBrfPtr(this);
        Sync_ChangeRecItemAction(lpri, pszDir, pszDir, lpnm->uAction);
        
        switch (lpnm->uActionOld)
        {
        case RAIA_TOOUT:
        case RAIA_TOIN:
        case RAIA_MERGE:
            // Is this a change from "do something" to "skip"?
            if (RAIA_SKIP == lpnm->uAction)
            {
                // Yes
                ASSERT(0 < this->pxupd->cDoSomething);
                this->pxupd->cDoSomething--;
            }
            break;
            
        case RAIA_SKIP:
        case RAIA_CONFLICT:
            // Is this a change from "skip"/"conflict" to "do something"?
            if (RAIA_TOOUT == lpnm->uAction ||
                RAIA_TOIN == lpnm->uAction ||
                RAIA_MERGE == lpnm->uAction)
            {
                // Yes
                this->pxupd->cDoSomething++;
            }
            break;
        }
        
        Upd_SetExitButtons(this, 0 == this->pxupd->cDoSomething);
    }
}


/*----------------------------------------------------------
Purpose: WM_NOTIFY handler
Returns: varies
Cond:    --
*/
LRESULT PRIVATE Upd_OnNotify(
                             PUPD this,
                             int idFrom,
                             NMHDR  * lpnmhdr)
{
    LRESULT lRet = 0;
    
    switch (lpnmhdr->code)
    {
    case RN_ITEMCHANGED:
        Upd_HandleItemChange(this, (NM_RECACT  *)lpnmhdr);
        break;
        
    default:
        break;
    }
    
    return lRet;
}


/*----------------------------------------------------------
Purpose: Info WM_COMMAND Handler
Returns: --
Cond:    --
*/
VOID PRIVATE Upd_OnCommand(
                           PUPD this,
                           int id,
                           HWND hwndCtl,
                           UINT uNotifyCode)
{
    HWND hwnd = this->hwnd;
    
    switch (id)
    {
    case IDOK:
    case IDCANCEL:
        EndDialog(hwnd, id);
        break;
    }
}


/*----------------------------------------------------------
Purpose: WM_DESTROY handler
Returns: --
Cond:    --
*/
void PRIVATE Upd_OnDestroy(
                           PUPD this)
{
}


static BOOL s_bUpdRecurse = FALSE;

LRESULT INLINE Upd_DefProc(
                           HWND hDlg, 
                           UINT msg,
                           WPARAM wParam,
                           LPARAM lParam) 
{
    ENTEREXCLUSIVE();
    {
        s_bUpdRecurse = TRUE;
    }
    LEAVEEXCLUSIVE();
    
    return DefDlgProc(hDlg, msg, wParam, lParam); 
}


/*----------------------------------------------------------
Purpose: Real Create Folder Twin dialog proc
Returns: varies
Cond:    --
*/
LRESULT Upd_DlgProc(
                    PUPD this,
                    UINT message,
                    WPARAM wParam,
                    LPARAM lParam)
{
    const static DWORD rgHelpIDs[] = {
        IDC_UPDATEACTIONS,  IDH_BFC_UPDATE_SCREEN,      // different
        IDOK,               IDH_BFC_UPDATE_BUTTON,
        0, 0 };
    
    switch (message)
    {
        HANDLE_MSG(this, WM_INITDIALOG, Upd_OnInitDialog);
        HANDLE_MSG(this, WM_COMMAND, Upd_OnCommand);
        HANDLE_MSG(this, WM_NOTIFY, Upd_OnNotify);
        HANDLE_MSG(this, WM_DESTROY, Upd_OnDestroy);
        
    case WM_HELP:
        WinHelp(((LPHELPINFO)lParam)->hItemHandle, c_szWinHelpFile, HELP_WM_HELP, (DWORD_PTR)(LPVOID)rgHelpIDs);
        return 0;
        
    case WM_CONTEXTMENU:
        WinHelp((HWND)wParam, c_szWinHelpFile, HELP_CONTEXTMENU, (DWORD_PTR)(LPVOID)rgHelpIDs);
        return 0;
        
    default:
        return Upd_DefProc(this->hwnd, message, wParam, lParam);
    }
}


/*----------------------------------------------------------
Purpose: Create Folder Twin Dialog Wrapper
Returns: varies
Cond:    --
*/
INT_PTR _export CALLBACK Upd_WrapperProc(
                                         HWND hDlg,      // std params
                                         UINT message,
                                         WPARAM wParam,
                                         LPARAM lParam)
{
    PUPD this;
    
    // Cool windowsx.h dialog technique.  For full explanation, see
    //  WINDOWSX.TXT.  This supports multiple-instancing of dialogs.
    //
    ENTEREXCLUSIVE();
    {
        if (s_bUpdRecurse)
        {
            s_bUpdRecurse = FALSE;
            LEAVEEXCLUSIVE();
            return FALSE;
        }
    }
    LEAVEEXCLUSIVE();
    
    this = Upd_GetPtr(hDlg);
    if (this == NULL)
    {
        if (message == WM_INITDIALOG)
        {
            this = GAlloc(sizeof(*this));
            if (!this)
            {
                MsgBox(hDlg, MAKEINTRESOURCE(IDS_OOM_UPDATEDIALOG), MAKEINTRESOURCE(IDS_CAP_UPDATE),
                    NULL, MB_ERROR);
                EndDialog(hDlg, IDCANCEL);
                return Upd_DefProc(hDlg, message, wParam, lParam);
            }
            this->hwnd = hDlg;
            Upd_SetPtr(hDlg, this);
        }
        else
        {
            return Upd_DefProc(hDlg, message, wParam, lParam);
        }
    }
    
    if (message == WM_DESTROY)
    {
        Upd_DlgProc(this, message, wParam, lParam);
        GFree(this);
        Upd_SetPtr(hDlg, NULL);
        return 0;
    }
    
    return SetDlgMsgResult(hDlg, message, Upd_DlgProc(this, message, wParam, lParam));
}


//---------------------------------------------------------------------------
// Update detection code
//---------------------------------------------------------------------------


/*----------------------------------------------------------
Purpose: Checks if the briefcase is empty.  This function skips the
"desktop.ini" and "Briefcase Database" files.

  Returns: TRUE if the briefcase is empty
  
    Cond:    --
*/
BOOL PRIVATE IsBriefcaseEmpty(
                              LPCTSTR pszPath)
{
    BOOL bRet = FALSE;
    
    ASSERT(pszPath);
    
    if (pszPath)
    {
        // Enumerate thru folder
        TCHAR szSearch[MAXPATHLEN];
        WIN32_FIND_DATA fd;
        HANDLE hfile;
        // This must be per instance, else it will cause a fixup in 
        // shared data segment.
        const static LPCTSTR s_rgszIgnore[] = { TEXT("."), TEXT(".."), g_szDBName, g_szDBNameShort, c_szDesktopIni };
        
        PathCombine(szSearch, pszPath, TEXT("*.*"));
        hfile = FindFirstFile(szSearch, &fd);
        if (INVALID_HANDLE_VALUE != hfile)
        {
            BOOL bCont = TRUE;
            
            bRet = TRUE;        // Default to empty folder
            while (bCont)
            {
                int bIgnore = FALSE;
                int i;
                
                // Is this file one of the files to ignore?
                for (i = 0; i < ARRAYSIZE(s_rgszIgnore); i++)
                {
                    if (IsSzEqual(fd.cFileName, s_rgszIgnore[i]))
                    {
                        // Yes
                        bIgnore = TRUE;
                        break;
                    }
                }
                
                // Is this a valid file/folder?
                if (FALSE == bIgnore)
                {
                    // Yes; return the briefcase is not empty
                    bRet = FALSE;
                    bCont = FALSE;  // stop the enumeration
                }
                else
                {
                    bCont = FindNextFile(hfile, &fd);
                }
            }
            
            FindClose(hfile);
        }
    }
    
    return bRet;
}


/*----------------------------------------------------------
Purpose: Create a DSA of RA_ITEMs

  Sets the cDoSomething, cUnavailable, cConflict and 
  cTombstone fields of pupdcount.
  
    Returns: TRUE on success
    Cond:    --
*/
HDPA PRIVATE ComposeUpdateList(
                               PCBS pcbs,
                               PRECLIST prl,
                               UPDCOUNT * pupdcount,
                               HWND hwndOwner)
{
    HRESULT hres;
    HDPA hdpa;
    
    ASSERT(prl);
    
    hdpa = DPA_Create(20);
    if (NULL != hdpa)
    {
        LPRA_ITEM pitem;
        PRECITEM pri;
        LPCTSTR pszBrf = Atom_GetName(pcbs->atomBrf);
        
        if (pszBrf)
        {
            DEBUG_CODE( Sync_DumpRecList(TR_SUCCESS, prl, TEXT("ComposeUpdateList")); )
            
            pupdcount->cUnavailable = 0;
            pupdcount->cDoSomething = 0;
            pupdcount->cTombstone = 0;
            pupdcount->cConflict = 0;
        
            for (pri = prl->priFirst; pri; pri = pri->priNext)
            {
                hres = RAI_CreateFromRecItem(&pitem, pszBrf, pri);
                if (SUCCEEDED(hres))
                {
                    // Is this a NOP?
                    if (RAIA_NOTHING == pitem->uAction ||
                        RAIA_ORPHAN == pitem->uAction)
                    {
                        // Yes; skip these guys altogether
                    }
                    else
                    {
                        pitem->mask |= RAIF_LPARAM;
                        pitem->lParam = (LPARAM)pri;
                    
#ifndef NEW_REC
                        // Has the file inside or outside the briefcase been deleted?
                        if (SI_DELETED == pitem->siInside.uState ||
                            SI_DELETED == pitem->siOutside.uState)
                        {
                            // Yes
                            pupdcount->cTombstone++;
                        }
                        else
#endif
                        // Is this a file entry?
                        if (IsFileRecItem(pri))
                        {
                            // Yes; add the item to the list.  
                            pitem->iItem = 0x7fff;
                            DPA_InsertPtr(hdpa, DPA_APPEND, pitem);
                        
                            // Is this unavailable?
                            if (RAIA_SKIP == pitem->uAction)
                            {
                                // Yes
                                ASSERT(SI_UNAVAILABLE == pitem->siInside.uState ||
                                    SI_UNAVAILABLE == pitem->siOutside.uState ||
                                    SI_NOEXIST == pitem->siInside.uState ||
                                    SI_NOEXIST == pitem->siOutside.uState);
                                pupdcount->cUnavailable++;
                            }
                            else if (RAIA_CONFLICT == pitem->uAction)
                            {
                                pupdcount->cConflict++;
                            }
                            else 
                            {
                                pupdcount->cDoSomething++;
                            }
                        
                            // (prevent pitem from being freed until 
                            // the dialog fills its list in Upd_FillList)
                            pitem = NULL;
                        }
                    }
                
                    RAI_Free(pitem);
                }
            }
        }
    }
    
    return hdpa;
}


/*----------------------------------------------------------
Purpose: Displays a messagebox error specific to updating files

  Returns: id of button
  Cond:    --
*/
int PRIVATE DoUpdateMsg(
                        HWND hwndOwner,
                        LPCTSTR pszPath,
                        UINT cFiles,
                        UINT uFlags)            // DUM_ flags
{
    UINT ids;
    UINT idi;
    int idRet;
    
    // Is this for Update All?
    if (IsFlagSet(uFlags, DUM_ALL))
    {
        // Yes
        idi = IDI_UPDATE_MULT;
        if (IsFlagSet(uFlags, DUM_ORPHAN))
        {
            // In this case, pszPath should be the briefcase root
            ASSERT(pszPath);
            
            if (IsBriefcaseEmpty(pszPath))
                ids = IDS_MSG_NoFiles;
            else
                ids = IDS_MSG_AllOrphans;
        }
        else if (IsFlagSet(uFlags, DUM_UPTODATE))
            ids = IDS_MSG_AllUptodate;
        else if (IsFlagSet(uFlags, DUM_UNAVAILABLE))
            ids = IDS_MSG_AllSomeUnavailable;
        else
        {
            ASSERT(0);  // should never get here
            ids = (UINT)-1;
        }
        
        idRet = MsgBox(hwndOwner, 
            MAKEINTRESOURCE(ids), 
            MAKEINTRESOURCE(IDS_CAP_UPDATE), 
            LoadIcon(g_hinst, MAKEINTRESOURCE(idi)), 
            MB_INFO);
    }
    else
    {
        // No
        TCHAR sz[MAX_PATH];
        
        ASSERT(0 != cFiles);
        ASSERT(pszPath);
        
        // Is this a single selection?
        if (1 == cFiles)
        {
            // Yes; assume it is a folder, then decrement the count
            // of the ids it is a file 
            if (IsFlagSet(uFlags, DUM_ORPHAN))
                ids = IDS_MSG_FolderOrphan;
            else if (IsFlagSet(uFlags, DUM_UPTODATE))
                ids = IDS_MSG_FolderUptodate;
            else if (IsFlagSet(uFlags, DUM_UNAVAILABLE))
                ids = IDS_MSG_FolderUnavailable;
            else if (IsFlagSet(uFlags, DUM_SUBFOLDER_TWIN))
                ids = IDS_MSG_FolderSubfolder;
            else
            {
                ASSERT(0);  // should never get here
                ids = (UINT)-1;
            }
            
            if (FALSE == PathIsDirectory(pszPath))
            {
                ASSERT(IsFlagClear(uFlags, DUM_SUBFOLDER_TWIN));
                ids--;      // use file-oriented messages
                idi = IDI_UPDATE_FILE;
            }
            else
            {
                idi = IDI_UPDATE_FOLDER;
            }
            
            idRet = MsgBox(hwndOwner, 
                MAKEINTRESOURCE(ids), 
                MAKEINTRESOURCE(IDS_CAP_UPDATE), 
                LoadIcon(g_hinst, MAKEINTRESOURCE(idi)), 
                MB_INFO,
                PathGetDisplayName(pszPath, sz));
        }
        else
        {
            // No; multi selection
            idi = IDI_UPDATE_MULT;
            
            if (IsFlagSet(uFlags, DUM_UPTODATE))
            {
                if (IsFlagSet(uFlags, DUM_ORPHAN))
                    ids = IDS_MSG_MultiUptodateOrphan;
                else
                    ids = IDS_MSG_MultiUptodate;
            }
            else if (IsFlagSet(uFlags, DUM_ORPHAN))
                ids = IDS_MSG_MultiOrphans;
            else if (IsFlagSet(uFlags, DUM_UNAVAILABLE))
                ids = IDS_MSG_MultiUnavailable;
            else if (IsFlagSet(uFlags, DUM_SUBFOLDER_TWIN))
                ids = IDS_MSG_MultiSubfolder;
            else
            {
                ASSERT(0);  // should never get here
                ids = (UINT)-1;
            }
            
            idRet = MsgBox(hwndOwner, 
                MAKEINTRESOURCE(ids), 
                MAKEINTRESOURCE(IDS_CAP_UPDATE), 
                LoadIcon(g_hinst, MAKEINTRESOURCE(idi)), 
                MB_INFO,
                cFiles);
        }
    }
    
    return idRet;
}
    
    
/*----------------------------------------------------------
Purpose: This function does some preliminary checks to determine
whether the dialog box needs to be invoked at all.

  Sets the cOrphans and cSubfolders fields of pupdcount.
  
Returns: standard result
Cond:    --
*/
HRESULT PRIVATE PrepForUpdateAll(
                                 PCBS pcbs,
                                 PRECLIST * pprl,
                                 UPDCOUNT * pupdcount,
                                 HWND hwndProgress)
{
    HRESULT hres = E_FAIL;
    TWINRESULT tr;
    HWND hwndOwner = GetParent(hwndProgress);
    BOOL bAnyTwins;
    
    pupdcount->cSubfolders = 0;
    
    // Are there any twins in the database?
    
    tr = Sync_AnyTwins(pcbs->hbrf, &bAnyTwins);
    if (TR_SUCCESS == tr)
    {
        if (FALSE == bAnyTwins)
        {
            // No
            DoUpdateMsg(hwndOwner, Atom_GetName(pcbs->atomBrf), 1, DUM_ALL | DUM_ORPHAN);
            hres = S_FALSE;
        }
        
        // Can we get a fresh reclist?
        else 
        {
            pupdcount->cOrphans = 0;
            hres = Sync_CreateCompleteRecList(pcbs->hbrf, UpdBar_GetAbortEvt(hwndProgress), pprl);
            if (FAILED(hres))
            {
                // No
                if (E_TR_ABORT != hres)
                {
                    MsgBox(hwndOwner, MAKEINTRESOURCE(IDS_OOM_UPDATEDIALOG), 
                        MAKEINTRESOURCE(IDS_CAP_UPDATE), NULL, MB_ERROR);
                }
            }
            else
            {
                // Yes
                if (*pprl)
                {
                    hres = S_OK;
                }
                else
                {
                    hres = E_UNEXPECTED;
                }
                // (reclist is freed inFinishUpdate())
            }
        }
    }
    
    return hres;
}


/*----------------------------------------------------------
Purpose: This function does some preliminary checks to determine
whether the dialog box needs to be invoked at all.

  Sets the cOrphans and cSubfolders fields of pupdcount.
  
Returns: standard result
Cond:    --
*/
HRESULT PRIVATE PrepForUpdateSelection(
                                       PCBS pcbs,
                                       PRECLIST  * pprl,
                                       LPCTSTR pszList,
                                       UINT cFiles,
                                       UPDCOUNT * pupdcount,
                                       HWND hwndProgress)
{
    HRESULT hres;
    TWINRESULT tr;
    HTWINLIST htl;
    HWND hwndOwner = GetParent(hwndProgress);
    
    pupdcount->cSubfolders = 0;
    
    // Create a twin list
    tr = Sync_CreateTwinList(pcbs->hbrf, &htl);
    
    if (TR_SUCCESS != tr)
    {
        // Failure
        MsgBox(hwndOwner, MAKEINTRESOURCE(IDS_OOM_UPDATEDIALOG), MAKEINTRESOURCE(IDS_CAP_UPDATE),
            NULL, MB_ERROR);
        hres = E_OUTOFMEMORY;
    }
    else
    {
        LPCTSTR psz;
        UINT cOrphans = 0;
        UINT cSubfolders = 0;
        UINT i;
        
        for (i = 0, psz = pszList; i < cFiles; i++)
        {
            // Is this object really a twin?
            if (S_FALSE == Sync_IsTwin(pcbs->hbrf, psz, 0) )
            {
                // No; is this a subfolder twin?
                if (IsSubfolderTwin(pcbs->hbrf, psz))
                {
                    // Yes
                    cSubfolders++;
                }
                else
                {
                    // No
                    cOrphans++;
                }
            }
            else 
            {
                // Yes; add it to the twin list
                Sync_AddPathToTwinList(pcbs->hbrf, htl, psz, NULL);
            }
            
            DataObj_NextFile(psz);      // Set psz to next file in list
        }
        
        // Are all the selected objects orphans?
        if (cOrphans < cFiles)
        {
            // No; create the reclist 
            hres = Sync_CreateRecListEx(htl, UpdBar_GetAbortEvt(hwndProgress), pprl);
        }
        else
        {
            // Yes
            DoUpdateMsg(hwndOwner, pszList, cFiles, DUM_SELECTION | DUM_ORPHAN);
            hres = S_FALSE;
        }
        pupdcount->cOrphans = cOrphans;
        pupdcount->cSubfolders = cSubfolders;
        Sync_DestroyTwinList(htl);          // Don't need this anymore
    }
    
    return hres;
}


/*----------------------------------------------------------
Purpose: Checks for the special cases that are listed at the top 
of this file.

Returns: PSC_ flags

Cond:    --
*/
UINT PRIVATE PassedSpecialCases(
                                HWND hwndOwner,
                                LPCTSTR pszList,
                                UPDCOUNT * pupdcount,
                                UINT uFlags)        // UF_ flags
{
    UINT uRet = 0;
    UINT dum = 0;
    UINT cSomeAction = pupdcount->cDoSomething + pupdcount->cConflict;
    
    // Is this Update All?
    if (IsFlagSet(uFlags, UF_ALL))
    {
        // Yes
        if (0 < pupdcount->cOrphans)
        {
            // Case A2
            dum = DUM_ALL | DUM_ORPHAN;
        }
        else if (0 == pupdcount->cUnavailable)
        {
            if (0 == cSomeAction)
            {
                // Case A3
                dum = DUM_ALL | DUM_UPTODATE;
            }
            else
            {
                // Case A4
                uRet = PSC_SHOWDIALOG;
            }
        }
        else
        {
            // Cases A5 and A6
            dum = DUM_ALL | DUM_UNAVAILABLE;
            uRet = PSC_SHOWDIALOG;
        }
        
#ifdef DEBUG
        
        if (IsFlagSet(g_uDumpFlags, DF_UPDATECOUNT))
        {
            TRACE_MSG(TF_ALWAYS, TEXT("Update All counts: files = %u, orphans = %u, unavailable = %u, dosomething = %u, conflict = %u, subfolders = %u"),
                pupdcount->cFiles, pupdcount->cOrphans, 
                pupdcount->cUnavailable, pupdcount->cDoSomething,
                pupdcount->cConflict, pupdcount->cSubfolders);
        }
        
#endif
    }
    else
    {
        // No; single selection?
        
        // Take caution in the comparisons below.  The counts do not
        // have a 1-to-1 correspondence.  They are split into two 
        // groups:        cFiles <---> cOrphans  <---> cSubfolders
        //          cUnavailable <---> cDoSomething
        //
        // This means comparing cFiles with cDoSomething or cUnavailable
        // will produce bogus results in the case when folders are 
        // selected.
        //
        // As long as the comparisons below do not break these limits,
        // everything is okay.
        
        if (1 == pupdcount->cFiles)
        {
            // Yes
            ASSERT(2 > pupdcount->cOrphans);
            ASSERT(2 > pupdcount->cSubfolders);
            if (1 == pupdcount->cOrphans)
            {
                // Case S1
                dum = DUM_SELECTION | DUM_ORPHAN;
            }
            else if (0 == pupdcount->cUnavailable)
            {
                if (0 == cSomeAction)
                {
                    if (0 == pupdcount->cSubfolders)
                    {
                        // Case S2
                        dum = DUM_SELECTION | DUM_UPTODATE;
                    }
                    else
                    {
                        dum = DUM_SELECTION | DUM_SUBFOLDER_TWIN;
                    }
                }
                else
                {
                    // Case S3
                    uRet = PSC_SHOWDIALOG;
                }
            }
            else 
            {
                // Case S4
                dum = DUM_SELECTION | DUM_UNAVAILABLE;
                uRet = PSC_SHOWDIALOG;
            }
        }
        else
        {
            // No; this is a multi selection
            
            if (0 < pupdcount->cSubfolders)
            {
                DoUpdateMsg(hwndOwner, pszList, pupdcount->cSubfolders, DUM_SELECTION | DUM_SUBFOLDER_TWIN);
                goto Leave;  // HACK
            }
            
            if (pupdcount->cFiles == pupdcount->cOrphans)
            {
                // Case S5
                dum = DUM_SELECTION | DUM_ORPHAN;
            }
            else if (0 < pupdcount->cUnavailable)
            {
                if (0 < cSomeAction)
                {
                    if (0 == pupdcount->cOrphans)
                    {
                        // Case S6
                        dum = DUM_SELECTION | DUM_UNAVAILABLE;
                        uRet = PSC_SHOWDIALOG;
                    }
                    else
                    {
                        // Case S7
                        dum = DUM_SELECTION | DUM_UNAVAILABLE;
                        uRet = PSC_SHOWDIALOG | PSC_POSTMSGBOX;
                    }
                }
                else 
                {
                    if (0 == pupdcount->cOrphans)
                    {
                        // Case S8
                        dum = DUM_SELECTION | DUM_UNAVAILABLE;
                        uRet = PSC_SHOWDIALOG;
                    }
                    else
                    {
                        // Case S9
                        dum = DUM_SELECTION | DUM_UNAVAILABLE;
                        uRet = PSC_SHOWDIALOG | PSC_POSTMSGBOX;
                    }
                }
            }
            else
            {
                if (0 < cSomeAction)
                {
                    if (0 == pupdcount->cOrphans)
                    {
                        // Case S10
                        uRet = PSC_SHOWDIALOG;
                    }
                    else
                    {
                        // Case S11
                        uRet = PSC_SHOWDIALOG | PSC_POSTMSGBOX;
                    }
                }
                else 
                {
                    if (0 == pupdcount->cOrphans)
                    {
                        // Case S12
                        dum = DUM_SELECTION | DUM_UPTODATE;
                    }
                    else
                    {
                        // Case S13
                        dum = DUM_SELECTION | DUM_UPTODATE | DUM_ORPHAN;
                    }
                }
            }
        }
        
Leave:
        ;
#ifdef DEBUG
        
        if (IsFlagSet(g_uDumpFlags, DF_UPDATECOUNT))
        {
            TRACE_MSG(TF_ALWAYS, TEXT("Update selection counts: files = %u, orphans = %u, unavailable = %u, dosomething = %u, conflict = %u, subfolders = %u"),
                pupdcount->cFiles, pupdcount->cOrphans, 
                pupdcount->cUnavailable, pupdcount->cDoSomething,
                pupdcount->cConflict, pupdcount->cSubfolders);
        }
        
#endif
    }
    
    if (0 != dum)
    {
        DoUpdateMsg(hwndOwner, pszList, pupdcount->cFiles, dum);
    }
    
    return uRet;
}


/*----------------------------------------------------------
Purpose: Show the update dialog and perform the reconcilation
if the user chooses OK

Returns: standard result
Cond:    --
*/
HRESULT PUBLIC Upd_DoModal(
                           HWND hwndOwner,
                           CBS * pcbs,
                           LPCTSTR pszList,         // May be NULL if uFlags == UF_ALL
                           UINT cFiles,
                           UINT uFlags)
{
    INT_PTR nRet;
    HRESULT hres;
    PRECLIST prl;
    UPDCOUNT updcount;
    HWND hwndProgress;
    
    hwndProgress = UpdBar_Show(hwndOwner, UB_CHECKING, DELAY_UPDBAR);
    
    // Get a reclist and other useful information
    updcount.cFiles = cFiles;
    
    if (IsFlagSet(uFlags, UF_ALL))
    {
        hres = PrepForUpdateAll(pcbs, &prl, &updcount, hwndProgress);
    }
    else
    {
        hres = PrepForUpdateSelection(pcbs, &prl, pszList, cFiles, &updcount, hwndProgress);
    }
    
    UpdBar_Kill(hwndProgress);
    
    if (S_OK == GetScode(hres))
    {
        XUPDSTRUCT xupd;
        xupd.lprl = prl;
        xupd.pcbs = pcbs;
        xupd.uFlags = uFlags;
        xupd.hdpa = ComposeUpdateList(pcbs, prl, &updcount, hwndOwner);
        xupd.cDoSomething = updcount.cDoSomething;
        
        if (NULL == xupd.hdpa)
        {
            hres = E_OUTOFMEMORY;
            MsgBox(hwndOwner, MAKEINTRESOURCE(IDS_OOM_UPDATEDIALOG), MAKEINTRESOURCE(IDS_CAP_UPDATE),
                NULL, MB_ERROR);
        }
        else
        {
            // Check for some of those special cases listed at top of file
            UINT uVal = PassedSpecialCases(hwndOwner, pszList, &updcount, uFlags);
            
            // Show the update dialog?
            if (IsFlagSet(uVal, PSC_SHOWDIALOG))
            {
                // Yes
                nRet = DoModal(hwndOwner, Upd_WrapperProc, IDD_UPDATE, (LPARAM)&xupd);
                
                switch (nRet)
                {
                case IDOK:
                    // Reconcile!
                    
                    hwndProgress = UpdBar_Show(hwndOwner, UB_UPDATING, 0);
                    
                    Sync_ReconcileRecList(prl, Atom_GetName(pcbs->atomBrf),
                        hwndProgress, RF_DEFAULT);
                    
                    UpdBar_Kill(hwndProgress);
                    
                    // Show a summary messagebox?
                    if (IsFlagSet(uVal, PSC_POSTMSGBOX))
                    {
                        // Yes
                        DoUpdateMsg(hwndOwner, pszList, updcount.cOrphans, DUM_SELECTION | DUM_ORPHAN);
                    }
                    
                    // Fall thru
                    //  |    |
                    //  v    v
                    
                case IDCANCEL:
                    hres = NOERROR;
                    break;
                    
                case -1:
                    MsgBox(hwndOwner, MAKEINTRESOURCE(IDS_OOM_UPDATEDIALOG), MAKEINTRESOURCE(IDS_CAP_UPDATE),
                        NULL, MB_ERROR);
                    hres = E_OUTOFMEMORY;
                    break;
                    
                default:
                    ASSERT(0);
                    break;
                }
            }
            
            DPA_Destroy(xupd.hdpa);
        }
        
        Sync_DestroyRecList(prl);
    }
    return hres;
}
