PDA

View Full Version : Recursive SPIN routine - USB thumbdrive file find



Ron Czapala
11-15-2011, 02:42 PM
I wanted to develop a routine for my VMUSIC2 project (http://www.parallax.com/Resources/ApplicationsContests/Propeller/IRRemoteVmusic2MP3Player/tabid/959/Default.aspx) which would locate a particular MP3 file on a USB thumbdrive inserted into the VMUSIC2.
Of course, there can be many nested folders and it seemed that a recursive routine was the best approach.
Since the Parallax memory stick datalogger module is similar to the VMUSIC2 (both use the Vinculum VNC1L USB host controller), this code should work
with the datalogger.

I have many vbscripts that do this using the FileSystemObject in the Windows scripting runtime DLL but doing this in SPIN for a Propeller
would be a different scenario due to memory constraints, etc.

What I did was define a global byte array in the DAT section which would hold up to 500 thirteen byte top-level directory names i.e. directories immediately under the root.

Then I created recursive method which defines a local variable to store up to 10 sub-folder names. It calls itself to handle nested sub-folders.
It passes an index pointer, directory counter, and subfolder array address to the supporting methods.
As it works it way through the directory structure, it looks at each file name and compares it to the search filename.

I use a variable named dirlvl to keep track of the nesting level. It is also used to build a array of the directory names maintaining a path to the current folder.
Of course, as the routine calls itself, more memory is being allocated and then freed up when a directory has been processed
and processing resumes with the parent directory.

I am trying to figure out the best way to monitor available memory during the recursion.
One way would be to check the dirlvl variable, but there may be a better way.
I also want to see if I can speed it up...


CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000
' ------- Pin assigments ------
vm_rts = 0 ' Request To Send --> VMusic2 pin 6 (CTS#) - green
vm_rx = 1 ' Receive Data <-- VMusic2 pin 5 (TXD) - yellow
vm_tx = 2 ' Transmit Data --> VMusic2 pin 4 (RXD) - orange
vm_cts = 3 ' Clear To Send <-- VMusic2 pin 2 (RTS#) - brown
vm_port = 0
BAUD = 9_600 ' LCD baud rate
maxdirs = 500 ' number of directory names to table
maxpath = 10 ' max number of folder names in path for recursive routine
maxsubdirs = 12 ' number of sub folders allowed under top level folder
maxbuff = 60 ' size of buffer for parsing VM2 serial output

VAR
long useser ' USB serial connection detected T/F
long debugsw ' control output to Parallax Terminal
long diskchk ' "No Disk" check
byte fname[13] ' file name playing
byte rxbuff[maxbuff] ' work buffer
byte mp3title[maxbuff] ' hold song title
byte artist[maxbuff] ' hold artist
byte buffptr
word dircntr 'directory counter
long dirptr 'directory pointer
byte dirlvl 'directory nesting level
byte findsw '1=recursive search for filename
long pathptr 'path pointer

OBJ
uarts : "pcFullDuplexSerial4FC" '1 COG for 4 serial ports
Debug : "FullDuplexSerial" '"SerialMirror" '"Extended_FDSerial"
I2C : "Basic_I2C_Driver_1"
' strng : "ASCII0_STREngine_1"

PUB Main | idx
dira[vm_rts]~~
outa[vm_rts]~ ' Take Vinculum Out Of Reset - set low

useser := false
if ina[31] == 1 ' RX (pin 31) is high if USB is connected
Debug.start(31, 30, 0, 57600) ' ignore tx echo on rx
useser := true ' Debug serial connection is active
debugsw := true
else
outa[30] := 0 ' Force Propeller Tx line LOW if USB not connected

''-----------------------
''override debugsw to disable terminal displays
' debugsw := false
''-----------------------
if debugsw == true
Debug.str(string(16,"Please wait",13)) ' CLS, disp msg
I2C.Initialize(I2C#BootPin) ' initialize I2C object

diskchk := 1
uarts.Init 'start four port serial cog
' uarts.AddPort(vm_port,vm_rx,vm_tx,vm_cts,vm_rts,UA RTS#DEFAULTTHRESHOLD,UARTS#NOMODE,UARTS#BAUD9600)
uarts.AddPort(vm_port,vm_rx,vm_tx,vm_cts,vm_rts,UA RTS#DEFAULTTHRESHOLD,UARTS#INVERTCTS,UARTS#BAUD960 0)
uarts.Start
Read_VMResp
waitcnt(clkfreq * 2 + cnt)
uarts.str(vm_port, string("IPA",$0D)) 'monitor mode ascii
Read_VMResp
waitcnt(clkfreq / 2 + cnt)
uarts.str(vm_port, string("ECS",$0D)) 'extended command set
Read_VMResp
waitcnt(clkfreq / 2 + cnt)
uarts.str(vm_port, string("CD / ")) 'CD root
Read_VMResp

{ store directory names in dirs table
}
ReadDirectory 'read and store directory names

bytemove(@fname, string("STARBU~1.MP3"),13)
FindFile

repeat
waitcnt(clkfreq * 5 + cnt)
{ store directory names in dirs table
}

PUB ReadDirectory | idx
dircntr := 0
dirptr := @dirs
' debug.dec(@rxbuff) 'address of rxbuff
' debug.tx(13)
debug.str(string("dirs addr: "))
debug.dec(dirptr) 'address of dirs
debug.tx(13)
buffptr := 0
uarts.str(vm_port, string("DIR",$0D)) 'directory list
Read_Dirs
if debugsw == true
repeat idx from 0 to dircntr-1
dirptr := @dirs + (idx * 13)
debug.dec(dirptr)
debug.tx(32)
debug.str(dirptr)
debug.tx(13)
buffptr := 0
waitcnt(clkfreq * 2 + cnt)
{ Read directory names (under root) and store into dirs byte array
Each directory name takes 13 bytes
}

PUB Read_Dirs| iobyte
iobyte:=0
repeat
iobyte:=uarts.rxtime(vm_port,500)
if iobyte < 0
quit
else
' debug.tx(iobyte)
if Process_Directory(iobyte)
if debugsw == true
debug.dec(dircntr)
debug.str(string(" Directories",13))

PUB Process_Directory(datain)
if datain == 13 'carriage return?
rxbuff[buffptr++] := 0
if StoreDir
return true
buffptr :=0
' bytefill(@rxbuff, 0, maxbuff) 'Clear Buff to 0
return
else
if buffptr < maxbuff - 1
rxbuff[buffptr++] := datain 'add byte to buffer

PUB StoreDir | dirlen
if findString(@rxbuff,string("D:\>")) > 0
return true
dirlen := findString(@rxbuff,string(" DIR")) - @rxbuff 'calc length of directory name
if dirlen > 0 'length of directory name
' debug.dec(dirlen)
' debug.tx(13)
bytemove(dirptr, @rxbuff, dirlen)
dircntr++
dirptr := @dirs + (dircntr * 13) 'calc addr to store next dir name
' debug.dec(dirptr)
' debug.tx(13)

PUB FindFile | idx
findsw := 1 'enable filename match
debug.str(string("Searching for: "))
debug.str(@fname)
debug.tx(13)
'loop thru top level directories
repeat idx from 0 to dircntr-1
dirptr := @dirs + (idx * 13)
dirlvl :=0
' debug.str(dirptr)
' debug.tx(13)
Recurse(dirptr) 'call recursive routine to search for file
debug.str(string("Done Searching"))
findsw := 0 'disable filename match

PUB Recurse(parent)| fldrptr, fldrcntr, fldrs[(maxsubdirs * 13) / 4], idx, psize
dirlvl++
' if dirlvl == 1
' bytefill(@dirpath,0,maxpath*13)
pathptr := @dirpath + ((dirlvl-1) * 13)
psize := strsize(parent)
bytemove(pathptr, parent, psize)
debug.str(string(13,"Path: "))
repeat idx from 0 to dirlvl-1
debug.tx("\")
debug.str(@dirpath + (idx * 13))
debug.tx(13)
{
debug.tx(13)
debug.str(string("- - - - - - - - - - - "))
debug.str(string("dir level: "))
debug.dec(dirlvl)
debug.tx(13)
}
fldrptr := @fldrs
fldrcntr := 0
longfill(@fldrs, 0, 39)

{
debug.str(string("fldrs addr: "))
debug.dec(fldrptr)
debug.tx(13)
}
if findString(parent,string("/")) <> @parent
{
debug.str(string("CD "))
debug.str(fldr)
debug.tx(13)
}
uarts.str(vm_port, string("CD "))
uarts.str(vm_port, parent) 'change directory
uarts.tx(vm_port, $0D) 'carriage return
Read_VMResp
{
buffptr := 0
if findsw == 1
uarts.str(vm_port, string("DIR "))
uarts.str(vm_port, @fname) 'dir 'filename'
uarts.tx(vm_port,$0D)
Read_VMResp
}

uarts.str(vm_port, string("DIR",$0D)) 'directory list
Read_SubDirs(@fldrptr, @fldrcntr, @fldrs)
'for each sub folder
if fldrcntr > 0
repeat idx from 0 to fldrcntr-1
fldrptr := @fldrs + (idx * 13)
Recurse(fldrptr)
uarts.str(vm_port, string("CD ..",$0D)) 'change to parent directory
buffptr := 0
pathptr := @dirpath + ((dirlvl-1) * 13)
bytefill(pathptr,0,13)
dirlvl--

PUB Read_SubDirs(fldrptr,fldrcntr,fldrs) | iobyte
iobyte:=0
repeat
iobyte:=uarts.rxtime(vm_port,500)
if iobyte < 0
quit
else
' debug.tx(iobyte)
if Process_SubDirectory(iobyte, fldrptr,fldrcntr,fldrs)
' if debugsw == true
' debug.dec(fldrcntr)
' debug.str(string(" Directories",13))

PUB Process_SubDirectory(datain,fldrptr,fldrcntr,fldrs )
' debug.str(string("proc dir: "))
' debug.dec(long[fldrptr])
' debug.tx(32)
' debug.dec(long[fldrcntr])
' debug.tx(13)
if datain == 13 'carriage return?
rxbuff[buffptr++] := 0
if StoreSubDir(fldrptr,fldrcntr,fldrs)
return true
buffptr :=0
' bytefill(@rxbuff, 0, maxbuff) 'Clear Buff to 0
return
else
if buffptr < maxbuff - 1
rxbuff[buffptr++] := datain 'add byte to buffer
PUB StoreSubDir(fldrptr,fldrcntr,fldrs) | dirlen
if findString(@rxbuff,string("D:\>")) > 0
return true
if findString(@rxbuff, string(". DIR")) == @rxbuff
return
if findString(@rxbuff, string(".. DIR")) == @rxbuff
return
dirlen := findString(@rxbuff,string(" DIR")) - @rxbuff 'calc length of directory name
if long[fldrcntr] > (maxsubdirs - 1) 'fldrs array is full
debug.str(string("Too many sub folders"))
debug.tx(13)
return
if dirlen > 0 'length of directory name
' debug.str(string("store dir: "))
' debug.dec(long[fldrptr])
' debug.tx(32)
' debug.dec(long[fldrcntr])
' debug.tx(32)
' debug.dec(dirlen)
' debug.tx(13)
bytemove(long[fldrptr], @rxbuff, dirlen)
long[fldrcntr]++
long[fldrptr] := fldrs + (long[fldrcntr] * 13) 'calc addr to store next dir name
else
if byte[@rxbuff][0] <> 0 ' ignore blank line at top of dir listing
debug.str(@rxbuff) ' file, not directory
debug.tx(13)
if findsw == 1
if findString(@rxbuff, @fname) == @rxbuff
debug.str(string("------> Found it!"))
debug.tx(13)
PUB Read_VMResp | iobyte
iobyte:=0
repeat
iobyte:=uarts.rxtime(vm_port,500)
if iobyte < 0
quit
else
Buffer_datain(iobyte)
' if debugsw == true
' debug.tx(iobyte)
PUB Buffer_datain(datain)
if datain == 13 'carriage return?
rxbuff[buffptr++] := 0
if debugsw == true
DisplayBuff
buffptr :=0
return
else
if buffptr < maxbuff - 1
rxbuff[buffptr++] := datain 'add byte to buffer
PUB DisplayBuff | idx
if strsize(@rxbuff) < 1 'suppress blank line after any dir command
return true
if findString(@rxbuff,string("D:\>")) > 0 'suppress standard response
' debug.tx(".")
return true
if findString(@rxbuff,string("Command Failed")) > 0
return true
if findsw == 1
if findString(@rxbuff, @fname) == @rxbuff
debug.str(string("------> Found it!"))
debug.tx(13)
debug.str(@rxbuff)
debug.tx(13)
PUB findString(stringToSearch, stringToFind) | index, size ' 7 Stack Longs
'// Author: Kwabena W. Agyeman
' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' // Searches a string of characters for the first occurence of the specified string of characters.
' //
' // Returns the address of that string of characters if found and zero if not found.
' //
' // StringToSearch - A pointer to the string of characters to search.
' // StringToFind - A pointer to the string of characters to find in the string of characters to search.
' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
size := strsize(stringToFind)
if(size--)
repeat strsize(stringToSearch--)
if(byte[++stringToSearch] == byte[stringToFind])
repeat index from 0 to size
if(byte[stringToSearch][index] <> byte[stringToFind][index])
result := true
quit
ifnot(result~)
return stringToSearch

DAT
nodisk byte "No Disk",0
dirs byte $00[maxdirs*13] 'array of directory names under root
dirpath byte $00[maxpath*13] 'allow ten deep directory path


Here is the terminal output using a some small folders - you can see the line "------> Found it!" when the file has been located.


Please wait
Ver 03.68VMSC1F On-Line:
Device Detected P2
No Upgrade
dirs addr: 96
3 Directories
96 JAZZ
109 VARIOU~1
122 PORTER
Searching for: STARBU~1.MP3
Path: \JAZZ
Path: \JAZZ\JAZZ1
BLOWIN~1.MP3
BLUEDA~1.MP3
BODYHE~1.MP3
HEARTS~1.MP3
HEAVEN.MP3
LOSTSU~1.MP3
NORTHE~1.MP3
REALLY~1.MP3
SEEYOU~1.MP3
SOUNDO~1.MP3
STROLL~1.MP3
WITHOU~1.MP3
Path: \JAZZ\JAZZ2
CANYOU~1.MP3
DOYOUR~1.MP3
GOODLO~1.MP3
INNERC~1.MP3
JUSTCA~1.MP3
SLOMOT~1.MP3
SMOOTH~1.MP3
SOMUCH~1.MP3
SUMMER~1.MP3
TIMETO~1.MP3
WALKIN~1.MP3
WONDER~1.MP3
Path: \JAZZ\JAZZ3
DON'TL~1.MP3
DOWNSO~1.MP3
DREAMS.MP3
LONDON~1.MP3
LONDON~2.MP3
LOSTIN~1.MP3
NEWDAW~1.MP3
NIGHTC~1.MP3
REDZON~1.MP3
STARCH~1.MP3
STILLT~1.MP3
TRIPPI~1.MP3
VENTUR~1.MP3
Path: \JAZZ\JAZZ4
KEYS_4.MP3
Path: \JAZZ\JAZZ5
KEYS_5.MP3
Path: \JAZZ\JAZZ6
KEYS_6.MP3
Path: \JAZZ\JAZZ7
KEYS_7.MP3
Path: \JAZZ\JAZZ8
KEYS_8.MP3
Path: \JAZZ\JAZZ9
KEYS_9.MP3
Path: \JAZZ\JAZZ10
KEYS_10.MP3
Path: \JAZZ\JAZZ11
KEYS_11.MP3
Path: \JAZZ\JAZZ12
KEYS_12.MP3
Path: \VARIOU~1
Path: \VARIOU~1\THEBIG~1
AWHITE~1.MP3
DANCIN~1.MP3
GOODLO~1.MP3
ISECON~1.MP3
ITSTHE~1.MP3
NATURA~1.MP3
TELLHI~1.MP3
TOOMAN~1.MP3
TRACKS~1.MP3
WHAT'S~1.MP3
Path: \VARIOU~1\TEENAG~1
BOBBYS~1.MP3
BORNTO~1.MP3
CHANCE~1.MP3
CRYMEA~1.MP3
DEVILO~1.MP3
HOLDME~1.MP3
IT'SAL~1.MP3
IT'SON~1.MP3
MYHEAR~1.MP3
MYSPEC~1.MP3
PRETTY~1.MP3
SEAOFL~1.MP3
SEALED~1.MP3
TEENAN~1.MP3
TELLLA~1.MP3
Path: \VARIOU~1\ONEHIT~1
986~1.MP3
COMEON~1.MP3
DOMINI~1.MP3
JUNGLE~1.MP3
MONTEG~1.MP3
ONETIN~1.MP3
REFLEC~1.MP3
YOUDON~1.MP3
Path: \VARIOU~1\MOREPU~1
DOYOUR~1.MP3
HANDST~1.MP3
IKNOWT~1.MP3
I'VEDO~1.MP3
JEOPARDY.MP3
LOVEIS~1.MP3
RIO.MP3
ROCKME~1.MP3
SHOUT.MP3
THELOO~1.MP3
THEPOW~1.MP3
THESAF~1.MP3
WALKIN~1.MP3
WOULDI~1.MP3
Path: \VARIOU~1\HARDTO~1.6MO
(DOWNA~1.MP3
(JUSTL~1.MP3
AKOOKI~1.MP3
BLACKI~1.MP3
EVERYD~1.MP3
EVERYB~1.MP3
GUANTA~1.MP3
HOLDME~1.MP3
I'MLEA~1.MP3
LAURIE~1.MP3
LIGHTN~1.MP3
MASTER~1.MP3
MYBOYL~1.MP3
NOTHIN~1.MP3
PATAPA~1.MP3
RUMORS.MP3
SAILOR~1.MP3
THEMOR~1.MP3
TOBACC~1.MP3
WALKAW~1.MP3
WITCHI~1.MP3
Path: \VARIOU~1\HARDTO~1.560
ANGELO~1.MP3
B-A-B-Y.MP3
DEEPPU~1.MP3
DON'TS~1.MP3
JOHNNY~1.MP3
LET'ST~1.MP3
NEXTPL~1.MP3
ONEBOY~1.MP3
SIXTEE~1.MP3
TAKEAL~1.MP3
TURNAR~1.MP3
Path: \VARIOU~1\HARDTO~1.219
BOBBY'~1.MP3
DIANE.MP3
DOMINI~1.MP3
ISAWLI~1.MP3
LITTLE~1.MP3
LITTLE~2.MP3
LOVERP~1.MP3
NAVYBL~1.MP3
PATCHES.MP3
POPSIC~1.MP3
SUGARS~1.MP3
WOODEN~1.MP3
YOUDON~1.MP3
Path: \VARIOU~1\DICKCL~1.2
CHAPEL~1.MP3
DUKEOF~1.MP3
EVERYB~1.MP3
HARLEM~1.MP3
LEADER~1.MP3
LETTHE~1.MP3
POETRY~1.MP3
THENYO~1.MP3
THOSEO~1.MP3
TILLIK~1.MP3
TOSSSI~1.MP3
VENUSI~1.MP3
Path: \VARIOU~1\DICKBA~1.2
ANGELO~1.MP3
CINNAMON.MP3
EVERLA~1.MP3
EXPRES~1.MP3
GIMMEG~1.MP3
LETITO~1.MP3
MORNIN~1.MP3
Path: \VARIOU~1\BILLBO~1
EASIER~1.MP3
FINGER~1.MP3
HE'SSO~1.MP3
LOUIE_~1.MP3
MYBOYF~1.MP3
SUGARS~1.MP3
SURFCI~1.MP3
SURFIN~1.MP3
WALKLI~1.MP3
Path: \VARIOU~1\BILLBO~2
ALLEY-~1.MP3
CATHY'~1.MP3
HANDYM~1.MP3
RUNNIN~1.MP3
SAVETH~1.MP3
WALK_D~1.MP3
Path: \VARIOU~1\AFTERN~1
CAMPBE~1.MP3
DAWN-K~1.MP3
GLADYS~1.MP3
HARRYN~1.MP3
JIMCRO~1.MP3
JOSEFE~1.MP3
STARBU~1.MP3
------> Found it!
STARLA~1.MP3
STEVEF~1.MP3
THEHOL~1.MP3
THELEM~1.MP3
THERAS~1.MP3
Path: \PORTER
Path: \PORTER\PORTONE
CANDLE~1.MP3
DBBLUE~1.MP3
FLIGHT~1.MP3
FOREVE~1.MP3
JUSTWA~1.MP3
LAKESH~1.MP3
LAYYOU~1.MP3
ONEMOR~1.MP3
WEAREO~1.MP3
WISHFU~1.MP3
Done Searching


 

Mike Green
11-15-2011, 03:09 PM
Checking the directory level is a good proxy for the memory usage of the recursive routine since you know how much memory each recursion takes.

If you're going to be looking up file names often, but changing the contents of the memory stick infrequently, you should consider having a separate program that recursively goes through the directory structure and builds a database that can be easily searched. This database could be stored on a root-level file. If you want to get fancy, you could use a Winbond SPI flash memory to hold the database. These come in sizes up to several megabytes and there's a driver in the ObEx that provides for named files and includes a program loader.

Ron Czapala
11-15-2011, 03:20 PM
Checking the directory level is a good proxy for the memory usage of the recursive routine since you know how much memory each recursion takes.

If you're going to be looking up file names often, but changing the contents of the memory stick infrequently, you should consider having a separate program that recursively goes through the directory structure and builds a database that can be easily searched. This database could be stored on a root-level file. If you want to get fancy, you could use a Winbond SPI flash memory to hold the database. These come in sizes up to several megabytes and there's a driver in the ObEx that provides for named files and includes a program loader.

Thanks for the suggestions Mike. I thought about using a vbscript to create a text file with the filenames and paths to load onto the drive beforehand.

The reason behind this effort was that when playing songs randomly on the VMUSIC2, it informs you of the filename playing, but not the path. I wanted to be able to delete the MP3 currently playing but you have to stop playing it first. When you do that, the VMUSIC returns to the root directory so you don't know what folder the file is in.

EDIT: Of course, with the limiting DOS 8.3 filenames, you could end up with two different MP3s with the same name in different folders.

The Winbond flash memory sounds interesting too!

Mike Green
11-15-2011, 04:10 PM
You could also scan the MP3 files for their metadata (see here (http://en.wikipedia.org/wiki/ID3)) and save the metadata in the database file along with the full path to the file. You could certainly have a script program on your PC to create the database, but you could also have a Spin program that would do it.

The Winbond flash memories come in sizes up to 8MB in PDIP and the ObEx driver handles up to that size.

Ron Czapala
11-15-2011, 04:30 PM
You could also scan the MP3 files for their metadata (see here (http://en.wikipedia.org/wiki/ID3)) and save the metadata in the database file along with the full path to the file. You could certainly have a script program on your PC to create the database, but you could also have a Spin program that would do it.

The Winbond flash memories come in sizes up to 8MB in PDIP and the ObEx driver handles up to that size.

I am very familiar with ID3Tag information and have written DLLs, code to modify tag information, and my own MP3 player in VB6 (using the Windows media player API).
I also wrote a vb program to delete unneccesay tag items (like the embedded album art,etc) and convert unicode tags to ascii (the VMUSIC2 won't handle unicode). Zip file (here (http://forums.parallax.com/showthread.php?135033-IR-Remote-controlled-VMUSIC2-MP3-Player&p=1043202&viewfull=1#post1043202))

Digikey has the 8Mb PDIP chips for $1.39 - very reasonable!

Ron Czapala
11-15-2011, 08:03 PM
Well, I created a vbscript to create a directory list file on the thumbdrive.
I attached a ZIP file with the vbscript and this readme.txt file:


Place the MakeDirList.vbs script on a thumbdrive and double click it in Windows explorer to run it.
It creates a tab-delimited file called directory.txt listing on the drive by recursively processing each folder and subfolder.
For each MP3 or WMA file, it creates a line with the 8.3 DOS format filename (shortname), the path (using folder shortnames), and the long filename.
It sorts the file back to itself and opens it notepad.

I have over 1010 MP3s on my thumbdrive and the sorted list shows many files with the same shortname in different directories.

For example, the are eight different songs named "YOU'RE~1.MP3" - songs that begin with "You're".

It could be a little tricky for the VMUSIC2 to find the right song to delete...


YOU'RE~1.MP3 \@MISC You're Sixteen.mp3
YOU'RE~1.MP3 \BARRYW~1\ALLTIM~1 You're the First, the Last, My Everything.mp3
YOU'RE~1.MP3 \CARLYS~1 You're So Vain.mp3
YOU'RE~1.MP3 \PATSYC~1\THEPAT~1 You're Stronger Than Me.mp3
YOU'RE~1.MP3 \SHANIA~1 You're Still The One.mp3
YOU'RE~1.MP3 \THEBES~3 You're a Big Girl Now.mp3
YOU'RE~1.MP3 \THESTY~1\THEBES~1 You're a Big Girl Now.mp3
YOU'RE~1.MP3 \THEVOG~1 You're The One.mp3