I want to read data stored locally by the Apple Calendar app on my Mac (12.1 Monterey).
The data is stored in subdirectories of ~/Library/Calendars/ with one subdirectory per calendar.
The problem: When I try to get a list of files from there, Java returns null:
String userHomeDir = System.getProperty("user.home");
File calendarRoot = new File(userHomeDir + "/Library/Calendars/");
File[] calendars = calendarRoot.listFiles();
System.out.println("Number of files: " + calendars.length); // NPE thrown here
File permissions are as follows:
~/Library: drwx------+ (owner: my user)
~/Library/Calendars: drwxr-xr-x# (owner: my user)
Listing files in Library works fine.
How can I access that folder?
Short answer
Give it up. Apple has made it next to impossible to elegantly get a Java app to read calendar data.
Long answer
Since some versions (Catalina?) the directory ~/Library/Calendars/ and all subdirectories (and files therein) are protected by MacOS using extended attributes, namely com.apple.quarantine.
It used to be possible to grant applications the specific right to access calendar data using System Settings - Security and Privacy - Privacy - Calendar. However, the manual +-Button has gone now.
What I will do is use some zsh script to export the desired calendar events to another directory and remove the com.apple.quarantine attribute from there, too.
This is not elegant and leaves the Java world, but for my case, having a Java command line application being started from a designated shell script, it works rather nicely.
Here's what I came up with:
#!/bin/zsh
calendars="/Users/yourUserName/Library/Calendars"
target="/Users/yourUserName/some/other/directory/Calendar_Export"
cd ${calendars}
calsource=""
for f in *.calendar
do
linesFound=`grep -c '<string>Your Calendar Name</string>' ${f}/Info.plist`
if [[ ${linesFound} -eq 1 ]]
then
echo "The relevant calendar resides at " ${f}", copying all events"
calsource=${calendars}/${f}/Events
fi
done
if [[ ${calsource} != "" ]]
then
rm ${target}/*
cp ${calsource}/* ${target}/
xattr -d com.apple.quarantine ${target}/*
fi
Related
I want to include Java source code from multiple directories (which are shared between projects) in a Qt for Android project. On http://imaginativethinking.ca/what-the-heck-how-do-i-share-java-code-between-qt-android-projects/ an approach is described which copies the Java source files:
# This line makes sure my custom manifest file and project specific java code is copied to the android-build folder
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
# This is a custom variable which holds the path to my common Java code
# I use the $$system_path() qMake function to make sure that my directory separators are correct for the platform I'm compiling on as you need to use the correct separator in the Make file (i.e. \ for Windows and / for Linux)
commonAndroidFilesPath = $$system_path( $$PWD/../CommonLib/android-sources/src )
# This is a custom variable which holds the path to the src folder in the output directory. That is where they need to go for the ANT script to compile them.
androidBuildOutputDir = $$system_path( $$OUT_PWD/../android-build/src )
# Here is the magic, this is the actual copy command I want to run.
# Make has a platform agnostic copy command macro you can use which substitutes the correct copy command for the platform you are on: $(COPY_DIR)
copyCommonJavaFiles.commands = $(COPY_DIR) $${commonAndroidFilesPath} $${androidBuildOutputDir}
# I tack it on to the 'first' target which exists by default just because I know this will happen before the ANT script gets run.
first.depends = $(first) copyCommonJavaFiles
export(first.depends)
export(copyCommonJavaFiles.commands)
QMAKE_EXTRA_TARGETS += first copyCommonJavaFiles
With later Qt versions the code has to be changed to this:
commonAndroidFilesPath = $$system_path($$PWD/android/src)
androidBuildOutputDir = $$system_path($$OUT_PWD/../android-build)
createCommonJavaFilesDir.commands = $(MKDIR) $${androidBuildOutputDir}
copyCommonJavaFiles.commands = $(COPY_DIR) $${commonAndroidFilesPath} $${androidBuildOutputDir}
first.depends = $(first) createCommonJavaFilesDir copyCommonJavaFiles
export(first.depends)
export(createCommonJavaFilesDir.commands)
export(copyCommonJavaFiles.commands)
QMAKE_EXTRA_TARGETS += first createCommonJavaFilesDir copyCommonJavaFiles
Is this the standard way to go, or is there some built-in functionality for including multiple Java source directories in Qt for Android projects?
Regards,
A much cleaner solution is this one:
CONFIG += file_copies
COPIES += commonJavaFilesCopy
commonJavaFilesCopy.files = $$files($$system_path($$PWD/android/src))
commonJavaFilesCopy.path = $$OUT_PWD/android-build
In Java code I want to "touch" a file. I want to update the timestamps to the current time. The file uses ACL. And this seems to be the problem.
The file:
$ ll file.xml
-rw-rwxrw-+ 1 root root 8611 Oct 4 17:28 file.xml
$ getfacl file.xml
# file: file.xml
# owner: root
# group: root
user::rw-
user:tomcat8:rwx
group::r-x
mask::rwx
other::rw-
And my Java app runs from Tomcat 8 with user tomcat8. A sudo -u tomcat8 touch file.xml works. It also works if I completely remove ACL and set tomcat8 as owner. But this is not possible in the production environment.
So at first I tried Apache common-io:
FileUtils.touch(path);
This causes an IOException. I debugged it a bit more and found out that the library calls FileSystem.setLastModifiedTime which calls the Linux function utimes.
I debugged the Linux touch command and saw it calls another more modern function: utimensat(0, NULL, NULL, 0). It also calls dup2and duplicates the file descriptor.
So I built my own touch method in Java:
long time = System.currentTimeMillis();
FileTime fileTimeNow = FileTime.fromMillis(time);
BasicFileAttributeView fileAttributeView = Files.getFileAttributeView(derivative.toPath(), BasicFileAttributeView.class);
fileAttributeView.setTimes(fileTimeNow, fileTimeNow, fileTimeNow);
This throws an Exception too (Operation not permitted).
Internally it calls utimensat(69, NULL, [{1538666780, 483000000}, {1538666780, 483000000}], 0).
I can not set null on .setTimes(...). This call gets ignored. And there is no Java-way to duplicate a file descriptor (dup2). So I can not test further steps to make it more like Linux' touch.
How to make this working when a file uses ACL? I don't want to run external programs (touch).
If the file is not written concurrently, you can open it, read its first byte, and write it back again at offset zero. This will update the modification time of the file without requiring ownership permissions.
(By the way, the ACL looks really curious, particularly the other:: rw- part.)
Here's man utimensat:
Permissions requirements
To set both file timestamps to the current time (i.e., times is NULL, or both tv_nsec fields specify UTIME_NOW), either:
the caller must have write access to the file;
the caller's effective user ID must match the owner of the file; or
the caller must have appropriate privileges.
To make any change other than setting both timestamps to the current time (i.e., times is not NULL, and neither tv_nsec field is UTIME_NOW and neither tv_nsec field is UTIME_OMIT), either condition 2 or 3 above must
apply.
You have #1, but not #2 or #3. If you ask touch to explicitly set the time to the current timestamp, it fails as well:
$ getfacl test | sed -e "s/$USER/myuser/"
# file: test
# owner: root
# group: root
user::rw-
user:myuser:rwx
group::r--
mask::rwx
other::r--
$ touch -d "#$(date +%s)" test
touch: setting times of ‘test’: Operation not permitted
I don't have any good suggestions for what to do instead though. You could either make a no-op change to the file, or call touch as an external command:
String path="some path";
// See https://stackoverflow.com/a/52651585 for why we're not doing this via Java
int result = Runtime.getRuntime().exec(new String[] { "touch", "--", path }).waitFor();
if(result != 0) throw new IOException("Can't update timestamp");
I'm writing scripts that will run in parallel and will get their input data from the same file. These scripts will open the input file, read the first line, store it for further treatment and finally erase this read line from the input file.
Now the problem is that multiple scripts accessing the file can lead to the situation where two scripts access the input file simultaneously and read the same line, which produces the unacceptable result of the line being processed twice.
Now one solution is to write a lock file (.lock_input) before accessing the input file, and then erase it when releasing the input file, but this solution is not appealing in my case because sometimes NFS slows down network communication randomly and may not have reliable locking.
Another solution is to put a process lock instead of writing a file, which means the first script to access the input file will launch a process called lock_input, and the other scripts will ps -elf | grep lock_input. If it is present on the process list they will wait. This may be faster than writing to the NFS but still not perfect solution ...
So my question is: Is there any bash command (or other script interpreter) or a service I can use that will behave like semaphore or mutex locks used for synchronization in thread programming?
Thank you.
Small rough example:
Let's say we have input_file as following:
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
Treatment script : TrScript.sh
#!/bin/bash
NbLines=$(cat input_file | wc -l)
while [ ! $NbLines = 0 ]
do
FirstLine=$(head -1 input_file)
echo "Hello World today is $FirstLine"
RemainingLines=$(expr $NbLines - 1 )
tail -n $RemainingLines input_file > tmp
mv tmp input_file
NbLines=$(cat input_file | wc -l)
done
Main script:
#! /bin/bash
./TrScript.sh &
./TrScript.sh &
./TrScript.sh &
wait
The result should be:
Hello World today is Monday
Hello World today is Tuesday
Hello World today is Wednesday
Hello World today is Thursday
Hello World today is Friday
Hello World today is Saturday
Hello World today is Sunday
use
line=`flock $lockfile -c "(gawk 'NR==1' < $infile ; gawk 'NR>1' < $infile > $infile.tmp ; mv $infile.tmp $infile)"`
for accessing the file you want to read from. This uses file locks, though.
gawk NR==1 < ...
prints the first line of the input
I have always liked the lockfile program (sample search result for lockfile manpage) from the procmail set of tools (should be available on most systems, though it might not be installed by default).
It was designed to lock mail spool files, which are (were?) commonly mounted via NFS, so it does work properly over NFS (as much as anything can).
Also, as long as you you are making the assumption that all your ‘workers’ are on the same machine (by assuming you can check for PIDs, which may not work properly when PIDs eventually wrap), you could put your lock file in some other, local, directory (e.g. /tmp) while processing files hosted on an NFS server. As long as all the workers use the same lock file location (and a one-to-one mapping of lockfile filenames to locked pathnames), it will work fine.
Using FLOM (Free LOck Manager) tool your main script can become as easy as:
#!/bin/bash
flom -- ./TrScript.sh &
flom -- ./TrScript.sh &
flom -- ./TrScript.sh &
wait
if you are running the script inside a single host and something like:
flom -A 224.0.0.1 -- ./TrScript.sh &
if you want to distribute your script on many hosts. Some usage examples are available at this URL: http://sourceforge.net/p/flom/wiki/FLOM%20by%20examples/
I am working on a application which first require to check the available free disk space before running any operation. We have set some Default required Space limit like 512MB, So if any working drive does not have more then 512mb space my program will prompt for less memory space available, please make sufficient space to run the program.
I am using following code for it.
long freeSpace = FileSystemUtils.freeSpaceKb() * 1024;
here I am coverting size into byte first to compare with our standard required size.
Due to the above statement i am gettign following exception:
Error-Command line returned OS error code '3' for command [cmd.exe, /C, dir /-c "F:\MyApp\"]Stacktrace java.io.IOException: Command line returned OS error code '3' for command [cmd.exe, /C, dir /-c "F:\MyApp"]
at org.apache.commons.io.FileSystemUtils.performCommand(FileSystemUtils.java:506)
at org.apache.commons.io.FileSystemUtils.freeSpaceWindows(FileSystemUtils.java:303)
at org.apache.commons.io.FileSystemUtils.freeSpaceOS(FileSystemUtils.java:270)
at org.apache.commons.io.FileSystemUtils.freeSpaceKb(FileSystemUtils.java:206)
at org.apache.commons.io.FileSystemUtils.freeSpaceKb(FileSystemUtils.java:240)
at org.apache.commons.io.FileSystemUtils.freeSpaceKb(FileSystemUtils.java:222)...
The OS returned Error Code is '3' thats mean it is not normal termination.
So now how can I resolve this issue ?
I also found alternative method available in java 1.6 - How to find how much disk space is left using Java?
new File("c:\\").getFreeSpace();
---------------------------------
**More Details :**
---------------------------------
OS Architecture : amd64
Temp Dir : c:\temp\
OS Name : Windows 7
OS Version : 6.1 amd64
Jre Version : 1.6.0_45-b06
User Home : C:\Users\Tej.Kiran
User Language : en
User Country: US
File Separator : \
Current Working Directory : F:\MyApp\
You can try executing that command from a prompt. Run cmd.exe and enter the following:
cmd.exe /C dir /-c "F:\MyApp\"
echo %errorlevel%
Error code 3 means the path doesn't exist, but in this case I wonder if it is related to permissions. Any non-zero errorlevel is a problem. If your Java app needs to know the free space on the drive it is installed on, you can do something like this:
// returns something like "file:/C:/MyApp/my/pkg/MyClass.class"
// -OR- "jar:file:/C:/MyApp/myjar.jar!/my/pkg/MyClass.class"
String myPath = my.pkg.MyClass.class.getResource(MyClass.class).toString();
int start = myPath.indexOf("file:/") + 6;
FileSystemUtils.freeSpaceKb(myPath.substring(start, myPath.indexOf("/", start));
Obviously this code wouldn't work in an applet, but that shouldn't be surprising. The substring logic should also be more robust, but this is just a simple example.
I have a bat file with the following contents:
set logfile= D:\log.txt
java com.stuff.MyClass %1 %2 %3 >> %logfile%
when I run the bat file though, I get the following:
C:\>set logfile= D:\log.txt
C:\>java com.stuff.MyClass <val of %1> <val of %2> <val of %3> 1>>D:\log.txt
The parameter is incorrect.
I'm almost positive the "The parameter is incorrect." is due to the extraneous 1 in there. I also think this might have something with the encoding of the .bat file, but I can't quite figure out what is causing it. Anyone ever run into this before or know what might be causing it and how to fix it?
Edit
And the lesson, as always, is check if its plugged in first before you go asking for help. The bat file, in version control, uses D:\log.txt because it is intended to be run from the server which contains a D drive. When testing my changes and running locally, on my computer which doesn't have a D drive, I failed to make the change to use C:\log.txt which is what caused the error. Sorry for wasting you time, thanks for the help, try to resist the urge to downvote me too much.
I doubt that that's the problem - I expect the command processor to deal with that part for you.
Here's evidence of it working for me:
Test.java:
public class Test
{
public static void main(String args[]) throws Exception
{
System.out.println(args.length);
for (String arg : args)
{
System.out.println(arg);
}
}
}
test.bat:
set logfile= c:\users\jon\test\test.log
java Test %1 %2 %3 >> %logfile%
On the command line:
c:\Users\Jon\Test> [User input] test.bat first second third
c:\Users\Jon\Test>set logfile= c:\users\jon\test\test.log
c:\Users\Jon\Test>java Test first second third 1>>c:\users\jon\test\test.log
c:\Users\Jon\Test> [User input] type test.log
3
first
second
third
the 1 is not extraneous: it is inserted by cmd.exe meaning stdout (instead of ">>", you can also write "1>>". contrast this to redirecting stderr: "2>>"). so the problem must be with your parameters.
This may seem like a stupid question, but is there an existing D: drive in the context that the bat file runs in?
Once I had a case where a bat file was used as the command line of a task within the Task Manager, but the Run As user was set to a local user on the box, giving no access to network drives.
Interpolated for your case, if the D: drive were a network drive, running the bat file as, say, the local administrator account on that machine instead of a domain user account would likely fail to have access to D:.