Link to home
Start Free TrialLog in
Avatar of seanpowell
seanpowellFlag for Canada

asked on

Crazy connections...

This is a basic tracking script that records which files are downloaded by a user(with the generous support of GaryC123):


download.asp
**********
<%
Response.Buffer = True

fileName = server.mappath(request("file"))
tempFile=split(filename,"\")
dbFileName = tempFile(ubound(tempFile))

set conn=Server.CreateObject("ADODB.Connection")
set rs=Server.CreateObject("ADODB.Recordset")
conn.open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & server.mappath("/_private/data/tracking.mdb")
rs.open "tracking",conn,2,2
rs.Addnew
rs("download")=dbFileName
rs("username")=session("fname") & " " & session("lname")
rs("date")=Now()
rs.update
set rs=nothing
set conn=nothing

dim objFSO, objTS
Set objFSO = Server.CreateObject("Scripting.FileSystemObject")
Set objTS = objFSO.OpenTextFile(fileName)

sFileType= Right(fileName,4)
NameFile=Right(FileName,Len(FileName)-InstrRev(FileName,"\"))

Select Case strFileType
  Case ".doc"
    ContentType = "application/msword"
  Case ".htm", "html"
    ContentType = "text/html"
  Case ".asp"
    ContentType = "text/asp"
  Case Else
    ContentType = "application/octet-stream"
End Select
   
response.AddHeader "content-disposition", "inline; filename=" & NameFile
response.contenttype=ContentType

Do While Not objTS.AtEndOfStream
strChunk = objTS.Read(32)
strTmp = ""
For i = 1 to Len(strChunk)
strTmp = strTmp & ChrB(Asc(Mid(strChunk, i, 1)))
Next
Response.BinaryWrite strTmp
Response.Flush
Loop
objTS.Close
Set objTS = Nothing
Set objFSO = Nothing
%>

This works fine the "first time" that I click a Word Doc link from an asp page. The file is downloaded, and it gets recorded in the database.

The second time I try it - same file or a different one - I get the following:

Server object error 'ASP 0177 : 800a004c'
Server.CreateObject Failed
download.asp, line 22
The operation completed successfully.

It still gets written to the db successfully - but hangs at the asp page

Can anyone see a problem above?

Also, can you explain what this does (in layman's terms) ?
tracking",conn,2,2

Thanks,
GM
Avatar of Gary
Gary
Flag of Ireland image

Strange I don't get an error, I can download as many files as I want.  The 2,2 opens the database for writing.
This is not good:

rs.open "tracking",conn,2,2
rs.Addnew
rs("download")=dbFileName
rs("username")=session("fname") & " " & session("lname")
rs("date")=Now()
rs.update
set rs=nothing
set conn=nothing

what happens here is that rs.open opens a record set, "tracking" means that it is grabbing each and every record from the tracking table, and the 2,2 sets the cursor type and locking properties (dynamic cursor and pessemistic locking)

What would be better here is to modify this slightly--grab some field from the table and specify a value so no records would be returned; something like this:

rs.open "SELECT * FROM tracking download='  ' ",conn,2,2

or some such. That way, instead of bringing over the whole table, you will get an empty recordset for appending a value.
AS far as the page haning goes, are you running Norton Antivirus? If so, disable the script blocking as that may be causing the File System Object portion of your page to hang.

FtB
Is this line 22?

Set objFSO = Server.CreateObject("Scripting.FileSystemObject")


FtB
Avatar of seanpowell

ASKER

If I count down, this is line 22:
Set objTS = objFSO.OpenTextFile(fileName)

assuming that I'm counting correctly. The lines are as per the code above, starting with <%

>>rs.open "SELECT * FROM tracking download='  ' ",conn,2,2
I don't believe I'm selecting any records, am I? Just inserting data into the db. (Filename, Date - login name...)
I don't know if I follow you there...


>>If so, disable the script blocking
Tried that. I have no problem the first time I download, just additional attempts fail.

Thanks - again...
Avatar of sybe
sybe

Why use FSO for reading a file, better use ADODB.Stream, that is much and much faster.

Sub WriteFileToBrowser(ByVal sFilePath, ByVal sContentType, ByVal sDisplayName)
    Dim oStream
    Set oStream = Server.CreateObject("ADODB.Stream")
    oStream.Type = 1
    oStream.Open
    oStream.LoadFromFile sFilePath
    Response.ContentType = sContentType
    Response.AddHeader "Content-Disposition", "filename=" & sDisplayName
    Response.AddHeader "Content-Length", oStream.Size

    Response.BinaryWrite oStream.Read
    oStream.Close
    Set oStream = Nothing
End Sub
Here's the thing:

compare:
rs.open "SELECT * FROM tracking download='  ' ",conn,2,2

with:

rs.open "tracking",conn,2,2


In both cases, you are specifying that you open a recordset with some material in it. In the first instance, the sql select would return no records, so the recordset would open with no records in it all, just an empty set of the fields that you need to do the .addNew() to. In the second instance, you are specifying bringiing over each and every item in the table into the recordset. Now, since you aren't going to be using any of that data, it is very inefficient to grab all of those records.

FtB
As far as Norton AntiVirus, just for fun, disable it, restart the server, and then test your code.

FtB
I agree with sybe. Here is a slightly longer routine that uses streaming and protects against errors and etc...:



<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0">
<TITLE></TITLE>
<%
Function downloadFile( strFile, strDownloadFilename )
     Dim strFilename,objStream,objFilesystem,objFilestream
     Dim intFileLength
     ' get full path of specified file
     strFilename = Server.MapPath( "../Uploads/temp/"  & strFile)
     ' clear the buffer
     Response.Buffer = True
     Response.Clear

     ' create stream
     Set objStream = Server.CreateObject("ADODB.Stream")
     objStream.Open

     ' set as binary
     objStream.Type = 1

     ' check the file exists
     Set objFilesystem = Server.CreateObject("Scripting.FileSystemObject")
     if not objFilesystem.FileExists(strFilename) then
          Response.Write("<h1>Error</h1>: " & strFilename & " does not exist<p>")
          Response.End
     end if


     ' get length of file
     Set objFilestream = objFilesystem.GetFile( strFilename )
     intFilelength = objFilestream.size
 
     objStream.LoadFromFile( strFilename )
     if err then
          Response.Write("<h1>Error: </h1>" & err.Description & "<p>")
          Response.End
     end if
     
     'format strFileName
     if Len( Trim(strDownloadFilename) ) > 0 then
          strDownloadFilename = Trim( strDownloadFilename )
     else
          strDownloadFilename = objFilestream.name
     end if

     ' send the headers to the users browser
     Response.AddHeader "Content-Disposition", "attachment; filename=" & strDownloadFilename
     Response.AddHeader "Content-Length", intFilelength
     Response.Charset = "UTF-8"
     Response.ContentType = "application/octet-stream"

     ' output the file to the browser
     Response.BinaryWrite objStream.Read
     Response.Flush

     ' tidy up
     objFilestream.Close
     Set objFilestream = Nothing

End Function
%>
</HEAD>
<BODY>
<%
Call downloadFile( Replace( Request("FILE") ,"/","\"), Request("FILENAME") )
'Response.Write (Request("FILE")  & "----" & Request("FILENAME") )
%>

</BODY>
</HTML>
>>disable it, restart the server
I do have it disabled. The server is at the hosting company - so no can do there.


Okay, so I now have:

set conn=Server.CreateObject("ADODB.Connection")
set rs=Server.CreateObject("ADODB.Recordset")
conn.open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & server.mappath("/_private/data/tracking.mdb")
rs.open "SELECT * FROM tracking download='  ' ",conn,2,2
rs.Addnew
rs("download")=dbFileName
rs("username")=session("fname") & " " & session("lname")
rs("date")=Now()
rs.update
set rs=nothing
set conn=nothing

and get this:

Microsoft JET Database Engine error '80040e14'
Syntax error in FROM clause.
/manual/download.asp, line 11
Sorry - missed your last post about the streaming - let me try it...
So, this might be your whole page here:


<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0">
<TITLE></TITLE>
<%
Function downloadFile( strFile, strDownloadFilename )
     Dim strFilename,objStream,objFilesystem,objFilestream
     Dim intFileLength
     ' get full path of specified file
     strFilename = Server.MapPath( "../Uploads/temp/"  & strFile)
     ' clear the buffer
     Response.Buffer = True
     Response.Clear

     ' create stream
     Set objStream = Server.CreateObject("ADODB.Stream")
     objStream.Open

     ' set as binary
     objStream.Type = 1

     ' check the file exists
     Set objFilesystem = Server.CreateObject("Scripting.FileSystemObject")
     if not objFilesystem.FileExists(strFilename) then
          Response.Write("<h1>Error</h1>: " & strFilename & " does not exist<p>")
          Response.End
     end if


     ' get length of file
     Set objFilestream = objFilesystem.GetFile( strFilename )
     intFilelength = objFilestream.size
 
     objStream.LoadFromFile( strFilename )
     if err then
          Response.Write("<h1>Error: </h1>" & err.Description & "<p>")
          Response.End
     end if
     
     'format strFileName
     if Len( Trim(strDownloadFilename) ) > 0 then
          strDownloadFilename = Trim( strDownloadFilename )
     else
          strDownloadFilename = objFilestream.name
     end if

     ' send the headers to the users browser
     Response.AddHeader "Content-Disposition", "attachment; filename=" & strDownloadFilename
     Response.AddHeader "Content-Length", intFilelength
     Response.Charset = "UTF-8"
     Response.ContentType = "application/octet-stream"

     ' output the file to the browser
     Response.BinaryWrite objStream.Read
     Response.Flush

     ' tidy up
     objFilestream.Close
     Set objFilestream = Nothing
End Function


fileName = server.mappath(request("file"))
tempFile=split(filename,"\")
dbFileName = tempFile(ubound(tempFile))

set conn=Server.CreateObject("ADODB.Connection")
set rs=Server.CreateObject("ADODB.Recordset")
conn.open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & server.mappath("/_private/data/tracking.mdb")
rs.open "SELECT * FROM tracking download='  ' ",conn,2,2
rs.Addnew
rs("download")=dbFileName
rs("username")=session("fname") & " " & session("lname")
rs("date")=Now()
rs.update
set rs=nothing
set conn=nothing

Call downloadFile( Replace( dbFileName ,"/","\"),dbFileName )

%>
</HEAD>
<BODY>

</BODY>
</HTML>

Fritz the Blank
Where is it writing to the database in that streaming example?
Take a look at my last post--I've integrated the database update and the streaming code into one page.

FtB
OKay...

And do I need to alter this?

strFilename = Server.MapPath( "../Uploads/temp/"  & strFile)
Oops, sorry about that. Please change that to match your environment.

FtB
No problem - wait... what location is that trying to point to?
It is trying to go to the location where the file is stored that the user wants to download.

FtB
Microsoft JET Database Engine error '80040e14'
Syntax error in FROM clause.
download.asp, line 70


Also, the files are in a number of different folders. I can specify a single one for the test - but I think I'll need flexibility for the rollout. Do the files have to be in only 1 folder?
70 appears to be:
rs.open "SELECT * FROM tracking download='  ' ",conn,2,2
Damn, SQL syntax error! I forgot the WHERE!


rs.open "SELECT * FROM tracking WHERE download='  ' ",conn,2,2

We can easily rework this so that we can pass the path as a parameter.

FtB

Okay...

Now we have:

Response object error 'ASP 0156 : 80004005'

Header Error

/manual/download.asp, line 12

The HTTP headers are already written to the client browser. Any HTTP header modifications must be made before writing page content
Just so I understand:
strFilename = Server.MapPath( "volunteer/"  & strFile)

This is relative to the download.asp page, or the file itself:

Folder Structure:
/manual/download.asp

Files are at:
/manual/foldername/foldername/file.doc
To be sure about that, create a test page and try the following:

response.write(Server.MapPath( "volunteer/"  & strFile) )


response.write(Server.MapPath( "/volunteer/"  & strFile) )

response.write(Server.MapPath( "../volunteer/"  & strFile) )

until you get what you need.

As far as your other error goes, put

response.buffer=true

at the top of your page.

FtB
I think wer'e in business.

Do I need the html code?

<% response.buffer=true %>
<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft FrontPage 4.0">
<TITLE></TITLE>
<%
Function downloadFile( strFile, strDownloadFilename )
I am not sure what you mean here, but you might want some html code to provide the user a way to navigate back to a friendlier page.

FtB
Okay - we're rocking and rolling. So now, the only problem left is the file location.

Currently I have:

folder/donwload.asp
folder/folder1/folder1/file
folder/folder1/folder2/file

folder/folder2/folder1/file
folder/folder2/folder2/file

etc.

>> provide the user a way to navigate back to a friendlier page

Actually it never leaves the original page with the link - meaning it never goes to download.asp in the browser - it just asks me to open or save the file.
Let's try something like this:

Function downloadFile( strFile, strDownloadFilename, strPath )

....

strFilename = Server.MapPath( strPath  & strFile)

.....


Of course, you will have to fuss with that line a little bit. I would recommend creating a separate test page and do:


Response.write( Server.MapPath( strPath  & strFile) )

Response.write( Server.MapPath( strPath  & "/"  strFile) )

and etc. until you get it

FtB

Microsoft VBScript runtime error '800a01c2'

Wrong number of arguments or invalid property assignment: 'downloadFile'

/manual/download.asp, line 78

Do we need to later this line as well then (78)?
Call downloadFile( Replace( dbFileName ,"/","\"),dbFileName )
Right, you will have to pass the path as an argument now to the function, e.g.:

Call downloadFile( Replace( dbFileName ,"/","\"),dbFileName ,"folder/folder1/folder1/" )

FtB
Hi guys,

Not trying to grab anybody's points here, just an observation.

In the original code above, I noticed that there was no:
rs.Close()
conn.Close()

before the:
set rs=nothing
set conn=nothing

I don't know if this will make a difference.

~Disrupted
@Disrupted--


Good catch--I was looking at some of the stranger things and missed that!

It must make a difference--doesn't the connection stay open until it times out otherwise?

Ft B
FtB,

Yes, if the connection is not closed, it will be unavailable to the connection pool on the server. If this code is run enough times, more connections can be added to the pool, taking up more system resources.

If I remember correctly, I think a connection will be returned back to the pool after 15 minutes of inactivity, if kept open.

Also, if the code somehow looped back to this object (either conn or rs), which has been set to nothing, but not closed, it is possible that the object could be re-assigned the same connection object, which possibly still has unknown parameters set, or data within.

~Disrupted


On a related note--I usually take care of that like this:

sub ClearRecordSet(ByRef strRecordSetName)
    if IsObject(strRecordSetName) then
         if not strRecordSetName is Nothing Then
              if strRecordSetName.state <> 0 then
                   strRecordSetName.close
              end if
              set strRecordSetName = Nothing
         end if
    end if
end sub

sub ClearConnection(ByRef strConnectionName)
    if IsObject(strConnectionName) then
         if not strConnectionName is Nothing Then
              if strConnectionName <> 0 then
                   strConnectionName.close
              end if
              set strConnectionName = Nothing
         end if
    end if

Fritz the Blank
>>Call downloadFile( Replace( dbFileName ,"/","\"),dbFileName ,"folder/folder1/folder1/" )

I'm not sure how I would do that, as the folder names vary?

I've added:
rs.Close()
conn.Close()
So do the file names, right? So what you want to do is to pass the path as a parameter when you pass the file name.

Fritz the Blank
That's what I thought.

The script is working beautifully now - with the file name hard-coded.
Now I just need to figure out how to generate the path dynamically...
Given that the path varies, I don't know how you might do that without passing a path parameter.

I suppose that you could try to iterate through the directories until the file is found, but that has two drawbacks:

1) you might have two files with the same name in two different directories
2) it would be a draw on resources

FtB
BTW, did you catch that part about closing your objects before setting them to nothing?

FtB
Yup - caught it, thanks.

>>Given that the path varies, I don't know how you might do that without passing a path parameter.
Yes - unfortunately I have no idea how to do that. Another Q I suppose after I do some research.

How to deal wqith the points?
50 - sybe for suggesting the stream
50 - Disrupted for noting your error :-)
And the rest to you?
ASKER CERTIFIED SOLUTION
Avatar of fritz_the_blank
fritz_the_blank
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thanks very much guys. :) The points were an unexpected bonus.

~Disrupted
Okay - new Q's have been posted...

I have about 8 different folders - it will probably be easier for me to have a separate download.asp in each rather than go nuts...
Yep... just saw them.
There is an idea. If you do that, put all of the code into an include file so that you have it in one place. Then on each of your pages, just have a function call and pass the parameter.

Frit z the Blank