
1. Working with ZIP, FTP and SFTP files
- Working with ZIP, FTP and SFTP files
- Opening and closing archives
- JSON options for FTP and SFTP
- Supported file-system functions
- Path rules
- Current paths
- Copying files and folders
- Moving files and folders
- Reading and writing remote files
- Listing files
- Checking and creating folders
- Complete ZIP example
- Packaging and uploading a release
- Working with EXCEL .xlsx files from FileMaker
- Good practices
This page is valid for ACF-Plugin version 1.7.9.1 and upwards.
ACF extends its regular file-system functions so they can also work inside ZIP archives and on remote FTP/SFTP servers. This means that the same ACF code style can be used for local files, files inside a ZIP archive, and files on a remote server.
The feature is based on path prefixes. When you open an archive or a remote connection, ACF returns a prefix string. Add that prefix in front of paths that belong to the archive or remote server.
Examples:
string zip = open_zip("~/Desktop/reports.zip");
string ftp = open_ftp("ftp.example.com", JSON("username", "user", "password", "secret"));
string sftp = open_sftp("sftp.example.com", JSON("username", "user", "privateKey", "~/keys/id_ed25519"));
copy_file("~/Desktop/report.pdf", zip + "/Reports/report.pdf");
copy_file("~/Desktop/report.pdf", ftp + "/incoming/report.pdf");
copy_file("~/Desktop/report.pdf", sftp + "~/incoming/report.pdf");
The returned prefixes look like this:
ZIP<0>:
FTP<1>:
SFTP<2>:
The number can vary. Always use the value returned by open_zip, open_ftp or open_sftp. Do not construct these prefixes manually.
1.1. Opening and closing archives ↑
1.1.1. ZIP ↑
string zip = open_zip(zipFilePath);
close_zip(zip);
open_zip opens an existing ZIP archive or creates a new one if the file does not exist. Always call close_zip after writing to a ZIP archive. ZIP archives are finalized when they are closed, and changes may not be complete before that.
1.1.2. FTP ↑
string ftp = open_ftp(server, optionsJSON);
close_ftp(ftp);
The server parameter can be a hostname, host:port, or a full FTP URL.
Examples:
string ftp = open_ftp(
"ftp.example.com",
JSON(
"username", "deploy",
"password", "secret",
"createDirectories", true
)
);
string ftp = open_ftp(
"ftp.example.com:2121",
JSON(
"username", "deploy",
"password", "secret"
)
);
1.1.3. SFTP ↑
string sftp = open_sftp(server, optionsJSON);
close_sftp(sftp);
SFTP can use username/password authentication or SSH private key authentication.
Username/password example:
string sftp = open_sftp(
"sftp.example.com",
JSON(
"username", "deploy",
"password", "secret",
"createDirectories", true
)
);
Private key example:
string sftp = open_sftp(
"sftp.example.com",
JSON(
"username", "deploy",
"privateKey", "~/keys/id_ed25519",
"passphrase", "optional key passphrase",
"knownHosts", "~/keys/known_hosts",
"createDirectories", true
)
);
For SFTP paths, ~/ is normally understood by the remote server as the remote user's home directory. ACF sends this path to the server; it is not expanded locally.
1.2. JSON options for FTP and SFTP ↑
The second parameter to open_ftp and open_sftp is a JSON object.
| Option | Type | FTP | SFTP | Description |
|---|---|---|---|---|
username |
string | yes | yes | Login username. |
password |
string | yes | yes | Login password. For SFTP this is used for password authentication unless a private key is used. |
port |
integer | yes | yes | Optional port number. You can also include the port in the server string, for example "localhost:2121". |
timeoutSeconds |
number | yes | yes | Optional timeout for remote operations. |
createDirectories |
boolean | yes | yes | When true, upload operations may create missing destination folders where supported. This defaults to true for uploads. |
privateKey |
string | no | yes | Path to an SSH private key file for SFTP authentication. |
passphrase |
string | no | yes | Optional passphrase for the private key. |
knownHosts |
string | no | yes | Optional path to a known hosts file used for SSH host verification. |
Keep credentials outside source code when possible. In FileMaker solutions, connection settings are often better stored in configuration tables, encrypted fields, or environment-specific setup records.
1.3. Supported file-system functions ↑
The following file-system functions can work with normal OS paths and prefixed ZIP, FTP or SFTP paths:
openclosereadreadlinewritecopy_filemove_filelist_filesfile_existsdirectory_existscreate_directorydelete_filedelete_directoryset_current_pathget_current_path
The markdown and document generation functions are not archive-aware by themselves, but their output can be written or copied to ZIP, FTP or SFTP paths afterward.
1.4. Path rules ↑
Use / as the directory separator in ZIP, FTP and SFTP paths.
copy_file("~/Desktop/report.pdf", zip + "/Reports/report.pdf");
copy_file("~/Desktop/report.pdf", ftp + "/incoming/report.pdf");
copy_file("~/Desktop/report.pdf", sftp + "~/incoming/report.pdf");
For local OS paths, ACF also uses POSIX-style paths on macOS and Unix-like systems. Windows drive-letter and UNC paths are treated as absolute Windows paths.
For ZIP paths, folders are virtual. A ZIP archive is really a list of file entries, where each entry can contain / in its name. Creating a directory inside a ZIP archive is therefore not required. If you write a file to ZIP<0>:/Reports/report.pdf, the Reports path is part of the file entry name.
For FTP and SFTP paths, folders are real remote directories. Use create_directory or set "createDirectories", true when uploading into folders that may not exist.
1.5. Current paths ↑
set_current_path(path) sets the current working path for the active runtime. get_current_path() returns it.
There is one current path for normal OS paths, and each open ZIP, FTP or SFTP connection can also have its own current path.
string zip = open_zip("~/Desktop/reports.zip");
set_current_path(zip + "/Reports");
int f = open(zip + "summary.txt", "w");
write(f, "Monthly summary");
close(f);
print get_current_path(); // /Reports
close_zip(zip);
For archive and remote paths, get_current_path() returns the path inside the archive or remote connection, not the prefix.
Examples:
set_current_path(zip + "/Reports");
// get_current_path() returns "/Reports"
set_current_path(sftp + "~/www/files");
// get_current_path() returns "~/www/files"
Absolute paths still behave as absolute paths. On macOS and Linux, paths starting with / are absolute. On Windows, drive-letter and UNC paths are absolute. A local path starting with ~ is expanded as the user's home directory. On SFTP, ~ is sent to the remote server.
1.6. Copying files and folders ↑
copy_file works between local paths, ZIP paths, FTP paths and SFTP paths.
Upload a local file to FTP:
string ftp = open_ftp(
"ftp.example.com",
JSON("username", "deploy", "password", "secret", "createDirectories", true)
);
string res = copy_file("~/Desktop/report.pdf", ftp + "/incoming/report.pdf");
close_ftp(ftp);
return res;
Upload a folder into a ZIP archive:
string zip = open_zip("~/Desktop/release.zip");
string res = copy_file("~/Desktop/MyReleaseFolder", zip + "/Release/MyReleaseFolder");
close_zip(zip);
return res;
Download a folder from SFTP:
string sftp = open_sftp(
"sftp.example.com",
JSON("username", "deploy", "privateKey", "~/keys/id_ed25519")
);
string res = copy_file(sftp + "~/www/files", "~/Desktop/files");
close_sftp(sftp);
return res;
When copying folders, ACF copies the folder tree recursively.
1.7. Moving files and folders ↑
move_file works like copy_file, but removes the source after a successful copy.
Move a local file into a ZIP archive:
string zip = open_zip("~/Desktop/archive.zip");
string res = move_file("~/Desktop/bigfile.dmg", zip + "/Downloads/bigfile.dmg");
close_zip(zip);
return res;
When moving files into a ZIP archive, ACF waits until close_zip has successfully completed before deleting the original local file. This protects the source file if the ZIP archive cannot be finalized.
Move a file on the same SFTP connection:
string sftp = open_sftp(
"sftp.example.com",
JSON("username", "deploy", "privateKey", "~/keys/id_ed25519")
);
string res = move_file(
sftp + "~/www/incoming/report.pdf",
sftp + "~/www/archive/report.pdf"
);
close_sftp(sftp);
return res;
When source and destination are on the same FTP or SFTP connection, ACF uses a remote rename operation instead of downloading and uploading the file.
1.8. Reading and writing remote files ↑
You can use open, read, readline, write and close with ZIP, FTP and SFTP paths.
Write a text file to SFTP:
string sftp = open_sftp(
"sftp.example.com",
JSON("username", "deploy", "privateKey", "~/keys/id_ed25519")
);
int f = open(sftp + "~/www/hello.txt", "w");
write(f, "Hello world\n");
write(f, "Line 2\n");
close(f);
close_sftp(sftp);
Append to a remote file:
int f = open(sftp + "~/www/hello.txt", "wa");
write(f, "Added line\n");
close(f);
Read a whole remote file:
int f = open(sftp + "~/www/hello.txt", "r");
string content = read(f);
close(f);
Read a remote file line by line:
int f = open(sftp + "~/www/hello.txt", "r");
string line;
string content;
repeat
line = readline(f);
if (line != "{{EOF}}") then
content += line + "\n";
end if
until (line == "{{EOF}}");
close(f);
1.9. Listing files ↑
Use list_files(path, option) to list local, ZIP, FTP or SFTP paths.
string ftp = open_ftp(
"ftp.example.com",
JSON("username", "deploy", "password", "secret")
);
string listing = list_files(ftp + "/incoming", 0);
close_ftp(ftp);
return listing;
The returned listing can be used to inspect remote folders before copying or deleting content.
1.10. Checking and creating folders ↑
if (! directory_exists(sftp + "~/www/archive")) then
string res = create_directory(sftp + "~/www/archive");
if (res != "OK") then
throw res;
end if
end if
For ZIP archives, create_directory returns OK because explicit directory objects are not required. For FTP and SFTP, it creates the remote directory.
1.11. Complete ZIP example ↑
The example below creates a ZIP archive, writes files into it, then adds a user-selected file.
function test_zip_archive (string archive, string filename, string content)
int x, y, z;
string path, xcontent;
string zip = open_zip(archive);
if (left(zip, 3) == "ZIP") then
x = open(zip + filename, "w");
y = open(zip + "ZipDirectory/Mytestfile6.txt", "w");
write(x, content);
write(y, "content into file6");
write(x, "\nSome more content in line two...");
write(y, "\nSome more content in line two into file6");
close(x);
close(y);
path = select_file("Select a file to include");
if (path != "") then
z = open(path, "r");
xcontent = read(z);
path = Get_FilenameFromPath(path);
close(z);
z = open(zip + "ZipDirectory/" + path, "w");
write(z, xcontent);
close(z);
end if
close_zip(zip);
else
alert(zip);
end if
return "OK";
end
1.12. Packaging and uploading a release ↑
A common workflow is to prepare files locally, pack them into a ZIP archive, then upload the completed archive to FTP or SFTP.
function PackAndUploadRelease (string folderPath, string zipPath)
string zip = open_zip(zipPath);
if (left(zip, 3) != "ZIP") then
throw "Could not open ZIP archive: " + zip;
end if
string res = copy_file(folderPath, zip + "/Release");
close_zip(zip);
if (res != "OK") then
throw res;
end if
string ftp = open_ftp(
"ftp.example.com",
JSON(
"username", "deploy",
"password", "secret",
"createDirectories", true
)
);
if (left(ftp, 3) != "FTP") then
throw "Could not open FTP connection: " + ftp;
end if
res = copy_file(zipPath, ftp + "/downloads/" + Get_FilenameFromPath(zipPath));
close_ftp(ftp);
return res;
end
1.13. Working with EXCEL .xlsx files from FileMaker ↑
EXCEL spreadsheet files in the .xlsx format are ZIP archives. This means that you can open them with open_zip and inspect or modify the XML files inside the workbook.
If we use list_files on an EXCEL file:
string path = "~/Desktop/MyExcelFile.xlsx";
string zip = open_zip(path);
print list_files(zip, 0);
close_zip(zip);
This returns a list similar to:
[Content_Types].xml
_rels/.rels
xl/_rels/workbook.xml.rels
xl/workbook.xml
xl/sharedStrings.xml
xl/styles.xml
xl/worksheets/sheet1.xml
xl/theme/theme1.xml
docProps/app.xml
docProps/core.xml
xl/calcChain.xml
The file xl/worksheets/sheet1.xml contains the worksheet data. The data is stored in XML, with a structure similar to this:
<worksheet>
<sheetData>
<row>
<c><v>some value</v></c>
<c><v>some value</v></c>
<c><v>some value</v></c>
</row>
</sheetData>
</worksheet>
The following snippet extracts values from row 5 to row 9, column 5:
string path = "~/Desktop/MyExcelFile.xlsx";
int x, i;
string content;
XML sheet1;
string zip = open_zip(path);
x = open(zip + "xl/worksheets/sheet1.xml", "r");
content = read(x);
close(x);
close_zip(zip);
sheet1 = content;
ARRAY STRING colE5_9;
for (i = 5, 9)
colE5_9[] = string(sheet1["worksheet.sheetData.row[" + i + "].c[5].v"]);
end for
return implode(",", colE5_9);
Example result:
6300,239519,74335,14500,108521
This technique is useful for advanced workflows where you need direct access to the internal XML files in an .xlsx package. For ordinary Excel report generation, the dedicated ACF Excel functions are usually easier and safer.
1.14. Good practices ↑
- Always close archives and remote connections with
close_zip,close_ftporclose_sftp. - Check return values from
copy_file,move_file,create_directoryand delete operations. - Use
"createDirectories", truewhen uploading into remote folder structures that may not exist yet. - Prefer SFTP over FTP when credentials or data should be protected in transit.
- Keep credentials out of source code when possible.
- Use
set_current_pathfor repeated work in one archive or remote folder, but use explicit absolute paths for critical operations where clarity matters.
