In my code I am prompting the user to load a json file.
I am then attempting to copy this file into an sqlite database.
Once I have the data I am then able to manipulate it as needed - but I need to get it there in the first place.
So step 1 is to get the data in.
I have progressed as far as prompting the user to navigate to the file they want - but when I try and read the file I get this error ..
ERROR: resources must reside in the root directory thus must start with a '/' character in Codename One! Invalid resource: file:///tmp/temp3257201851214246357..json
So I think that I need to copy this file to the root directory
I cannot find a link that shows me how to do this.
Here is my code so far ...
case "Import Script":
try
{
JSONParser json = new JSONParser();
if (FileChooser.isAvailable()) {
FileChooser.showOpenDialog(".json", e2-> {
String file = (String)e2.getSource();
if (file == null) {
home.add("No file was selected");
home.revalidate();
} else {
home.add("Please wait - busy importing");
home.revalidate();
String extension = null;
if (file.lastIndexOf(".") > 0) {
extension = file.substring(file.lastIndexOf(".")+1);
}
if ("json".equals(extension)) {
FileSystemStorage fs = FileSystemStorage.getInstance();
try {
InputStream fis = fs.openInputStream(file);
try(Reader r = new InputStreamReader(Display.getInstance().getResourceAsStream(getClass(), file), "UTF-8"))
{
Map<String, Object> data = json.parseJSON(r);
Result result = Result.fromContent(data);
...... I progress from here
The error is occurring on this line ...
try(Reader r = new InputStreamReader(Display.getInstance().getResourceAsStream(getClass(), file), "UTF-8"))
If I hard code a filename and manually place it in the /src folder it works ... like this ...
try(Reader r = new InputStreamReader(Display.getInstance().getResourceAsStream(getClass(), '/test.json'), "UTF-8"))
But that defeats the purpose of them selecting a file
Any help would be appreciated
Thanks
I suggest watching this video.
It explains the different ways data is stored. One of the core sources of confusion is the 3 different ways to store files:
Resources
File System
Storage
getResourceAsStream returns a read only path that's physically embedded in the jar. It's flat so all paths to getResourceAsStream must start with / and must have only one of those. I would suggest avoiding more than one . as well although this should work in theory.
The sqlite database must be stored in file system which is encapsulated as FileSystemStorage and that's really the OS native file system. But you can't store it anywhere you want you need to give the DB name to the system and it notifies you where the file is stored and that's whats explained in the code above.
Related
I am very new to backend development. Basically, I want to create a robust & simple application that will accept a zip file URL in the params and then download the zip file from the URL and finally extract the zip and return the bin file inside it. Note: The zip file size can range from 5MB to 150MB. I have tried doing the described operation in the following manner.
package la.sample
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.http.HttpStatusCode
import io.ktor.response.respond
import io.ktor.response.respondFile
import io.ktor.routing.get
import io.ktor.routing.routing
import java.io.*
fun Application.startServer() {
routing {
get("/get-bin") {
//Gets the AWS Url from params
val awsUrl = call.request.queryParameters.get("url") ?: "Error"
// Download the zip file from the AWS URL
val client = HttpClient()
val bytes = client.get<ByteArray>(awsUrl)
//Create a temp file on the server & write the zip file bytes into it.
val file = File(".", "data.zip")
file.writeBytes(bytes)
//Call a method to unzip the file
unzipAndReturnBinFile()?.let {
call.respondFile(it) //respond with bin file
} ?: kotlin.run{
call.respond(HttpStatusCode.InternalServerError)
}
}
}
}
fun unzipAndReturnBinFile(): File? {
var exitVal = 0
//Command shell to unzip the file
Runtime.getRuntime().exec("unzip bundle.zip -d data").let {//command shell to unzip the zip file
exitVal += it.waitFor()
}
//Check if the command executed successfully
if (exitVal == 0) {
var binFile: File? = null
//check if the extracted files contain `bin`
File("data").listFiles().forEach {
if (it.name.contains(".bin")) {
binFile = it
}
}
//return bin or null otherwise
return binFile
} else {
throw Exception("Command Shell Execution failed.")
}
}
The above codes work fine in local machine, irrespective of the Zip file size. But when it is deployed to AWS, the code breaks if the zip or the bin file is larger than 100 MB and gives a java.lang.OutOfMemoryError error. I will be very thankful if someone can suggest to me a proper way of handling large file operations in the backend with the ability to handle 100s of such concurrent calls. Thank you.
Java heap size of my remote machine is around 1 GB.
your problem is not from the unzipping procedure,
runtime exec command runs on a different process and only use of min size on the heap of the forked process to save instruction of return address.
the problem that causing the outOfMemory is in these lines
val bytes = client.get<ByteArray>(awsUrl)
val file = File(".", "data.zip")
file.writeBytes(bytes)
it will only take 6 concurrent requests of size 150Mb to finish all your Heap size.
instead of waiting for the file to fully download before saving it to the disk, you should use Stream, and then every time a chunk of data downloaded you saving it to the disk then in that way the full size of the downloaded file will never be at the RAM at the same time.
Use apache commons-io, for example :
FileUtils.copyURLToFile(URL, File)
or if you would like more control over the procedure try using Ben Noland answer
https://stackoverflow.com/a/921408/4267015
Based on #Naor's comment, I have updated the code to accept the multipart file and write every small chuck (part) to another file as soon as I get them, without storing the entire data in the memory. It has solved the issue. Below is the updated code snippet.
val file = File(".", Constant.FILE_PATH)
call.receiveMultipart().apply {
forEachPart {
if (it is PartData.FileItem) {
it.streamProvider().use { input ->
file.outputStream().buffered().use { output -> input.copyToSuspend(output) }
}
}
it.dispose
} }
I have the two following methods and I am using them to store a special value locally and be able to access it on application restart:
(Store value locally:)
private void SaveSet(string key, string value)
{
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this);
ISharedPreferencesEditor prefEditor = prefs.Edit();
prefEditor.PutString(key, value);
// editor.Commit(); // applies changes synchronously on older APIs
prefEditor.Apply(); // applies changes asynchronously on newer APIs
}
(Read it again:)
private string RetrieveSet(string key)
{
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this);
return prefs.GetString(key, null);
}
This works perfectly. Now is it possible to access and edit this Shared Preferences externally? Unfortunately, I cannot find any file when searching in folder
Phone\Android\data\com.<company_name>.<application_name>\files
nor anywhere else. I want / try to edit this value from my computer, after connecting the phone to it. Is this possible?
Alternatively: Can anyone maybe show me how to create a new file in the given path above, write/read it programmatically and how it stays there, even if application is closed / started again? So I can then edit this file with my computer anyhow?
I tried it with the following code, but unfortunately it doesn't work / no file is created or at least i cannot see it in the given path above:
//"This code snippet is one example of writing an integer to a UTF-8 text file to the internal storage directory of an application:"
public void SaveValueIntoNewFile(int value)
{
var backingFile = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "newFile.txt");
using (var writer = System.IO.File.CreateText(backingFile))
{
writer.WriteLine(value.ToString());
}
}
Would be very happy about every answer, thanks in advance and best regards
What you're looking for is where Android stores the Shared Preference file for applications that make use of it's default PreferenceManager.
I'd refer to this SO post which answers your question pretty well
SharedPreferences are stored in an xml file in the app data folder,
i.e.
/data/data/YOUR_PACKAGE_NAME/shared_prefs/YOUR_PREFS_NAME.xml
or the default preferences at:
/data/data/YOUR_PACKAGE_NAME/shared_prefs/YOUR_PACKAGE_NAME_preferences.xml
SharedPreferences added during runtime are not stored in the Eclipse
project.
Note: Accessing /data/data/ requires superuser
privileges
A simple method is to use Android Device Monotor,you can open it by clicking Tools--> android-->Android Device Monotor...
For example:
The path in my device is as follows:
/data/data/YOUR_PACKAGE_NAME/shared_prefs/YOUR_PACKAGE_NAME_preferences.xml
And we notice three buttons in the upper right corner of the picture.
The first one is used toPull a file from the device,the second one is used to Push a file onto the device,and the last one is used to delete the preferences.xml file.
So we can pull the preferences.xml file from current device to our computer and edit it as we want, and then push the updated preferences.xml to the folder again.Then we will get the value of preferences.xml file .
I am trying to write some kind of an external config file for my Xamarin.Android-application. Just .txt and this only needs to contain several strings, so no rocket science^^
e.g. (Sample Goal/Hopefully final content of the text file:)
[TestSection]
test=12345
I tried it with a tutorial and the following code:
//This code snippet is one example of writing a string to a UTF-8 text file and into the internal storage directory of an application:
public void SaveSomethingIntoExternalTextFile(string toWrite)
{
var backingFile = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "config.txt");
using (var writer = System.IO.File.CreateText(backingFile))
{
writer.WriteLine(toWrite);
}
}
But unfortunately this has no effect. As my only app-specific path is
Phone\Android\data\com.<company_name>.<application_name>\files
I cannot find any file there after running the code above. And even if I create a new file with the computer in the given path, name it as written (config.txt) and run the given code again, still nothing happens (so the string which's passed as parameter into the method unfortunately isn't written into that file as tried).
Just for the sake of completeness, my test application is pretty simple:
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SaveSomethingIntoExternalTextFile("[TestSection]");
SaveSomethingIntoExternalTextFile("test="+12345);
}
Can anyone maybe help? What am I doing wrong?
Would be really happy about every answer, thanks in advance and
Best regards
P.S.: If I open the by computer generated text file, even if in the file directory its displayed correctly, the heading of the file says config[1].txt, opening again config[2].txt and so on. Or does that not matter/has nothing to do with my attempt above?
If you use System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), the path is like
/data/data/App3.App3/files/config.txt
It means that you save it in internal storage, you could not see it without root permission,if you want to see it, you can save it in external storage.
public void SaveSomethingIntoExternalTextFile(string toWrite)
{
string path = Android.App.Application.Context.GetExternalFilesDir(null).ToString();
string filepath = Path.Combine(path, "text.txt");
using (var writer = System.IO.File.CreateText(filepath))
{
writer.WriteLine(toWrite);
}
}
The path like :
/storage/emulated/0/Android/data/App3.App3/files/text.txt
Okay via Debugging, I found out that indeed he does write the value, at least I can retrieve it with the following function (I changed the type of the value to integer as in my tutorial (https://learn.microsoft.com/de-de/xamarin/android/platform/files/index#reading-or-writing-to-files-on-internal-storage ) it was integer as well):
//This code snippet provides one way to read an int value that was stored in a text file:
public int ReadSomethingFromTextFile()
{
var backingFile = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "config.txt");
if (backingFile == null || !System.IO.File.Exists(backingFile))
{
return 0;
}
int count = 0;
using (var reader = new StreamReader(backingFile, true))
{
string line;
while ((line = reader.ReadLine()) != null)
{
if (int.TryParse(line, out var newcount))
{
count = newcount;
}
}
}
return count;
}
Nevertheless, I cannot find the written file. Is the Path System.Environment.SpecialFolder.Personal the right one? Or does this point to root directory, where I am always seeking in some kind of user folder?
Because when looking with the debugger what's inside backingFile, it's
/data/data/com.<company_name>.<application_name>/files/config.txt,
and as written in the post above, the only app-specific path I can access via explorer is
Phone/Android/data/com.<company_name>.<application_name>/files.
I read something about having to root my phone in order to be able to access this path? Makes no sense right?
Isn't there some kind of shared folder (/path), whichs accessable not only by application/operating system but also from outside?
I want to write entries to a log file stored in Azure file storage. I currently have this:
var log = "My log entry";
var client = _storageAccount.CreateCloudFileClient();
var share = client.GetShareReference(Config.LogShare);
share.CreateIfNotExists();
var root = share.GetRootDirectoryReference();
var logfile = root.GetFileReference("log.txt");
if (!logfile.Exists()) logfile.Create(0);
// What goes here to append to the file...?
I can see plenty of examples of how to do this with Blobs, or how to upload an entire file, but how do I just append to an existing file?
I have tried this:
var buffer = Encoding.GetEncoding("UTF-8").GetBytes(log.ToCharArray());
using (var fileStream = logfile.OpenWrite(0)) {
fileStream.Write(buffer, (int)logfile.Properties.Length, buffer.Length);
}
But then I get this error:
The remote server returned an error: (416) The range specified is invalid for the current size of the resource..
I managed to work this out myself. You just need to increase the size of the file by the number of new bytes you want to write to it, and then write the new data to that new empty space at the end of the file, like this:
var client = _storageAccount.CreateCloudFileClient();
var share = client.GetShareReference(Config.LogShare);
share.CreateIfNotExists();
var root = share.GetRootDirectoryReference();
var logfile = root.GetFileReference("log.txt");
if (!logfile.Exists()) logfile.Create(0);
var buffer = Encoding.UTF8.GetBytes($"{log}\r\n");
logfile.Resize(logfile.Properties.Length + buffer.Length);
using (var fileStream = logfile.OpenWrite(null)) {
fileStream.Seek(buffer.Length * -1, SeekOrigin.End);
fileStream.Write(buffer, 0, buffer.Length);
}
You can do this with blobs https://blogs.msdn.microsoft.com/windowsazurestorage/2015/04/13/introducing-azure-storage-append-blob/
Shame it doesn't work with files too
Azure file storage REST API doesn't support appending to an existing file. To achieve this, please mount the file share to your machine as a drive, and append to the file just like simple local files.
Actually, I don't think you really need appending functionality per your code above. You can specify the file size in CloudFile.OpenWrite() / CloudFile.Create(), or try CloudFile.UploadFromStream() instead of CloudFile.OpenWrite().
This error could also be due to multi-threaded access.
I bet if you tried to lock the file before you access it, you will not face this problem.
There are many ways to update the file.
Since you already managed to get the share, the root, the folder and the file.. Here is a portion of my code that worked for me.
if (!fileLock.IsWriteLockHeld) fileLock.EnterWriteLock();
try
{
using (var stream = new MemoryStream(content, false))
{
file.UploadFromStream(stream, null, options);
}
}
catch (Exception ex)
{
File.AppendAllText(FileName, ex.ToString());
}
finally
{
if (fileLock.IsWriteLockHeld)
fileLock.ExitWriteLock();
}
Where fileLock is declared as:
protected ReaderWriterLockSlim fileLock = new ReaderWriterLockSlim();
Having said that, I am not saying that this is the best way ever to do it.
The two things I would like you to keep in mind :
1-Lock the resource that is likely to be accessed by more than one thread (That is so common in AZURE)
2- Get familiar with asynchronous methods that Azure provides.. use them when they suit well.
Coming back to your original problem about appending to the existing file..
All the methods of the CloudFile will overwrite the existing file. Cloud Files are not for frequent writing, and they indeed impact performance if you keep writing on them frequently, add the lock impact on performance, they will be horrible.
Cloud files are meant to store big bulk of data once and for all, if you want to add another bulk you have the choice of creating another file.
Have all your data with the client till they reach some size and create an algorith to select the file name and upload them all at once.
I am trying to use Hadoop in java with multiple input files. At the moment I have two files, a big one to process and a smaller one that serves as a sort of index.
My problem is that I need to maintain the whole index file unsplitted while the big file is distributed to each mapper. Is there any way provided by the Hadoop API to make such thing?
In case if have not expressed myself correctly, here is a link to a picture that represents what I am trying to achieve: picture
Update:
Following the instructions provided by Santiago, I am now able to insert a file (or the URI, at least) from Amazon's S3 into the distributed cache like this:
job.addCacheFile(new Path("s3://myBucket/input/index.txt").toUri());
However, when the mapper tries to read it a 'file not found' exception occurs, which seems odd to me. I have checked the S3 location and everything seems to be fine. I have used other S3 locations to introduce the input and output file.
Error (note the single slash after the s3:)
FileNotFoundException: s3:/myBucket/input/index.txt (No such file or directory)
The following is the code I use to read the file from the distributed cache:
URI[] cacheFile = output.getCacheFiles();
BufferedReader br = new BufferedReader(new FileReader(cacheFile[0].toString()));
while ((line = br.readLine()) != null) {
//Do stuff
}
I am using Amazon's EMR, S3 and the version 2.4.0 of Hadoop.
As mentioned above, add your index file to the Distributed Cache and then access the same in your mapper. Behind the scenes. Hadoop framework will ensure that the index file will be sent to all the task trackers before any task is executed and will be available for your processing. In this case, data is transferred only once and will be available for all the tasks related your job.
However, instead of add the index file to the Distributed Cache in your mapper code, make your driver code to implement ToolRunner interface and override the run method. This provides the flexibility of passing the index file to Distributed Cache through the command prompt while submitting the job
If you are using ToolRunner, you can add files to the Distributed Cache directly from the command line when you run the job. No need to copy the file to HDFS first. Use the -files option to add files
hadoop jar yourjarname.jar YourDriverClassName -files cachefile1, cachefile2, cachefile3, ...
You can access the files in your Mapper or Reducer code as below:
File f1 = new File("cachefile1");
File f2 = new File("cachefile2");
File f3 = new File("cachefile3");
You could push the index file to the distributed cache, and it will be copied to the nodes before the mapper is executed.
See this SO thread.
Here's what helped me to solve the problem.
Since I am using Amazon's EMR with S3, I have needed to change the syntax a bit, as stated on the following site.
It was necessary to add the name the system was going to use to read the file from the cache, as follows:
job.addCacheFile(new URI("s3://myBucket/input/index.txt" + "#index.txt"));
This way, the program understands that the file introduced into the cache is named just index.txt. I also have needed to change the syntax to read the file from the cache. Instead of reading the entire path stored on the distributed cache, only the filename has to be used, as follows:
URI[] cacheFile = output.getCacheFiles();
BufferedReader br = new BufferedReader(new FileReader(#the filename#));
while ((line = br.readLine()) != null) {
//Do stuff
}