/*
** Amster - File Sharing
** Copyright © 2000-2001 by Jacob Laursen
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program 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 General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "amster.h"

#include <MUI/NListview_mcc.h>

#include <proto/asl.h>
#include <proto/dos.h>
#include <libraries/asl.h>
#include <libraries/dos.h>
#include <exec/memory.h>
#include <dos/exall.h>
#include <utility/tagitem.h>
#include <workbench/workbench.h>

#include "md5.h"
#include "amster_Cat.h"


unsigned int numsongs=0, numbytes=0;
BOOL sharechanged=FALSE, sharefailed=FALSE;

struct FileRequester *FReqAddFiles;
struct FileRequester *FReqAddDir;
struct FileRequester *FReqLibrary;


MUI_DISPATCH(share_dispatch)
{
	struct shdata *data;

	switch(msg->MethodID) {
		case OM_NEW:
			return(share_new(cl, obj, (APTR)msg));
		case OM_DISPOSE:
			if (FReqAddFiles) FreeAslRequest(FReqAddFiles);
			if (FReqAddDir)   FreeAslRequest(FReqAddDir);
			if (FReqLibrary)  FreeAslRequest(FReqLibrary);
			break;
		case SHARE_OPEN:
			data = INST_DATA(cl, obj);
			update_stat(data);
			set(obj, MUIA_Window_Open, TRUE);
			return NULL;
		case SHARE_CLOSE:
			set(obj, MUIA_Window_Open, FALSE);
			return NULL;
		case SHARE_ADD:
			data = INST_DATA(cl, obj);
			add_shares(data);
			return NULL;
		case SHARE_ADDDIR:
			data = INST_DATA(cl, obj);
			add_directory(data);
			return NULL;
		case SHARE_REMOVE:
			data = INST_DATA(cl, obj);
			remove_shares(data, (long)(((muimsg)msg)->arg1));
			return NULL;
		case SHARE_SAVE:
			data = INST_DATA(cl, obj);
			save_shares(data);
			return NULL;
		case SHARE_SAVEAS:
			data = INST_DATA(cl, obj);
			save_shares_as(data);
			return NULL;
		case SHARE_LOAD:
			data = INST_DATA(cl, obj);
			load_shares(data, 0);
			return NULL;
		case SHARE_LOADAS:
			data = INST_DATA(cl, obj);
			load_shares_as(data, (int)(((muimsg)msg)->arg1));
			return NULL;
		case SHARE_UPLOAD:
			data = INST_DATA(cl, obj);
			upload_file_confirm(data, (char *)(((muimsg)msg)->arg1), (char *)(((muimsg)msg)->arg2));
			return NULL;
		case SHARE_NOTIFYALL:
			data = INST_DATA(cl, obj);
			notify_shares(data);
			return NULL;
		case SHARE_PLAY:
			data = INST_DATA(cl, obj);
			play_share(data);
			return NULL;
		case SHARE_ADDFILE:
			data = INST_DATA(cl, obj);
			add_file(data, (song)(((muimsg)msg)->arg1), (char *)(((muimsg)msg)->arg2));
			return NULL;
		case SHARE_ADDFILEN:
			data = INST_DATA(cl, obj);
			add_filename(data, (char *)(((muimsg)msg)->arg1));
			return NULL;
		case SHARE_SETDIR:
			strcpy(FReqAddFiles->fr_Drawer, (char *)(((muimsg)msg)->arg1));
			strcpy(FReqAddDir->fr_Drawer, (char *)(((muimsg)msg)->arg1));
			return NULL;
		case SHARE_UPDCOUNT:
			data = INST_DATA(cl, obj);
			update_count(data, (char *)(((muimsg)msg)->arg1));
			return NULL;
	}
	return(DoSuperMethodA(cl, obj, msg));
}


MUI_LIST_DEST(sharelistdest, sharedata sd)
{
	char *path;

	numsongs--;
	numbytes -= sd->size;
	if (gui_onlinestate == ONLINE) {
		path = MakeWinPath(sd->title);
		nap_sendbuf(NAPC_REMOVESHARE, path);
		free(path);
	}
	free(sd);

	return(0);
}


MUI_LIST_DISP(sharelistdisp, sharedata sd)
{
	static char size[16], md5[33], time[20], bitrate[16], freq[16], count[16];

	if (sd) {
			*array++ = sd->title;
			sprintf(size, "\33r%ld", sd->size);
			*array++ = size;
			sprintf(time, "\33r%ld:%02ld", sd->time/60, sd->time%60);
			*array++ = time;
			sprintf(bitrate, "\33r%ld", sd->bitrate);
			*array++ = bitrate;
			sprintf(freq, "\33l%ld", sd->freq);
			*array++ = freq;
			sprintf(count, "\33r%ld", sd->reqcount);
			*array++ = count;
			strcpy(md5, sd->md5);
			*array = md5;
	} else {
		*array++ = (char *)MSG_LH_FILE;
		*array++ = (char *)MSG_LH_SIZE;
		*array++ = (char *)MSG_LH_TIME;
		*array++ = (char *)MSG_LH_BITRATE;
		*array++ = (char *)MSG_LH_FREQ;
		*array++ = (char *)MSG_LH_COUNT;
		*array   = (char *)MSG_LH_CHECKSUM;
	}
	return(0);
}


MUI_NLIST_COMP(sharelistcomp)
{
	sharedata entry1 = ncm->entry1;
	sharedata entry2 = ncm->entry2;
	LONG col1 = ncm->sort_type & MUIV_NList_TitleMark_ColMask;
	LONG col2 = ncm->sort_type2 & MUIV_NList_TitleMark2_ColMask;
	ULONG result = 0;

	if (ncm->sort_type == MUIV_NList_SortType_None) return (0);

	if (col1 == 0) {
		if (ncm->sort_type & MUIV_NList_TitleMark_TypeMask)
			result = (LONG) stricmp(entry2->title, entry1->title);
		else
			result = (LONG) stricmp(entry1->title, entry2->title);
	}
	else if (col1 == 1) {
		if (ncm->sort_type & MUIV_NList_TitleMark_TypeMask)
			result = entry2->size - entry1->size;
		else
			result = entry1->size - entry2->size;
	}
	else if (col1 == 2) {
		if (ncm->sort_type & MUIV_NList_TitleMark_TypeMask)
			result = entry2->time - entry1->time;
		else
			result = entry1->time - entry2->time;
	}
	else if (col1 == 3) {
		if (ncm->sort_type & MUIV_NList_TitleMark_TypeMask)
			result = entry2->bitrate - entry1->bitrate;
		else
			result = entry1->bitrate - entry2->bitrate;
	}
	else if (col1 == 4) {
		if (ncm->sort_type & MUIV_NList_TitleMark_TypeMask)
			result = entry2->freq - entry1->freq;
		else
			result = entry1->freq - entry2->freq;
	}
	else if (col1 == 5) {
		if (ncm->sort_type & MUIV_NList_TitleMark_TypeMask)
			result = entry2->reqcount - entry1->reqcount;
		else
			result = entry1->reqcount - entry2->reqcount;
	}
	else if (col1 == 6) {
		if (ncm->sort_type & MUIV_NList_TitleMark_TypeMask)
			result = (LONG) stricmp(entry2->md5, entry1->md5);
		else
			result = (LONG) stricmp(entry1->md5, entry2->md5);
	}

	if ((result != 0) || (col1 == col2)) return (result);

	if (col2 == 0) {
		if (ncm->sort_type & MUIV_NList_TitleMark2_TypeMask)
			result = (LONG) stricmp(entry2->title, entry1->title);
		else
			result = (LONG) stricmp(entry1->title, entry2->title);
	}
	else if (col2 == 1) {
		if (ncm->sort_type & MUIV_NList_TitleMark2_TypeMask)
			result = entry2->size - entry1->size;
		else
			result = entry1->size - entry2->size;
	}
	else if (col2 == 2) {
		if (ncm->sort_type & MUIV_NList_TitleMark2_TypeMask)
			result = entry2->time - entry1->time;
		else
			result = entry1->time - entry2->time;
	}
	else if (col2 == 3) {
		if (ncm->sort_type & MUIV_NList_TitleMark2_TypeMask)
			result = entry2->bitrate - entry1->bitrate;
		else
			result = entry1->bitrate - entry2->bitrate;
	}
	else if (col2 == 4) {
		if (ncm->sort_type & MUIV_NList_TitleMark2_TypeMask)
			result = entry2->freq - entry1->freq;
		else
			result = entry1->freq - entry2->freq;
	}
	else if (col2 == 5) {
		if (ncm->sort_type & MUIV_NList_TitleMark2_TypeMask)
			result = entry2->reqcount - entry1->reqcount;
		else
			result = entry1->reqcount - entry2->reqcount;
	}
	else if (col2 == 6) {
		if (ncm->sort_type & MUIV_NList_TitleMark2_TypeMask)
			result = (LONG) stricmp(entry2->md5, entry1->md5);
		else
			result = (LONG) stricmp(entry1->md5, entry2->md5);
	}

	return (result);
}


MUI_APPMSG(ShareListAppMsgFunc)
{
	struct WBArg *ap;
	struct AppMessage *amsg = *appmsg;
	int i;
	static char buf[256];

	for (ap=amsg->am_ArgList, i=0; i<amsg->am_NumArgs; i++, ap++)
	{
		NameFromLock(ap->wa_Lock, buf, sizeof(buf));
		AddPart(buf, ap->wa_Name, sizeof(buf));
		DoMethod(gui->shwin, SHARE_ADDFILEN, buf);
	}

	return(0);
}


ULONG share_new(struct IClass *cl, Object *obj, struct opSet *msg)
{
	static const struct Hook sharelistdispHook    = { {NULL, NULL}, &sharelistdisp, NULL, NULL };
	static const struct Hook sharelistcompHook    = { {NULL, NULL}, &sharelistcomp, NULL, NULL };
	static const struct Hook sharelistdestHook    = { {NULL, NULL}, &sharelistdest, NULL, NULL };
	static const struct Hook ShareListAppMsgHook  = { {NULL, NULL}, &ShareListAppMsgFunc, NULL, NULL };

	struct shdata *data;
	Object *list, *stat, *BT_Add, *BT_AddDir, *BT_Remove, *BT_RemoveAll;

	if (obj = (Object *)DoSuperNew(cl, obj,
		MUIA_HelpNode, "sharing",
		MUIA_Window_ID, MAKE_ID('S','H','A','R'),
		MUIA_Window_Title, MSG_SHARE_TITLE,
		MUIA_Window_AppWindow, TRUE,
		WindowContents, VGroup,
			MUIA_HelpNode, "sharing",
			Child, stat = TextObject,
				TextFrame,
				MUIA_Background, MUII_TextBack,
				MUIA_Text_PreParse, "\33c",
			End,
			Child, list = NListviewObject,
				MUIA_NListview_NList, NListObject,
					InputListFrame,
					MUIA_NList_Title, TRUE,
					MUIA_NList_Format, "BAR, BAR, BAR, BAR, BAR, BAR",
					MUIA_NList_DisplayHook, &sharelistdispHook,
					MUIA_NList_CompareHook2, &sharelistcompHook,
					MUIA_NList_DestructHook, &sharelistdestHook,
					MUIA_NList_MultiSelect, MUIV_NList_MultiSelect_Default,
					MUIA_NList_MinColSortable, 0,
					MUIA_CycleChain, 1,
				End,
			End,
			Child, HGroup,
				Child, BT_Add       = SimpleButton(MSG_SHARE_ADD_GAD         ),
				Child, BT_AddDir    = SimpleButton(MSG_SHARE_ADDRECURSIVE_GAD),
				Child, BT_Remove    = SimpleButton(MSG_SHARE_REMOVE_GAD      ),
				Child, BT_RemoveAll = SimpleButton(MSG_SHARE_REMOVEALL_GAD   ),
			End,
		End,
		TAG_MORE, msg->ops_AttrList))
	{
		data = INST_DATA(cl,obj);
		data->list = list;
		data->stat = stat;


		/* Default sort */

		DoMethod(list, MUIM_Set, MUIA_NList_TitleMark, 0);
		DoMethod(list, MUIM_NList_Sort3, 0, MUIV_NList_SortTypeAdd_2Values, MUIV_NList_Sort3_SortType_Both);

		/* Window notify */

		DoMethod(obj, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, gui->iconpanel, 1, PANEL_CLOSESHARE);

		/* List notifies */

		DoMethod(list, MUIM_Notify, MUIA_NList_DoubleClick, MUIV_EveryTime, obj,  1, SHARE_PLAY);
 		DoMethod(list, MUIM_Notify, MUIA_NList_TitleClick,  MUIV_EveryTime, list, 4, MUIM_NList_Sort3, MUIV_TriggerValue, MUIV_NList_SortTypeAdd_2Values, MUIV_NList_Sort3_SortType_Both);
		DoMethod(list, MUIM_Notify, MUIA_NList_TitleClick2, MUIV_EveryTime, list, 4, MUIM_NList_Sort3, MUIV_TriggerValue, MUIV_NList_SortTypeAdd_2Values, MUIV_NList_Sort3_SortType_2);
		DoMethod(list, MUIM_Notify, MUIA_NList_SortType,    MUIV_EveryTime, list, 3, MUIM_Set, MUIA_NList_TitleMark,  MUIV_TriggerValue);
		DoMethod(list, MUIM_Notify, MUIA_NList_SortType2,   MUIV_EveryTime, list, 3, MUIM_Set, MUIA_NList_TitleMark2, MUIV_TriggerValue);
		DoMethod(list, MUIM_Notify, MUIA_AppMessage,        MUIV_EveryTime, list, 3, MUIM_CallHook, &ShareListAppMsgHook, MUIV_TriggerValue);

		/* Button notifies */

		DoMethod(BT_Add,       MUIM_Notify, MUIA_Pressed, FALSE, obj, 1, SHARE_ADD      );
		DoMethod(BT_AddDir,    MUIM_Notify, MUIA_Pressed, FALSE, obj, 1, SHARE_ADDDIR   );
		DoMethod(BT_Remove,    MUIM_Notify, MUIA_Pressed, FALSE, obj, 2, SHARE_REMOVE, 0);
		DoMethod(BT_RemoveAll, MUIM_Notify, MUIA_Pressed, FALSE, obj, 2, SHARE_REMOVE, 1);

		/* Allocate file requesters */

		FReqAddFiles = AllocAslRequestTags(ASL_FileRequest,
			ASLFR_TitleText, MSG_SHARE_ADD_REQ,
			ASLFR_InitialPattern, (ULONG)"#?.(mp(2|3)|wma)",
			ASLFR_InitialDrawer, (ULONG)prf->dlpath,
			ASLFR_DoPatterns, TRUE,
			ASLFR_DoMultiSelect, TRUE,
			ASLFR_RejectIcons, TRUE,
			TAG_DONE);
		FReqAddDir = AllocAslRequestTags(ASL_FileRequest,
			ASLFR_TitleText, MSG_SHARE_ADDDIR_REQ,
			ASLFR_InitialDrawer, (ULONG)prf->dlpath,
			ASLFR_DrawersOnly, TRUE,
			TAG_DONE);
		FReqLibrary = AllocAslRequestTags(ASL_FileRequest,
			ASLFR_InitialFile, "Amster.shares",
			ASLFR_InitialDrawer, "PROGDIR:",
			ASLFR_RejectIcons, TRUE,
			TAG_DONE);

		return((ULONG)obj);
	}
	return(0);
}


/* Private functions */


void add_filename(struct shdata *data, char *fname)
{
	sharedata sd;
	struct FileInfoBlock *fib;
	BPTR lock;

	if (!(fib = AllocDosObject(DOS_FIB, NULL))) return;

	if (sd = malloc(sizeof(_sharedata))) {
		memset(sd, 0, sizeof(_sharedata));

		strcpy(sd->title, fname);
		if (lock = Lock(fname, ACCESS_READ)) {
			if (Examine(lock, fib)) {
				sd->size = fib->fib_Size;
				if (NameFromLock(lock, sd->title, 255)) {
					if (fib->fib_DirEntryType < 0) add_shareinfo(data, sd, lock);
					else if (fib->fib_DirEntryType > 0) {
						add_recursive(data, sd, lock, sd->title);
						UnLock(lock);
					}
				}
				else UnLock(lock);
			}
		}
	}

	FreeDosObject(DOS_FIB, fib);
}


void add_file(struct shdata *data, song sdl, char *fname)
{
	sharedata sd;
	struct FileInfoBlock *fib;
	BPTR lock;

	if (sd = malloc(sizeof(_sharedata))) {
		memset(sd, 0, sizeof(_sharedata));

		if (fib = AllocDosObject(DOS_FIB, NULL)) {
			if (lock = Lock(fname, ACCESS_READ)) {
				NameFromLock(lock, sd->title, 255);
				UnLock(lock);
			}
			FreeDosObject(DOS_FIB, fib);
		}

		if (!sd->title) strcpy(sd->title, fname);
		strncpy(sd->md5, sdl->md5, 32);
		sd->size    = sdl->size;
		sd->bitrate = sdl->bit;
		sd->freq    = sdl->freq;
		sd->time    = sdl->time;

		DoMethod(data->list, MUIM_NList_InsertSingle, sd, MUIV_NList_Insert_Sorted);
		numsongs++; numbytes += sd->size;
		sharechanged = TRUE;
		update_stat(data);
		if (gui_onlinestate == ONLINE) nap_notifyshare(sd);
	}

	return;
}


void add_shares(struct shdata *data)
{
	sharedata sd;
	BPTR lock;
	struct FileInfoBlock *fib;
	int i;
	u_long win;

	get(gui->shwin, MUIA_Window_Window, &win);

	if (FReqAddFiles) {
		if (AslRequestTags(FReqAddFiles, ASLFR_Window, win, TAG_DONE)) {
				if (fib = AllocDosObject(DOS_FIB, NULL)) {
					for (i=0; i<FReqAddFiles->fr_NumArgs; i++) {
						if (sd = malloc(sizeof(_sharedata))) {
							memset(sd, 0, sizeof(_sharedata));
							strcpy(sd->title, FReqAddFiles->fr_Drawer);
							AddPart(sd->title, FReqAddFiles->fr_ArgList[i].wa_Name, 255);
							if (lock = Lock(sd->title, ACCESS_READ)) {
								if (Examine(lock, fib)) {
									sd->size = fib->fib_Size;
									if (NameFromLock(lock, sd->title, 255)) {
										if (fib->fib_DirEntryType < 0) add_shareinfo(data, sd, lock);
										else if (fib->fib_DirEntryType > 0) {
											add_recursive(data, sd, lock, sd->title);
											UnLock(lock);
										}
									}
									else UnLock(lock);
								}
							}
						}
					}
					FreeDosObject(DOS_FIB, fib);
				}
		}
		if (sharefailed) {
			MUI_Request(gui->app, gui->shwin, 0L,
				(char *)MSG_ERROR_TITLE,
				(char *)MSG_OK_GAD,
				(char *)MSG_SHARE_ADDFAILED);
			sharefailed = FALSE;
		}
	}
}


void add_directory(struct shdata *data)
{
	sharedata sd;
	BPTR lock;
	struct FileInfoBlock *fib;
	u_long win;

	get(gui->shwin, MUIA_Window_Window, &win);

	if (FReqAddDir) {
		if (AslRequestTags(FReqAddDir, ASLFR_Window, win, TAG_DONE)) {
				if (fib = AllocDosObject(DOS_FIB, NULL)) {
					if (sd = malloc(sizeof(_sharedata))) {
						memset(sd, 0, sizeof(_sharedata));
						strcpy(sd->title, FReqAddDir->fr_Drawer);
						if (lock = Lock(sd->title, ACCESS_READ)) {
							if (Examine(lock, fib)) {
								sd->size = fib->fib_Size;
								if (NameFromLock(lock, sd->title, 255)) {
									if (fib->fib_DirEntryType > 0) {
										add_recursive(data, sd, lock, sd->title);
										UnLock(lock);
									}
								}
								else UnLock(lock);
							}
						}
					}
					FreeDosObject(DOS_FIB, fib);
				}
		}
		if (sharefailed) {
			MUI_Request(gui->app, gui->shwin, 0L,
				(char *)MSG_ERROR_TITLE,
				(char *)MSG_OK_GAD,
				(char *)MSG_SHARE_ADDFAILED);
			sharefailed = FALSE;
		}
	}
}


void add_recursive(struct shdata *data, sharedata sd, BPTR dirlock, const char *dirname)
{
	ULONG namelen;
	long len;
	struct ExAllControl *eac;
	struct ExAllData *ead;
	APTR eadata;
	BOOL more;
	char *name;
	BPTR lock;
	struct FileInfoBlock *fib;
	const long buffersize = 4096;

	if (dirlock) {
		namelen = strlen(dirname);
		eac = AllocDosObject(DOS_EXALLCONTROL, NULL);
		if (eac) {
			eac->eac_LastKey = 0;
			eac->eac_MatchString = 0;
			eac->eac_MatchFunc = 0;
			eadata = malloc(buffersize);
			if (eadata) {
				do {
					more = ExAll(dirlock, eadata, buffersize, ED_TYPE, eac);
					if (eac->eac_Entries > 0) {
						ead = eadata;
						do {
							len = namelen + strlen(ead->ed_Name);
							name = (char *)malloc(len+5);
							if (name) {
								strcpy(name, dirname);
								if (AddPart(name, ead->ed_Name, len+5)) {
									if (ead->ed_Type > 0) {
										lock = Lock(name, ACCESS_READ);
										if (lock) {
											add_recursive(data, sd, lock, name);
											UnLock(lock);
                                        }
                                    }
									else if (ead->ed_Type < 0) {
										if (sd = malloc(sizeof(_sharedata))) {
											memset(sd, 0, sizeof(_sharedata));
											strcpy(sd->title, name);
											if (fib = AllocDosObject(DOS_FIB, NULL)) {
												lock = Lock(name, ACCESS_READ);
												if (lock) {
													if (Examine(lock, fib)) {
														sd->size = fib->fib_Size;
														add_shareinfo(data, sd, lock);
													}
												}
												FreeDosObject(DOS_FIB, fib);
											}
										}
									}
								}
								free(name);
							}
							ead = ead->ed_Next;
						} while(ead);
					}
					if ((!more) && (IoErr() != ERROR_NO_MORE_ENTRIES)) break;
					if (eac->eac_Entries == 0) continue;
				} while (more);
				free(eadata);
			}
			FreeDosObject(DOS_EXALLCONTROL, eac);
		}
	}
}


void add_shareinfo(struct shdata *data, sharedata sd, BPTR lock)
{
	unsigned int m_id, m_layer, m_bit, m_freq, m_mode;
	APTR buffer;
	BPTR fh;
	int i=0, j=0, len, total, col;
	long vbrbytes = 0, vbrframes = 0, vbr = 0;
	BOOL header = FALSE;
	int badhdr[10];
	char *suffix;

	sharedata sdtmp;
	ULONG tmp;

	md5_state_t state;
	md5_byte_t digest[16];
	int di;

	/*	MP3 information source:
		http://mp3tech.free.fr/programmers/frame_header.html
	*/

	int mp3_bitrates[14][6] = {
	/*   V1L1,V1L2,V1L3,V2L1,V2L2,V2L3   */
		{  32,  32,  32,  32,   8,   8 },
		{  64,  48,  40,  48,  16,  16 },
		{  96,  56,  48,  56,  24,  24 },
		{ 128,  64,  56,  64,  32,  32 },
		{ 160,  80,  64,  80,  40,  40 },
		{ 192,  96,  80,  96,  48,  48 },
		{ 224, 112,  96, 112,  56,  56 },
		{ 256, 128, 112, 128,  64,  64 },
		{ 288, 160, 128, 144,  80,  80 },
		{ 320, 192, 160, 160,  96,  96 },
		{ 352, 224, 192, 176, 112, 112 },
		{ 384, 256, 224, 192, 128, 128 },
		{ 416, 320, 256, 224, 144, 144 },
		{ 448, 384, 320, 256, 160, 160 }
	};
/*	V1 = MPEG Version 1
	V2 = MPEG Version 2 and Version 2.5
	L1 = Layer I
	L2 = Layer II
	L3 = Layer III
*/

	int mp3_frequencies[3][3] = {
	/*    MPEG1, MPEG2, MPEG2.5    */
		{ 44100, 22050, 11025 },
		{ 48000, 24000, 12000 },
		{ 32000, 16000,  8000 }
	};

	if (IsIn(sd->title, '"') || IsIn(sd->title, '\\')) {
		gui_debugf((char *)MSG_SHARE_ILLEGALCHARS, sd->title);
		sharefailed = TRUE;
		UnLock(lock);
		return;
	}

	suffix = sd->title + strlen(sd->title) - 4;
	if (stricmp(suffix, ".mp3") != 0 && stricmp(suffix, ".mp2") != 0 && stricmp(suffix, ".wma") != 0) {
		gui_debugf((char *)MSG_SHARE_INVALIDEXTENSION, sd->title);
		sharefailed = TRUE;
		UnLock(lock);
		return;
	}

	GetAttr(MUIA_NList_Entries, data->list, &tmp);
	total = tmp;

	while (i < total) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &sdtmp);
		if (sdtmp) if (strcmp(sd->title, sdtmp->title) == 0) {
			gui_debugf((char *)MSG_SHARE_EXISTS, sd->title);
			sharefailed = TRUE;
			UnLock(lock);
			return;
		}
		i++;
	}

	if (fh = OpenFromLock(lock)) {
		if (buffer = AllocMem(300032, MEMF_ANY)) {

			md5_init(&state);

			len = Read(fh, buffer, 300032);
			if (len > 0) {

				md5_append(&state, (const md5_byte_t *)buffer, len);
				md5_finish(&state, digest);
				for (di = 0; di < 16; ++di)
					sprintf(sd->md5+di*2, "%02x", digest[di]);

				/* Only check first 128 kB for valid MP3 header */
				if (len > 128*1024) len = 128*1024;

				/* Check for VBR */
				for (i=0; i<len; i++) {
					if (*((unsigned char *)buffer+i) == 'X')
						if (*((unsigned char *)buffer+i+1) == 'i')
							if (*((unsigned char *)buffer+i+2) == 'n')
								if (*((unsigned char *)buffer+i+3) == 'g') {
									j = i + 7;
									if ((*((char *)buffer+j) & 1) && ((*((char *)buffer+j) & 2) >> 1)) {
										j++;
										vbrframes = *((unsigned long *)buffer+j/4);
										vbrbytes = *((unsigned long *)buffer+(j+4)/4);
										j += 7;
									}
									if (vbrframes > 0 && vbrbytes > 0) vbr = ((vbrbytes / vbrframes) * 1000) / 3265;
									break;
								}
				}

				i = j;
				if (stcd_i(buffer, badhdr) == 8 && i < 417) i = 417;
				/* Work-around for "MpegDJ Encoder" bug */

				while (!header && i<len-3) {
					/* Seek until synchronization is found (%11111111.111xxxxx) */
					if (*((unsigned char *)buffer+i) == 255) {
						if (*((unsigned char *)buffer+i+1) >= 224) {

							/*	MPEG Audio version ID
								00 - MPEG Version 2.5 (later extension of MPEG 2)
								01 - reserved
								10 - MPEG Version 2 (ISO/IEC 13818-3)
								11 - MPEG Version 1 (ISO/IEC 11172-3)
							*/

							m_id = 3 - ((*((char *)buffer+i+1) & 24)>>3);
							if (m_id == 3) m_id = 1;

							/*	Layer description
								00 - reserved
								01 - Layer III
								10 - Layer II
								11 - Layer I
							*/

							m_layer = 4 - ((*((char *)buffer+i+1) & (2+4))>>1);
							m_bit   =      (*((char *)buffer+i+2) & (16+32+64+128))>>4;
							m_freq  =      (*((char *)buffer+i+2) & (4+8))>>2;

							/*	Channel Mode
								00 - Stereo
								01 - Joint stereo (Stereo)
								10 - Dual channel (2 mono channels)
								11 - Single channel (Mono)
							*/

							m_mode  =      (*((char *)buffer+i+3) & (64+128))>>6;

							col = m_layer-1;
							if (m_id == 1) col=col+3;

							if (m_bit>0 && m_bit<15 && m_freq < 3 && m_layer < 4 && m_id != 2) {

								header = TRUE;

								sd->freq = mp3_frequencies[m_freq][m_id];
								if (vbr > 0) sd->bitrate = vbr;
								else sd->bitrate = mp3_bitrates[m_bit-1][col];

								if (m_layer == 2) { /* Sanity checks */
									if ((sd->bitrate == 32 || sd->bitrate == 48 || sd->bitrate == 46 || sd->bitrate == 80) && m_mode != 3) header = FALSE;
									if ((sd->bitrate == 224 || sd->bitrate == 256 || sd->bitrate == 320 || sd->bitrate == 384) && m_mode == 3) header = FALSE;
								}

								sd->time = (sd->size-i)/(sd->bitrate*125);
								/* 'i' is the length of ID3v2 tag. ID3v1 size is always 128 bytes,
								   so it's not worth the overhead to check for this.
								   128 bytes of a 128 kbps MP3 = 0.000008 seconds! */
							}
						}
					}
					i++;
				}
			}
			FreeMem(buffer, 300032);
		}
		Close(fh);
		if (header) {
			DoMethod(data->list, MUIM_NList_InsertSingle, sd, MUIV_NList_Insert_Sorted);
			numsongs++; numbytes += sd->size;
			sharechanged = TRUE;
			update_stat(data);
			if (gui_onlinestate == ONLINE) nap_notifyshare(sd);
		}
		else {
			gui_debugf((char *)MSG_SHARE_ADDERROR, sd->title, sd->size, sd->bitrate, sd->freq);
			sharefailed = TRUE;
		}
	}
	else UnLock(lock);

}


void notify_shares(struct shdata *data)
{
	sharedata sd;
	u_long tmp;
	int i, total;

	if (gui_onlinestate < LOGGING_IN) return;

	GetAttr(MUIA_NList_Entries, data->list, &tmp);
	total = tmp;

	if (total > 0) {
		gui_stat((char *)MSG_STATUS2_NOTIFYALL);
		for (i=0; i<total; i++) {
			DoMethod(data->list, MUIM_NList_GetEntry, i, &sd);
			if (!sd) break;
			nap_notifyshare(sd);
		}
	}
}


void remove_shares(struct shdata *data, long t)
{
	sharedata sd;
	u_long tmp;
	int total;

	GetAttr(MUIA_NList_Entries, data->list, &tmp);
	total = tmp;

	if (total == 0) return;

	switch(t) {
		case 0: /* Remove marked */
			set(data->list, MUIA_NList_Quiet, MUIV_NList_Quiet_Visual);
			DoMethod(data->list, MUIM_NList_Remove, MUIV_NList_Remove_Selected);
			update_stat(data);
			set(data->list,MUIA_NList_Quiet, MUIV_NList_Quiet_None);
			break;
		case 1: /* Remove all */
			set(data->list, MUIA_NList_Quiet, MUIV_NList_Quiet_Visual);
			for (;;) {
				DoMethod(data->list, MUIM_NList_GetEntry, 0, &sd);
				if (!sd) break;
				DoMethod(data->list, MUIM_NList_Remove, MUIV_NList_Remove_First);
			}
			update_stat(data);
			set(data->list, MUIA_NList_Quiet, MUIV_NList_Quiet_None);
			break;
	}
	sharechanged = TRUE;
}


void save_shares(struct shdata *data)
{
	sharedata sd;
	int i;
	BPTR fh;
	char buf[512], *tmp;

	strcpy(buf, FReqLibrary->fr_Drawer);
	AddPart(buf, FReqLibrary->fr_File, 511);

	fh = Open(buf, MODE_NEWFILE);
	if (!fh) return;

	for (i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &sd);
		if (!sd) break;
		if (tmp = strrep(sd->title, "\"", "*\"")) {
			sprintf(buf, "\"%s\" %s %ld %ld %ld %ld REQCOUNT %ld\n", tmp, sd->md5, sd->size, sd->bitrate, sd->freq, sd->time, sd->reqcount);
			free(tmp);
			Write(fh, buf, strlen(buf)); /* Should be buffered, check FWrite */
		}
	}

	Close(fh);
	sharechanged = FALSE;
}


void save_shares_as(struct shdata *data)
{
	u_long win;

	get(gui->win, MUIA_Window_Window, &win);
	if (FReqLibrary) {
		if (AslRequestTags(FReqLibrary,
			ASLFR_Window, win,
			ASLFR_TitleText, MSG_SHARE_SAVEAS,
			ASLFR_DoSaveMode, TRUE,
			TAG_DONE)) {
					save_shares(data);
		}
	}
}


void load_shares(struct shdata *data, int merge)
{
	sharedata sd;
	int line = 0;
	BPTR fh;
	char buf[512], sharefile[512];
	LONG argarray[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
	UBYTE *argstr = "PATH/A,MD5/A,SIZE/A/N,BITRATE/A/N,FREQUENCY/A/N,TIME/A/N,REQCOUNT/K/N";
	struct RDArgs *rdargs;

	if (merge == 0) remove_shares(data, 1);

	strcpy(sharefile, FReqLibrary->fr_Drawer);
	AddPart(sharefile, FReqLibrary->fr_File, 511);

	if (fh = Open(sharefile, MODE_OLDFILE)) {
		set(data->list, MUIA_NList_Quiet, MUIV_NList_Quiet_Full);
		if (rdargs = AllocDosObject(DOS_RDARGS, NULL)) {
			while (FGets(fh, buf, 512)) {
				line++;

				rdargs->RDA_Buffer = NULL;
				rdargs->RDA_Source.CS_Buffer = buf;
				rdargs->RDA_Source.CS_Length = strlen(buf);
				rdargs->RDA_Source.CS_CurChr = 0;

				if (ReadArgs(argstr, argarray, rdargs)) {
					if (sd = malloc(sizeof(_sharedata))) {
						strcpy(sd->title,  (char *)argarray[ ARG_PATH     ]);
						strcpy(sd->md5,    (char *)argarray[ ARG_MD5      ]);
						sd->size     =   *((long *)argarray[ ARG_SIZE     ]);
						sd->bitrate  =   *((long *)argarray[ ARG_BITRATE  ]);
						sd->freq     =   *((long *)argarray[ ARG_FREQ     ]);
						sd->time     =   *((long *)argarray[ ARG_TIME     ]);
						if (argarray[ARG_REQCOUNT]) sd->reqcount = *((long *)argarray[ ARG_REQCOUNT ]);
						else sd->reqcount = 0;

						DoMethod(data->list, MUIM_NList_InsertSingle, sd, MUIV_NList_Insert_Bottom);
						numsongs++; numbytes += sd->size;
						if (gui_onlinestate == ONLINE) nap_notifyshare(sd);
					}
					FreeArgs(rdargs);
				}
				else gui_debugf((char *)MSG_PARSE_ERROR, sharefile, line);
			}
			FreeDosObject(DOS_RDARGS, rdargs);
		}

		set(data->list, MUIA_NList_Quiet, MUIV_NList_Quiet_None);
		update_stat(data);

		Close(fh);
	}
	sharechanged = FALSE;
}


void load_shares_as(struct shdata *data, int merge)
{
	u_long win;

	get(gui->win, MUIA_Window_Window, &win);
	if (FReqLibrary) {
		if (AslRequestTags(FReqLibrary,
			ASLFR_Window, win,
			ASLFR_TitleText, MSG_SHARE_LOADAS,
			TAG_DONE)) {
					load_shares(data, merge);
		}
	}
}


void update_stat(struct shdata *data)
{
	static char buf[100], totalsize[8];
	char descriptor[16];
	unsigned long number, tempnumber;

	if (numbytes >= (1024*1024*1024)) {
		strcpy(descriptor, MSG_SHARE_STAT_GB);
		tempnumber = numbytes/1024/1024*10/1024;
		number = tempnumber/10;
		tempnumber = tempnumber-(number*10);
		sprintf(totalsize,"%d.%d", number, tempnumber);
	}
	else if (numbytes >= (1024*1024)) {
		strcpy(descriptor, MSG_SHARE_STAT_MB);
		tempnumber = numbytes/1024*10/1024;
		number = tempnumber/10;
		tempnumber = tempnumber-(number*10);
		sprintf(totalsize, "%d.%d", number, tempnumber);
	}
	else {
		strcpy(descriptor, MSG_SHARE_STAT_KB);
		number = numbytes/1024;
		sprintf(totalsize, "%d", number);
	}

	sprintf(buf, MSG_SHARE_STAT_TMP, numsongs, totalsize, descriptor);
	set(data->stat, MUIA_Text_Contents, buf);
}


void play_share(struct shdata *data)
{
	sharedata sd;
	char buf[1024], *command;

	DoMethod(data->list, MUIM_NList_GetEntry, MUIV_NList_GetEntry_Active, &sd);
	if (sd) {
		if (prf->scripts[PRFE_LIB_DCLICK]) {
			sprintf(buf, "Run <>NIL: %s", prf->scripts[PRFE_LIB_DCLICK]);
			if (command = strrep(buf, "%f", sd->title)) {
				Execute(command, 0, 0);
				free(command);
			}
		}
	}
}


void upload_file_confirm(struct shdata *data, char *user, char *fname)
/* Called from napster.c */
{
	sharedata sd;
	song s;
	int i=0, total;
	ULONG tmp;
	BOOL found = FALSE;
	char *path;

	GetAttr(MUIA_NList_Entries, data->list, &tmp);
	total = tmp;

	gui_debugf((char *)MSG_SHARE_REQFILE, user, fname);

	while (i<total && !found) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &sd);
		if (sd) {
			path = MakeWinPath(sd->title);
			if (strcmp(path, fname) == 0) found = TRUE;
			free(path);
		}
		i++;
	}

	if (!found) {
		gui_debug((char *)MSG_SHARE_NOTFOUND);
		return;
	}

	s = malloc(sizeof(_song));
	if (!s) return;
	memset(s, 0, sizeof(_song));

	s->title = strdup(sd->title);
	s->md5 = strdup(sd->md5);
	s->size = sd->size;
	s->bit = sd->bitrate;
	s->freq = sd->freq;
	s->time = sd->time;
	s->user = strdup(user);
	/* At this point we don't know IP and link speed - can't complete the struct */

	ul_addq(s);
}


void update_count(struct shdata *data, char *fname)
{
	sharedata sd;
	int i;
	long pos;

	for (i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &sd);
		if (!sd) return;
		if (strcmp(sd->title, fname) == 0) break;
	}

	/* Update statistics (count) */
	sd->reqcount++;
	sharechanged = TRUE;

	pos = MUIV_NList_GetPos_Start;
	DoMethod(data->list, MUIM_NList_GetPos, sd, &pos);
	DoMethod(data->list, MUIM_NList_Redraw, pos);
}


void nap_notifyshare(sharedata sd)
{
	char *path, *suffix;

	/* In case invalid files were added manually or with an earlier version */
	if (IsIn(sd->title, '"') || IsIn(sd->title, '\\')) {
		gui_debugf((char *)MSG_SHARE_ILLEGALCHARS, sd->title);
		return;
	}

	suffix = sd->title + strlen(sd->title) - 4;
	if (stricmp(suffix, ".mp3") != 0 && stricmp(suffix, ".mp2") != 0 && stricmp(suffix, ".wma") != 0) {
		gui_debugf((char *)MSG_SHARE_INVALIDEXTENSION, sd->title);
		return;
	}

	path = MakeWinPath(sd->title);
	sprintf(nap_buf, "\"%s\" %s-%ld %ld %ld %ld %ld", path, sd->md5, sd->size, sd->size, sd->bitrate, sd->freq, sd->time);
	free(path);
	nap_send(NAPC_NOTIFYSHARE);
}


char *strrep(char *str1, char *old_str, char *new_str)
{
	char *str;
	int len, i;

	/* Allocate memory for the new string (worst-case) */
	if (!(str = malloc(strlen(str1)*(strlen(new_str)/strlen(old_str)+1)))) return NULL;
	str[0] = '\0';

	for (i=0; i<strlen(str1); i++) {
		if (strncmp(str1+i, old_str, strlen(old_str)) == 0) {
			strcat(str, new_str);
			i += strlen(old_str)-1;
		}
		else {
			len = strlen(str);
			str[len] = str1[i];
			str[len+1] = '\0';
		}
	}

	return str;
}


char *MakeWinPath(char *AmigaPath)
/* PRE: The AmigaPath must contain max. 1 colon */
{
	char *WinPath;
	int i, j;

	if (!(WinPath = malloc(strlen(AmigaPath)+2))) return AmigaPath;

	for (i=0, j=0; i<strlen(AmigaPath); i++, j++) {
		if (AmigaPath[i] == '/') WinPath[j] = '\\';
		else if (AmigaPath[i] == ':') {
			WinPath[j++] = ':';
			WinPath[j] = '\\';
		}
		else WinPath[j] = AmigaPath[i];
	}
	WinPath[j] = '\0';

	return WinPath;
}


BOOL IsIn(char *string, char c)
{
	int i;

	for (i=0; i<strlen(string); i++)
		if (string[i] == c) return TRUE;

	return FALSE;
}
