Java UnZip program - java

I have a zip file with the following structure:
MyZip.zip
|-- FOLDER_1
| `-- FOLDER_11
| |-- file_a
| `-- file_b
|-- FOLDER_2
| `-- FOLDER_22
| `-- file_c
`-- FOLDER_3
`-- FOLDER_33
`-- file_d
I have tried to use this example but it doesn't work with my zip structure. What do I need to chage to make that class unzip my zip file properly? It will create C:\outputzip\FOLDER1\FOLDER_11 where FOLDER_11 isn't a folder, yet a file caled FOLDER_11 and then the program breaks.
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class UnZip {
List<String> fileList;
private static final String INPUT_ZIP_FILE = "C:\\MyFile.zip";
private static final String OUTPUT_FOLDER = "C:\\outputzip";
public static void main( String[] args )
{
UnZip unZip = new UnZip();
unZip.unZipIt(INPUT_ZIP_FILE,OUTPUT_FOLDER);
}
/**
* Unzip it
* #param zipFile input zip file
* #param output zip file output folder
*/
public void unZipIt(String zipFile, String outputFolder){
byte[] buffer = new byte[1024];
try{
//create output directory is not exists
File folder = new File(OUTPUT_FOLDER);
if(!folder.exists()){
folder.mkdir();
}
//get the zip file content
ZipInputStream zis =
new ZipInputStream(new FileInputStream(zipFile));
//get the zipped file list entry
ZipEntry ze = zis.getNextEntry();
while(ze!=null){
String fileName = ze.getName();
File newFile = new File(outputFolder + File.separator + fileName);
System.out.println("file unzip : "+ newFile.getAbsoluteFile());
//create all non exists folders
//else you will hit FileNotFoundException for compressed folder
new File(newFile.getParent()).mkdirs();
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
ze = zis.getNextEntry();
}
zis.closeEntry();
zis.close();
System.out.println("Done");
} catch(IOException ex){
ex.printStackTrace();
}
}
}

Change your while() loop to below and play around if you will have some problems:
while (ze != null) {
String fileName = ze.getName();
File newFile = new File(outputFolder + File.separator
+ fileName);
if (ze.isDirectory()) {
File directory = new File(newFile.getPath());
directory.mkdirs();
ze = zis.getNextEntry();
continue;
}
System.out.println("file unzip : " + newFile.getAbsoluteFile());
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
ze = zis.getNextEntry();
}

Related

Appending string to destination to create new directory

Hello I am attempting to append some variables to a directory to create a new sub directory within the directory I am in but I am not sure how to do this, I know that .mkdir() allows me to create a new directory but I am not sure how I could append the variables and then create the new directory, here is my attempt so far:
package movefile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class MoveFile
{
static String newfile;
public static void main(String[] args)
{
File srcFolder = new File("/Users/francis/Desktop/folder1");
File destFolder = new File("/Users/francis/Desktop/folder2");
//make sure source exists
if(!srcFolder.exists()){
System.out.println("Directory does not exist.");
//just exit
System.exit(0);
}else{
try{
copyFolder (srcFolder,destFolder);
}catch(IOException e){
e.printStackTrace();
//error, just exit
System.exit(0);
}
}
System.out.println("Done");
}
public static void copyFolder(File src, File dest)
throws IOException{
if(src.isDirectory()){
//if directory not exists, create it
if(!dest.exists()){
dest.mkdir();
System.out.println("Directory copied from "
+ src + " to " + dest);
}
//list all the directory contents
String files[] = src.list();
FilenameFilter filter = new FilenameFilter()
{
#Override public boolean accept(File dir, String name)
{
return name.endsWith(".pdf");
}
};
for (String file : files) {
//construct the src and dest file structure
File srcFile = new File(src, file); //needed for moving
//third attempt
File f = null;
File f1 = null;
String vendor;
String orderno;
String desc;
String v, v1, p;
boolean bool = false;
try {
f = new File("/Users/francis/Desktop/folder1/1234567898.pdf");
f1 = new File("/Users/francis/Desktop/folder2/");
v = f.getName();
v1 = f1.getName();
v = v.length() > 9 ? v.substring(0,8) : v;
p = "c3269";
vendor = "VendorName";
v = "file_" + p + " - " + v + ".pdf";
File destFile = new File(dest, v); //get dest and insert (append) project number so it creates new folder
copyFolder(srcFile,destFile);
File appendProject = new File("/Users/francis/Desktop/folder2/");
boolean successfull = appendProject.mkdir();
if (successfull)
{
System.out.println("New directory was created:");
}
else
System.out.println("Failed to create new directory");
bool = f.exists();
if(bool)
{
System.out.println("File name:" + v);
}
bool = f1.exists();
if (bool)
{
System.out.println("Folder name:" + v1);
}
}catch(Exception e){
e.printStackTrace();
}
}
}else{
//if file, then copy it
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest);
byte[] buffer = new byte[1024];
int length;
//copy the file content in bytes
while ((length = in.read(buffer)) > 0){
out.write(buffer, 0, length);
}
in.close();
out.close();
System.out.println("File copied from " + src + " to " + dest);
}
}
}
At the moment this is checking the folder for .pdf files appending P and V values to the name, trimming the string to 8 characters and moving the files into a new folder but I desire them to be moved and it creates new directory based on the project number which is located inside of the variable p.
Any help would be greatly appreciated, thanks a bunch!
Assuming you want to copy file File srcFile to folder File destDir you could do the following:
if( !destDir.exists() ) {
//create destDir and all missing parent folders
destDir.mkdirs(); //For simplicity I'll leave out the success checks, don't forget them in your code
}
String destFilename = srcFile.getName(); //adapt according to your needs
File destFile = new File( destDir, destFilename );
//You'd need to either implement that or use a library like Apache Commons IO
copy( srcFile, destFile );
If you want to create a directory from the filename first, do something like this:
String destSubDirName = makeDirName( destFilename ); //whatever you need to do here
File destSubDir = new File( destDir, destSubDirName );
destSubDir.mkdirs(); //again don't forget the checks
File destFile = new File (destSubDir, destFilename );
copy( srcFile, destFile );

Extract a zip file denied access

I'm trying to extract a zip file to another location, and I'm getting denied access error..
CODE:
package org.spoutcraft.launcher;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class UnZip
{
List<String> fileList;
private static final String INPUT_ZIP_FILE = "C:\\Level Up! Games" + File.separator + "Perfect World" + File.separator + "Updates" + File.separator + "Launcher.zip";
private static final String OUTPUT_FOLDER = "C:\\Level Up! Games" + File.separator + "Perfect World";
public static void main( String[] args )
{
UnZip unZip = new UnZip();
unZip.unZipIt(INPUT_ZIP_FILE,OUTPUT_FOLDER);
}
/**
* Unzip it
* #param zipFile input zip file
* #param output zip file output folder
*/
public void unZipIt(String zipFile, String outputFolder)
{
byte[] buffer = new byte[1024];
try
{
//create output directory is not exists
File folder = new File(OUTPUT_FOLDER);
if(!folder.exists())
{
folder.mkdir();
}
//get the zip file content
ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile));
//get the zipped file list entry
ZipEntry ze = zis.getNextEntry();
while(ze!=null)
{
String fileName = ze.getName();
File newFile = new File(outputFolder + File.separator + fileName);
System.out.println("file unzip : "+ newFile.getAbsoluteFile());
//create all non exists folders
//else you will hit FileNotFoundException for compressed folder
new File(newFile.getParent()).mkdirs();
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0)
{
fos.write(buffer, 0, len);
}
fos.close();
ze = zis.getNextEntry();
}
zis.closeEntry();
zis.close();
System.out.println("Done");
}
catch(IOException ex)
{
ex.printStackTrace();
}
}
}
In the main class i've used this code:
After download the zip file:
try {
UnZip.main(null);
And im getting this error:
file unzip : C:\Level Up! Games\Perfect World\config
java.io.FileNotFoundException: C:\Level Up! Games\Perfect World\config (Acesso negado)
at java.io.FileOutputStream.open(Native Method)
at java.io.FileOutputStream.<init>(Unknown Source)
at java.io.FileOutputStream.<init>(Unknown Source)
at org.spoutcraft.launcher.UnZip.unZipIt(UnZip.java:57)
at org.spoutcraft.launcher.UnZip.main(UnZip.java:20)
Can someone help me?
I assume that in line
FileOutputStream fos = new FileOutputStream(newFile);
you are creating stream to entry representing directory, where you should only create streams to files.
Try changing your loop to
while (ze != null) {
String fileName = ze.getName();
File newFile = new File(outputFolder + File.separator
+ fileName);
System.out.println("file unzip : " + newFile);
// create all non exists folders
// else you will hit FileNotFoundException for compressed folder
if (ze.isDirectory()) {
newFile.mkdirs();
} else {
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
}
ze = zis.getNextEntry();
}

Java - How to move a file into a zip file?

That's it. I have a text file, and I need to move it to a (existing) Zip File in a given directory.
File file = new File("C:\\afolder\\test.txt");
File dir = new File(directoryToGo+"existingzipfile.zip");
boolean success = file.renameTo(new File(dir, file.getName()));
But it does not work. Is there a way to move a file into a existing Zip File?
Thank you.
Hmm you could use something like:
public static void addFilesToExistingZip(File zipFile, File[] files) throws IOException {
// get a temp file
File tempFile = File.createTempFile(zipFile.getName(), null);
// delete it, otherwise you cannot rename your existing zip to it.
tempFile.delete();
boolean renameOk = zipFile.renameTo(tempFile);
if (!renameOk) {
throw new RuntimeException(
"could not rename the file " + zipFile.getAbsolutePath() + " to " + tempFile.getAbsolutePath());
}
byte[] buf = new byte[1024];
ZipInputStream zin = new ZipInputStream(new FileInputStream(tempFile));
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile));
ZipEntry entry = zin.getNextEntry();
while (entry != null) {
String name = entry.getName();
boolean notInFiles = true;
for (File f : files) {
if (f.getName().equals(name)) {
notInFiles = false;
break;
}
}
if (notInFiles) { // Add ZIP entry to output stream.
out.putNextEntry(new ZipEntry(name)); // Transfer bytes from the ZIP file to the output file
int len;
while ((len = zin.read(buf)) > 0) {
out.write(buf, 0, len);
}
}
entry = zin.getNextEntry();
} // Close the streams
zin.close(); // Compress the files
for (int i = 0; i < files.length; i++) {
InputStream in = new FileInputStream(files[i]); // Add ZIP entry to output stream.
out.putNextEntry(new ZipEntry(files[i].getName())); // Transfer bytes from the file to the ZIP file
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
} // Complete the entry
out.closeEntry();
in.close();
} // Complete the ZIP file
out.close();
tempFile.delete();
}
Reference:
http://www.dzone.com/snippets/adding-files-existing-jar-file
You'll need to build a new zip file:
Open the existing zip file for reading
Open a new zip file for writing
Copy all the entries from the old zip file to the new one, ignoring an entry corresponding to your extra file, if there is one
Add your extra file
Close both the input and the output files
Delete the old zip file
Rename the new zip file to the old one's name
Starting with Java 7 you have a zip filesystem provider which allows you to write this code:
final Path src = Paths.get("c:\\afolder\\test.txt");
final String filename = src.getFileName().toString();
final Path zip = Paths.get(directoryToGo, "existingzipfile.zip");
final URI uri = URI.create("jar:" + zip.toUri());
final Map<String, ?> env = Collections.emptyMap();
try (
final FileSystem zipfs = FileSystems.newFileSystem(uri, env);
) {
Files.move(src, zipfs.getPath("/" + filename),
StandardCopyOption.REPLACE_EXISTING);
}
You can do like this, here uploadPath+fileName is filename with its path:
String FileName="Urzip file name. zip";
FileOutputStream outputStream = new FileOutputStream(uploadPath+fileName);
ZipOutputStream zipFile = new ZipOutputStream(outputStream);
byte[] buffer = new byte[1024];
// Then, here I have list of pdf files in a LIST:
// continuation ...
for (int i = 0; i < filename.size(); i++) {
String file = filename.get(i);
FileInputStream input = new FileInputStream(uploadPath+file);
ZipEntry entry = new ZipEntry(file);
zipFile.putNextEntry(entry);
int len;
while ((len = input.read(buffer)) > 0) {
zipFile.write(buffer, 0, len);
}
zipFile.closeEntry();
input.close();
}
// Next, here "downFile" is the other file which you have to add in your existing zip:
// continuation ...
FileInputStream input = new FileInputStream(uploadPath+downFile);
ZipEntry e = new ZipEntry(downFile);
zipFile.putNextEntry(e);
int len;
while ((len = input.read(buffer)) > 0) {
zipFile.write(buffer, 0, len);
}
zipFile.closeEntry();
input.close();
zipFile.close();
Adding the class to move the file to inside jar/zip folder based on accepted answer.
The accepted answer didn't hold full executable code ,So i have added the class which helps to move/copy the file to jar/zip
package ZipReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class ZipWrite {
public static void main(String args[]) throws IOException
{
File file=new File("F:/MyProjects/New folder/mysql-connector-java-5.1.18-bin.jar");
File filetoPush=new File("F:/MyProjects/New folder/BestResponseTimeBalanceStrategy.class");
File[] files=new File[1];
files[0]=filetoPush;
addFilesToExistingZip(file,files);
}
public static void addFilesToExistingZip(File zipFile, File[] files)
throws IOException {
// get a temp file
File tempFile = File.createTempFile(zipFile.getName(), null);
// delete it, otherwise you cannot rename your existing zip to it.
tempFile.delete();
boolean renameOk = zipFile.renameTo(tempFile);
if (!renameOk) {
throw new RuntimeException("could not rename the file "
+ zipFile.getAbsolutePath() + " to "
+ tempFile.getAbsolutePath());
}
byte[] buf = new byte[1024];
ZipInputStream zin = new ZipInputStream(new FileInputStream(tempFile));
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile));
ZipEntry entry = zin.getNextEntry();
while (entry != null) {
String name = entry.getName();
boolean notInFiles = true;
for (File f : files) {
if (f.getName().equals(name)) {
System.out.println(name);
notInFiles = false;
break;
}
}
if (notInFiles) {
System.out.println("adding");
// Add ZIP entry to output stream.
out.putNextEntry(new ZipEntry(name)); // Transfer bytes from the
// ZIP file to the
// output file
int len;
while ((len = zin.read(buf)) > 0) {
out.write(buf, 0, len);
}
}
entry = zin.getNextEntry();
} // Close the streams
zin.close(); // Compress the files
for (int i = 0; i < files.length; i++) {
FileInputStream in = new FileInputStream(files[i]);
// Add ZIP entry to output stream.
System.out.println("files[i].getName()-->"+files[i].getName());
out.putNextEntry(new ZipEntry("com/mysql/jdbc/util/"+files[i].getName()));
// Transfer bytes from the file to the ZIP file
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
// Complete the entry
out.closeEntry();
in.close();
}
// Complete the ZIP file
out.close();
tempFile.delete();
}
}

Can't Unzip EPub File

IMO, I thought that epub is a kind of zip . Thus, I've tried to unzip in a way.
public class Main {
public static void main(String argv[ ]) {
final int BUFFER = 2048;
try {
BufferedOutputStream dest = null;
FileInputStream fis = new FileInputStream("/Users/yelinaung/Documents/unzip/epub/doyle.epub");
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
System.out.println("Extracting: " + entry);
int count;
byte data[] = new byte[BUFFER];
// write the files to the disk
FileOutputStream fos = new FileOutputStream("/Users/yelinaung/Documents/unzip/xml/");
dest = new BufferedOutputStream(fos, BUFFER);
while ((count = zis.read(data, 0, BUFFER))
!= -1) {
dest.write(data, 0, count);
}
dest.flush();
dest.close();
}
zis.close();
} catch ( IOException e) {
e.printStackTrace();
}
}
}
I got that following error ..
java.io.FileNotFoundException: /Users/yelinaung/Documents/unzip/xml (No such file or directory)
though I've created a folder in that way .. and
my way of unzipping epub is right way ? .. Correct Me
I've got the answer.. I haven't checked that the object that have to be extracted is folder or not .
I've corrected in this way .
public static void main(String[] args) throws IOException {
ZipFile zipFile = new ZipFile("/Users/yelinaung/Desktop/unzip/epub/doyle.epub");
String path = "/Users/yelinaung/Desktop/unzip/xml/";
Enumeration files = zipFile.entries();
while (files.hasMoreElements()) {
ZipEntry entry = (ZipEntry) files.nextElement();
if (entry.isDirectory()) {
File file = new File(path + entry.getName());
file.mkdir();
System.out.println("Create dir " + entry.getName());
} else {
File f = new File(path + entry.getName());
FileOutputStream fos = new FileOutputStream(f);
InputStream is = zipFile.getInputStream(entry);
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = is.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
fos.close();
System.out.println("Create File " + entry.getName());
}
}
}
Then, got it .
You are creating a FileOutputStream with the same name for every file inside your epub file.
The FileNotFoundException is thrown if the file exists and it is a directory. The first time you pass through the loop, the directory is created and in the next time, the exception is raised.
If you want to change the directory where the epub is extracted, you should do something like this:
FileOutputStream fos = new FileOutputStream("/Users/yelinaung/Documents/unzip/xml/" + entry.getName())
Update
Creating the folder before to extract each file I was able to open the epub file
...
byte data[] = new byte[BUFFER];
String path = entry.getName();
if (path.lastIndexOf('/') != -1) {
File d = new File(path.substring(0, path.lastIndexOf('/')));
d.mkdirs();
}
// write the files to the disk
FileOutputStream fos = new FileOutputStream(path);
...
And to change the folder where the files are extracted, just change the line where the path is defined
String path = "newFolder/" + entry.getName();
This method worked fine for me,
public void doUnzip(String inputZip, String destinationDirectory)
throws IOException {
int BUFFER = 2048;
List zipFiles = new ArrayList();
File sourceZipFile = new File(inputZip);
File unzipDestinationDirectory = new File(destinationDirectory);
unzipDestinationDirectory.mkdir();
ZipFile zipFile;
// Open Zip file for reading
zipFile = new ZipFile(sourceZipFile, ZipFile.OPEN_READ);
// Create an enumeration of the entries in the zip file
Enumeration zipFileEntries = zipFile.entries();
// Process each entry
while (zipFileEntries.hasMoreElements()) {
// grab a zip file entry
ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();
String currentEntry = entry.getName();
File destFile = new File(unzipDestinationDirectory, currentEntry);
if (currentEntry.endsWith(".zip")) {
zipFiles.add(destFile.getAbsolutePath());
}
// grab file's parent directory structure
File destinationParent = destFile.getParentFile();
// create the parent directory structure if needed
destinationParent.mkdirs();
try {
// extract file if not a directory
if (!entry.isDirectory()) {
BufferedInputStream is =
new BufferedInputStream(zipFile.getInputStream(entry));
int currentByte;
// establish buffer for writing file
byte data[] = new byte[BUFFER];
// write the current file to disk
FileOutputStream fos = new FileOutputStream(destFile);
BufferedOutputStream dest =
new BufferedOutputStream(fos, BUFFER);
// read and write until last byte is encountered
while ((currentByte = is.read(data, 0, BUFFER)) != -1) {
dest.write(data, 0, currentByte);
}
dest.flush();
dest.close();
is.close();
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
zipFile.close();
for (Iterator iter = zipFiles.iterator(); iter.hasNext();) {
String zipName = (String)iter.next();
doUnzip(
zipName,
destinationDirectory +
File.separatorChar +
zipName.substring(0,zipName.lastIndexOf(".zip"))
);
}
}

Zip and Unzip the Folders and Files Using Java

If My Application wants to zip the Resultant files(group of Files) using java in a dynamic way, what are the available options in Java?
When i Browsed I have got java.util.zip package to use, but is there any other way where I can use it to implement?
public class FolderZiper {
public static void main(String[] a) throws Exception {
zipFolder("c:\\a", "c:\\a.zip");
}
static public void zipFolder(String srcFolder, String destZipFile) throws Exception {
ZipOutputStream zip = null;
FileOutputStream fileWriter = null;
fileWriter = new FileOutputStream(destZipFile);
zip = new ZipOutputStream(fileWriter);
addFolderToZip("", srcFolder, zip);
zip.flush();
zip.close();
}
static private void addFileToZip(String path, String srcFile, ZipOutputStream zip)
throws Exception {
File folder = new File(srcFile);
if (folder.isDirectory()) {
addFolderToZip(path, srcFile, zip);
} else {
byte[] buf = new byte[1024];
int len;
FileInputStream in = new FileInputStream(srcFile);
zip.putNextEntry(new ZipEntry(path + "/" + folder.getName()));
while ((len = in.read(buf)) > 0) {
zip.write(buf, 0, len);
}
}
}
static private void addFolderToZip(String path, String srcFolder, ZipOutputStream zip)
throws Exception {
File folder = new File(srcFolder);
for (String fileName : folder.list()) {
if (path.equals("")) {
addFileToZip(folder.getName(), srcFolder + "/" + fileName, zip);
} else {
addFileToZip(path + "/" + folder.getName(), srcFolder + "/" + fileName, zip);
}
}
}
}
The original Java implementation is known to have some bugs related to files encoding. For example it can't properly handle filenames with umlauts.
TrueZIP is an alternative that we've used in our project: https://truezip.dev.java.net/
Check the documentation on the site.
You can use ZIP file handling library that comes with JDK
Also this tutorials might be helpful.
Java have a java.util.zip.ZipInputStream and along with this you can use ZipEntry ... Something like
public static void unZipIt(String zipFile, String outputFolder){
File folder = new File(zipFile);
List<String> files = listFilesForFolder(folder);
System.out.println("Size " + files.size());
byte[] buffer = new byte[1024];
try{
Iterator<String> iter = files.iterator();
while(iter.hasNext()){
String file = iter.next();
System.out.println("file name " + file);
ZipInputStream zis = new ZipInputStream(new FileInputStream(file));
ZipEntry ze = zis.getNextEntry();
while(ze!=null){
String fileName = ze.getName();
File newFile = new File(outputFolder + File.separator + fileName);
System.out.println("file unzip : "+ newFile.getAbsoluteFile());
new File(newFile.getParent()).mkdirs();
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
ze = zis.getNextEntry();
}
zis.closeEntry();
zis.close();
System.out.println("Done");
}
}catch(IOException ex){
ex.printStackTrace();
}
}

Categories

Resources