668 lines
13 KiB
C++
668 lines
13 KiB
C++
/********************************************************************
|
|
WRAPSTOR.CPP
|
|
|
|
Owner: ErinFox
|
|
|
|
Created: 28-Feb-1997
|
|
|
|
Description: This file contains file system calls that are wrapped
|
|
with the InfoTech IStorage implementation
|
|
|
|
*********************************************************************/
|
|
|
|
|
|
// For IStorage support
|
|
#define INITGUID
|
|
|
|
#include <windows.h>
|
|
#include <basetyps.h>
|
|
#include <comdef.h>
|
|
#include <MSITStg.h>
|
|
|
|
// InfoTech includes
|
|
#include <mvopsys.h>
|
|
#include <_mvutil.h>
|
|
#include <wrapstor.h>
|
|
#include "iofts.h"
|
|
|
|
// DOSFILE support
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
#include <sys\types.h>
|
|
#include <sys\stat.h>
|
|
|
|
|
|
|
|
#define RESERVED 0
|
|
|
|
// I "borrowed" these from Tome code
|
|
#define PLI_To_PFO(PLI) ((FILEOFFSET *) PLI)
|
|
#define LI_To_FO(LI) (*PLI_To_PFO(&LI))
|
|
|
|
#define PFO_To_PLI(PFO) ((LARGE_INTEGER *) PFO)
|
|
#define FO_To_LI(FO) (*PFO_To_PLI(&FO))
|
|
|
|
|
|
// was in iofts.c
|
|
#ifdef _DEBUG
|
|
static char s_aszModule[] = __FILE__; // Used by error return functions.
|
|
#endif
|
|
|
|
// was in iofts.c
|
|
#define OPENED_HFS (BYTE)0x80
|
|
|
|
// was in iofts.c
|
|
PRIVATE HANDLE NEAR PASCAL IoftsWin32Create(LPCSTR lpstr, DWORD w);
|
|
PRIVATE HANDLE NEAR PASCAL IoftsWin32Open(LPCSTR lpstr, DWORD w);
|
|
|
|
// was in iofts.c
|
|
#define CREAT(sz, w) IoftsWin32Create(sz, w)
|
|
#define OPEN(sz, w) IoftsWin32Open(sz, w)
|
|
|
|
|
|
|
|
//
|
|
// These functions came from subfile.c
|
|
//
|
|
PUBLIC HF PASCAL FAR EXPORT_API HfOpenHfs(HFS hfs, LPCWSTR wsz,
|
|
BYTE bFlags, LPERRB lperrb)
|
|
{
|
|
HF hf;
|
|
DWORD grfMode;
|
|
HRESULT hr;
|
|
|
|
// TODO: find out how big this really should be
|
|
// OLECHAR wsz[_MAX_PATH];
|
|
// MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, -1, wsz, _MAX_PATH);
|
|
|
|
|
|
if (bFlags & HFOPEN_READWRITE)
|
|
grfMode = STGM_READWRITE;
|
|
else if (bFlags & HFOPEN_READ)
|
|
grfMode = STGM_READ;
|
|
|
|
|
|
if (!(bFlags & HFOPEN_CREATE))
|
|
{
|
|
hr = hfs->OpenStream(wsz, NULL, grfMode, RESERVED, &hf);
|
|
}
|
|
else
|
|
{
|
|
hr = hfs->CreateStream(wsz, grfMode, RESERVED, RESERVED, &hf);
|
|
}
|
|
|
|
// make sure we pass back a NULL if open/create failed
|
|
if (!SUCCEEDED(hr))
|
|
hf = NULL;
|
|
|
|
// sometimes we get passed NULL for lperrb
|
|
if (lperrb)
|
|
*lperrb = hr;
|
|
|
|
return hf;
|
|
}
|
|
|
|
|
|
PUBLIC HF PASCAL FAR EXPORT_API HfCreateFileHfs(HFS hfs, LPCSTR sz,
|
|
BYTE bFlags, LPERRB lperrb)
|
|
{
|
|
HF hf;
|
|
DWORD grfMode;
|
|
HRESULT hr;
|
|
|
|
// TODO: find out how big this really should be
|
|
OLECHAR wsz[_MAX_PATH];
|
|
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, -1, wsz, _MAX_PATH);
|
|
|
|
|
|
if (bFlags & HFOPEN_READWRITE)
|
|
grfMode = STGM_READWRITE;
|
|
else if (bFlags & HFOPEN_READ)
|
|
grfMode = STGM_READ;
|
|
|
|
hr = hfs->CreateStream(wsz, grfMode, RESERVED, RESERVED, &hf);
|
|
|
|
// make sure we pass back NULL if failure
|
|
if (!SUCCEEDED(hr))
|
|
hf = NULL;
|
|
|
|
if (lperrb)
|
|
*lperrb = hr;
|
|
|
|
return hf;
|
|
}
|
|
|
|
// This is currently the same as HfOpenHfs, but once we have Ron's
|
|
// enhancements to Tome we will be able to set the size on the file
|
|
PUBLIC HF PASCAL FAR EXPORT_API HfOpenHfsReserve(HFS hfs, LPCSTR sz,
|
|
BYTE bFlags, FILEOFFSET foReserve, LPERRB lperrb)
|
|
{
|
|
HF hf;
|
|
DWORD grfMode;
|
|
HRESULT hr;
|
|
|
|
// TODO: find out how big this really should be
|
|
OLECHAR wsz[_MAX_PATH];
|
|
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, -1, wsz, _MAX_PATH);
|
|
|
|
if (bFlags & HFOPEN_READWRITE)
|
|
grfMode = STGM_READWRITE;
|
|
else if (bFlags & HFOPEN_READ)
|
|
grfMode = STGM_READ;
|
|
|
|
if (!(bFlags & HFOPEN_CREATE))
|
|
{
|
|
hr = hfs->OpenStream(wsz, NULL, grfMode, RESERVED, &hf);
|
|
}
|
|
else
|
|
{
|
|
hr = hfs->CreateStream(wsz, grfMode, RESERVED, RESERVED, &hf);
|
|
}
|
|
|
|
if (!SUCCEEDED(hr))
|
|
hf = NULL;
|
|
|
|
if (lperrb)
|
|
*lperrb = hr;
|
|
|
|
return hf;
|
|
}
|
|
|
|
PUBLIC LONG PASCAL FAR EXPORT_API LcbReadHf(HF hf, LPVOID lpvBuffer,
|
|
LONG lcb, LPERRB lperrb)
|
|
{
|
|
ULONG cbRead;
|
|
HRESULT hr;
|
|
|
|
hr = hf->Read(lpvBuffer, lcb, &cbRead);
|
|
|
|
if (lperrb)
|
|
*lperrb = hr;
|
|
|
|
return cbRead;
|
|
}
|
|
|
|
PUBLIC LONG PASCAL FAR EXPORT_API LcbWriteHf(HF hf, LPVOID lpvBuffer,
|
|
LONG lcb, LPERRB lperrb)
|
|
{
|
|
ULONG cbWrote;
|
|
HRESULT hr;
|
|
|
|
hr = hf->Write(lpvBuffer, lcb, &cbWrote);
|
|
|
|
if (lperrb)
|
|
*lperrb = hr;
|
|
|
|
return cbWrote;
|
|
}
|
|
|
|
|
|
PUBLIC FILEOFFSET PASCAL FAR EXPORT_API FoSeekHf(HF hf, FILEOFFSET foOffset,
|
|
WORD wOrigin, LPERRB lperrb)
|
|
{
|
|
|
|
HRESULT hr;
|
|
ULARGE_INTEGER liNewPos;
|
|
LARGE_INTEGER liOffset = FO_To_LI(foOffset);
|
|
|
|
hr = hf->Seek(liOffset, (DWORD)wOrigin, &liNewPos);
|
|
|
|
if (lperrb)
|
|
*lperrb = hr;
|
|
|
|
return LI_To_FO(liNewPos);
|
|
|
|
|
|
}
|
|
|
|
PUBLIC RC PASCAL FAR EXPORT_API RcCloseHf(HF hf)
|
|
{
|
|
if (hf)
|
|
hf->Release();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
PUBLIC BOOL PASCAL FAR EXPORT_API FAccessHfs( HFS hfs, LPCSTR szName, BYTE bFlags, LPERRB lperrb)
|
|
{
|
|
HRESULT hr;
|
|
HF hf = NULL;
|
|
BOOL fRet = FALSE;
|
|
|
|
if (lperrb)
|
|
*lperrb = S_OK; // for now
|
|
|
|
// TODO: find out how big this really should be
|
|
OLECHAR wszName[_MAX_PATH];
|
|
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szName, -1, wszName, _MAX_PATH);
|
|
|
|
hr = hfs->OpenStream(wszName, NULL, STGM_READWRITE, RESERVED, &hf);
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
if (bFlags & FACCESS_EXISTS)
|
|
fRet = TRUE;
|
|
|
|
}
|
|
else if (STG_E_FILENOTFOUND == hr)
|
|
{
|
|
SetErrCode (lperrb, E_FILENOTFOUND);
|
|
}
|
|
// else
|
|
// {
|
|
// Need to set error if anything but NOTFOUND
|
|
// }
|
|
|
|
if (hf)
|
|
hf->Release();
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
PUBLIC FILEOFFSET PASCAL FAR EXPORT_API FoSizeHf(HF hf, LPERRB lperrb)
|
|
{
|
|
STATSTG stat;
|
|
HRESULT hr;
|
|
FILEOFFSET foSize;
|
|
|
|
hr = hf->Stat(&stat, STATFLAG_NONAME);
|
|
|
|
if (S_OK == hr)
|
|
foSize = LI_To_FO(stat.cbSize);
|
|
else
|
|
foSize = foNil;
|
|
|
|
if (lperrb)
|
|
*lperrb = hr;
|
|
|
|
|
|
return foSize;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// These functions came from iofts.c
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
PUBLIC HFPB FAR PASCAL FileCreate (HFPB hfpbSysFile, LPCSTR lszFilename,
|
|
int fFileType, LPERRB lperrb)
|
|
{
|
|
LPFPB lpfpb; /* Pointer to file parameter block */
|
|
HANDLE hMem;
|
|
HFPB hfpb;
|
|
HRESULT hr;
|
|
OLECHAR wszFileName[_MAX_PATH];
|
|
FM fm;
|
|
HFS hfs;
|
|
|
|
/* Check for valid filename */
|
|
if (lszFilename == NULL )
|
|
{
|
|
SetErrCode (lperrb, E_INVALIDARG);
|
|
return 0;
|
|
}
|
|
|
|
/* Allocate a file's parameter block */
|
|
if (!(hMem = _GLOBALALLOC(GMEM_ZEROINIT, sizeof(FPB))))
|
|
{
|
|
SetErrCode(lperrb, E_OUTOFMEMORY);
|
|
return NULL;
|
|
}
|
|
|
|
if (!(lpfpb = (LPFPB)_GLOBALLOCK(hMem)))
|
|
{
|
|
_GLOBALUNLOCK(hMem);
|
|
SetErrCode(lperrb,E_OUTOFMEMORY);
|
|
return NULL;
|
|
}
|
|
|
|
_INITIALIZECRITICALSECTION(&lpfpb->cs);
|
|
lpfpb->hStruct = hMem;
|
|
|
|
switch (fFileType)
|
|
{
|
|
case FS_SYSTEMFILE:
|
|
|
|
IClassFactory* pCF;
|
|
|
|
hr = CoGetClassObject(CLSID_ITStorage, CLSCTX_INPROC_SERVER, NULL,
|
|
IID_IClassFactory, (VOID **)&pCF);
|
|
|
|
// Error check!
|
|
|
|
IITStorage* pITStorage;
|
|
hr = pCF->CreateInstance(NULL, IID_ITStorage,
|
|
(VOID **)&pITStorage);
|
|
if (pCF)
|
|
pCF->Release();
|
|
|
|
fm = FmNewSzDir((LPSTR)lszFilename, dirCurrent, NULL);
|
|
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, fm, -1, wszFileName, _MAX_PATH);
|
|
|
|
hr = pITStorage->StgCreateDocfile(wszFileName, STGM_READWRITE, RESERVED, &hfs);
|
|
|
|
// if call failed, make sure to set hfs NULL
|
|
if (!SUCCEEDED(hr))
|
|
{
|
|
hfs = NULL;
|
|
SetErrCode(lperrb, hr);
|
|
}
|
|
|
|
lpfpb->fs.hfs = hfs;
|
|
|
|
|
|
if (pITStorage)
|
|
pITStorage->Release();
|
|
|
|
DisposeFm(fm);
|
|
|
|
break;
|
|
|
|
|
|
case FS_SUBFILE:
|
|
hfs = GetHfs(hfpbSysFile,lszFilename,TRUE,lperrb);
|
|
if (hfs)
|
|
{
|
|
lpfpb->fs.hf = HfCreateFileHfs(hfs,GetSubFilename(lszFilename,
|
|
NULL),HFOPEN_READWRITE,lperrb);
|
|
|
|
lpfpb->ioMode = OF_READWRITE;
|
|
|
|
if (lpfpb->fs.hf == NULL)
|
|
{
|
|
if (!hfpbSysFile)
|
|
RcCloseHfs(hfs);
|
|
goto ErrorExit;
|
|
}
|
|
else
|
|
{
|
|
if (!hfpbSysFile)
|
|
lpfpb->ioMode |= OPENED_HFS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
break;
|
|
|
|
case REGULAR_FILE:
|
|
// Create the file
|
|
if ((lpfpb->fs.hFile = (HFILE_GENERIC)CREAT (lszFilename, 0))
|
|
== HFILE_GENERIC_ERROR)
|
|
{
|
|
SetErrCode(lperrb,E_FILECREATE);
|
|
goto ErrorExit;
|
|
}
|
|
lpfpb->ioMode = OF_READWRITE;
|
|
break;
|
|
}
|
|
|
|
// Set the filetype
|
|
lpfpb->fFileType = (BYTE) fFileType;
|
|
|
|
_GLOBALUNLOCK(hfpb = lpfpb->hStruct);
|
|
return hfpb;
|
|
|
|
ErrorExit:
|
|
_DELETECRITICALSECTION(&lpfpb->cs);
|
|
_GLOBALFREE(hMem);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
PUBLIC HFPB FAR PASCAL FileOpen (HFPB hfpbSysFile, LPCSTR lszFilename,
|
|
int fFileType, int ioMode, LPERRB lperrb)
|
|
{
|
|
LPFPB lpfpb; /* Pointer to file parameter block */
|
|
HANDLE hMem;
|
|
HFPB hfpb;
|
|
FM fm;
|
|
OLECHAR wszFileName[_MAX_PATH];
|
|
HFS hfs;
|
|
|
|
HRESULT hr;
|
|
|
|
/* Check for valid filename */
|
|
if (lszFilename == NULL )
|
|
{
|
|
SetErrCode (lperrb, E_INVALIDARG);
|
|
return 0;
|
|
}
|
|
|
|
/* Allocate a file's parameter block */
|
|
if (!(hMem = _GLOBALALLOC(GMEM_ZEROINIT, sizeof(FPB))))
|
|
{
|
|
SetErrCode(lperrb,E_OUTOFMEMORY);
|
|
return NULL;
|
|
}
|
|
|
|
if (!(lpfpb = (LPFPB)_GLOBALLOCK(hMem)))
|
|
{
|
|
_GLOBALUNLOCK(hMem);
|
|
SetErrCode(lperrb,E_OUTOFMEMORY);
|
|
return NULL;
|
|
}
|
|
|
|
_INITIALIZECRITICALSECTION(&lpfpb->cs);
|
|
lpfpb->hStruct = hMem;
|
|
|
|
switch (fFileType)
|
|
{
|
|
case FS_SYSTEMFILE:
|
|
|
|
IClassFactory* pCF;
|
|
|
|
hr = CoGetClassObject(CLSID_ITStorage, CLSCTX_INPROC_SERVER, NULL,
|
|
IID_IClassFactory, (VOID **)&pCF);
|
|
|
|
// Error check!
|
|
|
|
IITStorage* pITStorage;
|
|
hr = pCF->CreateInstance(NULL, IID_ITStorage,
|
|
(VOID **)&pITStorage);
|
|
if (pCF)
|
|
pCF->Release();
|
|
|
|
|
|
fm = FmNewSzDir((LPSTR)lszFilename, dirCurrent, NULL);
|
|
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, fm, -1, wszFileName, _MAX_PATH);
|
|
|
|
hr = pITStorage->StgOpenStorage(wszFileName, NULL, (ioMode==READ) ? STGM_READ:STGM_READWRITE,
|
|
NULL, RESERVED, &hfs);
|
|
|
|
if (!SUCCEEDED(hr))
|
|
{
|
|
hfs = NULL;
|
|
SetErrCode(lperrb, hr);
|
|
}
|
|
|
|
lpfpb->fs.hfs = hfs;
|
|
|
|
if (pITStorage)
|
|
pITStorage->Release();
|
|
|
|
DisposeFm(fm);
|
|
|
|
break;
|
|
|
|
case FS_SUBFILE:
|
|
hfs = GetHfs(hfpbSysFile,lszFilename,FALSE,lperrb);
|
|
if (hfs)
|
|
{
|
|
OLECHAR wsz[_MAX_PATH];
|
|
LPCSTR sz = GetSubFilename(lszFilename, NULL);
|
|
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, -1, wsz, _MAX_PATH);
|
|
lpfpb->fs.hf = HfOpenHfs(hfs, wsz,
|
|
(BYTE)((ioMode==READ)?HFOPEN_READ:HFOPEN_READWRITE),lperrb);
|
|
|
|
lpfpb->ioMode = (BYTE) ioMode;
|
|
|
|
if (lpfpb->fs.hf == 0)
|
|
{
|
|
if (!hfpbSysFile)
|
|
RcCloseHfs(hfs);
|
|
SetErrCode (lperrb, E_NOTEXIST);
|
|
goto ErrorExit;
|
|
}
|
|
else
|
|
{
|
|
if (!hfpbSysFile)
|
|
lpfpb->ioMode|=OPENED_HFS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetErrCode (lperrb, E_NOTEXIST);
|
|
goto ErrorExit;
|
|
}
|
|
break;
|
|
|
|
case REGULAR_FILE:
|
|
/* Set the IO mode and appropriate error messages */
|
|
if (ioMode == READ)
|
|
{
|
|
/* Open the file */
|
|
if ((lpfpb->fs.hFile = (HFILE_GENERIC)OPEN (lszFilename,
|
|
ioMode)) == HFILE_GENERIC_ERROR)
|
|
{
|
|
SetErrCode(lperrb,E_NOTEXIST);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
ioMode = OF_READWRITE;
|
|
/* Open the file */
|
|
if ((lpfpb->fs.hFile = (HFILE_GENERIC)OPEN(lszFilename, ioMode))
|
|
== HFILE_GENERIC_ERROR)
|
|
{
|
|
SetErrCode(lperrb,E_FILECREATE);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
lpfpb->ioMode = (BYTE) ioMode;
|
|
break;
|
|
|
|
}
|
|
|
|
// set filetype
|
|
lpfpb->fFileType = (BYTE) fFileType;
|
|
|
|
_GLOBALUNLOCK(hfpb = lpfpb->hStruct);
|
|
return hfpb;
|
|
|
|
ErrorExit:
|
|
_DELETECRITICALSECTION(&lpfpb->cs);
|
|
_GLOBALFREE(hMem);
|
|
return 0;
|
|
|
|
}
|
|
|
|
PUBLIC RC FAR PASCAL FileClose(HFPB hfpb)
|
|
{
|
|
RC rc;
|
|
LPFPB lpfpb;
|
|
|
|
|
|
if (hfpb == NULL)
|
|
return E_HANDLE;
|
|
|
|
lpfpb = (LPFPB)_GLOBALLOCK(hfpb);
|
|
_ENTERCRITICALSECTION(&lpfpb->cs);
|
|
|
|
rc = S_OK;
|
|
switch (lpfpb->fFileType)
|
|
{
|
|
case FS_SYSTEMFILE:
|
|
if (lpfpb->fs.hfs)
|
|
lpfpb->fs.hfs->Release();
|
|
break;
|
|
|
|
case FS_SUBFILE:
|
|
if (lpfpb->fs.hf)
|
|
lpfpb->fs.hf->Release();
|
|
break;
|
|
|
|
case REGULAR_FILE:
|
|
if (lpfpb->fs.hFile)
|
|
rc = (!CloseHandle(lpfpb->fs.hFile))?E_FILECLOSE:S_OK;
|
|
break;
|
|
}
|
|
|
|
/* Free the file parameter block structure */
|
|
_LEAVECRITICALSECTION(&lpfpb->cs);
|
|
_DELETECRITICALSECTION(&lpfpb->cs);
|
|
|
|
_GLOBALUNLOCK(hfpb);
|
|
_GLOBALFREE(hfpb);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
// These are verbatim from iofts.c. I put them here so we can compile
|
|
PRIVATE HANDLE NEAR PASCAL IoftsWin32Create(LPCSTR lpstr, DWORD w)
|
|
{
|
|
SECURITY_ATTRIBUTES sa;
|
|
HANDLE hfile;
|
|
|
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
sa.lpSecurityDescriptor = NULL;
|
|
sa.bInheritHandle = 0;
|
|
|
|
hfile= CreateFile(lpstr, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
|
|
&sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
return hfile;
|
|
}
|
|
|
|
PRIVATE HANDLE NEAR PASCAL IoftsWin32Open(LPCSTR lpstr, DWORD w)
|
|
{
|
|
SECURITY_ATTRIBUTES sa;
|
|
|
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
sa.lpSecurityDescriptor = NULL;
|
|
sa.bInheritHandle = 0;
|
|
|
|
return CreateFile(lpstr, (w == READ) ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ, &sa,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
}
|
|
|
|
#if 0
|
|
// This is new functionality
|
|
PUBLIC FM EXPORT_API FAR PASCAL FmFromHfs(HFS hfs)
|
|
{
|
|
char szStoreName[_MAX_PATH];
|
|
FM fm;
|
|
ERRB errb;
|
|
|
|
STATSTG StoreStat;
|
|
HRESULT hr;
|
|
|
|
// get storage name
|
|
// Turns out this isn't IMPLEMENTED yet!
|
|
hr = hfs->Stat(&StoreStat, STATFLAG_DEFAULT);
|
|
if (S_OK == hr)
|
|
{
|
|
// and convert it since FMs aren't Unicode
|
|
WideCharToMultiByte(CP_ACP, 0, StoreStat.pwcsName, -1,
|
|
szStoreName, _MAX_PATH, NULL, NULL);
|
|
|
|
// free memory associated with pwcsName
|
|
CoTaskMemFree(StoreStat.pwcsName);
|
|
|
|
// create new FM
|
|
fm = FmNew(szStoreName, &errb);
|
|
}
|
|
else
|
|
fm = NULL;
|
|
|
|
return fm;
|
|
}
|
|
#endif
|