Skip to content

Commit f914720

Browse files
committed
iRODS: Add iRODS profile, TLS support, fix PAM and various bugs
1 parent 04a77cd commit f914720

23 files changed

+846
-484
lines changed

irods/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<dependency>
2828
<groupId>org.irods</groupId>
2929
<artifactId>irods4j</artifactId>
30-
<version>0.3.0-java8-SNAPSHOT</version>
30+
<version>0.4.0-java8</version>
3131
</dependency>
3232
<dependency>
3333
<groupId>ch.cyberduck</groupId>

irods/src/main/java/ch/cyberduck/core/irods/IRODSAttributesFinderFeature.java

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,23 @@
2222
import ch.cyberduck.core.exception.NotfoundException;
2323
import ch.cyberduck.core.features.AttributesAdapter;
2424
import ch.cyberduck.core.features.AttributesFinder;
25-
import ch.cyberduck.core.io.Checksum;
2625

27-
import org.apache.commons.io.FilenameUtils;
28-
import org.apache.commons.lang3.StringUtils;
26+
import org.apache.logging.log4j.LogManager;
27+
import org.apache.logging.log4j.Logger;
2928
import org.irods.irods4j.high_level.catalog.IRODSQuery;
30-
import org.irods.irods4j.high_level.catalog.IRODSQuery.GenQuery1QueryArgs;
3129
import org.irods.irods4j.high_level.connection.IRODSConnection;
3230
import org.irods.irods4j.high_level.vfs.IRODSFilesystem;
33-
import org.irods.irods4j.low_level.api.GenQuery1Columns;
31+
import org.irods.irods4j.high_level.vfs.LogicalPath;
32+
import org.irods.irods4j.high_level.vfs.ObjectStatus;
3433
import org.irods.irods4j.low_level.api.IRODSException;
3534

3635
import java.io.IOException;
3736
import java.util.List;
3837

3938
public class IRODSAttributesFinderFeature implements AttributesFinder, AttributesAdapter<List<String>> {
4039

40+
private static final Logger log = LogManager.getLogger(IRODSAttributesFinderFeature.class);
41+
4142
private final IRODSSession session;
4243

4344
public IRODSAttributesFinderFeature(final IRODSSession session) {
@@ -47,42 +48,56 @@ public IRODSAttributesFinderFeature(final IRODSSession session) {
4748
@Override
4849
public PathAttributes find(final Path file, final ListProgressListener listener) throws BackgroundException {
4950
try {
50-
final IRODSConnection conn = session.getClient();
51+
log.debug("looking up path attributes.");
52+
5153
final String logicalPath = file.getAbsolute();
52-
if(!IRODSFilesystem.exists(session.getClient().getRcComm(), logicalPath)) {
53-
throw new NotfoundException(file.getAbsolute());
54-
}
54+
final IRODSConnection conn = session.getClient();
5555

56-
GenQuery1QueryArgs input = new GenQuery1QueryArgs();
56+
ObjectStatus status = IRODSFilesystem.status(session.getClient().getRcComm(), logicalPath);
5757

58-
// select DATA_MODIFY_TIME, DATA_CREATE_TIME, DATA_SIZE, DATA_CHECKSUM ...
59-
input.addColumnToSelectClause(GenQuery1Columns.COL_D_MODIFY_TIME);
60-
input.addColumnToSelectClause(GenQuery1Columns.COL_D_CREATE_TIME);
61-
input.addColumnToSelectClause(GenQuery1Columns.COL_DATA_SIZE);
62-
input.addColumnToSelectClause(GenQuery1Columns.COL_D_DATA_CHECKSUM);
58+
if(IRODSFilesystem.isDataObject(status)) {
59+
log.debug("data object exists in iRODS. fetching data using GenQuery2.");
60+
String query = String.format(
61+
"select DATA_CREATE_TIME, DATA_MODIFY_TIME, DATA_SIZE, DATA_CHECKSUM, DATA_REPL_STATUS where COLL_NAME = '%s' and DATA_NAME = '%s' order by DATA_REPL_STATUS desc, DATA_MODIFY_TIME desc",
62+
LogicalPath.parentPath(logicalPath),
63+
LogicalPath.objectName(logicalPath));
64+
log.debug("query = [{}]", query);
65+
List<List<String>> rows = IRODSQuery.executeGenQuery2(conn.getRcComm(), query);
6366

64-
// where COLL_NAME = '<parent_path>' and DATA_NAME = '<filename>'
65-
String collNameCondStr = String.format("= '%s'", FilenameUtils.getFullPathNoEndSeparator(logicalPath));
66-
String dataNameCondStr = String.format("= '%s'", FilenameUtils.getName(logicalPath));
67-
input.addConditionToWhereClause(GenQuery1Columns.COL_COLL_NAME, collNameCondStr);
68-
input.addConditionToWhereClause(GenQuery1Columns.COL_DATA_NAME, dataNameCondStr);
67+
PathAttributes attrs = new PathAttributes();
6968

70-
final PathAttributes attrs = new PathAttributes();
69+
if(!rows.isEmpty()) {
70+
List<String> row = rows.get(0);
71+
if("0".equals(row.get(4)) || "1".equals(row.get(4))) {
72+
setAttributes(attrs, row);
73+
}
74+
}
7175

72-
IRODSQuery.executeGenQuery1(conn.getRcComm(), input, row -> {
73-
attrs.setModificationDate(Long.parseLong(row.get(0)) * 1000); // seconds to ms
74-
attrs.setCreationDate(Long.parseLong(row.get(1)) * 1000);
75-
attrs.setSize(Long.parseLong(row.get(2)));
76+
return attrs;
77+
}
7678

77-
String checksum = row.get(3);
78-
if(!StringUtils.isEmpty(checksum)) {
79-
attrs.setChecksum(Checksum.parse(checksum));
79+
if(IRODSFilesystem.isCollection(status)) {
80+
log.debug("collection exists in iRODS. fetching data using GenQuery2.");
81+
String query = String.format("select COLL_CREATE_TIME, COLL_MODIFY_TIME where COLL_NAME = '%s'", logicalPath);
82+
log.debug("query = [{}]", query);
83+
List<List<String>> rows = IRODSQuery.executeGenQuery2(conn.getRcComm(), query);
84+
85+
PathAttributes attrs = new PathAttributes();
86+
87+
if(!rows.isEmpty()) {
88+
// Collections do not have the same properties as data objects
89+
// so fill in the gaps to satisfy requirements of setAttributes.
90+
List<String> row = rows.get(0);
91+
row.add("0"); // Data size
92+
row.add(""); // Checksum
93+
row.add(""); // Replica status
94+
setAttributes(attrs, row);
8095
}
8196

82-
return false;
83-
});
97+
return attrs;
98+
}
8499

85-
return attrs;
100+
throw new NotfoundException(logicalPath);
86101
}
87102
catch(IOException | IRODSException e) {
88103
throw new IRODSExceptionMappingService().map("Failure to read attributes of {0}", e, file);
@@ -91,18 +106,18 @@ public PathAttributes find(final Path file, final ListProgressListener listener)
91106

92107
@Override
93108
public PathAttributes toAttributes(final List<String> row) {
94-
final IRODSConnection conn = session.getClient();
95-
final PathAttributes attributes = new PathAttributes();
96-
97-
attributes.setModificationDate(Long.parseLong(row.get(0)) * 1000); // seconds to ms
98-
attributes.setCreationDate(Long.parseLong(row.get(1)) * 1000);
99-
attributes.setSize(Long.parseLong(row.get(2)));
100-
101-
String checksum = row.get(3);
102-
if(!StringUtils.isEmpty(checksum)) {
103-
attributes.setChecksum(Checksum.parse(checksum));
104-
}
109+
PathAttributes attrs = new PathAttributes();
110+
setAttributes(attrs, row);
111+
return attrs;
112+
}
105113

106-
return attributes;
114+
private static void setAttributes(final PathAttributes attrs, final List<String> row) {
115+
log.debug("path attribute info: created at [{}], modified at [{}], data size = [{}], checksum = [{}]",
116+
row.get(0), row.get(1), row.get(2), row.get(3));
117+
attrs.setCreationDate(Long.parseLong(row.get(0)) * 1000); // seconds to ms
118+
attrs.setModificationDate(Long.parseLong(row.get(1)) * 1000);
119+
attrs.setSize(Long.parseLong(row.get(2)));
120+
attrs.setChecksum(IRODSChecksumUtils.toChecksum(row.get(3)));
107121
}
122+
108123
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package ch.cyberduck.core.irods;
2+
3+
/*
4+
* Copyright (c) 2002-2025 iterate GmbH. All rights reserved.
5+
* https://cyberduck.io/
6+
*
7+
* This program is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*/
17+
18+
import ch.cyberduck.core.io.Checksum;
19+
20+
import org.apache.commons.codec.binary.Base64;
21+
import org.apache.commons.codec.binary.Hex;
22+
import org.apache.commons.lang3.StringUtils;
23+
import org.apache.logging.log4j.LogManager;
24+
import org.apache.logging.log4j.Logger;
25+
26+
public final class IRODSChecksumUtils {
27+
28+
private static final Logger log = LogManager.getLogger(IRODSChecksumUtils.class);
29+
30+
public static Checksum toChecksum(String irodsChecksum) {
31+
if(StringUtils.isBlank(irodsChecksum)) {
32+
return Checksum.NONE;
33+
}
34+
35+
int colon = irodsChecksum.indexOf(':');
36+
if(-1 == colon) {
37+
log.debug("no hash algorithm prefix found in iRODS checksum. ignoring checksum.");
38+
return Checksum.NONE;
39+
}
40+
41+
if(colon + 1 >= irodsChecksum.length()) {
42+
log.debug("iRODS checksum may be corrupted. ignoring checksum.");
43+
return Checksum.NONE;
44+
}
45+
46+
log.debug("checksum from iRODS server is [{}].", irodsChecksum);
47+
String checksum = irodsChecksum.substring(colon + 1);
48+
checksum = Hex.encodeHexString(Base64.decodeBase64(checksum));
49+
log.debug("base64-decoded, hex-encoded checksum is [{}].", checksum);
50+
return Checksum.parse(checksum);
51+
}
52+
}

irods/src/main/java/ch/cyberduck/core/irods/IRODSChunkWorker.java

Lines changed: 36 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -15,105 +15,82 @@
1515
* GNU General Public License for more details.
1616
*/
1717

18-
import org.irods.irods4j.high_level.connection.IRODSConnectionPool;
19-
import org.irods.irods4j.high_level.io.IRODSDataObjectInputStream;
20-
import org.irods.irods4j.high_level.io.IRODSDataObjectOutputStream;
21-
import org.irods.irods4j.high_level.io.IRODSDataObjectStream;
18+
import ch.cyberduck.core.exception.ConnectionCanceledException;
19+
import ch.cyberduck.core.io.StreamListener;
20+
import ch.cyberduck.core.transfer.TransferStatus;
21+
22+
import org.apache.logging.log4j.LogManager;
23+
import org.apache.logging.log4j.Logger;
2224
import org.irods.irods4j.low_level.api.IRODSException;
2325

24-
import java.io.FileInputStream;
25-
import java.io.FileOutputStream;
2626
import java.io.IOException;
2727
import java.io.InputStream;
2828
import java.io.OutputStream;
2929

3030
public class IRODSChunkWorker implements Runnable {
3131

32-
private final IRODSConnectionPool.PoolConnection conn;
32+
private static final Logger log = LogManager.getLogger(IRODSChunkWorker.class);
33+
34+
private final TransferStatus status;
35+
private final StreamListener streamListener;
3336
private final InputStream in;
3437
private final OutputStream out;
3538
private final long offset;
3639
private final long chunkSize;
3740
private final byte[] buffer;
3841

39-
public IRODSChunkWorker(IRODSConnectionPool.PoolConnection conn, InputStream in, OutputStream out, long offset, long chunkSize, int bufferSize) throws IOException, IRODSException {
40-
this.conn = conn;
42+
public IRODSChunkWorker(TransferStatus status, StreamListener streamListener, InputStream in, OutputStream out, long offset, long chunkSize, int bufferSize) {
43+
log.info("constructing iRODS chunk worker.");
44+
log.info("offset = [{}]", offset);
45+
log.info("chunk size = [{}]", chunkSize);
46+
log.info("buffer size = [{}]", bufferSize);
47+
this.status = status;
48+
this.streamListener = streamListener;
4149
this.in = in;
4250
this.out = out;
4351
this.offset = offset;
4452
this.chunkSize = chunkSize;
4553
this.buffer = new byte[bufferSize];
54+
log.info("iRODS chunk worker constructed.");
4655
}
4756

4857
@Override
4958
public void run() {
5059
try {
51-
seek(in);
52-
seek(out);
60+
IRODSStreamUtils.seek(in, offset);
61+
IRODSStreamUtils.seek(out, offset);
5362

5463
long remaining = chunkSize;
5564
while(remaining > 0) {
65+
try {
66+
status.validate();
67+
}
68+
catch(ConnectionCanceledException e) {
69+
log.info("transfer cancelled.");
70+
return;
71+
}
72+
5673
int count = (int) Math.min(buffer.length, remaining);
5774

5875
int bytesRead = in.read(buffer, 0, count);
76+
log.info("read [{}] of [{}] requested bytes from input stream.", bytesRead, count);
5977
if(-1 == bytesRead) {
6078
break;
6179
}
6280

81+
streamListener.recv(bytesRead);
6382
out.write(buffer, 0, bytesRead);
83+
log.info("wrote [{}] bytes to output stream.", bytesRead);
84+
streamListener.sent(bytesRead);
6485
remaining -= bytesRead;
6586
}
66-
}
67-
catch(IOException | IRODSException e) {
68-
// TODO Log error
69-
}
70-
finally {
71-
try {
72-
in.close();
73-
}
74-
catch(Exception e) { /* Ignored */ }
75-
try {
76-
out.close();
77-
}
78-
catch(Exception e) { /* Ignored */ }
79-
}
80-
}
8187

82-
private void seek(InputStream in) throws IRODSException, IOException {
83-
if(in instanceof IRODSDataObjectInputStream) {
84-
IRODSDataObjectInputStream stream = (IRODSDataObjectInputStream) in;
85-
long totalOffset = offset;
86-
while(totalOffset > 0) {
87-
if(totalOffset >= Integer.MAX_VALUE) {
88-
totalOffset -= Integer.MAX_VALUE;
89-
stream.seek(Integer.MAX_VALUE, IRODSDataObjectStream.SeekDirection.CURRENT);
90-
}
91-
else {
92-
stream.seek((int) totalOffset, IRODSDataObjectStream.SeekDirection.CURRENT);
93-
}
94-
}
88+
log.info("total bytes remaining = [{}]", remaining);
89+
log.info("done. wrote [{}] of [{}] bytes to the replica.", chunkSize - remaining, chunkSize);
9590
}
96-
else if(in instanceof FileInputStream) {
97-
((FileInputStream) in).getChannel().position(offset);
91+
catch(IOException | IRODSException e) {
92+
log.error(e.getMessage());
9893
}
9994
}
10095

101-
private void seek(OutputStream out) throws IRODSException, IOException {
102-
if(out instanceof IRODSDataObjectOutputStream) {
103-
IRODSDataObjectOutputStream stream = (IRODSDataObjectOutputStream) out;
104-
long totalOffset = offset;
105-
while(totalOffset > 0) {
106-
if(totalOffset >= Integer.MAX_VALUE) {
107-
totalOffset -= Integer.MAX_VALUE;
108-
stream.seek(Integer.MAX_VALUE, IRODSDataObjectStream.SeekDirection.CURRENT);
109-
}
110-
else {
111-
stream.seek((int) totalOffset, IRODSDataObjectStream.SeekDirection.CURRENT);
112-
}
113-
}
114-
}
115-
else if(out instanceof FileOutputStream) {
116-
((FileOutputStream) out).getChannel().position(offset);
117-
}
118-
}
11996
}

0 commit comments

Comments
 (0)