FTP Helper

Posted by GrahamRanson, Posted on January 15, 2011

2 votes

FTP Helper allows you to simply upload, download and append files using FTP.

Usage
Include the module and create a new connection.

local ftp = require("ftp")
 
local connection = ftp.newConnection{ 
        host = "domain.com", 
        user = "username", 
        password = "password", 
        port = 21 -- Optional. Will default to 21.
}

Upload a file from your resources directory, onSuccess and onError are optional event handlers.

connection:upload{
        localFile = system.pathForFile("image.jpg", system.ResourcesDirectory),
        remoteFile =  "/public_html/image.jpg",
        onSuccess = onUploadSuccess,
        onError = onError
}

Download a file from into your documents directory and save it as "image.jpg", will either overwrite and existing file or create a new one.

connection:download{
        remoteFile = "/public_html/image.jpg",
        localFile = "image.jpg",
        onSuccess = onDownloadSuccess,
        onError = onError
}

Append the contents of one file on to another. Note, I know this works for text files but I have no idea what would happen if you tried it on binary files.

connection:append{
        localFile = system.pathForFile("todaysLog.txt", system.ResourcesDirectory),
        remoteFile = "/public_html/completeLog.txt",
        onSuccess = onAppendSuccess,
        onError = onError
}

Handlers

The onSuccess for uploading and downloading will have a parameter passed as "event.path", for uploading it is the remote path and downloading it is the path to your new file.

All onError handlers have "event.error" which is the FTP error code and message as passed from Lua sockets.

All Code (ftp.lua)

-- 
-- Abstract: Simplifies the use of FTP functions with Lua.
-- 
-- Author: Graham Ranson - http://www.grahamranson.co.uk
--
-- Version: 1.0
-- 
-- FTP Helper is MIT licensed, see http://developer.anscamobile.com/code/license
-- Copyright (C) 2010 ANSCA Inc. All Rights Reserved.
 
module(..., package.seeall)
 
local ftp = require("socket.ftp")
local ltn12 = require("ltn12")
 
function newConnection(params)
        
        local self = {}
        self.host = params.host or "anonymous.org"
        self.user = params.user or "anonymous"
        self.password = params.password or ""
        self.port = params.port or 21
 
        local putFile = function(params, command)
                        
                success, error = ftp.put{
                        host = self.host, 
                        user = self.user,
                        password = self.password,
                        port = self.port,
                        type = "i",
                        step = ltn12.all,
                        command = command,
                        argument = params.remoteFile,
                        source = ltn12.source.file( io.open( params.localFile, "rb" ) )  
                }
                
                if success then
                        if params.onSuccess then
                                params.onSuccess( { path = self.host .. params.remoteFile } )
                        end
                else
                        if params.onError then
                                params.onError( { error = error } ) 
                        end
                end
                                
                return success, error
                
        end
        
        local getFile = function(params)
        
                local success, error = ftp.get{
                        host = self.host, 
                        user = self.user,
                        password = self.password,
                        port = self.port,
                        type = "i",
                        step = ltn12.all,
                        command = command,
                        argument = params.remoteFile,
                        sink = ltn12.sink.file(params.localFile)
                }
                
                if success then
                        if params.onSuccess then
                                params.onSuccess( { path = params.localPath } )
                        end
                else
                        if params.onError then
                                params.onError( { error = error } ) 
                        end
                end
                        
                return success, error
        end
 
        function self:upload(params)
                return putFile(params, "stor")
        end
        
        function self:download(params)
                
                params.localPath = system.pathForFile( params.localFile, system.DocumentsDirectory )
                params.localFile = io.open( params.localPath, "w+b" ) 
 
                return getFile(params)
        
        end
        
        function self:append(params)
                return putFile(params, "appe")
        end
        
        return self
        
end

Example Usage

local ftp = require("ftp")
 
local connection = ftp.newConnection{ 
                host = "monkeydead.com", 
                user = "monkeyde", 
                password = "A4MUaC8wS8", 
                port = 21 -- Optional. Will default to 21.
        }
 
local onUploadSuccess = function(event)
        print("File uploaded to " .. event.path)
end
 
local onDownloadSuccess = function(event)
        print("File downloaded to " .. event.path)
end
 
local onAppendSuccess = function(event)
        print("File appended")
end
 
local onError = function(event)
        print("Error: " .. event.error)
end
 
connection:upload{
        localFile = system.pathForFile("image.jpg", system.ResourcesDirectory),
        remoteFile =  "/public_html/imagei.jpg",
        onSuccess = onUploadSuccess,
        onError = onError
}
        
connection:download{
        remoteFile = "/public_html/image.jpg",
        localFile = "image2.jpg",
        onSuccess = onDownloadSuccess,
        onError = onError
}
        
connection:append{
        localFile = system.pathForFile("todaysLog.txt", system.ResourcesDirectory),
        remoteFile = "/public_html/completeLog.txt",
        onSuccess = onAppendSuccess,
        onError = onError
}

Notes

I have tested this on iOS but not Android.

I hope someone finds this useful.

Compatibility: 
Corona 2.0

Replies

darwinyo
User offline. Last seen 19 weeks 42 min ago. Offline
Joined: 29 Dec 2010

Any idea how to make this use SSL? It's pretty easy to sniff out upload credentials without.

GrahamRanson's picture
GrahamRanson
User offline. Last seen 6 days 7 hours ago. Offline
Joined: 29 Mar 2010

At present I am not, this was only a quick thing and was "good enough" for me at the time. I will have a think and get back to you.

jalemanyf
User offline. Last seen 1 week 4 days ago. Offline
Joined: 16 Jan 2011

I m going to test it in Android devices (nexus one and Asus ee transformer) but I want more sample code about who you do the connection... could you show me a little bit more, please? thanks in advance

GrahamRanson's picture
GrahamRanson
User offline. Last seen 6 days 7 hours ago. Offline
Joined: 29 Mar 2010

The connection is created by calling newConnection

1
2
3
4
5
6
7
8
local ftp = require("ftp")
 
local connection = ftp.newConnection{ 
        host = "domain.com", 
        user = "username", 
        password = "password", 
        port = 21 -- Optional. Will default to 21.
}

jalemanyf
User offline. Last seen 1 week 4 days ago. Offline
Joined: 16 Jan 2011

First of all, thanks for your quickly responds.

Then, when you have the connection, how do you upload a file? I saw the code you published but, It is all the possibilities(upload, download,...) but I don t see a main routine to call them and control the process.

If a transfer is broken, it could continue from the last position?

Best Regards

GrahamRanson's picture
GrahamRanson
User offline. Last seen 6 days 7 hours ago. Offline
Joined: 29 Mar 2010

The description of the code at the top of this post shows how to upload a file ( as well as download and append ).

It does not do anything to deal with broken transfers, at a guess what you could probably do is encode the file into something like base64 and then upload that in small chunks keeping track of where you are. That way if it breaks you know which chunks are already up. You will then need to join and decode the chunks at the other end.

jalemanyf
User offline. Last seen 1 week 4 days ago. Offline
Joined: 16 Jan 2011

I have test it on Android devices and the emulator. On the emulator works ok, great! but on Android devices (Nexus One and Asus eePad Transformer) works only with text files (xml works ok for me) but not with binary files: if I transfer an JPG image or a PNG image it is created on the FTP server (FileZilla) with 0 Kbytes!!!

I thinked it could be the type of the ftp transfer. Initially the type is "i" (binary) and it seems to bee correct, but

I changed to type="a", doesn't work!

I changed to type="L 8", doesn't work!

My app must transfer an SQLite database so, I need the ftp to work...

Any ideas and suggestions are welcome!

GrahamRanson's picture
GrahamRanson
User offline. Last seen 6 days 7 hours ago. Offline
Joined: 29 Mar 2010

I'm not sure what the difference is with Android as I haven't worked with it all that much however you could try skipping FTP altogether to upload your database files.

You could tray encoding the file contents in base64, POST that to a webserver, and the decode the result back into a file. Not entirely sure if it would work for database files but it works for text and image files.

MBD's picture
MBD
User offline. Last seen 3 hours 38 min ago. Offline
Joined: 14 Sep 2010

Does anyone know how to NOT connect using PASV? For whatever reason it defaults to that and my server isn't behind a firewall.

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

I have a SQL file on my ftp server that I want to put more data in with my app.

I can do this by first downloading it, add som content and then upload it back again. The problem is that this method wont work if multiple users do the same thing at once. So is it possible to use connection:append in some way to upload data to the SQL file without first downloading the whole file?

It would be amazing if I could do something like this:

1
2
3
4
5
6
connection:append{
        data = [[INSERT INTO myTable (column1, column2) VALUES ("Value1", "Value2")]],
        remoteFile = "myAmazingDatabase.db",
        onSuccess = onAppendSuccess,
        onError = onError
}

GrahamRanson's picture
GrahamRanson
User offline. Last seen 6 days 7 hours ago. Offline
Joined: 29 Mar 2010

As far as I am aware that wouldn't be possible however if you just want to modify the content of an SQL database the better way to do it would be to have some scripts, possibly PHP etc, running on your web server that can accept POST or GET requests and act accordingly.

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

Would this work:

I have two files on my ftp server, myDatabase.db and myPHP.php.
Then I have some scripts in my PHP file that can communicate with the db file? (Haven't learned any PHP so far)

Then in my app I use network.request( "ftp.myftpserver.com", "POST", networkListener, params) to add params to the PHP file which then sends that to the db file?

Is this possible?
Is it the best way of doing this on a ftp server?
How do I do it?

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

Also, when I upload a file I get an error. I've set onError to start a function that prints to the terminal but I can't see what the error is all about, how do I do that? When I look on my ftp server on my computer, the file is there but it's 0kb.

EDIT:

Never mind, fixed it. My mistake :)

GrahamRanson's picture
GrahamRanson
User offline. Last seen 6 days 7 hours ago. Offline
Joined: 29 Mar 2010

For the former yes that is essentially what I do however I use mySQL rather than sqlite and as for the latter, glad you got it solved :-)

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

Okay great!

I wrote a php script but how do I test it?

GrahamRanson's picture
GrahamRanson
User offline. Last seen 6 days 7 hours ago. Offline
Joined: 29 Mar 2010

Try inserting some data remotely? Or if you just want to make sure the script is getting called you could just print out some data in php and that will be the response of the network request.

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

I found some code on the internetz that I tried to use just to see if everything was working:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php
try 
{
  //create or open the database
  $database = new SQLiteDatabase('myDatabase.sqlite', 0666, $error);
}
catch(Exception $e) 
{
  die($error);
}
 
//add Movie table to database
$query = 'CREATE TABLE Movies ' .
         '(Title TEXT, Director TEXT, Year INTEGER)';
         
if(!$database->queryExec($query, $error))
{
  die($error);
}
 
//insert data into database
$query = 
  'INSERT INTO Movies (Title, Director, Year) ' .
  'VALUES ("The Dark Knight", "Christopher Nolan", 2008); ' .
         
  'INSERT INTO Movies (Title, Director, Year) ' .
  'VALUES ("Cloverfield", "Matt Reeves", 2008); ' .
         
  'INSERT INTO Movies (Title, Director, YEAR) ' .
  'VALUES ("Beverly Hills Chihuahua", "Raja Gosnell", 2008)';
 
if(!$database->queryExec($query, $error))
{
  die($error);
}
 
//read data from database
$query = "SELECT * FROM Movies";
if($result = $database->query($query, SQLITE_BOTH, $error))
{
  while($row = $result->fetch())
  {
    print("Title: {$row['Title']} <br />" .
          "Director: {$row['Director']} <br />".
          "Year: {$row['Year']} <br /><br />");
  }
}
else
{
  die($error);
}
 
?>

I test it with XAMPP in my web browser and it works.

This creates a file for me which I put on my ftp server. Then in corona I download it but I get an error: file is encrypted or is not a database

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

Found a new example that worked :D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
  try
  {
    //open the database
    $db = new PDO('sqlite:dogsDb_PDO.sqlite');
 
    //create the database
    $db->exec("CREATE TABLE Dogs (Id INTEGER PRIMARY KEY, Breed TEXT, Name TEXT, Age INTEGER)");    
 
    //insert some data...
    $db->exec("INSERT INTO Dogs (Breed, Name, Age) VALUES ('Labrador', 'Tank', 2);".
               "INSERT INTO Dogs (Breed, Name, Age) VALUES ('Husky', 'Glacier', 7); " .
               "INSERT INTO Dogs (Breed, Name, Age) VALUES ('Golden-Doodle', 'Ellie', 4);");
 
    //now output the data to a simple html table...
    print "<table border=1>";
    print "<tr><td>Id</td><td>Breed</td><td>Name</td><td>Age</td></tr>";
    $result = $db->query('SELECT * FROM Dogs');
    foreach($result as $row)
    {
      print "<tr><td>".$row['Id']."</td>";
      print "<td>".$row['Breed']."</td>";
      print "<td>".$row['Name']."</td>";
      print "<td>".$row['Age']."</td></tr>";
    }
    print "</table>";
 
    // close the database connection
    $db = NULL;
  }
  catch(PDOException $e)
  {
    print 'Exception : '.$e->getMessage();
  }
?>

Though it works I would like to know why this works and the other one don't. Do you know?

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

And how do I go further with this?
Let's say that I use the exact table and data above, what should I write inside the network.request() brackets if I want to add another row of data remotely from the app, if both the php file and sqlite file are on the same ftp server and in the same folder?

GrahamRanson's picture
GrahamRanson
User offline. Last seen 6 days 7 hours ago. Offline
Joined: 29 Mar 2010

You would want to create a PHP script that can take data in a POST variable and then pass whatever data you want through the network.request call. Your PHP would then have to deal with the data and decided what it needs to do.

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

Do you got any good example on how to make a PHP script that can take data in a POST variable?

GrahamRanson's picture
GrahamRanson
User offline. Last seen 6 days 7 hours ago. Offline
Joined: 29 Mar 2010

This has some nice short info on both POST and GET variables - http://www.tizag.com/phpT/postget.php

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

I found a piece of code here on the forums and it works, but how do I write it with network.request() ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
http = require("socket.http")
 
ltn12 = require("ltn12")
url = require("socket.url")
 
 
local post = "InputName=" .. "Robot7001"
post = post .. "&InputAge=" .. "8"
local response = {}
 
local r, c, h = http.request {
    url = "https://www.yoursite.com/TestPost.php?",
    method = "POST",
    headers = {
        ["content-length"] = #post,
        ["Content-Type"] =  "application/x-www-form-urlencoded"
    },
    source = ltn12.source.string(post),
    sink = ltn12.sink.table(response)
}
print(response[1])

Sure I can use this code but I would like to understand the network.request function better :)

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

How do I use the code above to POST to a php and sqlite file on a ftp server?

GrahamRanson's picture
GrahamRanson
User offline. Last seen 6 days 7 hours ago. Offline
Joined: 29 Mar 2010

The page here has a bit showing how to send data via POST - http://developer.anscamobile.com/reference/index/networkrequest

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

Well actually I dont need to use network.request since the last code that I wrote here works.
But how would I write my current code if I want to access a php file on a ftp server?

GrahamRanson's picture
GrahamRanson
User offline. Last seen 6 days 7 hours ago. Offline
Joined: 29 Mar 2010

Sorry I don't really have the time to write out the code for you, all I can really do is point you in the right places and that way you'll better understand your resulting code anyway.

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

That's more than enough for me, thanks! :)

GrahamRanson's picture
GrahamRanson
User offline. Last seen 6 days 7 hours ago. Offline
Joined: 29 Mar 2010

If you have any specific questions then I can try to help, but can't guarantee anything :-)

oskwish's picture
oskwish
User offline. Last seen 42 min 17 sec ago. Offline
Joined: 21 Jan 2011

Thanks! :)