/****************************************************************************
**
** 		Created using Monkey Studio IDE v1.8.4.0 (1.8.4.0)
** Authors   : Filipe AZEVEDO aka Nox P@sNox <pasnox@gmail.com>
** Project   : Fresh Library
** FileName  : pPartitionModel_linux.cpp
** Date      : 2011-02-20T00:41:35
** License   : LGPL v3
** Home Page : http://bettercodes.org/projects/fresh
** Comment   : Fresh Library is a Qt 4 extension library providing set of new core & gui classes.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Leser General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This package is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
**
****************************************************************************/
#include "pPartitionModel.h"

//http://svn.openpilot.org/browse/OpenPilot/trunk/ground/openpilotgcs/src/plugins/rawhid/usbmonitor.h?r=3021

#include <libudev.h>
#include <sys/statfs.h>

#include <QStringList>
#include <QFileInfo>
#include <QSocketNotifier>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusVariant>
#include <QDebug>

class DisksSession : public QObject
{
	Q_OBJECT
	
public:
	DisksSession( pPartitionModel* model )
		: QObject( model )
	{
		mModel = model;
		mContext = udev_new();
		mMonitor = udev_monitor_new_from_netlink( mContext, "udev" );
		
		if ( !mContext ) {
			qWarning( "%s: UDev not valid.", Q_FUNC_INFO );
			return;
		}
		
		if ( !mMonitor ) {
			qWarning( "%s: UDevMonitor not valid.", Q_FUNC_INFO );
			return;
		}
		
		udev_monitor_filter_add_match_subsystem_devtype( mMonitor, "block", NULL );
		udev_monitor_enable_receiving( mMonitor );
		
		mNotifier = new QSocketNotifier( udev_monitor_get_fd( mMonitor ), QSocketNotifier::Read, this );
		
		connect( mNotifier, SIGNAL( activated( int ) ), this, SLOT( deviceEventReceived() ) );

		start();
	}
	
	virtual ~DisksSession()
	{
		udev_monitor_unref( mMonitor );
		udev_unref( mContext );
	}
	
	pPartition createPartition( struct udev_device* device )
	{
		struct udev_list_entry* entries = udev_device_get_properties_list_entry( device );
		struct udev_list_entry* entry = 0;
		pPartition partition;
		QVariantMap properties;
		
		udev_list_entry_foreach( entry, entries ) {
			const QString name = udev_list_entry_get_name( entry );
			const QString value = QString::fromLocal8Bit( udev_device_get_property_value( device, name.toLocal8Bit().constData() ) );

			properties[ name ] = value;
		}
		
		if ( properties.contains( "UDISKS_PARTITION_TYPE" ) ) {
			properties[ "UDISKS_PARTITION_TYPE" ] = properties[ "UDISKS_PARTITION_TYPE" ].toString().toLongLong( 0, 16 );
		}
		
		const QString devName = QFileInfo( properties[ "DEVNAME" ].toString() ).fileName();
		QDBusMessage question = QDBusMessage::createMethodCall( "org.freedesktop.UDisks", QString( "/org/freedesktop/UDisks/devices/%1" ).arg( devName ), "org.freedesktop.DBus.Properties", "Get" );
		question << "org.freedesktop.UDisks.Device" << "DeviceMountPaths";
		QDBusMessage answer = QDBusConnection::systemBus().call( question, QDBus::Block, 1 );
		QStringList mountPoints;
		
		foreach ( const QVariant& variant, answer.arguments() ) {
			const QStringList values = variant.value<QDBusVariant>().variant().toStringList();
			
			if ( !values.isEmpty() ) {
				mountPoints << values;
			}
		}
		
		if ( pPartition::isWBFSPartition( properties[ "DEVNAME" ].toString() ) ) {
			properties[ "UDISKS_PARTITION_TYPE" ] = 0x25;
			properties[ "ID_FS_TYPE" ] = pPartition::fileSystemIdToString( 0x25 );
		}
		
		qint64 total = properties.value( "UDISKS_PARTITION_SIZE", -1 ).toLongLong();
		qint64 free = -1;
		
		if ( !mountPoints.isEmpty() ) {
			struct statfs stats;
			
			if ( statfs( qPrintable( mountPoints.first() ), &stats ) == 0 ) {
				/*const qint64 total = stats.f_blocks *stats.f_bsize;
				partition.used = total -( stats.f_bfree *stats.f_bsize );
				partition.free = partition.total -partition.used;*/
				
				total = stats.f_blocks *stats.f_bsize;
				free = stats.f_bfree *stats.f_bsize;
			}
			
			properties[ "UDISKS_PARTITION_MOUNT_POINTS" ] = mountPoints;
		}
		
		properties[ "REMOVABLE" ] = "0";

		// Check removable attr
		struct udev_device* parent = 0;
		
		if ( properties[ "DEVTYPE" ] == "partition" ) {
			parent = udev_device_get_parent( device );
		}
		else if ( properties[ "DEVTYPE" ] == "disk" ) {
			parent = device; // disk without partitions
		}

		if ( parent != 0 ) {
			const char* value = udev_device_get_sysattr_value( parent, "removable" );

			if ( value ) {
				properties[ "REMOVABLE" ] = QString::fromLocal8Bit( value );
			}
		}
		
		partition.setProperties( properties );
		partition.updateSizes( total, free );
		
		return partition;
	}
	
protected:
	void start()
	{
		struct udev_enumerate* enumerate  = udev_enumerate_new( mContext );
		
		if ( !enumerate ) {
			qWarning( "%s: Can't enumerate.", Q_FUNC_INFO );
			return;
		}
		
		udev_enumerate_add_match_subsystem( enumerate, "block" );
		udev_enumerate_scan_devices( enumerate );
		
		struct udev_device* device = 0;
		struct udev_list_entry* entries = udev_enumerate_get_list_entry( enumerate );
		struct udev_list_entry* entry = 0;
		
		udev_list_entry_foreach( entry, entries ) {
			device = udev_device_new_from_syspath( mContext, udev_list_entry_get_name( entry ) );
			
			if ( !device ) {
				continue;
			}
			
			diskAppeared( device );
			
			udev_device_unref( device );
		}

		udev_enumerate_unref( enumerate );
	}

protected slots:
	void deviceEventReceived()
	{
		struct udev_device* device = udev_monitor_receive_device( mMonitor );
		
		if ( device ) {
			const QString action = QString( udev_device_get_action( device ) );
			const QString devtype = QString( udev_device_get_devtype( device ) );
			
			if ( action == "add" ) {
				diskAppeared( device );
			}
			else if ( action == "change" ) {
				diskChanged( device );
			}
			else if ( action == "remove" ){
				diskDisappeared( device );
			}
			else {
				qWarning() << Q_FUNC_INFO << "------- Got Device Event" <<  "type" << devtype << " - action" << action;
			}

			udev_device_unref( device );
		}
		else {
			qWarning( "%s: No Device. An error occured.", Q_FUNC_INFO );
		}
	}
	
	void diskAppeared( struct udev_device* device )
	{
		const pPartition partition = createPartition( device );
		
		if ( partition.properties()[ "DEVTYPE" ] == "partition"
			|| ( partition.properties()[ "ID_TYPE" ] == "cd" 
				&& partition.properties()[ "ID_FS_USAGE" ] == "filesystem" ) ) {
			mModel->updatePartition( partition );
		}
	}
	
	void diskChanged( struct udev_device* device )
	{
		diskAppeared( device );
	}
	
	void diskDisappeared( struct udev_device* device )
	{
		const QString devicePath = QString::fromLocal8Bit( udev_device_get_property_value( device, "DEVNAME" ) );
		mModel->removePartition( devicePath );
	}
	
protected:
	pPartitionModel* mModel;
	udev* mContext;
	udev_monitor* mMonitor;
	QSocketNotifier* mNotifier;
};

void pPartitionModel::platformInit()
{
	emit layoutAboutToBeChanged();
	mData = new DisksSession( this );
	emit layoutChanged();
}

void pPartitionModel::platformDeInit()
{
	delete (DisksSession*)mData;
}

void pPartitionModel::platformUpdate()
{
	// Code commented as the linux implementation has the concept of live auto update, thanks to UDev librarie :)
	/*const QStringList partitions = customPartitions();
	
	emit layoutAboutToBeChanged();
	delete (DisksSession*)mData;
	mPartitions.clear();
	foreach ( const QString& partition, partitions ) {
		mPartitions << pPartition( partition );
	}
	mData = new DisksSession( this );
	emit layoutChanged();*/
}

#include "pPartitionModel_linux.moc"
