// PropertyAccess.cpp
// 
// Copyright (c) 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
// 
// Initial Contributors:
// Accenture - Initial contribution
//
#include <fshell/common.mmh>
#include <e32cmn.h>
#include <fshell/extrabtrace.h>

#include <kernel.h>
#include <kern_priv.h>
#include "memoryaccess.h"
#include "PropertyAccess.h"

DPropertyAccess::DPropertyAccess(DThread* aClient)
	: iClient(aClient)
	{
	}

TInt DPropertyAccess::HandleProperty(TInt aFunction, TAny* aParams, TAny* aVal)
	{
	return DoHandleProperty(iClient, aFunction, aParams, aVal);
	}

TInt DPropertyAccess::DoHandleProperty(DThread* aClient, TInt aFunction, TAny* aParams, TAny* aVal)
	{
	if (aFunction == RMemoryAccess::EControlPropertyNotify)
		{
		if (iNotifyClientStatus) return KErrAlreadyExists;
		iNotifyClientStatus = (TRequestStatus*)aVal;
		iClientNotifyPtr = aParams;
		return KErrNone;
		}
	else if (aFunction == RMemoryAccess::EControlPropertyNotifyCancel)
		{
		CancelNotifys();
		return KErrNone;
		}

	TInt err;
	TProp clientParams;
	err = Kern::ThreadRawRead(aClient, aParams, &clientParams, sizeof(TProp));
	if (err)
		{
		return err;
		}
	//Kern::Printf("HandleProperty %d, iDefine=%d iCat=0x%x iKey=%d", aFunction, clientParams.iDefine, clientParams.iCategory, clientParams.iKey);

	NKern::ThreadEnterCS();
	RPropertyRef property;
	err = property.Open(clientParams.iCategory, clientParams.iKey);
	//Kern::Printf("HandleProperty Open ret=%d", err);
	if (!err)
		{
		// Check if it exists but isn't defined (and if it isn't, treat it the same as if it didn't exist)
		TPropertyStatus stat;
		NKern::LockSystem();
		TBool defined = property.GetStatus(stat);
		NKern::UnlockSystem();
		if (!defined)
			{
			property.Close();
			err = KErrNotFound;
			}
		}

	if (err == KErrNotFound && clientParams.iDefine && (aFunction == RMemoryAccess::EControlSetPropertyDesc || aFunction == RMemoryAccess::EControlSetPropertyInt))
		{
		err = property.Attach(clientParams.iCategory, clientParams.iKey);
		TInt attr = (aFunction == RMemoryAccess::EControlSetPropertyInt) ? RProperty::EInt : RProperty::EByteArray;
		_LIT_SECURITY_POLICY_PASS(KPass);
		if (!err) err = property.Define(attr, KPass, KPass);
		}
	NKern::ThreadLeaveCS();
	if (err)
		{
		return err;
		}

	switch (aFunction)
		{
		case RMemoryAccess::EControlGetPropertyInt:
			{
			TInt val;
			err = property.Get(val);
			if (!err)
				{
				TPckg<TInt> valBuf(val);
				err = Kern::ThreadDesWrite(aClient, aVal, valBuf, 0);
				}
			break;
			}
		case RMemoryAccess::EControlGetPropertyDesc:
			{
			err = Kern::ThreadGetDesMaxLength(aClient, aVal);
			TInt size = err;
			if (size < 0) break;
			
			NKern::ThreadEnterCS();
			HBuf8* buf = HBuf8::New(size);
			if (!buf)
				{
				err = KErrNoMemory;
				NKern::ThreadLeaveCS();
				break;
				}
			err = property.Get(*buf);
			if (!err || err == KErrOverflow)
				{
				TInt writeerr = Kern::ThreadDesWrite(aClient, aVal, *buf, 0, NULL);
				if (writeerr) err = writeerr;
				}
			delete buf;
			NKern::ThreadLeaveCS();
			if (err == KErrOverflow)
				{
				NKern::LockSystem();
				TPropertyStatus status;
				property.GetStatus(status);
				NKern::UnlockSystem();

				clientParams.iActualSize = status.iSize;
				err = Kern::ThreadRawWrite(aClient, aParams, (TAny*)&clientParams, sizeof(TProp));
				}
			break;
			}
		case RMemoryAccess::EControlSetPropertyInt:
			{
			err = property.Set((TInt)aVal);
			break;
			}
		case RMemoryAccess::EControlSetPropertyDesc:
			{
			err = Kern::ThreadGetDesLength(aClient, aVal);
			TInt size = err;
			if (size < 0) break;
			
			NKern::ThreadEnterCS();
			HBuf8* buf = HBuf8::New(size);
			if (!buf)
				{
				err = KErrNoMemory;
				NKern::ThreadLeaveCS();
				break;
				}
			buf->SetLength(size);
			err = Kern::ThreadDesRead(aClient, aVal, *buf, 0);
			if (!err)
				{
				err = property.Set(*buf);
				}
			delete buf;
			NKern::ThreadLeaveCS();
			break;
			}
		case RMemoryAccess::EControlDeleteProperty:
			NKern::ThreadEnterCS();
			err = property.Delete();
			NKern::ThreadLeaveCS();
			break;
		case RMemoryAccess::EControlSubscribeToProperty:
			{
			TBool useBtrace = (TBool)aVal;
			TNotifyRequest* req = new TNotifyRequest(&PropertyChanged, clientParams.iCategory, clientParams.iKey, this, useBtrace);
			err = req->iProperty.Attach(clientParams.iCategory, clientParams.iKey); // Allow non-existant properties
			if (!err)
				{
				err = req->iProperty.Subscribe(req->iRequest);
				if (err)
					{
					// Don't think this will ever happen
					req->iProperty.Close();
					req->iRequest.iPtr = NULL;
					}
				}
			if (err)
				{
				delete req;
				}
			else
				{
				iRequests.Add(&req->iLink);
				}
			}
			break;			
		default:
			break;
		}

	NKern::ThreadEnterCS();
	property.Close();
	NKern::ThreadLeaveCS();
	return err;
	}

DPropertyAccess::~DPropertyAccess()
	{
	CancelNotifys();
	}

void DPropertyAccess::CancelNotifys()
	{
	for (SDblQueLink* link = iRequests.First(); link != NULL && link != &iRequests.iA;)
		{
		TNotifyRequest* request = _LOFF(link, TNotifyRequest, iLink);
		link=link->iNext; // Do this before anything else because we'll be deleting the TNotifyRequest
		request->iProperty.Cancel(request->iRequest);
		request->iProperty.Close();
		delete request;
		}

	if (iNotifyClientStatus)
		{
		Kern::RequestComplete(iClient, iNotifyClientStatus, KErrCancel);
		}
	iClientNotifyPtr = NULL;
	}

void DPropertyAccess::PropertyChanged(TAny* aPtr, TInt aReason)
	{
	TNotifyRequest* req = static_cast<TNotifyRequest*>(aPtr);
	req->iPropertyAccess->DoPropertyChanged(*req, aReason);
	}

void DPropertyAccess::DoPropertyChanged(TNotifyRequest& aRequest, TInt aReason)
	{
	// First, rerequest
	if (aReason == KErrNone)
		{
		aRequest.iProperty.Subscribe(aRequest.iRequest);
		}
	// Then try and notify
	if (iNotifyClientStatus && !aRequest.iBtrace)
		{
		TPropNotifyResult result;
		result.iCategory = aRequest.iCat.iUid;
		result.iKey = aRequest.iKey;
		result.iMissedChanges = iMissedChanges;
		result.iError = aReason;
		iMissedChanges = 0; // No need to lock/safeswap because we run in the context of the pubsub DFC
		TInt err = Kern::ThreadRawWrite(iClient, iClientNotifyPtr, &result, sizeof(TPropNotifyResult));
		Kern::RequestComplete(iClient, iNotifyClientStatus, err);
		}
	else if (aRequest.iBtrace)
		{
#ifdef FSHELL_TRACE_SUPPORT
		TUint cat = aRequest.iCat.iUid;
		TUint key = aRequest.iKey;

		TInt intVal;
		TInt err = aRequest.iProperty.Get(intVal);
		if (err == KErrNone)
			{
			BTrace12(ExtraBTrace::EPubSub, ExtraBTrace::EPubSubIntPropertyChanged, cat, key, intVal);
			}
		else if (err == KErrArgument)
			{
			TBuf8<KMaxBTraceDataArray> buf; // No point using larger buffer, as this is the max we can output over btrace (80 bytes)
			err = aRequest.iProperty.Get(buf);
			if (err == KErrNone || err == KErrOverflow)
				{
				BTraceN(ExtraBTrace::EPubSub, ExtraBTrace::EPubSubDataPropertyChanged, cat, key, buf.Ptr(), buf.Length());
				}
			}
#endif
		}
	else
		{
		iMissedChanges++;
		}
	}
