6182 |
25 Mar 21 |
nicklas |
1 |
package net.sf.basedb.reggie.grid; |
6182 |
25 Mar 21 |
nicklas |
2 |
|
7051 |
17 Feb 23 |
nicklas |
3 |
import java.net.URI; |
6182 |
25 Mar 21 |
nicklas |
4 |
import java.util.ArrayList; |
6182 |
25 Mar 21 |
nicklas |
5 |
import java.util.List; |
6182 |
25 Mar 21 |
nicklas |
6 |
import java.util.regex.Matcher; |
6182 |
25 Mar 21 |
nicklas |
7 |
import java.util.regex.Pattern; |
6182 |
25 Mar 21 |
nicklas |
8 |
|
6182 |
25 Mar 21 |
nicklas |
9 |
import net.sf.basedb.core.DataFileType; |
6182 |
25 Mar 21 |
nicklas |
10 |
import net.sf.basedb.core.DbControl; |
6182 |
25 Mar 21 |
nicklas |
11 |
import net.sf.basedb.core.DerivedBioAssay; |
6182 |
25 Mar 21 |
nicklas |
12 |
import net.sf.basedb.core.Directory; |
6182 |
25 Mar 21 |
nicklas |
13 |
import net.sf.basedb.core.File; |
6182 |
25 Mar 21 |
nicklas |
14 |
import net.sf.basedb.core.FileServer; |
6182 |
25 Mar 21 |
nicklas |
15 |
import net.sf.basedb.core.FileSetMember; |
6209 |
13 Apr 21 |
nicklas |
16 |
import net.sf.basedb.core.InvalidDataException; |
6182 |
25 Mar 21 |
nicklas |
17 |
import net.sf.basedb.core.ItemList; |
6182 |
25 Mar 21 |
nicklas |
18 |
import net.sf.basedb.core.ItemNotFoundException; |
6182 |
25 Mar 21 |
nicklas |
19 |
import net.sf.basedb.core.ItemSubtype; |
6182 |
25 Mar 21 |
nicklas |
20 |
import net.sf.basedb.core.Job; |
6182 |
25 Mar 21 |
nicklas |
21 |
import net.sf.basedb.core.Path; |
6182 |
25 Mar 21 |
nicklas |
22 |
import net.sf.basedb.core.Protocol; |
6182 |
25 Mar 21 |
nicklas |
23 |
import net.sf.basedb.core.SessionControl; |
6182 |
25 Mar 21 |
nicklas |
24 |
import net.sf.basedb.core.Software; |
6182 |
25 Mar 21 |
nicklas |
25 |
import net.sf.basedb.core.StringParameterType; |
6182 |
25 Mar 21 |
nicklas |
26 |
import net.sf.basedb.opengrid.JobDefinition; |
6182 |
25 Mar 21 |
nicklas |
27 |
import net.sf.basedb.opengrid.JobStatus; |
6182 |
25 Mar 21 |
nicklas |
28 |
import net.sf.basedb.opengrid.OpenGridCluster; |
6182 |
25 Mar 21 |
nicklas |
29 |
import net.sf.basedb.opengrid.OpenGridSession; |
6182 |
25 Mar 21 |
nicklas |
30 |
import net.sf.basedb.opengrid.ScriptBuilder; |
6182 |
25 Mar 21 |
nicklas |
31 |
import net.sf.basedb.opengrid.config.ClusterConfig; |
6182 |
25 Mar 21 |
nicklas |
32 |
import net.sf.basedb.opengrid.config.JobConfig; |
6182 |
25 Mar 21 |
nicklas |
33 |
import net.sf.basedb.opengrid.service.JobCompletionHandler; |
6182 |
25 Mar 21 |
nicklas |
34 |
import net.sf.basedb.reggie.Reggie; |
6182 |
25 Mar 21 |
nicklas |
35 |
import net.sf.basedb.reggie.XmlConfig; |
6182 |
25 Mar 21 |
nicklas |
36 |
import net.sf.basedb.reggie.dao.Annotationtype; |
6182 |
25 Mar 21 |
nicklas |
37 |
import net.sf.basedb.reggie.dao.BiomaterialList; |
6182 |
25 Mar 21 |
nicklas |
38 |
import net.sf.basedb.reggie.dao.Datafiletype; |
6209 |
13 Apr 21 |
nicklas |
39 |
import net.sf.basedb.reggie.dao.DemuxedSequences; |
6215 |
16 Apr 21 |
nicklas |
40 |
import net.sf.basedb.reggie.dao.DoNotUse; |
6182 |
25 Mar 21 |
nicklas |
41 |
import net.sf.basedb.reggie.dao.Fileserver; |
6182 |
25 Mar 21 |
nicklas |
42 |
import net.sf.basedb.reggie.dao.Library; |
6182 |
25 Mar 21 |
nicklas |
43 |
import net.sf.basedb.reggie.dao.MergedSequences; |
6182 |
25 Mar 21 |
nicklas |
44 |
import net.sf.basedb.reggie.dao.Pipeline; |
6182 |
25 Mar 21 |
nicklas |
45 |
import net.sf.basedb.reggie.dao.Subtype; |
6182 |
25 Mar 21 |
nicklas |
46 |
import net.sf.basedb.util.Values; |
7051 |
17 Feb 23 |
nicklas |
47 |
import net.sf.basedb.util.uri.UriMetadata; |
6182 |
25 Mar 21 |
nicklas |
48 |
|
6182 |
25 Mar 21 |
nicklas |
49 |
/** |
6182 |
25 Mar 21 |
nicklas |
Helper class for creating items needed for importing |
6182 |
25 Mar 21 |
nicklas |
FASTQ files as well as generating the script and send it to the cluster for |
6182 |
25 Mar 21 |
nicklas |
execution. |
6182 |
25 Mar 21 |
nicklas |
53 |
|
6182 |
25 Mar 21 |
nicklas |
@author nicklas |
6182 |
25 Mar 21 |
nicklas |
@since 4.32 |
6182 |
25 Mar 21 |
nicklas |
56 |
*/ |
6182 |
25 Mar 21 |
nicklas |
57 |
public class ImportFastqJobCreator |
6674 |
11 Apr 22 |
nicklas |
58 |
extends AbstractJobCreator |
6182 |
25 Mar 21 |
nicklas |
59 |
{ |
6182 |
25 Mar 21 |
nicklas |
60 |
private Software mergeSoftware; |
6182 |
25 Mar 21 |
nicklas |
61 |
private Protocol mergeProtocol; |
6182 |
25 Mar 21 |
nicklas |
62 |
|
6182 |
25 Mar 21 |
nicklas |
63 |
public ImportFastqJobCreator() |
6182 |
25 Mar 21 |
nicklas |
64 |
{} |
6182 |
25 Mar 21 |
nicklas |
65 |
|
6182 |
25 Mar 21 |
nicklas |
66 |
/** |
6182 |
25 Mar 21 |
nicklas |
Set the software item to set on created MergedSequences. |
6182 |
25 Mar 21 |
nicklas |
@see DerivedBioAssay#setSoftware(Software) |
6182 |
25 Mar 21 |
nicklas |
69 |
*/ |
6182 |
25 Mar 21 |
nicklas |
70 |
public void setMergeSoftware(Software software) |
6182 |
25 Mar 21 |
nicklas |
71 |
{ |
6182 |
25 Mar 21 |
nicklas |
72 |
this.mergeSoftware = software; |
6182 |
25 Mar 21 |
nicklas |
73 |
} |
6182 |
25 Mar 21 |
nicklas |
74 |
|
6182 |
25 Mar 21 |
nicklas |
75 |
/** |
6182 |
25 Mar 21 |
nicklas |
Set the protocol item to set on created MergedSequences. |
6182 |
25 Mar 21 |
nicklas |
@see DerivedBioAssay#setProtocol(Protocol) |
6182 |
25 Mar 21 |
nicklas |
78 |
*/ |
6182 |
25 Mar 21 |
nicklas |
79 |
public void setMergeProtocol(Protocol protocol) |
6182 |
25 Mar 21 |
nicklas |
80 |
{ |
6182 |
25 Mar 21 |
nicklas |
81 |
this.mergeProtocol = protocol; |
6182 |
25 Mar 21 |
nicklas |
82 |
} |
6182 |
25 Mar 21 |
nicklas |
83 |
|
6182 |
25 Mar 21 |
nicklas |
84 |
/** |
6215 |
16 Apr 21 |
nicklas |
Create a child bioassays for all given demuxed sequences and schedule |
6215 |
16 Apr 21 |
nicklas |
jobs on the given cluster for importing FASTQ files. |
6182 |
25 Mar 21 |
nicklas |
@return A list with the corresponding jobs in BASE |
6182 |
25 Mar 21 |
nicklas |
88 |
*/ |
6209 |
13 Apr 21 |
nicklas |
89 |
@SuppressWarnings("unchecked") |
6215 |
16 Apr 21 |
nicklas |
90 |
public List<JobDefinition> createFastqImportJobs(DbControl dc, OpenGridCluster cluster, List<DemuxedSequences> demuxedSequences) |
6182 |
25 Mar 21 |
nicklas |
91 |
{ |
6182 |
25 Mar 21 |
nicklas |
92 |
SessionControl sc = dc.getSessionControl(); |
6182 |
25 Mar 21 |
nicklas |
93 |
|
6182 |
25 Mar 21 |
nicklas |
94 |
String mergeParameterSet = (String)Annotationtype.PARAMETER_SET.getAnnotationValue(dc, mergeSoftware); |
6182 |
25 Mar 21 |
nicklas |
95 |
|
6182 |
25 Mar 21 |
nicklas |
96 |
ClusterConfig clusterCfg = cluster.getConfig(); |
6182 |
25 Mar 21 |
nicklas |
97 |
XmlConfig cfg = Reggie.getConfig(cluster.getId()); |
6182 |
25 Mar 21 |
nicklas |
98 |
if (cfg == null) |
6182 |
25 Mar 21 |
nicklas |
99 |
{ |
6182 |
25 Mar 21 |
nicklas |
100 |
throw new ItemNotFoundException("No configuration in reggie-config.xml for cluster: " + cluster.getId()); |
6182 |
25 Mar 21 |
nicklas |
101 |
} |
6182 |
25 Mar 21 |
nicklas |
102 |
|
6182 |
25 Mar 21 |
nicklas |
// Get global options |
6693 |
22 Apr 22 |
nicklas |
104 |
String global_env = ScriptUtil.multilineIndent(cfg.getConfig("global-env")); |
6658 |
31 Mar 22 |
nicklas |
105 |
String projectArchive = cfg.getRequiredConfig("project-archive", null); |
6658 |
31 Mar 22 |
nicklas |
106 |
String externalArchive = cfg.getConfig("external-archive", null, projectArchive); |
6182 |
25 Mar 21 |
nicklas |
107 |
|
6182 |
25 Mar 21 |
nicklas |
// Options for the programs |
7372 |
06 Oct 23 |
nicklas |
109 |
String import_submit = cfg.getConfig("ascat/submit-import", mergeParameterSet, null); |
7372 |
06 Oct 23 |
nicklas |
110 |
String import_submit_debug = cfg.getConfig("ascat/submit-import-debug", mergeParameterSet, null); |
6658 |
31 Mar 22 |
nicklas |
111 |
String demux_env = ScriptUtil.multilineIndent(cfg.getRequiredConfig("demux/env", mergeParameterSet)); |
6658 |
31 Mar 22 |
nicklas |
112 |
String import_env = ScriptUtil.multilineIndent(cfg.getRequiredConfig("demux/env-import", mergeParameterSet)); |
6658 |
31 Mar 22 |
nicklas |
113 |
String import_envdebug = ScriptUtil.multilineIndent(cfg.getConfig("demux/env-import-debug", mergeParameterSet, null)); |
6658 |
31 Mar 22 |
nicklas |
114 |
String import_execute = ScriptUtil.multilineIndent(cfg.getConfig("demux/execute-import", mergeParameterSet, "./import-fastq.sh")); |
6658 |
31 Mar 22 |
nicklas |
115 |
|
6182 |
25 Mar 21 |
nicklas |
// Load common items |
6182 |
25 Mar 21 |
nicklas |
117 |
ItemSubtype fastqImportJobType = Subtype.FASTQ_IMPORT_JOB.get(dc); |
6215 |
16 Apr 21 |
nicklas |
118 |
ItemSubtype mergedType = Subtype.MERGED_SEQUENCES.get(dc); |
6182 |
25 Mar 21 |
nicklas |
119 |
|
6182 |
25 Mar 21 |
nicklas |
// Selected items must be removed from this list |
6182 |
25 Mar 21 |
nicklas |
121 |
ItemList importPipeline = BiomaterialList.FASTQ_IMPORT_PIPELINE.load(dc); |
6182 |
25 Mar 21 |
nicklas |
122 |
|
6182 |
25 Mar 21 |
nicklas |
// Options common for all jobs |
6182 |
25 Mar 21 |
nicklas |
124 |
JobConfig jobConfig = new JobConfig(); |
6182 |
25 Mar 21 |
nicklas |
125 |
if (priority != null) jobConfig.setPriority(priority); |
7372 |
06 Oct 23 |
nicklas |
126 |
if (partition != null) jobConfig.setSbatchOption("partition", ScriptUtil.checkValidScriptParameter(partition)); |
7372 |
06 Oct 23 |
nicklas |
127 |
jobConfig.convertOptionsTo(clusterCfg.getType()); |
7372 |
06 Oct 23 |
nicklas |
128 |
if (submitOptionsOverride != null) |
7372 |
06 Oct 23 |
nicklas |
129 |
{ |
7372 |
06 Oct 23 |
nicklas |
130 |
ScriptUtil.addSubmitOptions(jobConfig, submitOptionsOverride, clusterCfg.getType()); |
7372 |
06 Oct 23 |
nicklas |
131 |
} |
7372 |
06 Oct 23 |
nicklas |
132 |
else |
7372 |
06 Oct 23 |
nicklas |
133 |
{ |
7372 |
06 Oct 23 |
nicklas |
134 |
ScriptUtil.addSubmitOptions(jobConfig, import_submit, clusterCfg.getType()); |
7372 |
06 Oct 23 |
nicklas |
135 |
if (debug) ScriptUtil.addSubmitOptions(jobConfig, import_submit_debug, clusterCfg.getType()); |
7372 |
06 Oct 23 |
nicklas |
136 |
} |
6182 |
25 Mar 21 |
nicklas |
137 |
|
6182 |
25 Mar 21 |
nicklas |
// We submit one job for each item to the cluster |
6215 |
16 Apr 21 |
nicklas |
139 |
List<JobDefinition> jobDefs = new ArrayList<JobDefinition>(demuxedSequences.size()); |
6182 |
25 Mar 21 |
nicklas |
140 |
|
6215 |
16 Apr 21 |
nicklas |
141 |
for (DemuxedSequences ds : demuxedSequences) |
6182 |
25 Mar 21 |
nicklas |
142 |
{ |
6215 |
16 Apr 21 |
nicklas |
143 |
ds = DemuxedSequences.getById(dc, ds.getId()); // Ensure item is loaded in this transaction |
6215 |
16 Apr 21 |
nicklas |
144 |
DerivedBioAssay demuxed = ds.getDerivedBioAssay(); |
6215 |
16 Apr 21 |
nicklas |
145 |
String demuxName = ScriptUtil.checkValidFilename(demuxed.getName()); |
6182 |
25 Mar 21 |
nicklas |
146 |
|
6215 |
16 Apr 21 |
nicklas |
147 |
List<String> rawFastqNames = (List<String>)Annotationtype.RAW_FASTQ.getAnnotationValues(dc, demuxed); |
6209 |
13 Apr 21 |
nicklas |
148 |
if (rawFastqNames == null || rawFastqNames.size() != 2) |
6209 |
13 Apr 21 |
nicklas |
149 |
{ |
6209 |
13 Apr 21 |
nicklas |
150 |
throw new InvalidDataException("Annotation RawFASTQ on " + |
6215 |
16 Apr 21 |
nicklas |
151 |
demuxName + " must have two values: " + rawFastqNames); |
6209 |
13 Apr 21 |
nicklas |
152 |
} |
6474 |
04 Nov 21 |
nicklas |
153 |
String fastqNameR1 = null; |
6474 |
04 Nov 21 |
nicklas |
154 |
String fastqNameR2 = null; |
6474 |
04 Nov 21 |
nicklas |
// NOTE! Order in the list is not specified so we don't know which is R1 and R2 |
6474 |
04 Nov 21 |
nicklas |
// Search for _R1 and _R2 |
6474 |
04 Nov 21 |
nicklas |
157 |
for (String fqName : rawFastqNames) |
6474 |
04 Nov 21 |
nicklas |
158 |
{ |
6474 |
04 Nov 21 |
nicklas |
159 |
if (fqName.contains("_R1")) fastqNameR1 = fqName; |
6474 |
04 Nov 21 |
nicklas |
160 |
if (fqName.contains("_R2")) fastqNameR2 = fqName; |
6474 |
04 Nov 21 |
nicklas |
161 |
} |
6474 |
04 Nov 21 |
nicklas |
162 |
if (fastqNameR1 == null) |
6474 |
04 Nov 21 |
nicklas |
163 |
{ |
6474 |
04 Nov 21 |
nicklas |
164 |
throw new InvalidDataException("Annotation RawFASTQ on " + |
6474 |
04 Nov 21 |
nicklas |
165 |
demuxName + " is missing an R1 fastq file: " + rawFastqNames); |
6474 |
04 Nov 21 |
nicklas |
166 |
} |
6474 |
04 Nov 21 |
nicklas |
167 |
if (fastqNameR2 == null) |
6474 |
04 Nov 21 |
nicklas |
168 |
{ |
6474 |
04 Nov 21 |
nicklas |
169 |
throw new InvalidDataException("Annotation RawFASTQ on " + |
6474 |
04 Nov 21 |
nicklas |
170 |
demuxName + " is missing an R2 fastq file: " + rawFastqNames); |
6474 |
04 Nov 21 |
nicklas |
171 |
} |
6209 |
13 Apr 21 |
nicklas |
172 |
|
6209 |
13 Apr 21 |
nicklas |
// Get SequencingRun so that we can get the path to the FASTQ folder. |
6215 |
16 Apr 21 |
nicklas |
174 |
String rawFastqFolder = ScriptUtil.checkValidPath((String)Annotationtype.DATA_FILES_FOLDER.getAnnotationValue(dc, demuxed), true, true); |
6209 |
13 Apr 21 |
nicklas |
175 |
|
6215 |
16 Apr 21 |
nicklas |
176 |
importPipeline.removeItem(demuxed); |
6215 |
16 Apr 21 |
nicklas |
177 |
Library lib = Library.get(demuxed.getExtract()); |
6215 |
16 Apr 21 |
nicklas |
178 |
boolean isExternal = Reggie.isExternalItem(demuxName); |
6182 |
25 Mar 21 |
nicklas |
179 |
String rootName = isExternal ? lib.getTopExtractOrSample(dc).getName() : null; |
6182 |
25 Mar 21 |
nicklas |
180 |
|
6182 |
25 Mar 21 |
nicklas |
// Create job |
6215 |
16 Apr 21 |
nicklas |
182 |
String mergedName = lib.getNextMergedSequencesName(dc); |
6182 |
25 Mar 21 |
nicklas |
183 |
Job importJob = Job.getNew(dc, null, null, null); |
6182 |
25 Mar 21 |
nicklas |
184 |
importJob.setItemSubtype(fastqImportJobType); |
6182 |
25 Mar 21 |
nicklas |
185 |
importJob.setPluginVersion("reggie-"+Reggie.VERSION); |
6182 |
25 Mar 21 |
nicklas |
186 |
importJob.setSendMessage(Values.getBoolean(sc.getUserClientSetting("plugins.sendmessage"), false)); |
6215 |
16 Apr 21 |
nicklas |
187 |
importJob.setName("FASTQ Import to " + mergedName); |
6211 |
14 Apr 21 |
nicklas |
188 |
importJob.setParameterValue("pipeline", new StringParameterType(), Pipeline.RNA_SEQ.getId()); |
6182 |
25 Mar 21 |
nicklas |
189 |
if (debug) importJob.setName(importJob.getName() + " (debug)"); |
6981 |
17 Jan 23 |
nicklas |
190 |
if (partition != null) importJob.setParameterValue("partition", new StringParameterType(), partition); |
7372 |
06 Oct 23 |
nicklas |
191 |
if (submitOptionsOverride != null) importJob.setParameterValue("jobOptions", new StringParameterType(), submitOptionsOverride); |
6182 |
25 Mar 21 |
nicklas |
192 |
dc.saveItem(importJob); |
6215 |
16 Apr 21 |
nicklas |
193 |
|
6215 |
16 Apr 21 |
nicklas |
// Created MERGED derived bioassay set |
6215 |
16 Apr 21 |
nicklas |
195 |
DerivedBioAssay merged = DerivedBioAssay.getNew(dc, demuxed, importJob); |
6215 |
16 Apr 21 |
nicklas |
196 |
merged.setItemSubtype(mergedType); |
6215 |
16 Apr 21 |
nicklas |
197 |
Pipeline.RNA_SEQ.setAnnotation(dc, merged); |
6215 |
16 Apr 21 |
nicklas |
198 |
merged.setName(mergedName); |
6215 |
16 Apr 21 |
nicklas |
199 |
merged.setExtract(lib.getExtract()); |
6182 |
25 Mar 21 |
nicklas |
200 |
merged.setSoftware(mergeSoftware); |
6182 |
25 Mar 21 |
nicklas |
201 |
merged.setProtocol(mergeProtocol); |
6215 |
16 Apr 21 |
nicklas |
202 |
DoNotUse.copyDoNotUseAnnotations(dc, lib.getExtract(), merged, false); |
6215 |
16 Apr 21 |
nicklas |
// Copy READS |
6215 |
16 Apr 21 |
nicklas |
204 |
Annotationtype.READS.copyAnnotationValues(dc, demuxed, merged, false); |
6215 |
16 Apr 21 |
nicklas |
205 |
Annotationtype.PF_READS.copyAnnotationValues(dc, demuxed, merged, false); |
6215 |
16 Apr 21 |
nicklas |
206 |
dc.saveItem(merged); |
6182 |
25 Mar 21 |
nicklas |
207 |
|
6658 |
31 Mar 22 |
nicklas |
208 |
String fastqFolder = ScriptUtil.checkValidPath(MergedSequences.generateDataFilesFolderForProjectArchive(mergedName, rootName, debug), true, true); |
6658 |
31 Mar 22 |
nicklas |
209 |
Annotationtype.DATA_FILES_FOLDER.setAnnotationValue(dc, merged, fastqFolder); |
6182 |
25 Mar 21 |
nicklas |
210 |
if (autoConfirm) |
6182 |
25 Mar 21 |
nicklas |
211 |
{ |
6182 |
25 Mar 21 |
nicklas |
212 |
Annotationtype.AUTO_PROCESSING.setAnnotationValue(dc, merged, "AutoConfirm"); |
6182 |
25 Mar 21 |
nicklas |
213 |
} |
6182 |
25 Mar 21 |
nicklas |
214 |
|
6658 |
31 Mar 22 |
nicklas |
215 |
String archiveFolder = isExternal ? externalArchive : projectArchive; |
6215 |
16 Apr 21 |
nicklas |
216 |
String baseFileName = isExternal ? Reggie.removePrefix(mergedName) : mergedName; |
6209 |
13 Apr 21 |
nicklas |
217 |
|
6182 |
25 Mar 21 |
nicklas |
218 |
ScriptBuilder script = new ScriptBuilder(); |
6665 |
05 Apr 22 |
nicklas |
219 |
script.cmd(debug ? "set -ex" : "set -e"); |
6658 |
31 Mar 22 |
nicklas |
220 |
|
6182 |
25 Mar 21 |
nicklas |
// Set file permissions based on consent or external group! |
6215 |
16 Apr 21 |
nicklas |
222 |
String externalGroup = isExternal ? Reggie.getExternalGroup(mergedName) : null; |
6182 |
25 Mar 21 |
nicklas |
223 |
ScriptUtil.setUmaskForItem(dc, lib, externalGroup, script); |
6693 |
22 Apr 22 |
nicklas |
224 |
script.newLine(); |
6693 |
22 Apr 22 |
nicklas |
225 |
script.cmd(global_env); |
6658 |
31 Mar 22 |
nicklas |
226 |
script.export("ArchiveFolder", archiveFolder); |
6658 |
31 Mar 22 |
nicklas |
227 |
script.export("FASTQ1", ScriptUtil.checkValidFilename(fastqNameR1)); |
6658 |
31 Mar 22 |
nicklas |
228 |
script.export("FASTQ2", ScriptUtil.checkValidFilename(fastqNameR2)); |
6658 |
31 Mar 22 |
nicklas |
229 |
script.export("MergedName", mergedName); |
6693 |
22 Apr 22 |
nicklas |
230 |
script.export("ImportArchive", "${ImportArchiveRoot}"+rawFastqFolder); |
6658 |
31 Mar 22 |
nicklas |
231 |
script.export("FastqFolder", "${ArchiveFolder}"+fastqFolder); |
6658 |
31 Mar 22 |
nicklas |
232 |
script.export("BaseFileName", baseFileName); |
6182 |
25 Mar 21 |
nicklas |
233 |
script.newLine(); |
6658 |
31 Mar 22 |
nicklas |
234 |
script.cmd(demux_env); |
6658 |
31 Mar 22 |
nicklas |
235 |
script.cmd(import_env); |
6658 |
31 Mar 22 |
nicklas |
236 |
if (debug) script.cmd(import_envdebug); |
6658 |
31 Mar 22 |
nicklas |
237 |
script.cmd(import_execute); |
6182 |
25 Mar 21 |
nicklas |
238 |
|
6182 |
25 Mar 21 |
nicklas |
239 |
if (externalGroup != null) |
6182 |
25 Mar 21 |
nicklas |
240 |
{ |
6658 |
31 Mar 22 |
nicklas |
241 |
ScriptUtil.addChgrp(externalGroup, fastqFolder, mergedName, archiveFolder+"/"+Reggie.getPrefix(mergedName), script); |
6182 |
25 Mar 21 |
nicklas |
242 |
} |
6182 |
25 Mar 21 |
nicklas |
243 |
|
6674 |
11 Apr 22 |
nicklas |
244 |
JobDefinition jobDef = new JobDefinition("FastqImport", jobConfig, batchConfig, importJob); |
6658 |
31 Mar 22 |
nicklas |
245 |
jobDef.addFile(ScriptUtil.upload("import-fastq.sh")); |
6658 |
31 Mar 22 |
nicklas |
246 |
jobDef.addFile(ScriptUtil.upload("demux-utils.sh")); |
6658 |
31 Mar 22 |
nicklas |
247 |
jobDef.addFile(ScriptUtil.upload("reggie-utils.sh")); |
6658 |
31 Mar 22 |
nicklas |
248 |
jobDef.addFile(ScriptUtil.upload("stdwrap.sh")); |
6658 |
31 Mar 22 |
nicklas |
249 |
jobDef.addFile(ScriptUtil.upload("singlecolumnaverager.awk")); |
6658 |
31 Mar 22 |
nicklas |
250 |
jobDef.addFile(ScriptUtil.upload("readlength_averager.awk")); |
6182 |
25 Mar 21 |
nicklas |
251 |
jobDef.setDebug(debug); |
6182 |
25 Mar 21 |
nicklas |
252 |
jobDef.setCmd(script.toString()); |
6182 |
25 Mar 21 |
nicklas |
253 |
jobDefs.add(jobDef); |
6182 |
25 Mar 21 |
nicklas |
254 |
} |
6182 |
25 Mar 21 |
nicklas |
255 |
|
6182 |
25 Mar 21 |
nicklas |
256 |
return jobDefs; |
6182 |
25 Mar 21 |
nicklas |
257 |
} |
6182 |
25 Mar 21 |
nicklas |
258 |
|
6182 |
25 Mar 21 |
nicklas |
259 |
/** |
6182 |
25 Mar 21 |
nicklas |
Job completion handler for FASTQ import jobs. |
6182 |
25 Mar 21 |
nicklas |
261 |
|
6182 |
25 Mar 21 |
nicklas |
The handler downloads the |
6182 |
25 Mar 21 |
nicklas |
'demultiplex_metrics.txt' file from the job folder and parses out number of |
6182 |
25 Mar 21 |
nicklas |
reads and passed filter information for each sequenced library. |
6182 |
25 Mar 21 |
nicklas |
265 |
|
6182 |
25 Mar 21 |
nicklas |
The information |
6182 |
25 Mar 21 |
nicklas |
is stored on {@link MergedSequences} items in {@link Annotationtype#READS} |
6182 |
25 Mar 21 |
nicklas |
and {@link Annotationtype#PF_READS} annotations. |
6182 |
25 Mar 21 |
nicklas |
269 |
*/ |
6182 |
25 Mar 21 |
nicklas |
270 |
public static class FastqImportJobCompletionHandler |
6182 |
25 Mar 21 |
nicklas |
271 |
implements JobCompletionHandler |
6182 |
25 Mar 21 |
nicklas |
272 |
{ |
6182 |
25 Mar 21 |
nicklas |
273 |
private XmlConfig cfg; |
6182 |
25 Mar 21 |
nicklas |
274 |
|
6182 |
25 Mar 21 |
nicklas |
275 |
public FastqImportJobCompletionHandler() |
6182 |
25 Mar 21 |
nicklas |
276 |
{} |
6182 |
25 Mar 21 |
nicklas |
277 |
|
6182 |
25 Mar 21 |
nicklas |
278 |
@Override |
6182 |
25 Mar 21 |
nicklas |
279 |
public String jobCompleted(SessionControl sc, OpenGridSession session, Job job, JobStatus status) |
6182 |
25 Mar 21 |
nicklas |
280 |
{ |
6182 |
25 Mar 21 |
nicklas |
281 |
String jobName = status.getName(); |
6182 |
25 Mar 21 |
nicklas |
282 |
String trimmomatic = session.getJobFileAsString(jobName, "trimmomatic.out", "UTF-8"); |
6182 |
25 Mar 21 |
nicklas |
283 |
String fragments = session.getJobFileAsString(jobName, "fragments.out", "UTF-8"); |
6421 |
23 Sep 21 |
nicklas |
284 |
String readlength = session.getJobFileAsString(jobName, "readlength.out", "UTF-8"); |
6182 |
25 Mar 21 |
nicklas |
285 |
String files = session.getJobFileAsString(jobName, "files.out", "UTF-8"); |
6182 |
25 Mar 21 |
nicklas |
286 |
|
6182 |
25 Mar 21 |
nicklas |
287 |
cfg = Reggie.getConfig(session.getHost().getId()); |
6182 |
25 Mar 21 |
nicklas |
288 |
|
6421 |
23 Sep 21 |
nicklas |
289 |
Metrics total = parseImportMetrics(sc, job, trimmomatic, fragments, readlength, files); |
6182 |
25 Mar 21 |
nicklas |
290 |
String msg = Values.formatNumber(total.reads/1000000f, 1) + "M reads; "; |
6182 |
25 Mar 21 |
nicklas |
291 |
msg += Values.formatNumber(total.passedFilter/1000000f, 1) + "M passed filter; "; |
6182 |
25 Mar 21 |
nicklas |
292 |
msg += Values.formatNumber(total.passedTrimmomatic[1]/1000000f, 1) + "M passed trimmomatic; "; |
6215 |
16 Apr 21 |
nicklas |
293 |
return msg; |
6182 |
25 Mar 21 |
nicklas |
294 |
} |
6182 |
25 Mar 21 |
nicklas |
295 |
|
6421 |
23 Sep 21 |
nicklas |
296 |
private Metrics parseImportMetrics(SessionControl sc, Job job, String trimmomatic, String align, String readlength, String files) |
6182 |
25 Mar 21 |
nicklas |
297 |
{ |
6182 |
25 Mar 21 |
nicklas |
298 |
Metrics metrics = new Metrics(); |
6182 |
25 Mar 21 |
nicklas |
299 |
|
6182 |
25 Mar 21 |
nicklas |
// Parse the trimmomatic.out file |
6182 |
25 Mar 21 |
nicklas |
301 |
Pattern before = Pattern.compile(".*Input Read Pairs:\\s+(\\d+).*"); |
6182 |
25 Mar 21 |
nicklas |
302 |
Pattern after = Pattern.compile(".*Both Surviving:\\s+(\\d+).*"); |
6182 |
25 Mar 21 |
nicklas |
303 |
int trimmomaticStep = 0; |
6182 |
25 Mar 21 |
nicklas |
304 |
for (String line : trimmomatic.split("\n")) |
6182 |
25 Mar 21 |
nicklas |
305 |
{ |
6182 |
25 Mar 21 |
nicklas |
306 |
Matcher m = after.matcher(line); |
6182 |
25 Mar 21 |
nicklas |
307 |
if (m.matches()) |
6182 |
25 Mar 21 |
nicklas |
308 |
{ |
6182 |
25 Mar 21 |
nicklas |
309 |
metrics.passedTrimmomatic[trimmomaticStep] = Values.getLong(m.group(1), -1); |
6182 |
25 Mar 21 |
nicklas |
310 |
trimmomaticStep++; |
6182 |
25 Mar 21 |
nicklas |
311 |
if (trimmomaticStep == 2) break; |
6182 |
25 Mar 21 |
nicklas |
312 |
} |
6182 |
25 Mar 21 |
nicklas |
313 |
m = before.matcher(line); |
6182 |
25 Mar 21 |
nicklas |
314 |
if (m.matches()) |
6182 |
25 Mar 21 |
nicklas |
315 |
{ |
6474 |
04 Nov 21 |
nicklas |
316 |
metrics.reads = Values.getLong(m.group(1), -1); |
6474 |
04 Nov 21 |
nicklas |
317 |
metrics.passedFilter = metrics.reads; |
6182 |
25 Mar 21 |
nicklas |
318 |
} |
6182 |
25 Mar 21 |
nicklas |
319 |
} |
6182 |
25 Mar 21 |
nicklas |
320 |
|
6182 |
25 Mar 21 |
nicklas |
// Parse fragments.out file |
6182 |
25 Mar 21 |
nicklas |
322 |
Pattern p = Pattern.compile("(\\d+)\\t(\\d+\\.?\\d*)\\t(\\d+\\.?\\d*)"); |
6182 |
25 Mar 21 |
nicklas |
323 |
for (String line : align.split("\n")) |
6182 |
25 Mar 21 |
nicklas |
324 |
{ |
6182 |
25 Mar 21 |
nicklas |
325 |
Matcher m = p.matcher(line); |
6182 |
25 Mar 21 |
nicklas |
326 |
if (m.matches()) |
6182 |
25 Mar 21 |
nicklas |
327 |
{ |
6182 |
25 Mar 21 |
nicklas |
328 |
metrics.fragmentSizeCount = Values.getInt(m.group(1), -1); |
6182 |
25 Mar 21 |
nicklas |
329 |
metrics.fragmentSizeAvg = Values.getInt(m.group(2), -1); |
6182 |
25 Mar 21 |
nicklas |
330 |
metrics.fragmentSizeStd = Values.getInt(m.group(3), -1); |
6182 |
25 Mar 21 |
nicklas |
331 |
break; |
6182 |
25 Mar 21 |
nicklas |
332 |
} |
6182 |
25 Mar 21 |
nicklas |
333 |
} |
6182 |
25 Mar 21 |
nicklas |
334 |
|
6421 |
23 Sep 21 |
nicklas |
// Parse readlength.out file |
6421 |
23 Sep 21 |
nicklas |
336 |
p = Pattern.compile("(\\d+)\\t(\\d+\\.?\\d*)"); |
6421 |
23 Sep 21 |
nicklas |
337 |
int readNo = -1; |
6421 |
23 Sep 21 |
nicklas |
338 |
for (String line : readlength.split("\n")) |
6421 |
23 Sep 21 |
nicklas |
339 |
{ |
6421 |
23 Sep 21 |
nicklas |
340 |
Matcher m = p.matcher(line); |
6421 |
23 Sep 21 |
nicklas |
341 |
if (m.matches()) |
6421 |
23 Sep 21 |
nicklas |
342 |
{ |
6421 |
23 Sep 21 |
nicklas |
343 |
readNo++; |
6421 |
23 Sep 21 |
nicklas |
344 |
if (readNo >= metrics.readlengthCount.length) break; |
6421 |
23 Sep 21 |
nicklas |
345 |
metrics.readlengthCount[readNo] = Values.getInt(m.group(1), -1); |
6421 |
23 Sep 21 |
nicklas |
346 |
metrics.readlengthAvg[readNo] = Values.getInt(m.group(2), -1); |
6421 |
23 Sep 21 |
nicklas |
347 |
} |
6421 |
23 Sep 21 |
nicklas |
348 |
} |
6421 |
23 Sep 21 |
nicklas |
349 |
|
6182 |
25 Mar 21 |
nicklas |
350 |
DbControl dc = null; |
6182 |
25 Mar 21 |
nicklas |
351 |
try |
6182 |
25 Mar 21 |
nicklas |
352 |
{ |
6599 |
22 Feb 22 |
nicklas |
353 |
dc = sc.newDbControl("Reggie: FASTQ import completed handler"); |
6182 |
25 Mar 21 |
nicklas |
354 |
|
6184 |
26 Mar 21 |
nicklas |
355 |
MergedSequences mergedSequences = MergedSequences.getByJob(dc, job); |
6184 |
26 Mar 21 |
nicklas |
356 |
DerivedBioAssay merged = mergedSequences.getItem(); |
6182 |
25 Mar 21 |
nicklas |
357 |
|
6182 |
25 Mar 21 |
nicklas |
358 |
DataFileType fastqData = Datafiletype.FASTQ.load(dc); |
6182 |
25 Mar 21 |
nicklas |
359 |
ItemSubtype fastqType = fastqData.getGenericType(); |
6182 |
25 Mar 21 |
nicklas |
360 |
FileServer projectArchive = Fileserver.PROJECT_ARCHIVE.load(dc); |
6182 |
25 Mar 21 |
nicklas |
361 |
FileServer externalArchive = Fileserver.EXTERNAL_ARCHIVE.load(dc); |
7051 |
17 Feb 23 |
nicklas |
362 |
FileServer importGateway = Fileserver.IMPORT_GATEWAY.load(dc); |
7051 |
17 Feb 23 |
nicklas |
363 |
FileServer importArchive = Fileserver.IMPORT_ARCHIVE.load(dc); |
6182 |
25 Mar 21 |
nicklas |
364 |
|
6182 |
25 Mar 21 |
nicklas |
365 |
Software mergeSoftware = merged.getSoftware(); |
6182 |
25 Mar 21 |
nicklas |
366 |
String mergeParameterSet = (String)Annotationtype.PARAMETER_SET.getAnnotationValue(dc, mergeSoftware); |
6182 |
25 Mar 21 |
nicklas |
367 |
int bowtie_fragment_count_limit = Values.getInt(cfg.getConfig("demux/bowtie-fragment-count-limit", mergeParameterSet, "20000")); |
6182 |
25 Mar 21 |
nicklas |
368 |
|
6474 |
04 Nov 21 |
nicklas |
369 |
Annotationtype.READS.setAnnotationValue(dc, merged, metrics.reads); |
6182 |
25 Mar 21 |
nicklas |
370 |
Annotationtype.PF_READS.setAnnotationValue(dc, merged, metrics.passedFilter); |
6182 |
25 Mar 21 |
nicklas |
371 |
Annotationtype.ADAPTER_READS.setAnnotationValue(dc, merged, metrics.passedFilter - metrics.passedTrimmomatic[0]); |
6182 |
25 Mar 21 |
nicklas |
372 |
Annotationtype.PT_READS.setAnnotationValue(dc, merged, metrics.passedTrimmomatic[1]); |
6182 |
25 Mar 21 |
nicklas |
373 |
Annotationtype.FRAGMENT_SIZE_AVG.setAnnotationValue(dc, merged, metrics.fragmentSizeCount < bowtie_fragment_count_limit ? -1 : metrics.fragmentSizeAvg); |
6182 |
25 Mar 21 |
nicklas |
374 |
Annotationtype.FRAGMENT_SIZE_STDEV.setAnnotationValue(dc, merged, metrics.fragmentSizeCount < bowtie_fragment_count_limit ? -1 : metrics.fragmentSizeStd); |
6421 |
23 Sep 21 |
nicklas |
375 |
Annotationtype.READLENGTH_AVG_R1.setAnnotationValue(dc, merged, metrics.readlengthCount[0] < bowtie_fragment_count_limit ? -1 : metrics.readlengthAvg[0]); |
6421 |
23 Sep 21 |
nicklas |
376 |
Annotationtype.READLENGTH_AVG_R2.setAnnotationValue(dc, merged, metrics.readlengthCount[1] < bowtie_fragment_count_limit ? -1 : metrics.readlengthAvg[1]); |
6421 |
23 Sep 21 |
nicklas |
377 |
|
6182 |
25 Mar 21 |
nicklas |
// Create FASTQ file links |
6182 |
25 Mar 21 |
nicklas |
379 |
boolean useExternalProjectArchive = Reggie.isExternalItem(merged.getName()); |
6182 |
25 Mar 21 |
nicklas |
380 |
FileServer fileArchive = useExternalProjectArchive ? externalArchive : projectArchive; |
6182 |
25 Mar 21 |
nicklas |
381 |
String analysisDir = useExternalProjectArchive ? Reggie.EXTERNAL_ANALYSIS_DIR : Reggie.SECONDARY_ANALYSIS_DIR; |
6182 |
25 Mar 21 |
nicklas |
382 |
|
6182 |
25 Mar 21 |
nicklas |
383 |
String dataFilesFolder = (String)Annotationtype.DATA_FILES_FOLDER.getAnnotationValue(dc, merged); |
6182 |
25 Mar 21 |
nicklas |
384 |
String baseFolder = Reggie.convertDataFilesFolderToBaseFolder(dataFilesFolder); |
6182 |
25 Mar 21 |
nicklas |
385 |
Directory localDataDir = Directory.getNew(dc, new Path(analysisDir+baseFolder, Path.Type.DIRECTORY)); |
6182 |
25 Mar 21 |
nicklas |
386 |
|
6182 |
25 Mar 21 |
nicklas |
387 |
int lineNo = 0; |
6182 |
25 Mar 21 |
nicklas |
388 |
for (String line : files.split("\n")) |
6182 |
25 Mar 21 |
nicklas |
389 |
{ |
6182 |
25 Mar 21 |
nicklas |
390 |
lineNo++; |
6182 |
25 Mar 21 |
nicklas |
391 |
|
6182 |
25 Mar 21 |
nicklas |
392 |
File f = File.getFile(dc, localDataDir, line.substring(line.lastIndexOf("/")+1), true); |
6182 |
25 Mar 21 |
nicklas |
393 |
f.setFileServer(fileArchive); |
6182 |
25 Mar 21 |
nicklas |
394 |
f.setItemSubtype(fastqType); |
6182 |
25 Mar 21 |
nicklas |
395 |
f.setDescription(metrics.reads + " READS; " + metrics.passedFilter + " PF_READS; " + metrics.passedTrimmomatic[1] + " PT_READS"); |
6421 |
23 Sep 21 |
nicklas |
396 |
if (f.getName().contains(".fastq")) |
6421 |
23 Sep 21 |
nicklas |
397 |
{ |
6421 |
23 Sep 21 |
nicklas |
398 |
f.setDescription(f.getDescription()+"; ReadLengthAvg="+metrics.readlengthAvg[f.getName().contains("R1.fastq")?0:1]); |
6421 |
23 Sep 21 |
nicklas |
399 |
} |
6182 |
25 Mar 21 |
nicklas |
400 |
String fileUrl = "sftp://" + fileArchive.getHost() + dataFilesFolder + "/" + f.getName(); |
6182 |
25 Mar 21 |
nicklas |
401 |
try |
6182 |
25 Mar 21 |
nicklas |
402 |
{ |
6182 |
25 Mar 21 |
nicklas |
403 |
f.setUrl(fileUrl, true); |
6182 |
25 Mar 21 |
nicklas |
404 |
} |
6182 |
25 Mar 21 |
nicklas |
405 |
catch (RuntimeException ex) |
6182 |
25 Mar 21 |
nicklas |
406 |
{ |
6182 |
25 Mar 21 |
nicklas |
407 |
f.setUrl(fileUrl, false); |
6182 |
25 Mar 21 |
nicklas |
408 |
} |
6182 |
25 Mar 21 |
nicklas |
409 |
if (!f.isInDatabase()) |
6182 |
25 Mar 21 |
nicklas |
410 |
{ |
6182 |
25 Mar 21 |
nicklas |
411 |
dc.saveItem(f); |
6182 |
25 Mar 21 |
nicklas |
412 |
} |
6182 |
25 Mar 21 |
nicklas |
413 |
FileSetMember member = merged.getFileSet().addMember(f, fastqData); |
6182 |
25 Mar 21 |
nicklas |
414 |
} |
6182 |
25 Mar 21 |
nicklas |
415 |
|
7051 |
17 Feb 23 |
nicklas |
// Change the raw FASTQ files on the DemuxedSequences item to point |
7051 |
17 Feb 23 |
nicklas |
// to the new location in the ImportArchive |
7051 |
17 Feb 23 |
nicklas |
418 |
DemuxedSequences dx = mergedSequences.getSingleDemuxedSequences(dc); |
7051 |
17 Feb 23 |
nicklas |
419 |
String rawFastqFilesFolder = (String)Annotationtype.DATA_FILES_FOLDER.getAnnotationValue(dc, dx.getItem()); |
7051 |
17 Feb 23 |
nicklas |
420 |
for (File f : Datafiletype.FASTQ.getAllFiles(dc, dx.getItem())) |
7051 |
17 Feb 23 |
nicklas |
421 |
{ |
7051 |
17 Feb 23 |
nicklas |
422 |
if (importGateway.equals(f.getFileServer())) |
7051 |
17 Feb 23 |
nicklas |
423 |
{ |
7051 |
17 Feb 23 |
nicklas |
424 |
String uri = "sftp://"+importArchive.getHost()+rawFastqFilesFolder+"/"+f.getName(); |
7051 |
17 Feb 23 |
nicklas |
425 |
UriMetadata metadata = getUriMetadata(f, uri); |
7051 |
17 Feb 23 |
nicklas |
426 |
f.setFileServer(importArchive); |
7051 |
17 Feb 23 |
nicklas |
427 |
f.setUrl(uri, metadata); |
7051 |
17 Feb 23 |
nicklas |
428 |
} |
7051 |
17 Feb 23 |
nicklas |
429 |
} |
6182 |
25 Mar 21 |
nicklas |
430 |
dc.commit(); |
6182 |
25 Mar 21 |
nicklas |
431 |
} |
6182 |
25 Mar 21 |
nicklas |
432 |
finally |
6182 |
25 Mar 21 |
nicklas |
433 |
{ |
6182 |
25 Mar 21 |
nicklas |
434 |
if (dc != null) dc.close(); |
6182 |
25 Mar 21 |
nicklas |
435 |
} |
6182 |
25 Mar 21 |
nicklas |
436 |
|
6182 |
25 Mar 21 |
nicklas |
437 |
return metrics; |
6182 |
25 Mar 21 |
nicklas |
438 |
} |
7051 |
17 Feb 23 |
nicklas |
439 |
|
7051 |
17 Feb 23 |
nicklas |
440 |
/** |
7051 |
17 Feb 23 |
nicklas |
Copy the existing metadata from the file. |
7051 |
17 Feb 23 |
nicklas |
442 |
*/ |
7051 |
17 Feb 23 |
nicklas |
443 |
private UriMetadata getUriMetadata(File f, String uri) |
7051 |
17 Feb 23 |
nicklas |
444 |
{ |
7051 |
17 Feb 23 |
nicklas |
445 |
UriMetadata m = new UriMetadata(URI.create(uri)); |
7051 |
17 Feb 23 |
nicklas |
446 |
m.setLastModified(f.getLastUpdate()); |
7051 |
17 Feb 23 |
nicklas |
447 |
m.setLength(f.getSize()); |
7051 |
17 Feb 23 |
nicklas |
448 |
m.setMd5(f.getMd5()); |
7051 |
17 Feb 23 |
nicklas |
449 |
m.setMimeType(f.getMimeType()); |
7051 |
17 Feb 23 |
nicklas |
450 |
m.setCharacterSet(f.getCharacterSet()); |
7051 |
17 Feb 23 |
nicklas |
451 |
return m; |
7051 |
17 Feb 23 |
nicklas |
452 |
} |
6182 |
25 Mar 21 |
nicklas |
453 |
} |
6182 |
25 Mar 21 |
nicklas |
454 |
|
6182 |
25 Mar 21 |
nicklas |
455 |
|
6182 |
25 Mar 21 |
nicklas |
456 |
static class Metrics |
6182 |
25 Mar 21 |
nicklas |
457 |
{ |
6182 |
25 Mar 21 |
nicklas |
458 |
long reads = 0; |
6182 |
25 Mar 21 |
nicklas |
459 |
long passedFilter = 0; |
6182 |
25 Mar 21 |
nicklas |
460 |
long[] passedTrimmomatic = new long[2]; |
6182 |
25 Mar 21 |
nicklas |
461 |
int fragmentSizeAvg = -1; |
6182 |
25 Mar 21 |
nicklas |
462 |
int fragmentSizeStd = -1; |
6182 |
25 Mar 21 |
nicklas |
463 |
int fragmentSizeCount = -1; |
6421 |
23 Sep 21 |
nicklas |
464 |
int[] readlengthAvg = new int[2]; |
6421 |
23 Sep 21 |
nicklas |
465 |
long[] readlengthCount = new long[2]; |
6182 |
25 Mar 21 |
nicklas |
466 |
} |
6182 |
25 Mar 21 |
nicklas |
467 |
|
6182 |
25 Mar 21 |
nicklas |
468 |
|
6182 |
25 Mar 21 |
nicklas |
469 |
|
6182 |
25 Mar 21 |
nicklas |
470 |
} |