//----------------------------------------------------------------------------
// ObjectWindows
// (C) Copyright 1993, 1994 by Borland International, All Rights Reserved
//
//   Implements classes TStorageDocument, TDocFile, streams
//----------------------------------------------------------------------------
#define INC_OLE2
#include <owl/owlpch.h>
#include <owl/docview.h>      // force Windows headers in before OLE
#include <osl/ustring.h>
#include <owl/stgdoc.h>

// Simple refcount debug assistant
//
#if defined(CHECK_REFCOUNT)
static void RefCountCheck(IStorage far* si) {
  uint32 count = si->AddRef();
  count = si->Release();
}
#else
# define RefCountCheck(si)
#endif

const int B_size    = 516;                // default buffer size
const char DefaultStreamName[] = "Contents";

//
//  class TStorageBuf
//  ----- -----------
//
class  _OWLCLASS_RTL TStorageBuf : public streambuf {
  public:
    // constructors, destructor
    TStorageBuf _FAR * open(IStorage& stg, LPCSTR name, int omode);
    TStorageBuf();                       // make a closed TStorageBuf
    virtual ~TStorageBuf();

    int is_open() { return opened; }     // is the file open
    IStream* fd() { return strm; }

    TStorageBuf _FAR * close();          // flush and close file
//  TStorageBuf _FAR * attach(IStream*); // attach this TStorageBuf to opened IStream
    virtual int overflow(int = EOF);
    virtual int underflow();
    virtual int sync();
    virtual streampos  seekoff(streamoff, ios::seek_dir, int);
    virtual streambuf _FAR * setbuf(char _FAR *, int);

  protected:
//  IStorage* stg;       // parent storage
    IStream*  strm;
    int       mode;      // the opened mode
    short     opened;    // non-zero if stream is open
    uint64    last_seek;
    char      lahead[2]; // current input char if unbuffered
};

//
//  class TStorageStreamBase
//  ----- ------------------
//
class _OWLCLASS_RTL TStorageStreamBase : virtual public ios {
  public:
    TStorageStreamBase(IStorage& stg, const char far* name, int mode);
   ~TStorageStreamBase() {}
    void    setbuf(char _FAR *, int);
    void    close();

    TStorageBuf buf;
};

//
//  class TStorageInStream
//  ----- ----------------
//
class _OWLCLASS_RTL TStorageInStream : public TStorageStreamBase,
                                       public TInStream {
  public:
    TStorageInStream(TStorageDocument& doc,  const char far* name, int mode)
                           : TInStream(doc,         name,     mode),
                   TStorageStreamBase(*doc.StorageI,name,     mode) {}
   ~TStorageInStream() {}
};

//
//  class TStorageOutStream
//  ----- -----------------
//
class _OWLCLASS_RTL TStorageOutStream : public TStorageStreamBase,
                                        public TOutStream {
  public:
    TStorageOutStream(TStorageDocument& doc,  const char far* name, int mode)
                           : TOutStream(doc,         name,     mode),
                    TStorageStreamBase(*doc.StorageI,name,     mode) {}
   ~TStorageOutStream() {}
};


//----------------------------------------------------------------------------
//  class TStorageDocument
//

TStorageDocument::~TStorageDocument()
{
  ReleaseDoc();
  SetDirty(false); // ~TDocument() will Close() after destroying children
}

//
// Release the IStorage and close the document
//
bool
TStorageDocument::ReleaseDoc()
{
  CanRelease = true; // now we can release the storage
  return Close();
}

//
// Open the compound file with a given path
//
bool
TStorageDocument::Open(int omode, const char far* name)
{
  if (StorageI > 0) {
    return true;
  }
  do {  // establish try block
    HRESULT hres;
    IStorage* parentStg;
    int pmode = 0;

    if (!omode)
      omode = GetOpenMode();

    if (GetParentDoc()) {
      pmode = GetParentDoc()->GetOpenMode();
      if (!(GetParentDoc()->Open(pmode ? pmode : omode, name)))
        return false;
    }
    ++OpenCount;
    if (!omode)
      omode = pmode;
    if (!(omode & (ofRead | ofWrite)))
      break;

    // Provide default share mode flags & enforce root storage restrictions
    // when in direct mode
    //
    if (!(omode & shMask)) {
      if (!(omode & ofTransacted) && !GetParentDoc()) { // direct mode root stg
        if (omode & ofWrite)
          omode |= ofRead | shNone; // let others do nothing if we are writing
        else
          omode |= shRead;          // let others only read if we are readonly
      }
      else {
        if (omode & ofWrite)
          omode |= shRead;       // let others only read if we are writing
        else
          omode |= shReadWrite;  // let others read/write if we are readonly
      }
    }

    ThisOpen = omode;
    SetOpenMode(omode);  // remember the open mode
    if (name && name[0])
      SetDocPath(name);
    else
      name = GetDocPath();
    long shareMode = ((omode & shMask) - shCompat) >> 5;
    long grfMode = ((omode & (ofRead|ofWrite)) - 1)
                 | shareMode
                 | ((long)(omode & (ofTransacted|ofPreserve|ofPriority)) << 4)
                 | ((omode & ofTemporary) ? STGM_DELETEONRELEASE : 0);
    long childMode = (grfMode ^ shareMode) | STGM_SHARE_EXCLUSIVE;

    bool stgopen = true;
    bool stgcreate = false;

    if (omode & ofWrite) {
      if (omode & ofNoReplace) {
        stgopen = false;
        stgcreate = true;
      }
      else if (!(omode & ofNoCreate)) {
        stgcreate = true;
        if (omode & ofTruncate || !(omode & (ofAtEnd | ofAppend))) {
          stgopen = false;
          grfMode |= STGM_CREATE;
          childMode |= STGM_CREATE;
        }
      }
    }
    if (GetParentDoc()) {
      if (!GetParentDoc()->GetProperty(FindProperty("IStorage Instance"),
                                       &parentStg, 0))
        break;
      if (stgopen) {
        hres = parentStg->OpenStorage(OleStr(name), 0, childMode, 0, 0, &StorageI);
        if (SUCCEEDED(hres))
          stgcreate = false;
      }
      if (stgcreate) {
        hres = parentStg->CreateStorage(OleStr(name), childMode, 0, 0, &StorageI);
      }
    }
    else {
      if (stgopen) {
        hres = ::StgOpenStorage(OleStr(name), 0, grfMode, 0, 0, &StorageI);
        if (SUCCEEDED(hres))
          stgcreate = false;
      }
      if (stgcreate) {
        hres = ::StgCreateDocfile(OleStr(name), grfMode, 0, &StorageI);
      }
    }
    RefCountCheck(StorageI);

    if (!SUCCEEDED(hres))
      break;
    NotifyViews(vnDocOpened,ThisOpen);
    return true;  // successful return
  } while (0);    // dummy for break scoping, never executed

  // exception handling
  //
  if (GetParentDoc())
    GetParentDoc()->Close();
  --OpenCount;

  return false;
}

const int stgRdWrMask  = (int)(STGM_READWRITE | STGM_READ | STGM_WRITE);
const int stgShareMask = (int)(STGM_SHARE_DENY_NONE | STGM_SHARE_DENY_READ |
                         STGM_SHARE_DENY_WRITE| STGM_SHARE_EXCLUSIVE);
const long stgModeMask  = (STGM_TRANSACTED | STGM_PRIORITY | STGM_CONVERT);

//
// Give an IStorage to document. This typically happens for OLE servers.
//
bool
TStorageDocument::SetStorage(IStorage* stg, bool remember)
{
  if (stg == StorageI)
    return true; // already set

  if (StorageI) {
    RefCountCheck(StorageI);
    if (remember) {
      StorageI->Release();  // Release the ole one
      OrgStorageI = 0;
    }
    else
      OrgStorageI = StorageI;
  }

  StorageI = stg;
  if (!StorageI)
    return true;  // done

  StorageI->AddRef();
  RefCountCheck(StorageI);

  STATSTG stgInfo;
  if (!SUCCEEDED(stg->Stat(&stgInfo, 0)))
    return false;

  SetOpenMode((int)((stgInfo.grfMode & stgRdWrMask) + 1)
           | (int)(((stgInfo.grfMode & stgShareMask) << 5) + shCompat)
           | (int)((stgInfo.grfMode & stgModeMask) >> 4)
           | ((stgInfo.grfMode & STGM_DELETEONRELEASE) ? ofTemporary : 0));

  ThisOpen = GetOpenMode();
  if (remember)
    if (stgInfo.pwcsName)
      SetDocPath(OleStr(stgInfo.pwcsName));
    else
      SetDocPath("  ");

  if (stgInfo.pwcsName) {
    IMalloc* memmgr;
    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &memmgr))) {
      memmgr->Free(stgInfo.pwcsName);    //!CQ really do this here?
      memmgr->Release();
    }
  }

//  NotifyViews(vnDocOpened,ThisOpen);
  return true;
}

//
// Restores the original root IStorage before the save operation
//
bool
TStorageDocument::RestoreStorage()
{
  if (OrgStorageI) {
    if (StorageI)
      StorageI->Release();

    StorageI = OrgStorageI;
    OrgStorageI = 0;
  }

  return true;
}

//
//  Create an istorage based on a memeroy handle
//
bool
TStorageDocument::OpenHandle(int omode, HANDLE hGlobal)
{
  int pmode = 0;

  if (!omode)
    omode = GetOpenMode();

  if (!omode)
    omode = pmode;
  if (!(omode & (ofRead | ofWrite)))
    return false;
  if (!(omode & shMask))
    omode |= shNone;

  ThisOpen = omode;
  SetOpenMode(omode);  // remember the open mode
  long shareMode = ((omode & shMask) - shCompat) >> 5;
  long grfMode = ((omode & (ofRead|ofWrite)) - 1)
               | shareMode
               | ((long)(omode & (ofTransacted|ofPreserve|ofPriority)) << 4)
               | STGM_CREATE;
  if (!SUCCEEDED(CreateILockBytesOnHGlobal(hGlobal, (omode & ofTemporary)!=0, &LockBytes)))
    return false;
  if (!SUCCEEDED(StgCreateDocfileOnILockBytes(LockBytes, grfMode, 0, &StorageI))) {
    LockBytes->Release();
    LockBytes = 0;
    return false;
  }
  ++OpenCount;
  NotifyViews(vnDocOpened,omode);
  return true;
}

//
//  Replace the IStorage with an istorage based on a memory handle
//
bool
TStorageDocument::SetHandle(int omode, HANDLE hGlobal, bool create, bool remember)
{
  int pmode = 0;

  if (!omode)
    omode = GetOpenMode();

  if (!omode)
    omode = pmode;
  if (!(omode & (ofRead | ofWrite)))
    return false;
  if (!(omode & shMask))
    omode |= shNone;

  ThisOpen = omode;
  SetOpenMode(omode);  // remember the open mode
  long shareMode = ((omode & shMask) - shCompat) >> 5;
  long grfMode = ((omode & (ofRead|ofWrite)) - 1)
               | shareMode
               | ((long)(omode & (ofTransacted|ofPreserve|ofPriority)) << 4);
  if (!SUCCEEDED(CreateILockBytesOnHGlobal(hGlobal, (omode & ofTemporary)!=0, &LockBytes)))
    return false;

  IStorage* storageI = 0;
  if (!SUCCEEDED(StgOpenStorageOnILockBytes(LockBytes, 0, grfMode, 0, 0, &storageI))) {
    if (create && !SUCCEEDED(StgCreateDocfileOnILockBytes(LockBytes,
                             grfMode |STGM_CREATE, 0, &storageI))) {
      LockBytes->Release();
      LockBytes = 0;
      return false;
    }

    if (!storageI)
      return false;
  }

  RefCountCheck(storageI);

  // Don't change the embedded flag
  bool embed = IsEmbedded();
  SetStorage(storageI, remember);
  SetEmbedded(embed);

  storageI->Release(); // release extra refcount held by SetStorage
  return true;
}

//
//  Get the global handle from ILockBytes
//
bool
TStorageDocument::GetHandle(HGLOBAL* handle)
{
  if (!LockBytes)
    return false;

  GetHGlobalFromILockBytes(LockBytes, handle);
  return true;
}

//
//
//
bool
TStorageDocument::Close()
{
  if (!StorageI || !CanRelease)
    return true;

  if (StorageI)
    StorageI->Release();

  StorageI = 0;
  NotifyViews(vnDocClosed,ThisOpen);
  SetDirty(false);
  CanRelease = false;
  if (GetParentDoc())
    GetParentDoc()->Close();
  return true;
}

//
//
//
void
TStorageDocument::DetachStream(TStream& strm)
{
  TDocument::DetachStream(strm);
  TStorageDocument::Close();
}

//
//
//
TInStream*
TStorageDocument::InStream(int omode, const char far* strmId)
{
  TInStream* inStream;
  if (omode == ofParent)
    omode = IsOpen() ? ThisOpen : GetOpenMode();
  if (!(omode & ofRead))
    return 0;
  if (!TStorageDocument::Open(GetOpenMode() ? GetOpenMode() | ofRead : omode, strmId))
    return 0;

  inStream = new TStorageInStream(*this, strmId, omode);
  if (inStream && !inStream->good()) {
    delete inStream;
    TStorageDocument::Close();  // close
    return 0;
  }

  return inStream;
}

//
//
//
TOutStream*
TStorageDocument::OutStream(int omode, const char far* strmId)
{
  TOutStream* outStream;
  if (omode == ofParent)
    omode = IsOpen() ? ThisOpen : GetOpenMode();
  if (!(omode & ofWrite))
    return 0;
  if (!TStorageDocument::Open(GetOpenMode() ? GetOpenMode() | ofWrite : omode, strmId))
    return 0;
  outStream = new TStorageOutStream(*this, strmId, omode); // change
  if (outStream && !outStream->good()) {
    delete outStream;
    TStorageDocument::Close();  // change
    return 0;
  }
  SetDirty();     // we don't really know at this point if it will be dirty!!
  return outStream;
}

//
//
//
bool
TStorageDocument::SetDocPath(const char far* path)
{
  TDocument::SetDocPath(path);
  return true;
}

//
//
//
bool
TStorageDocument::Commit(bool force)
{
  if (!TDocument::Commit(force))      // flush views and child docs
    return false;
  if (!StorageI)
    return true;                      // return OK if storage already released

  CommitTransactedStorage();
  SetDirty(false);
  return true;
}

//
//
//
bool
TStorageDocument::CommitTransactedStorage()
{
  HRESULT cres = StorageI->Commit(0);  // try 2phase commit first
  if (!SUCCEEDED(cres)) // check for STG_S_TRYOVERWRITE, but GetScode not in lib
    cres = StorageI->Commit(STGC_OVERWRITE);   // try less robust method
  if (!SUCCEEDED(cres))
    return false;

  return true;
}

//
//
//
bool
TStorageDocument::Revert(bool clear)
{
  if (!StorageI)
    return true;                    // return OK if storage already released
  if (!TDocument::Revert(clear) || !SUCCEEDED(StorageI->Revert()))
    return false;
  SetDirty(false);
  return true;
}

//
//
//
static char* PropNames[] = {
  "Create Time",      // CreateTime
  "Modify Time",      // ModifyTime
  "Access Time",      // AccessTime
  "Storage Size",     // StorageSize
  "IStorage Instance",// IStorageInstance
};

//
//
//
static int PropFlags[] = {
  pfGetBinary|pfGetText,   // CreateTime
  pfGetBinary|pfGetText,   // ModifyTime
  pfGetBinary|pfGetText,   // AccessTime
  pfGetBinary|pfGetText,   // StorageSize
  pfGetBinary,             // IStorage
};

//
//
//
const char*
TStorageDocument::PropertyName(int index)
{
  if (index <= PrevProperty)
    return TDocument::PropertyName(index);
  else if (index < NextProperty)
    return PropNames[index-PrevProperty-1];
  else
    return 0;
}

//
//
//
int
TStorageDocument::PropertyFlags(int index)
{
  if (index <= PrevProperty)
    return TDocument::PropertyFlags(index);
  else if (index < NextProperty)
    return PropFlags[index-PrevProperty-1];
  else
    return 0;
}

//
//
//
int
TStorageDocument::FindProperty(const char far* name)
{
  for (int i = 0; i < NextProperty-PrevProperty-1; i++)
    if (strcmp(PropNames[i], name) == 0)
      return i+PrevProperty+1;
  return 0;
}

//
//
//
int
TStorageDocument::GetProperty(int prop, void far* dest, int textlen)
{
  STATSTG stgInfo;

  switch (prop) {
    case IStorageInstance:
      if (textlen)
        return 0;
      *(IStorage*far*)dest = StorageI;
      return sizeof(IStorage*);

    default:
      if (StorageI) {
        StorageI->Stat(&stgInfo, STATFLAG_NONAME);
        switch (prop) {
          case StorageSize:
            if (!textlen) {
              *(ulong far*)dest = uint64(stgInfo.cbSize).LowPart;
              return sizeof(ulong);
            }
            else {
              char buf[10];
              int len = wsprintf(buf, "%ld", uint64(stgInfo.cbSize).LowPart);
              if (textlen > len)
                textlen = len;
              memcpy(dest, buf, textlen);
              *((char far*)dest + textlen) = 0;
              return len;
            }
          case AccessTime:
#if defined(BI_PLAT_WIN32)
            return FormatFileTime(&stgInfo.atime, dest, textlen);
#endif
          case CreateTime:
#if defined(BI_PLAT_WIN32)
            return FormatFileTime(&stgInfo.ctime, dest, textlen);
#endif
          case ModifyTime:
            return FormatFileTime(&stgInfo.mtime, dest, textlen);
        }
      }
      return TDocument::GetProperty(prop, dest, textlen);
  }
}

//
//
//
bool
TStorageDocument::SetProperty(int prop, const void far* src)
{
  // docfile properties currently not settable
  //
  return TDocument::SetProperty(prop, src);
}

//
//
//
IMPLEMENT_STREAMABLE1(TStorageDocument, TDocument);

//
//
//
void*
TStorageDocument::Streamer::Read(ipstream& is, uint32 /*version*/) const
{
  ReadBaseObject((TDocument*)GetObject(), is);
  return GetObject();
}

//
//
//
void
TStorageDocument::Streamer::Write(opstream& os) const
{
  WriteBaseObject((TDocument*)GetObject(), os);
}

//----------------------------------------------------------------------------
//  class TStorageStreamBase
//

TStorageStreamBase::TStorageStreamBase(IStorage& stg, const char far* name, int mode)
:
  buf()
{
  ios::init(&buf);

  if (buf.is_open())
    clear(ios::failbit);     // fail - already open
  else if (buf.open(stg, name, mode))
    clear(ios::goodbit);     // successful open
  else
    clear(ios::badbit);      // open failed
}

void TStorageStreamBase::setbuf(char* newbuf, int len)
{
  if (buf.setbuf(newbuf, len))
    clear(ios::goodbit);
  else
    setstate(ios::failbit);
}

void TStorageStreamBase::close()
{
  if (buf.close())
    clear(ios::goodbit);
  else
    setstate(ios::failbit);
}

//----------------------------------------------------------------------------
//  class TStorageBuf
//

//
// make a closed TStorageBuf
//
TStorageBuf::TStorageBuf()
{
  mode = 0;
  opened = 0;
  char* p = new char[B_size];
  if (p) {
    setb(p, p+B_size, 1);   // ~streambuf() will delete buffer
    setp(p+4, p+4);
    setg(p, p+4, p+4);
  }
}

//
// We assume that mode= means that we attached to an already-open file,
// and should not now close it.  We do flush it in any case.
//
TStorageBuf::~TStorageBuf()
{
  if (mode)
    close();
  else
    overflow(EOF);
}

//
// Open or create IStream with mode and protection, attach to this TStorageBuf.
//
TStorageBuf* TStorageBuf::open(IStorage& stg, const char far* name, int omode)
{
//int share = omode & shMask;
//if (share < shCompat)
//  share = shNone;
//how = (share-shCompat) >> 5;

  if (opened || !omode)
    return 0;

  if (!name)
    name = DefaultStreamName;

  bool  stgopen = true;
  bool  stgcreate = false;
  uint32 how = STGM_SHARE_EXCLUSIVE;  // must open streams and child stg exclusive

  if (omode & ofWrite) {
    if (!(mode & (ofAtEnd | ofAppend | ofRead)))
      omode |= ofTruncate;  // output implies truncate unless in, app, or ate
    if (omode & ofRead)
      how |= STGM_READWRITE;
    else
      how |= STGM_WRITE;
    if (omode & ofNoReplace) {
      stgopen = false;
      stgcreate = true;
    }
    else if (!(omode & ofNoCreate)) {
      stgcreate = true;
      if (omode & ofTruncate) {
        stgopen = false;
        how |= STGM_CREATE;
      }
    }
  }
  else if (omode & ofRead)
    how |= STGM_READ;
  else
    return 0;   // must specfify in, out, or in/out

//if (omode & ofAppend)    // what does this mean for docfile?!!
//    how |= O_APPEND;

  // Now try to open or create
  //
  if (stgopen) {
    HRESULT hres = stg.OpenStream(OleStr(name), 0, how, 0, &strm);
    if (SUCCEEDED(hres))
      stgcreate = false;
    else
      return 0;
  }
  if (stgcreate) {
    HRESULT hres = stg.CreateStream(OleStr(name), how, 0, 0, &strm);
    if (!SUCCEEDED(hres))
      return 0;
  }

  // Finish up
  //
  opened = 1;
  mode = omode;
  if ((omode & ofAtEnd) != 0
      && !SUCCEEDED(strm->Seek(int64(), STREAM_SEEK_END, (ULARGE_INTEGER*)&last_seek))) {
    strm->Release();
    strm = 0;
    return 0;
  }
  char* b = base();       // buffer address
  int pb = b ? ((blen() > 8) ? 4 : 1) : 0;    // putback area size
  setp(b+pb, b+pb);
  setg(b, b+pb, b+pb);

  return this;
}

#if 0
//
// attach this TStorageBuf to open IStream -- assume fd is actually open
//
TStorageBuf* TStorageBuf::attach(IStream* f)
{
  STATSTG stat;
  if (opened)
    return 0;

  if (f->Stat(&stat, STATFLAG_NONAME) != 0)
    return 0;

  if (f->CreateStream(STREAMNAME,stat.grfMode & ~STGM_TRANSACTED,0,0,&strm)!)
    return 0;

  stg = f;        // assumed to be valid
  opened = 1;
  int rwmode = stat.grfMode & (STGM_READ | STGM_WRITE | STGM_READWRITE);
  mode = rwmode != STGM_WRITE ? ofRead : 0;
  if (rwmode != STGM_READ)
    mode |= ofWrite;

  char* b = base();       // buffer address
  if (!b) {
    b = new char[B_size];
    if (b)
      setb(b, b+B_size, 1);   // ~streambuf() will delete buffer
  }
  int pb = b ? ((blen() > 8) ? 4 : 1) : 0;    // putback area size
  setp(b+pb, b+pb);
  setg(b, b+pb, b+pb);
  return this;
}
#endif

streambuf* TStorageBuf::setbuf(char* b, int len)
{
  if (opened && base())
    return 0;        // already open with a buffer -- no change

  int pb;            // putback area size
  if (b && len > 0)  // use b as the new buffer
    pb = (len > 8) ? 4 : 1; // guard against tiny buffers
  else {             // unbuffered
    len = pb = 0;
    b = 0;
  }
  setb(b, b+len, 0);      // will delete old buffer if needed
  setp(b+pb, b+pb);
  setg(b, b+pb, b+pb);
  return this;
}

//
// Seek file to position.
// We take a simple approach, and don't check for small position changes
// within the current buffer.
//
streampos
TStorageBuf::seekoff(streamoff off, ios::seek_dir dir, int /* mode ignored */)
{
  long loff = off;
  HRESULT hres;
  unsigned long actual;
  int count = out_waiting();

  if (count) {  // flush the output
    hres = strm->Write(pbase(), (ulong)count, &actual);
    if (!SUCCEEDED(hres) || (int)actual != count)
      return EOF;
  }
  else if (dir == ios::cur)
    if ((count = in_avail()) != 0) {
      loff -= count;

      //  if we're in text mode, need to allow for newlines
      //  in the buffer
      //
      if ((mode & ofBinary) == 0) {
        char *tptr = gptr();
        while (tptr != egptr())
          if (*tptr++ == '\n')
            loff--;
      }
    }

  uint32 w = (dir == ios::beg) ? STREAM_SEEK_SET
       : ((dir == ios::cur) ? STREAM_SEEK_CUR
       :       /* ios::end */ STREAM_SEEK_END);

  if (!SUCCEEDED(strm->Seek(int64(loff), w, (ULARGE_INTEGER*)&last_seek)))
    return EOF; //?!!
  if (!unbuffered() && base()) {      // set up get and put areas
    int pb = (blen() > 8) ? 4 : 1;  // putback area size
    char *b = base();
    setp(b+pb, b+pb);
    setg(b, b+pb, b+pb);
  }
  return last_seek.LowPart;
}

//
//
//
int TStorageBuf::sync()
{
  HRESULT hres;
  if (!opened)
    return EOF;

  ulong actual;
  int count = out_waiting();
  if (count) {
    char* curp;
    char* srcp = pbase();
    char* endp = srcp + count;

    // convert LF's to CR/LF if text mode
    //
    if ((mode & ofBinary) == 0) {
      for (curp = srcp; curp < endp; curp++) {
        if (*curp == '\n') {
          *curp = '\r';
          count = (int)(curp - srcp + 1);
          hres = strm->Write(srcp, (unsigned long)count, &actual);
          if (!SUCCEEDED(hres) || (int)actual != count)
            return EOF;
          *(srcp = curp) = '\n';
        }
      }
      count = (int)(curp - srcp);  // write what remains in the buffer below
    }
    hres = strm->Write(srcp, (unsigned long)count, &actual);
    if (!SUCCEEDED(hres) || (int)actual != count)
        return EOF;

    // reset get and put areas
    //
    int pb = (blen() > 8) ? 4 : 1;  // putback area size
    char *b = base();
    setp(b+pb, b+blen());
    setg(b, b+pb, b+pb);

  }
  else if (in_avail()) {
    if (!SUCCEEDED(strm->Seek(int64(long(-in_avail())), STREAM_SEEK_CUR, (ULARGE_INTEGER*)&last_seek)))
      return EOF;
    setg(eback(), gptr(), gptr());
    setp(gptr(), gptr());
  }
  return 0;
}

//
//
//
int TStorageBuf::underflow()
{
  HRESULT  hres;
  ulong    actual;
  unsigned count;  // input character count
  int      c;      // the return value

  if (!opened || (mode & (ofRead | ofWrite)) == ofWrite)
    return EOF;

  if (in_avail())                   // no action needed
    return (unsigned char)*gptr();

  if (!unbuffered() && base()) {    // this is buffered
    if (sync() != 0)
      return EOF;

    // find buffer data
    //
    int pb = (blen() > 8) ? 4 : 1;  // putback area size
    char* begp = base() + pb;

    // read in a new buffer
    //
    hres = strm->Read(begp, blen()-pb, &actual);
    if (!SUCCEEDED(hres))
      return EOF;
    count = (unsigned)actual;

    // remove CR's if text mode
    //
    if ((mode & ofBinary) == 0) {
      char* endp = begp + count;
      char* dstp = 0;
      char* srcp = 0;
      char* curp;

      for (curp = begp;  curp < endp; curp++) {
        if (*curp == '\r') {
          if (dstp) {
            memcpy(dstp, srcp, (int)(curp - srcp));
            dstp += (int)(curp - srcp);
          }
          else
            dstp = curp;
          srcp = curp + 1;
        }
      }
      if (dstp) {
        endp = dstp + (int)(curp - srcp);
        if (curp != srcp)
          memcpy(dstp, srcp, (int)(curp - srcp));
      }
      count = (int)(endp - begp);
    }

    // set up get and put areas
    //
    setg(base(), begp, begp + count);
    setp(begp, begp);

    if (count)
      c = (unsigned char)*gptr();

  }
  else {     // this is not buffered
    for (;;) {
      hres = strm->Read(lahead, 1, &actual);
      if (!SUCCEEDED(hres) || actual == 0) {
        c = EOF;
        setg(0, 0, 0);
      }
      else {
        c = (unsigned char)lahead[0];
        if ((mode & ofBinary) == 0  && c == '\r')
          continue;
        setg(lahead, lahead, lahead+1);
      }
      break;
    }
  }
  if (!count)
    c = EOF;    // end of file
  return c;
}

//
// always flush
//
int TStorageBuf::overflow(int c)
{
  if (!opened || (mode & (ofRead | ofWrite)) == ofRead)
    return EOF;

  if (unbuffered() || !base()) {
    if (c != EOF) {
      int count;
      char b[2];
      if (c == '\n' && (mode & ofBinary) == 0) {
        b[0] = '\r';
        b[1] = (char)c;
        count = 2;
      }
      else {
        b[0] = (char)c;
        count = 1;
      }
      ulong actual = 0;
      strm->Write(&c, (ulong)count, &actual);
      if ((int)actual != count)
        return EOF;
    }
  }
  else {    // now we know this is buffered and state is not bad

    // resets get and put areas
    //
    if (sync() != 0)
      return EOF;

    // reset get and put areas
    //
    int pb = (blen() > 8) ? 4 : 1;  // putback area size
    char *b = base();
    setp(b+pb, b+blen());
    setg(b, b+pb, b+pb);

    if (c != EOF) {
      sputc(c);
      gbump(1);       // pptr and gptr must be the same
    }
  }
  return 1;
}

//
// flush and close file
//
TStorageBuf* TStorageBuf::close()
{
  if (!opened)
    return 0;         // nothing to do

  int ores = 0;       // result of overflow()
  if (out_waiting() && overflow(EOF) == EOF)
    ores = 1;
  if ((mode & ofWrite) /* && !strm->Commit(0) */)
    ores = 1;

  strm->Release();    //! Question: should we release if commit fails?!!
  strm = 0;
  opened = 0;
  return ores ? 0 : this;
}
