From 87de8ee8c36790f0830886a4b6de37c1dcc76296 Mon Sep 17 00:00:00 2001 From: "vasary.daniel" Date: Fri, 5 Feb 2021 12:22:45 +0000 Subject: [PATCH] git-tfs-id: [http://tfs.userrendszerhaz.hu:8080/tfs/DefaultCollection]$/MediaCube;C32130 --- .../build-mediacube-plugins.launch | 19 ++ .../run-mediacube-server-hirtv.launch | 2 +- .../run-mediacube-server-mv.launch | 2 +- .../run-mediacube-server-test.launch | 2 +- .../run-mediacube-server-user.launch | 2 +- .../run-mediacube-server.launch | 2 +- server/-dependencies/jobengine.target | 16 +- ...elix.gogo.command_0.10.0.v201209301215.jar | Bin 0 -> 57040 bytes ...quinox.launcher_1.3.200.v20160318-1642.jar | Bin 0 -> 51690 bytes server/-dependencies/pom.xml | 71 +++--- server/-modules/plugins/pom.xml | 130 ++++++++++ server/-modules/pom.xml | 1 - server/-product/mediacube.product | 19 +- .../mediacube/executors/tests/SmallTests.java | 88 ++++++- server/user.commons.log4j2/pom.xml | 32 +++ .../server/steps/ForkDownloadStep.java | 24 -- .../server/steps/PASAPOOLTransferToStep.java | 39 ++- .../server/steps/ReportServerStatusStep.java | 64 +++++ .../jobengine/server/steps/TransferStep.java | 40 +++- .../config/config-mv.xml | 4 +- .../server/steps/CancelableStep.java | 10 +- .../steps/CleanupMountedLocationStep.java | 8 +- .../server/steps/TSMRestoreStep.java | 25 ++ .../server/steps/TestForkCancelableStep.java | 2 +- .../META-INF/MANIFEST.MF | 2 + server/user.jobengine.osgi.commons/pom.xml | 32 +++ .../src/user/commons/mediaarea/InfoKind.java | 39 +++ .../user/commons/mediaarea/InformParser.java | 88 +++++++ .../user/commons/mediaarea/InformResult.java | 56 +++++ .../user/commons/mediaarea/LibMediaInfo.java | 57 +++++ .../src/user/commons/mediaarea/MediaArea.java | 41 ++++ .../src/user/commons/mediaarea/MediaInfo.java | 222 ++++++++++++++++++ .../user/commons/mediaarea/MediaMetadata.java | 64 +++++ .../src/user/commons/mediaarea/Status.java | 14 ++ .../user/commons/mediaarea/StreamKind.java | 8 + .../user/commons/strings/FileSizeUtils.java | 31 +++ .../026_insert_itemtype_demo_nyers.sql | 11 - .../026_insert_itemtype_muszter_demo.sql | 11 + .../029_alter_PASA_storeuri_to_FTP.sql | 52 ++++ .../030_insert_itemtype_musor_clean.sql | 11 + server/user.jobengine.osgi.db/pom.xml | 32 +++ server/user.jobengine.osgi.server/pom.xml | 32 +++ .../src/user/jobengine/server/IJobEngine.java | 2 + .../jobengine/server/IJobStepExecutor.java | 6 +- .../src/user/jobengine/server/JobEngine.java | 35 ++- .../jobengine/server/JobStepExecutor.java | 111 ++++++--- .../user/jobengine/server/steps/IJobStep.java | 3 +- .../user/jobengine/server/steps/JobStep.java | 5 + .../test/log4j2-test.xml | 13 + .../jobengine/server/JobStepExecutorTest.java | 105 +++++---- .../server/JobStepExecutorTest1.java | 142 +++++++++++ server/user.jobengine.osgi.services/pom.xml | 51 +++- server/user.mediacube.gui/pages/joblist.zul | 1 + server/user.mediacube.gui/pom.xml | 35 ++- .../resources/i3-label_hu.properties | 2 +- .../jobengine/zk/model/FileSizeConverter.java | 13 +- .../user/jobengine/zk/model/JobListModel.java | 9 + server/user.mediacube.metadata/pom.xml | 30 +++ server/user.tsm.client/pom.xml | 31 +++ 59 files changed, 1759 insertions(+), 240 deletions(-) create mode 100644 server/-configuration/build-mediacube-plugins.launch create mode 100644 server/-dependencies/libs/org.apache.felix.gogo.command_0.10.0.v201209301215.jar create mode 100644 server/-dependencies/libs/org.eclipse.equinox.launcher_1.3.200.v20160318-1642.jar create mode 100644 server/-modules/plugins/pom.xml create mode 100644 server/user.jobengine.executors/amc/user/jobengine/server/steps/ReportServerStatusStep.java create mode 100644 server/user.jobengine.osgi.commons/src/user/commons/mediaarea/InfoKind.java create mode 100644 server/user.jobengine.osgi.commons/src/user/commons/mediaarea/InformParser.java create mode 100644 server/user.jobengine.osgi.commons/src/user/commons/mediaarea/InformResult.java create mode 100644 server/user.jobengine.osgi.commons/src/user/commons/mediaarea/LibMediaInfo.java create mode 100644 server/user.jobengine.osgi.commons/src/user/commons/mediaarea/MediaArea.java create mode 100644 server/user.jobengine.osgi.commons/src/user/commons/mediaarea/MediaInfo.java create mode 100644 server/user.jobengine.osgi.commons/src/user/commons/mediaarea/MediaMetadata.java create mode 100644 server/user.jobengine.osgi.commons/src/user/commons/mediaarea/Status.java create mode 100644 server/user.jobengine.osgi.commons/src/user/commons/mediaarea/StreamKind.java create mode 100644 server/user.jobengine.osgi.commons/src/user/commons/strings/FileSizeUtils.java delete mode 100644 server/user.jobengine.osgi.db/migrations/scripts/026_insert_itemtype_demo_nyers.sql create mode 100644 server/user.jobengine.osgi.db/migrations/scripts/026_insert_itemtype_muszter_demo.sql create mode 100644 server/user.jobengine.osgi.db/migrations/scripts/029_alter_PASA_storeuri_to_FTP.sql create mode 100644 server/user.jobengine.osgi.db/migrations/scripts/030_insert_itemtype_musor_clean.sql create mode 100644 server/user.jobengine.osgi.server/test/log4j2-test.xml create mode 100644 server/user.jobengine.osgi.server/test/user/jobengine/server/JobStepExecutorTest1.java diff --git a/server/-configuration/build-mediacube-plugins.launch b/server/-configuration/build-mediacube-plugins.launch new file mode 100644 index 00000000..544a7670 --- /dev/null +++ b/server/-configuration/build-mediacube-plugins.launch @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/server/-configuration/run-mediacube-server-hirtv.launch b/server/-configuration/run-mediacube-server-hirtv.launch index 689f6fb2..f92465a3 100644 --- a/server/-configuration/run-mediacube-server-hirtv.launch +++ b/server/-configuration/run-mediacube-server-hirtv.launch @@ -22,7 +22,7 @@ - + diff --git a/server/-configuration/run-mediacube-server-mv.launch b/server/-configuration/run-mediacube-server-mv.launch index 68ece584..c56d7cff 100644 --- a/server/-configuration/run-mediacube-server-mv.launch +++ b/server/-configuration/run-mediacube-server-mv.launch @@ -22,7 +22,7 @@ - + diff --git a/server/-configuration/run-mediacube-server-test.launch b/server/-configuration/run-mediacube-server-test.launch index a17e092c..ed4ce421 100644 --- a/server/-configuration/run-mediacube-server-test.launch +++ b/server/-configuration/run-mediacube-server-test.launch @@ -22,7 +22,7 @@ - + diff --git a/server/-configuration/run-mediacube-server-user.launch b/server/-configuration/run-mediacube-server-user.launch index 3f71cb6e..4b40742d 100644 --- a/server/-configuration/run-mediacube-server-user.launch +++ b/server/-configuration/run-mediacube-server-user.launch @@ -22,7 +22,7 @@ - + diff --git a/server/-configuration/run-mediacube-server.launch b/server/-configuration/run-mediacube-server.launch index f5ff4bb2..eee244c3 100644 --- a/server/-configuration/run-mediacube-server.launch +++ b/server/-configuration/run-mediacube-server.launch @@ -21,7 +21,7 @@ - + diff --git a/server/-dependencies/jobengine.target b/server/-dependencies/jobengine.target index 62660c20..ba125a69 100644 --- a/server/-dependencies/jobengine.target +++ b/server/-dependencies/jobengine.target @@ -1,5 +1,5 @@ - + @@ -15,10 +15,11 @@ + - - - + + + @@ -34,9 +35,11 @@ + + - - + + @@ -47,6 +50,7 @@ + diff --git a/server/-dependencies/libs/org.apache.felix.gogo.command_0.10.0.v201209301215.jar b/server/-dependencies/libs/org.apache.felix.gogo.command_0.10.0.v201209301215.jar new file mode 100644 index 0000000000000000000000000000000000000000..6c7c9d75ac97919677407342c7510172ee314b07 GIT binary patch literal 57040 zcmeFZW3a7Fo29#K+cx*IZQHhO>}Bj_+qP}nwryJ{-a_|RRo&J7oj4Ka&pUt4h;b*L zjFB_P_2iV70tP_=fPjDiDA5cR0QiRw3IGUzjEJ%Tt%R&7y^MgYgs6y;GM$X*_c#DR zm8z6exj4#p$@q_c(XlNVTkbAbQZU7b#U>Ak>Uqi`Fy*j1we#5bn2P zG0}qkUSF=5UoR}~sU94-mz}@1xn&4Am4)jKZa)~^rURTKf*H{alQ;DfK?WTRL`RUI zbpUTIAXqn~IH`oKCX)8?1rXwx_^1$xlpJ)XTTK;+H zKz=5fW_+fJCuv^*?WUL17u{Ltj~* zue6UHQ8#CVi@AgzJXB~OMy->A&;4_iD`*xs0ZJJm zM*=@s3aw=(><%0ks7mO!$_u$Z3^WwNn}}#pnMnwR)MrBXJ89Q8R`mECtYzyT#Ma)} z!9O*ex{ne7QWKeU0%Ng1(~Ztav##Hw^DDZJ#G9VF>eI|02-~oK7^lPN-2=4fSBkUQ zG0;<%ri<~CGc_vGD#EHd^e~1v$^EIXE~&g9#0!M64aB$)*ICMPUqt)$o!MG}boi0x z*n%}Ym>WbBV0U&FGq@^9pn)ro6+SdGBO*>bx?FytOA?>jiyXy{3SYotreH_*8<)EUmv}SOqdCRdoi&{Ir!%>H68NXMIXtR}E2Z5=8pU(!OF|Y@9#EG`GIY{4GC2 z%cxH(*{%4dhd+Hwl!+;k(&qD$iHQFbuz%6rw)!NfT= zeRnw%|E9)TXCW}U!c!!5IX#@<)(<#gxV%`yR9!032 zH^wb`+tT&Mbd=psQ1)47&|1>i5~X}TSyOycCtt3xCFCyX>-s<;xk87_1{Vz>F}n5yv;Ni~pAcW15z3KH}i zJsx4scipyNPMbPrg;bMvI;3OUG|4xEgl?j}azayQ*^<`7J}`0PC3jfV-jbH>bG47y zikmVDgcMjxtk4%wUJ4wdksjb*u;ATuju9=*ELm+_6UJ*tlo0FwmYd-09A-9PJ7g8^TKaG@gI+lUjX=!F z0bdpOjMU4eY`P@ij-dvUTEIx9h1SRL3lcqk5mW=)vv+jAV1i&=G*kuuH<9Ev4?ju| z9U%Ov&&8?>K>SRnAAtSI`nV-!2{AH;rXDooigox04g5YgbkrqtQ1a~T)y!O|tnGK5tc35lAYKKKuB}EM!X?ecOgV;cW#J z-65_rGIkEG)<`yPtuu`Jm1+lW}UMgi~6+=Xb<<`P+UOju#|6R!PPZ|SG!dAMR&Vfh5)c~P`A$ym}d0O+Kd#Z zoTniO(KFFv=$B)RzeOWitDxp9oGsJ}qXir7JA-=PDY(%Pv0;s#)L#4E$g(r(KFEcy zaOz6pQ683h3(==8st7|a-WF~*h#38_{l-5#ajuy3WJnF9kWU^IBuVCp22LCEVnX6! z*_m96i=uh%M7^Z;9Ysep*Yp^`PQLUY2ieY#r8CNJTG5TAU39S2;%ztt zBh(pXzHcghI~p_v*qUosFgcf_AIi@Ro6GcG(u;V%?Hz4lOM_EChPI0DaGDq_*sOCK zL3JD`NEnhjwws3(d(4PIc1$J$wcNVu_uXpHy2mc`&hXz0oD*8bvz*g7 zQDwnwo0pz&eYLkl{e`?L|3qJ|IVE=M};8}*JUSI%#ZixTR0Z{*!4xp$c zFr^O3zPR!6l7$&r7cxA`s!<>OkR_2kTCKbi|GbG2nTIh6BRC&<6;iLAh^--+S5H>9 z5#XpLLS?a#L~0C=_&&9ozw!0+!SnO;@Ui8Q?P0p@wC$Ar(s}aW!+V0$+mCy=rxc-$ z&p`!oN5rVXZr9s?CB%;~3IFQex)dDy$7EC6@23JeaSRE_FPH|LMp%>#36Kzh2ng41 zgCtu^cBqL%M*Inwhk*SC-k~8hp~OtcR^;vvVe|Tclt0Eu?;priFM00CQD(61@N*z3 zAV^uJo>Xyue!pVsj)~x{qO<)gP--9#S#(8dOP?UftZOmij{U01yNs?bOm`+^0%}4C z)q!6yro0^_AYr#a4}7R#(D8by0Dm%|#{z=dRShx=5aafeEnUAT{ zm2NFX=IP(?twQfSskkyhcyT@BTA0)L87Zh$A!DL_lKc@N5+K4O`0%$7&2IogE21Fq z$t$0IZ;zVZtG}$Xr^%t29WUDAm*~K?1)hcl<7=CtPf!DJ^mHY0Mbg`c)yD{r_nEotNcrO>Q z(of=60N*;~mw1~g;~JRENDF896meZSErI=0$QuR*zGE~f1V3k2yPP@)Y02~pQTNem;P zCr`l|OXUA+1nppT6JYEckTDaC5Cx6Yb}mcjmEn#Tdbu`&!^_p&E<%V5JbdfVt%)NL7jNB%t)VuSCYKImwi}6jiMZf&2K7 zpxr{uD$pP`X(-xg9dE*M#l@xqlAHWJvmeF|?ubd;vZvFK^gG?4Y z?6-nl1~sK>5uaPiZJA5W>6$Okp!n}n@8IT?1B27}rE8c!AA=l1MdRQpf)!w$#8$&P zTODaw5?jSb2X*x|S>V+hisL*ru&|?7$m&xOuuB}VEgRJ4{EPFHl?s?Hzm2ZyE|ySO zUPz}xIYIu|vJHX^l?mlJ(h=1kh!rMwavwO3OUU0HC{fo7doR#8D#xgltS41GmyYk4nbZW}W0>36VZL0Dw!gql+)NIk)l3yM( zSIfEY^o`AH(frk2(=_?+th=H+*>;=vfO1i^b*k{GkYu!0x6mgrJB2Z+?eqFnN;ctiId1mHB(uybqdn zIepff;6ICLPmY8R?Pn1$QISExO0HC-wgYer;V zv)uEX6OlCE2^JK1?9N&`WS`D4OId}|-wi2t3PMWp=~2=&RB-02=c+%1)@vr0k2C(( zLk;pb?Jvual+v6ZTrmM{>p;(DzwmQ2zo$j}(~s*i|GX9apm~{ZN@|$$a)|6f{!Z61bsvFP$wyCT(g3uGQI?w9XcI%X;@#Clpe{YV;b3T(;I zO3Y!*C}%h&BN6-eMA{E*{XRk|D?xzDgBiYhip}{nA_bGRfV?SL?WnUu{;Xjqt1?*F z@gA&`YxY(@^{~-^ek_pq?Y%{1?ZL1{tGnmx?3lZh9bY+h*xsIu&X`8P@n+(uAw&hO z)Awl4%a0yq5~=l*@W6-Rd3rlB6!$e~kuTZv{UBTDPS{Edwn%7%q{|lj++k_9?~m#V zGO1^aZ{?D!<4M{(NiiNRTIQTxE3d1E*VNweQ4%#}c(f}Umn;U3dkz}A1~HN011WcF z#eD6)t(n=?$d*Kfyz&uK-f5@Tmsqx>=5?E+sUE3LoO;WaCR}=_eFy9!?<>s&)uOuZ zU3J|V|F4_4>((JtYtOr?(00gr9pgEwe%(cZM+AX@{5du#frFo8|izMe?4`d?AVJyK)Ncl0x@E>fz@g1}BEEd%Snv zFR)4ZT=-m|D``VAoU*4DW>GhCL}qa&j79rAEBRduu#bsa6+Y%k=aARs+8St1Zf~bY z&@Zjd5g0uJybu2$`0&stpu1mRhll6KAarY^$zx*rIB7hWLkVm5+L#gc!p zRZ(_Y5N}OxVq>;F8)h0Qk0mX}9ov|H->{&xq?0hVOT3%}CBTv9L@;|PVS&JF2YxcO z_{`;!b9*YTeV^-B?qn=`Wq)kqE8=Z?9hh5om{5$G+LXF4Ng!0ORnVK$NX9T?rnam4 zo;YgVk|Wm=26FnSmWkcLkCMu`%#a3se@HY~&#gZvjD1Q~6VN+)1Ra1_)qxzHC`M|QSXZ~0oTq$Uq+|8H25{Ns)PWI~qLKBa7 zs{@%H`UVp846H+6w?uG24h6JAAWDaZ@bMG{7mJ>zW-Y%0j(Lv*6Th%%g?gFV zzr*7?N-G=la?E1WpI_tjP%`uGbcN2QCtnuV;j84WSlv#4Hebwy8)bBo(!46&oS=ML zr~-Rv_ve$m&`o)^*2kkQ;PrO`e^IUIW@+pFKCkPa;!0g)Osg29OE}XY^#ad?m%Y7T z$5Ru3dj8owqfu9ock?julzt33U{MgbF=qUYoa~-uhm|`>m9MIzUZQ7H@|5u#ry`M} zYTWk1T^w~;nhlC(t=Gi&)bvMC!n+yC@)aq{XkYEQ8+Im>1FDrvL5d>jsaI*oMt@fj_AFDpw84H^UBdk`4geM zd)L&7eLqO=a+AufrFu#!_{BAlH6$zltphB4daTfev{`JYH#@g)bg%o-wEX2*iW6 zeOV~|SOZJ~7GUqYG%#-PT|8Snx&=k337H38FWl9&Ymti7-{5opfQa9oXuGL?{EN#A z%NtKWnRLCAuc`}aVyl!Kk@}2nP?Y&-#pxYyf8dps_y(!Jj}ddj>gt@ps`1jtx*&c! z>-Sd|un5tXj5e<}vU9XaDa&lF*eh`qQ+I>eBdj#FYTUKM`dw5{BwW#1H=fsPtA%m; z$JrF7nP>f)(GxaZy`O@Q_Bfic;nKXG8^Hv2o3AM8fB9!CF)Kel2=jc6hu!TIJH4G!IQp_0oGBAmUqB#(r z=Rnx--~t5J%$hy``$NnKeB_{B{U~G3EV^BiYY4BqRY5gNT~J!C3_{9uxlF3qwz|Ju z`DZPw6TYLBjL*iwOv!#h3>AUVujFZ$%WX?)HdlBk&B`aFMf!`&=gv>OmictR-A)Es zF$xuN+!rTT_h8S}df3mumzUTp-k zc!6ib(QP>bjnIdPq&2jc24UpR^9BbQbb(g6mOHmJwQ9l8C=svk@|aF4fEoWx>7dUMa@>L zCPk5uYa7!Ha1mXy5r76Z8Sps@kd)5uRxph%czW{$g%(xI84gfb5Z~Kt)&LLaD<$aZ zrGa%KDkMsrEd5N^tEQ{#)`pj4y|zY!w78HnC)U_|PaVm6b1j2|NUiasWy}j7&h^5) zYX9CVV}*?V2lTHayw4X|ydOLOKmf)+9^n;z{v-8({Kq3aFu-5J zzcN0ce+&BlXZi>HpM>!LD6}zfHL?AtF|alMr&{|D!H2Mjyoju@h^&x=h|*MipXI=zgT500;^z>k*6IurI9|^qQ}(>OH*zbg#+Qmx@zTT`jdP(~>nXG!DO5o`ICmyywm& zQD3qpA@2B1BE%H&dX#9+JBSGrTOQdWdB2oE3{%BiC=0{bMa?l7cE$U?Zy zmMkHZw3eU+tag-+ApGlzC#B23(Ae!=Ri>Cl;&QFwra|rsbtw)4@A`9uprHr#g zm;2$6FVSV)dH>?=Lhw_@wV)CF?&)G>hr7+34dyFDt>*dTOM9Nx$*83UYkR!Q(;xb# z=Eg@eV(475vr9vV_9lg`qRHLOiJZ5x2HL^xx0jpK(;lsl&a9H!#M9T-eJK1ZEaa;t zUA1x*7muH#?RUQw(*t=rWJ`h!u_h@OL$3t5i59uBTG+fZY7J6TouP9%g6h+?tscF>yJIFvjj+W8*AA^T zl2Ir)$yHJ;sRX2^ALgzG{cta^u0R!z9V{4br3DmW6%;;}VtenxZi|sxU;TVz)mbOW zPwwi4!HA>#K%2W6&DmT`GaCZKSoz#upBNLpiKv*vzM$T{VuM;Uz>CH$5>+18w;LfC zu_UDI$Vn&4cNmSn2sKxm$|?8eO4QLbwd$WQ*}1#EkH#93wKkJytOCeFZS&cn%>&nV zF^Q(?zVxno((vxr=cgNb3ohVeg8^`XgI%y%&Ub)K<0NP#fkROj{UF6aYVvs|o`eZs z)&ptnUd_*xES+QB(4Sc{_;xIXWK<7tcffPR#w9pZz%H#T98m5dOme`sBa+4CS}jOQ zh_fX;F-<#_s>!2@O=7%8q)>F_hS#e5OEi=L!e6+XAMi+L2qw zMnd)?po`E=VgX$kKKY=}nkU!BN3PZ)1x)Wv>u=CJ`||cnxKG?xBWnsiODoOSy3H}T zMmR_yg*gsy#Sp)IJFK5NG{ssEacu^k+!P0AY4HqQBSB2XX?!Q>H~=%h8glUIIcVAU z-qS02xD(q7D%v6Y(+4A8=H~@SipxEz`BZxdV%j3V>wX49pxU#~I!z70SZ?vHQ5ocW zzJVg3ITYa9MBYT>!C*gIeK2?Serzo$yk%om4j6TK#1E@9;_Y)>PLxz z;)EActCP%$D7DgdF5n%R@1H0!z_LpTkMx6eZ=FxS(z|ZNJ8Q61n-oVrmAe{ID9zD1 zMJp;T;bOtJ$Qd`m@<9&@6RO%_WLE%$o0CaV!-y*4=1R#W^IQ_k5>|GI5;89rbAcyRa(!^tpS6LgOe0(L}h@`>+6?S@;W&CW|Yyr5q-9fem z=0wsHJb6*giTm%6H4!_dkFM7&P1f-aei!tnvZU@?+~1ol=}zvand+jgd_YXp zinGM}EdyYnp9V)+Q$#7yNrs*TB;m-H1G6Ml5;uPLI`QSShMOK}1c zor9sX@ly(Ds@4;sI)by6E*~MlvA8L#3rd#9eM7qPsq&gq$5|bT(lcVD2@9CoG-BIt zIyx&KS&Ss#&M%C{1i69X&P^UxvyH zVw2(n%da`vuMw}X-)Ome_16e{8Xa01IbSm+lWM?8LRFTC z!xSe7@|zyq|AP*|5u^k(aIplC47mqByQ1tBrCq?ZC3W8!h# zp#_@5jL-qDL=>B&?6mW<7m~LTfEsJWGkga*Ps)BX0$T}6EtvZp6~rI2<2S8siTnE? zOk}&|h?&ojA?_#vcP7!PAI)_Zp9y?R5@hQK_(r>zaS1+Ef7TJkpU6KT8arQV?m>g; z22cmwfa@*y&k<;FX$-4N6gPahpCy3Ln=hb3BN`jwJUoq5#(4EwnIbUrHibl%$kN4f6nY21+JxD_AB}|(z^Zv zr5+;Sw_lvFs@z00dJwQf=tTu>=?sZodFGB`Of^K4EnPCpJKwV)Tr89kC#T7*ma2d) z>9;h&3HD2baxGCy^Ke^=@h@&s3(-TlPfTul5#kryMPR%p1uZ%t{kmN5eGswISDao2 z8U3LwyQYLpMvfDNXw=U+BqdM=>7n8{3<@Q4(ci^EqQpn3^d9~y4lcbYA~(V6adZgH zP~9+n=vC13wQ`jhP~B+}TuE{v$Bi!3@Q`cK3J~`4;I>SC1!n4aZ6FrJ)~rCI3OIxX z{y%JWL~d!Us3W>NE%z(A0bt?c(`~TZrUK>n!bmOE-OlL{(5OOr zD8LZ-#8F>8LhwZ#5$UN9Ihn^^rlI3)X}_ABcp1{LGmV|KBZEg zE^0IoQAlJym>P1IA~N0zoI(txtg;|GZTFj9N%ny;Vv*uDE4?Ld(@U&D4MMQ~MqW%5 z7%{#u&XZ7XGjn<61%Hr;J@Vke<%iTE&+LnDlR>>z?yeguOETH@TuYpI0W+&q#y5dN zgg6CQ(;%2trN4^}JQ&U&=G-&gC>?f%bXA;?l!a5o7uRC715*r8dz57Y>@N>RI|HLy*VurvVL z^-lm-3_}zQP6&p6NW+-$0@Z0+D){Oh`}gWExh<_s7KL-FKh5*C*^s?+yVOpgPAB=9ZM#U2v=2^-}S zsL_?!W@Exd4AEppX{%*8EB==hwa`O%rhcm@TEMjuj04bbuI zJM1_Oqt>ZOQFYlkI;6!hTzK$~spmHLXAiy$=^p7KmZpFMM5*nnd(UwzHIsf3`&Y0_ zDN%dHFbl3ySGdWDic?YYaGB*OdLRZ^#&mbs3>~|GlG-2On5q&g46z#Zmz^ZAus5b(t7+{e1&;;20qD%QJx4Y8O3>B9ip| zI>o1?(gRS(40vC36rX@xhVuFxBGUPy4kp5!=pCv-WBIfyha7p3`Dm6WTz*}T9DT*G z;a?Ap3~g;0y7YAFMr3q+YJMMQsaN`-`RY|y`(2xN1Nsb}l}eG%1yQT8lF48SJ1Jt? zp99FyrG;Y=c|q0bPktPIeOv%lc6+vIcXeo0e|vr{Je_wV@agPyeIYh#f7W{UbpE6X zo^IXyxO5$_fTQUSU+u%;Jnwz&%WnF#c64dzIW-+^^n=-<@&DklE;(QDvoUR=$_{{X zo5|R*%sX+~!_EcA$bXU!P8}C<+c?9qBjU03Hn|2}PA76UkHMUOXD1ccy@^~{Fiq6G zq;Pn{kqwF5G;N-S(9NW%dw|a7*b?xlgLyB_)*?Fx$#P*g1!d4c+2P@ z)9l;0F<>4IkIk4Jd{% zPekD8`xCyR$X0MYEi+FV;BEujLz%3L^Q zmYCIoOtk zILt(F%M?8`#8-31y^1YD5ahh_oA-KIPdd74>6g&gZvNx~ zf!7;4P?w9vV$NK21W5k70=#an(Q#K-wurWUIKPvaJEFvNPgMYWAT(Au`qEbmK5|$J z*%`-8{zO8fqvi;6Q~Yp;R?V%_$8o5wSTGqJx=epZ=2lUdY=f%QZSVIKJ$V@jZrq!L zuA}}gE1a1^!qCPeIJ-&?jCqrNdhpi|l}|y>z-+OW%AWZHUh(ouv-A+TxE=&5x?BI( z>7%0;3Zh412oUY=&sXkQ_pLY9#9RhZ(@JOF?$&3|`P{|~W`temohkVsN&7gYZ* zl;9WcAzW0Fs4Zve038tU-~O@M8_*1FBxOr2Y_EZ*1e`fHpZ4jwGBLHHVrJ%TM(THW zrCq4ha4F(CIeDrN9GB2bn|rwU_oYS-ce_>7gSvly%RvenbEU!@Mq$&CiOVhSL*jxr z<~F=$gC_8v)K-=|;1qQm#2cx}6s^VztU26smbIfn=YVnms zXJ6Hbk(s`ql_e=QI3{U+c0WM<6;3!LfZ#U)008_3{O?9A{g2_qzXo0ZIY|01U;M{p z$==R}&fd|^-o(+_!oZ&`-fuUH5us4dF;3tjzm=>>jHWJ4lPFP(^6 zH~4RRlRe2O;7Pk#tT?E*B!|6BU3NFfG^uNbFwEknfiCD)V0O24U#|Wm?!IYGvutgA z36sWC8cpPiXGiFRn|XN~3&Ndm4AH1?-IgelRQSIJ?EVf7?FIkV(SUhpzZj40Jv_=6utrr$~0T_FPWGXxws;NQ0f4`Scndwz6uWaeLd2#zj*o7 z95B;2hhH_ZWkmJ7^_^!ud6x9uxV12Grz*xc6j8zh%2B%JRo@c35bPwyTE#eTbGhw>dJ2lt{13q$9t@rOJUq=gl2%R?TD9&*>?jBe~*9^>|r ziW9LT5K2&yU?j@h)24$uoziGRPqJvqLG02@6V_47x|UHe?y!Ng~(n5iEt9bo-}a%bBu~q955; zsXRU?90p^f#zgQtkJ;w97}mdkmMiQfMdfc-4k=1!a)}1p|D~f%_fpRl;;a_8UVlfGp<0 zLnVc`k2&t!*>kKhIz(DuJedJi!#?`ygZvXXQVm4zGU}7;JAdCFq1yWFW)^TEB{ybL!-86|Ui7B{9UE2>od}jGA zQ}+zw#ddl46(Z%T$&Bk%^*Y=N-e$4oY{%Qq>tA`STPdICzi%*AImc+y9Tn50G0Gs*oov#7T+MAOHXW zc8+HMlm`0WH2+F?{wXQqf0X|7a0u;xRp2l9^G|gH?|)n6{|!R=3%va`hjvb87XMT? z{(Uz7Md)PW=xSl~&rRoF@aliG{O<*K+ zQ@pv>nKaHsdV6@L`*0mg+$L)bLK~7oAPBHu{^QFQsJIRd*OQPQiEb@=rqi=uU=Htg zjOh-U(WrK{{{8L9)f7a&EzZvp{?OgXn%L{d!{r2R=CiE+d8D*9PRva{o5Hd78)5WC z`}NXSHtU4@GmEZMvJ#vXUB~jsqhgBa02{ELaaKjYE^or_<;_K`Ntbp&((oe_=I#q#X=41utYC;#lX*dxynoRQfxh)U%D1_S8qm^rdH zZLvG^wU3I&fF47%R&CSfp}>I+H+T2>$uP=2swlLfP>sskD;5%TXD2IC;0zfq6|$^s zax10FXxF=3IT19oFuItD;MwG*@`=P0E(2Gr)ZsF>X}CShr1NIsV8|%cFvP9qWJ!5k zErFp69^~)DLFL}^E}asG;u(wM$t`s~=-7lKOE4u3Hj9VeFKm-oVb8PsmFdvT*-si8 zj|~TQQ%GIVgYQ%I8 zbzDLu(V?5za)Q^L zxevLGSQnBc1uVWsm2t!NtLjKTBeK3J$d7y_07)Kz3wK~^B;(}gt&|;Ma~>5J=pHhO zAaC44-CKM*hc?cGD}n{Y9YkJ}3{*WV5?t2lR-Jr+vhM7jFrbi`xt&8z1w*v1tC`JicR`OE_q#LHGx&Gtl^scmI+T%_`n z54$Zm%v9+xZ*~~id?c^FCoO%5Z9f1LdzbXh36VEXZ6DdjO#7{XG?Mp4PYNCm>f2A@ zzD;5F!iG+ygjpvaC%O12jza&T_H8tbvmEHeBx7BHd*iD@GLxVRIksSnwocT-ty8#X zP8LhRLpP*LQ!h2!|`F9&RJOSa)Fl^Y#(hP5h;{eXIA^;pSI; zWS<`h06-Vif4A)+|GRJf+qU;NZT_1!|4p0!rptT`m|>tT}m699D)UEHCo$&B!pV4 z!GaK*6xv?<*}C=v(3BEv{kfLXh2^mPl%wggyiPsls?fEq<>s^VMB;2uC>_eOH~B|y z=EmH~&(k-UV%e0CO5#{8i z*r+-cqZmmJDx#XAqQfkhH{+>^45SA($nfq~rL{MsY=uv%EGiqWKR?~y1SsJ6Aw^n&456qH5g%PJ z6I~SM6DFK5`xU=4YXO8?2sn2Lms$9TeW`c4=x+v8KyHs_kU8KqZwQF2;Vml%37?A; z*eT9OB7lK9=t7dH`#LYV?joz>=Df;4tS9G*EbA<3L# zCaFfZgRfvnVp5bsq9hxCsVJKeNmUbfsj5qksWcsbsjyA3R*^XY`BMf+V|Oq{m;6wM zyC~{AqGnKe3BQ9We1kCekhC zid3CSQK*G7PYlID3dOPD+;iTcH&qgpf@{qzc-iEN!$Sft@B?lipn3@@dJ7!?3Q74H zS+P3A(%I|s8HN1{&Q|nmxafho@}xUy=X@9=$Q4j;mOa%Otl>41$Xm)jH8$pqX$1$# zc0mFr5wyq#59bl(wE2-Z6V81Kq7W4E`QvN4<@=rQuk>+v?4j5N1OQsG#2Sr4B%H`ze!{#LTB%^rbCE<#mL}mc%QI3Qa|=Ua1o#!BQOP z)1h}@k58UNNf4Ke95XN4u4Rv6i+ptGU5Ap1q9MO`#*LeXR}IxYcQ$t8?blk!jxdh# zQ)DeC)II9crR6Lzi7E)MIj8^GnMEG9A`6i;iAUkBT3AV@{G&fJ2#x+^<}}7{&6}LM z1kB{g*@Pi(ny|)C#Knd_Aq^f9jxxz0&N_3dO2#JBmoz_IiR2v`_nQ<5zAZrtvmMwA zx71lb2UrE}(k*x09}CPRnxJ6=(CuLnKk$) z6yRKWXPKhhje2b$@BK;^;CYS5!&amefEz4dit+R!G?8MeSZIGTK1}6 zf*g7Y#TrE;J{YV$Br6rq#!iW$N|?X7`OsIKKDX!F%Y|7HrR0VjDVw%`wUafxX$#`Z z6uIE+BTjv~nXlH+jip67>Be_Xs`wQYSHf1Lx=%_#R>!(7s7F;4cI^5YK{(QgaI~Kl z>DnHYK1*V&+V|aqq~LWS=2lbiQ%2_p9-!b;)512&k44G%FcEW&K(D8=c89#5QVAMa z-HN2Gtlbq4pOdF*3lXfh20T=>uG-jqV#3i)vp865_N7r>6xeQ{64$D5|Xa=ZyI{%s=;66Pp>NwpQ>4%l?5NI@7^>&KAKx7I86J_!S1JLpK0p7%Nb5)QA>R%b!IU8xbWVx%SN zaVIhKFlE~=X5q(x<_;7f!D<*(uQSXGHL-odHH zB)&(M9NRZCx{o}0z_i&9OtUM6@d_64nq0y?) zj?O+=Z+R?@S`~!(H8?4$K?U#AD==L-d5zhbxz_5_|4d=>oe}KJcswjt_}fwv;;K9I zeH*iBRG-8$DNlTnY#^>wM^-E!4l_(RixDdS%Pi%~Rr zjJ-{XY}B!hKU|x{ZH%;DkZf!ViST9AWS&^*P`6D`bzH?UM|g~?P4qeiLxa|#UKGi= zN;&~tm>hl_<}8WCL)>(1Af6~@6!yM!lO#qWy)uF0zSUSf$9T*$jU4+B?JNanjRM*@ z0mHcAaLjL$OzRNNLlA9DwN0vdjHg~la}w!i=PxE;KxX=of(H1B|wx{#m&TwH9(YSW=cqdG>vB%tEISZT`pSE z9MVL$6w#_OchQ$DDy400UZ!haF0Zn(va+huv`YHP{Lt0aHD%=gc=_;6e7Np-+4P#> z`Dml(ei(Xr0qj-w2!xYzQ7;}S;trXG@rNf<`+Y`j4Ti!#$rCTz{fJ-^EZ-Y}iwJ(BGJF{ycm1ZtaNriiaS1eKo^j-PTHnM0xpZh8)}oYfQw(?1{L}UyI`U#lWCk z7k7UKA;Q3Q1)ZaafCJmKIs~vpZk71vQ{yN>pBiEg`unCGY)NqxokQ|Om1$y{6k-oz z4Kb@#8g#R8_lItyTaK5AP!7yl@Dc|ELyeXfn0YewjpL(IG0d`*vzFVujo#-=OB%~E zXkE2*rOYbW612gIO~w%uEmib%^{S4MLDiUg(RD&B^IGh^zdATFVsaLV~u7ulJNR%bp0H=DX9Hnb^tIW|hJ|E*0$SuiL8L45GL}_jn;o1&oq{|vdU7RNsRVDv zbp(`Ziw-l?mh%zj+5$`;T{17dhq%)nIMNLYkcGeWv6|4;NKL9=5o4%t!KY zK7IA*GS!AOL8nl$e2=9Ee-8^`Y-qO0bn-sZb|t;!ATWmb@87kYLaCIsO!kZ%DOzHT z*>OTbfVK}s?mit7AEGe~J_c~sv8yx~FK54NVk~k>jxA$#11}au?zF+IQHmR@G0~b{ z%oUj#U5+X!%$YpqCU~eSku>D5%9$9>m9o2M&9&!J0$!nanxsnPnnW6Lwew<>*OVsI z1x~GwnqvJJdnI%cyR~Yu5!FK#54tT?VS;GM(a_yqXQFA)3c+p(D$+d4I8^)~fbqUg z!TVrH*`cVKZc%s5luHUQH+l3VW2iNk97Q)F#fw*(OOG9=gLgvY8S|-{6H`!~J)A`; zlDdI38<#9Zg*>12L-hSwV&XX>C-tuRAX46G2pp3Dn{b*J*Y2t3DAq03luwG${kH2J z$#pnOi+jglQOtE@GoGqfM>AilZMEU1#_9m9i6)W1d$hq1|2ZUE)l{N-u+hHAAWT;} zQ4i{5WZd613U^;k|H6Dhi1if{#cjS)=S?6pXW*vt-7-pF#|5F6!=t^m@v4LMl6sc* zFVA)&MW{R!4jtaX39lt;r4>ud-sFvwJXyvy*KedmSIMGO$}{uu z`Izz{WlHqOzABBl6jNAR67587{27hBBF$EWs~vp1eR2|@YygDwAKngyK3EXx5(bzo@}F-eP~L;ObC}_CG>sx6JzLqs{Y!SOp?w^k)u4N zA)|1!AlUz_q^!%n!yB*PSLp?vQ$`^F)4J^ux`e;2yvr>%*})R$i3E8mj>jbCmW`D!&BY#79y;Db^24N z0XFU+(B36BdwUT!QA+d7(!_0;`b?{+77uAOxOY_qH|57LKYsOyX-FnqPsW)| z6zj}1?qXL|M-{UE7McP_f;D55_%w69vZ9HpOO`I)GaoM*%VbT$L(M4^OpangUDLhw zd1p)P-!*5o@XR&kXr%C!EYmg>yeYfbhG6m)+s4gomQZr+5u@hR%=uhEG^NlitxcTS zlA2iNy`{Bri$In}t$41!4D)=7jBd&gsQ5EwbO85E8<*DGon=v(eWg4~&5qeHQxar& zN?&hvat}Ooid;Q#OYUXQHc;!gKj&7FceGt^m^jqOBRl0n^m^HcandmhBXKRfz zoQ`-wQ0T7JK^o5$>21-u-w%;e{KK#;0g25~% z7bnS@hj;F9xKhi`D&FXN_V!pj^EYupx}}9jdF^*&P*3%u8cpruWEAxkOJt^uJV!DH zGP#F=Bx7A?cP_liv%}*Wro!l1A;|w0v1nr4J@?aygg{eabqHmVV9{vs$C15)Gh5xb z$rewB)gSX6gP%((5WJ!AA8B#pDQ!~pU0zX{X}Tl69{i~j59L~`gej9Q$JTN+${02q z7*~<_E*dkRj*0x3mq}n13^I$7yJBY0}M5G_hH-ioy_MYcRCA76NACmhLnH;!c z{XoADrqn6gSyQ6?J&%_aGGsUKn+MdN7(%c8$E(EDbPP=9j6QjABx^d#_$~qp#oDC3 zIMyk5S6xFV(o%S<(8p`D%(ISEn3kxOptI51TIw9L#AdYf-HMk|W7gp5EpQ~QSUsQE zDNH=&i^Iq5d3>Ou%@s=36l474a26@LIF;p;&D1EL8kUDCd`!lZRGDq{NREC=M~Stt z3l~Kbnl-LZdXLD(PuM<#Ee%O3Nbq71kXyG^tzp$!n_1vw3TWiT`ktkd;asuf)l`>* zD&$(*SWI91Z6~{K#%Z1=sLU9X?uoJ-vf~^N6JwHk12b~YhuWrZ_uKGtCN-5}3U+u+ zH2whx)$ED&GtQ-Ko3L&KpYkSq0k}oH-5*%*Tk;&xXBh3QG+bvC{dMg1=7g1}ojLMl zucL`WDXO`DQbxID%dqG4bVj)mH^WtW3Sv$39O!ct5`+;CBzM$%+R)nIbk}l3?3URF zX+zf?`O?p<+yK}PB-rCi61*8>J>YDM=Q$tDRaGuK!OD}Xi-ZD6GzNb&R8xGyB^0RJ z$+cF{;~svg^zfA}(GO;N@tBcO<~b2rqNZw3S2g*Whuhfzg4eBR)>+E zEVC4gN-=2p4ltpO0+8a2q+{c>LiJEu1bc%zu zuIMrJGJSePR3}*JiLxH-w=yf1RNbjEZG+hTTs@*>DFE_Gzb019wwYxbSBc zR29_&o#KQcvw4dc4qf%KS)azS-`PqFjb&_XZV!`1b8`#TWP&BxJ=KN^!b~^_%lAEz zs4TgahotcpRvP4noRR3*YnB>hhOCjwEwzgGS@0E>9kap~Irue(@bFixxmAY_*mFva zrG^eTa%%Spkxr~VgTkn+xfO>vkv2Il|1wwY!ywJAFqavZhdCi(;JaIS#)U~Esp2OQ zy&218gz3GhG>bMw(qtWTBp&gZY0?(!CcsQ%U0V(%JwPVq# z{BNC09uWvR&*Xc9<`ZE;)qpjJ8M^MK;Yqus=@?=ei8lUT z87cX8vhn;kLJe#NPG*9Vw?bMk)YM-n?0Z5*k3xDcmw!?}0(x+6IBRNr10Rh%wRgb3 ze}&kM=aJq1-MdVvZEu;b^CL38M^{5`ihHPMg0Zk1pMVQXNj;Ipg7`{|MIk3_nLNJ? zK1RB}>ls4-09zdzMSc_M>#qr5O3sY7{{{K2$7}cH=5-f24i5Y7!S>?(WAbqo>2JSj z;P;;u?J1C6x4Ydp4S&jsD?fXSZQ!2;J@Xgo1c_slXs>>|)v+xEyEwPulVa&?iKEG4 zLwn2y$$02Ut?AbiYv&^0f*z^_wv_BQ#2LKiBf(&2m>4yx_IjEmPD?@CFqW)2SI>}N zk~;g$Lp5^Yl0O^ccnE{uflJ&fXsYR!I>&)#rf7#
acR7Ib{?UVhZ2e6 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 O2!R0rSQdW%^#1^FtQHdh literal 0 HcmV?d00001 diff --git a/server/-dependencies/libs/org.eclipse.equinox.launcher_1.3.200.v20160318-1642.jar b/server/-dependencies/libs/org.eclipse.equinox.launcher_1.3.200.v20160318-1642.jar new file mode 100644 index 0000000000000000000000000000000000000000..2aabbf4bc5eba76bed36ddc8c5b0057943be6f18 GIT binary patch literal 51690 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 - libs/humble-video-arch-x86_64-pc-linux-gnu6-0.2.1.jar - humble.video - linux - 0.2.1 + libs/sqljdbc42.jar + com.microsoft.sqlserver + sqljdbc + 6.0.8112-100 jar @@ -73,10 +73,10 @@ install-file - libs/humble-video-arch-x86_64-w64-mingw32-0.2.1.jar - humble.video - windows - 0.2.1 + libs/org.apache.felix.gogo.command_0.10.0.v201209301215.jar + org.apache.felix + org.apache.felix.gogo.command + 0.10.0.v201209301215 jar @@ -87,24 +87,10 @@ install-file - libs/humble-video-noarch-0.2.1.jar - humble.video - noarch - 0.2.1 - jar - - - - install5 - package - - install-file - - - libs/sqljdbc42.jar - com.microsoft.sqlserver - sqljdbc - 6.0.8112-100 + libs/org.eclipse.equinox.launcher_1.3.200.v20160318-1642.jar + org.eclipse.platform + org.eclipse.equinox.launcher + 1.3.200.v20160318-1642 jar @@ -119,6 +105,25 @@ default-cli + + org.eclipse.platform:org.eclipse.equinox.launcher:1.3.200.v20160318-1642 + + + org.apache.felix:org.apache.felix.fileinstall:3.6.8 + true + + + net.java.dev.jna:jna:4.2.0 + + + io.humble:humble-video-all:0.2.1 + + + io.humble:humble-video-arch-x86_64-pc-linux-gnu6:0.2.1 + + + io.humble:humble-video-arch-x86_64-w64-mingw32:0.2.1 + org.ow2.asm:asm:5.0.1 @@ -159,11 +164,12 @@ org.apache.felix:org.apache.felix.gogo.shell:0.10.0 - org.apache.felix:org.apache.felix.gogo.command:0.10.0 + org.apache.felix:org.apache.felix.gogo.runtime:0.10.0 - org.apache.felix:org.apache.felix.gogo.runtime:0.10.0 + org.apache.felix:org.apache.felix.gogo.command:0.10.0.v201209301215 + commons-io:commons-io:2.2 @@ -203,15 +209,6 @@ org.mybatis:mybatis:3.5.2 - - humble.video:linux:0.2.1 - - - humble.video:windows:0.2.1 - - - humble.video:noarch:0.2.1 - com.fasterxml.jackson.core:jackson-core:${fasterxml-jackson-core} diff --git a/server/-modules/plugins/pom.xml b/server/-modules/plugins/pom.xml new file mode 100644 index 00000000..30cf7f03 --- /dev/null +++ b/server/-modules/plugins/pom.xml @@ -0,0 +1,130 @@ + + + 4.0.0 + user.jobengine + MediaCube-plugins + 1.0.0 + pom + + ../../user.mediacube.gui + ../../user.mediacube.metadata + ../../user.jobengine.osgi.commons + ../../user.jobengine.osgi.db + ../../user.jobengine.osgi.server + ../../user.jobengine.osgi.services + ../../user.commons.log4j2 + ../../user.commons.zk + ../../user.tsm.client + + + + 1.0.0 + 1.0.0 + 1.8 + 1.8 + UTF-8 + UTF-8 + + + + + dependencies + p2 + file:${project.basedir}/../-dependencies/target/repository/ + + + + + + + org.eclipse.tycho + tycho-packaging-plugin + ${tycho.version} + + ${project.artifactId}_${project.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + true + + + + org.eclipse.tycho + tycho-maven-plugin + ${tycho.version} + true + + + + + + org.eclipse.tycho + target-platform-configuration + ${tycho.version} + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + + org.eclipse.tycho + + + tycho-packaging-plugin + + + [1.0.0,) + + + validate-id + + validate-version + + build-qualifier + + build-qualifier-aggregator + + + + + + + + + + + org.apache.maven.plugins + + + maven-clean-plugin + + + [2.5,) + + + clean + + + + + + + + + + + + + + diff --git a/server/-modules/pom.xml b/server/-modules/pom.xml index 668b8789..6fe687f7 100644 --- a/server/-modules/pom.xml +++ b/server/-modules/pom.xml @@ -35,7 +35,6 @@ eclipse-neon http://download.eclipse.org/releases/neon - p2 diff --git a/server/-product/mediacube.product b/server/-product/mediacube.product index 3f116596..314bd6eb 100644 --- a/server/-product/mediacube.product +++ b/server/-product/mediacube.product @@ -107,10 +107,11 @@ + - - - + + + @@ -126,9 +127,11 @@ - - - + + + + + @@ -138,6 +141,8 @@ + + @@ -157,7 +162,7 @@ - + diff --git a/server/hu.user.mediacube.executors.tests/src/hu/user/mediacube/executors/tests/SmallTests.java b/server/hu.user.mediacube.executors.tests/src/hu/user/mediacube/executors/tests/SmallTests.java index f6f0feb4..cccf6a20 100644 --- a/server/hu.user.mediacube.executors.tests/src/hu/user/mediacube/executors/tests/SmallTests.java +++ b/server/hu.user.mediacube.executors.tests/src/hu/user/mediacube/executors/tests/SmallTests.java @@ -6,6 +6,9 @@ import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; import java.lang.reflect.Method; import java.nio.file.DirectoryStream; import java.nio.file.FileStore; @@ -14,6 +17,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Timestamp; import java.text.DecimalFormat; +import java.text.Normalizer; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.time.Duration; @@ -36,6 +40,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; @@ -49,6 +54,7 @@ import com.ibm.nosql.json.api.BasicDBObject; import groovy.lang.GroovyClassLoader; import user.commons.RemoteFile; import user.commons.StoreUri; +import user.commons.mediaarea.StreamKind; import user.commons.mediatool.Timecode; import user.commons.mediatool.Timecode.Type; import user.commons.remotestore.RemoteStoreProtocol; @@ -70,6 +76,8 @@ public class SmallTests { class PojoRoot { } + private static final String DOT = "."; + public static String readableFileSize(long size) { if (size <= 0) return "0"; @@ -268,7 +276,7 @@ public class SmallTests { System.out.println(value); } String name = "valammi.mxf"; - System.out.println(name.substring(0, name.lastIndexOf("."))); + System.out.println(name.substring(0, name.lastIndexOf(DOT))); boolean create = false; create |= false; @@ -568,7 +576,7 @@ public class SmallTests { System.out.println("Start"); for (String fileName : missing) { String onlyId = fileName.trim(); - onlyId = onlyId.substring(0, onlyId.lastIndexOf(".")); + onlyId = onlyId.substring(0, onlyId.lastIndexOf(DOT)); boolean tsmContains = tsm.contains(fileName); @@ -642,7 +650,7 @@ public class SmallTests { omCount++; String fileName = omFilePath.trim().substring(omFilePath.lastIndexOf("/") + 1); - String fileId = fileName.substring(0, fileName.lastIndexOf(".")); + String fileId = fileName.substring(0, fileName.lastIndexOf(DOT)); boolean excludeContains = exclude.contains(fileId); if (excludeContains) { @@ -903,8 +911,8 @@ public class SmallTests { String[] tokens = t.split("\t"); String id = tokens[0].replace(".part", ""); - if (id.contains(".")) - id = id.substring(0, id.lastIndexOf(".")); + if (id.contains(DOT)) + id = id.substring(0, id.lastIndexOf(DOT)); if (tsmmap.containsKey(id)) { String tsmtime = tsmmap.get(id); if (tsmtime.compareTo(tokens[1]) > 0) { @@ -1138,4 +1146,72 @@ public class SmallTests { System.out.println(FileUtils.byteCountToDisplaySize(size / diff) + "/sec"); } -} \ No newline at end of file + + @Test + public void test99995() throws Exception { + Pattern DIACRITICS_AND_FRIENDS = Pattern.compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+"); + String name = "kedves_kiskacsa_furdik_a_toban_anyjahoz_keszul_lengyelorszagba.mxf"; + String type = "Muszter DEMO"; + int limit = 20; + String extension = ""; + if (name.contains(DOT)) { + extension = DOT + name.substring(name.lastIndexOf(DOT) + 1); + name = name.substring(0, name.lastIndexOf(DOT)); + } + String typeName = Normalizer.normalize(type, Normalizer.Form.NFD); + typeName = DIACRITICS_AND_FRIENDS.matcher(typeName).replaceAll(""); + typeName = typeName.replace(" ", "_"); + int allowedSize = limit - typeName.length() - 1 - extension.length(); + if (name.length() > allowedSize) + name = name.substring(0, allowedSize); + String formatatted = String.format("%s_%s%s", name, typeName, extension); + System.out.println(formatatted + " " + formatatted.length()); + } + + @Test + public void test99996() throws Exception { + System.out.println(System.getProperty("user.home")); + System.setProperty("java.library.path", "/users/elgekko"); + Path filePath = Paths.get("/opt/AMC/2021/ISSUES/#203/IFL000000071_43.mxf"); + user.commons.mediatool.MediaInfo mi = new user.commons.mediatool.MediaInfo(filePath); + mi.process(); + System.out.println(String.format("%s %d %s", mi.getCodecProfileName(), mi.getHeight(), mi.getDisplayAspect())); + } + + @Test + public void test99997() throws Exception { + + System.setProperty("jna.library.path", "/users/elgekko"); + List files = Arrays.asList("c:\\opt\\AMC\\2021\\ISSUES\\#203\\IFL000000071_43.mxf", "c:\\opt\\AMC\\2021\\ISSUES\\#203\\MX_12290041.mxf", + "c:\\opt\\AMC\\2021\\ISSUES\\#203\\DK_20344100_02_high_16_9.mxf", "c:\\opt\\AMC\\2021\\ISSUES\\#203\\IPR000011027_169.mxf"); + for (String file : files) { + try (user.commons.mediaarea.MediaInfo mi = new user.commons.mediaarea.MediaInfo(file)) { + //Display aspect ratio + //Format profile + //String inform = mi.inform(); + System.out.println("---------------------------------------"); + System.out.println(file); + System.out.println(mi.get(StreamKind.Video, 0, "Width")); + System.out.println(mi.get(StreamKind.Video, 0, "Height")); + System.out.println(mi.get(StreamKind.Video, 0, "DisplayAspectRatio/String")); + System.out.println(mi.get(StreamKind.Video, 0, "Format_Profile")); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + } + + @Test + public void test99998() throws Exception { + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + + for (Long threadID : threadMXBean.getAllThreadIds()) { + ThreadInfo info = threadMXBean.getThreadInfo(threadID); + System.out.println("Thread name: " + info.getThreadName()); + System.out.println("Thread State: " + info.getThreadState()); + long cpuTime = threadMXBean.getThreadCpuTime(threadID); + System.out.println(String.format("CPU time: %.2f %%", cpuTime == 0 ? cpuTime : cpuTime * 100 / 1000000000d)); + } + } +} diff --git a/server/user.commons.log4j2/pom.xml b/server/user.commons.log4j2/pom.xml index daf78393..e401fb54 100644 --- a/server/user.commons.log4j2/pom.xml +++ b/server/user.commons.log4j2/pom.xml @@ -12,4 +12,36 @@ user.commons.log4j2 eclipse-plugin 1.0.0 + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + + copy-jar + install + + copy-resources + + + ${project.basedir}}/../-product/target/deploy + + + target + + ${project.artifactId}_${project.version}.jar + + + + + + + + + + \ No newline at end of file diff --git a/server/user.jobengine.executors/amc/user/jobengine/server/steps/ForkDownloadStep.java b/server/user.jobengine.executors/amc/user/jobengine/server/steps/ForkDownloadStep.java index 8b72db9d..cc386f64 100644 --- a/server/user.jobengine.executors/amc/user/jobengine/server/steps/ForkDownloadStep.java +++ b/server/user.jobengine.executors/amc/user/jobengine/server/steps/ForkDownloadStep.java @@ -9,14 +9,12 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import user.commons.DownloadableMedia; import user.commons.JobStatus; -import user.commons.MediaCubeMarker; import user.commons.StoreUri; import user.commons.remotestore.RemoteStoreProtocol; import user.jobengine.db.Store; @@ -28,9 +26,6 @@ public class ForkDownloadStep extends JobStep { @StepEntry public Object[] execute(String tempStoreName, String template, String expectedColorSpace, int limit) throws Exception { - - sendStatusReport(); - //return null; DirectoryStream directoryStream = null; int count = limit; int allCount = 0; @@ -164,25 +159,6 @@ public class ForkDownloadStep extends JobStep { return true; } - private void sendStatusReport() { - IJobEngine engine = getEngine(); - int jobCount = 0; - StringBuilder sb = new StringBuilder(); - Map jobs = engine.getJobs(); - if (jobs != null) { - Set keys = jobs.keySet(); - jobCount = keys.size(); - for (Long key : keys) { - IJobRuntime runtime = jobs.get(key); - if (JobStatus.SUSPENDED.equals(runtime.getStatus())) - sb.append(runtime.getRelated() + " SUSPENDED: " + runtime.getDescription() + "
"); - } - } - MediaCubeMarker marker = new MediaCubeMarker(); - marker.setSubject("AMC MediaCube feldolgozási sor " + jobCount); - logger.info(marker, sb.toString()); - } - private boolean targetExists(StoreUri storeUri, String fileName) { try { Path targetFile = Paths.get(storeUri.toString(true), fileName); diff --git a/server/user.jobengine.executors/amc/user/jobengine/server/steps/PASAPOOLTransferToStep.java b/server/user.jobengine.executors/amc/user/jobengine/server/steps/PASAPOOLTransferToStep.java index 52c90280..b5106b6e 100644 --- a/server/user.jobengine.executors/amc/user/jobengine/server/steps/PASAPOOLTransferToStep.java +++ b/server/user.jobengine.executors/amc/user/jobengine/server/steps/PASAPOOLTransferToStep.java @@ -3,7 +3,6 @@ package user.jobengine.server.steps; import java.nio.file.Path; import java.nio.file.Paths; -import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -11,7 +10,8 @@ import org.apache.logging.log4j.MarkerManager; import user.commons.RemoteFile; import user.commons.StoreUri; -import user.commons.mediatool.MediaInfo; +import user.commons.mediaarea.MediaArea; +import user.commons.strings.FileSizeUtils; public class PASAPOOLTransferToStep extends TransferStep { private static final Logger logger = LogManager.getLogger(); @@ -32,11 +32,13 @@ public class PASAPOOLTransferToStep extends TransferStep { long start = System.currentTimeMillis(); Object[] result = super.execute(sourceStoreUri, sourceFileName, targetStoreUri, targetFileName); RemoteFile remoteFile = sourceStoreUri.getRemoteFile(targetFileName); - if (remoteFile != null) { + if (remoteFile != null && remoteFile.getSize() > 0) { long diff = (System.currentTimeMillis() - start) / 1000; - long bytesSpeed = remoteFile.getSize() / diff; - String speed = FileUtils.byteCountToDisplaySize(bytesSpeed); - logger.info(getMarker(), "{} size is {}, upload speed was {}/sec", targetFileName, FileUtils.byteCountToDisplaySize(remoteFile.getSize()), speed); + if (diff > 0) { + long bytesSpeed = remoteFile.getSize() / diff; + String speed = FileSizeUtils.sizeAsString(bytesSpeed); + logger.info(getMarker(), "{} size is {}, upload speed was {}/sec", targetFileName, FileSizeUtils.sizeAsString(remoteFile.getSize()), speed); + } } return result; } @@ -44,7 +46,7 @@ public class PASAPOOLTransferToStep extends TransferStep { // HD // height = 1080 // >PEABLEBEACH | \\10.170.100.21\media\BeachPool - private boolean isHD(MediaInfo mi) { + private boolean isHD(MediaArea mi) { return mi.getHeight() == 1080; } @@ -87,8 +89,9 @@ public class PASAPOOLTransferToStep extends TransferStep { // profile=High // aspect=16:9 // >SELENIOPOOL_16_9 | \\10.170.100.21\media\SelenioPool\16_9 - private boolean isSD_HIGH_16_9(MediaInfo mi) { - return mi.getHeight() < 650 && CODEC_PROFILE_HIGH.equals(mi.getCodecProfileName()) && DISPLAY_ASPECT_16_9.equals(mi.getDisplayAspect()); + private boolean isSD_HIGH_16_9(MediaArea mi) { + String formatProfileName = mi.getFormatProfileName(); + return mi.getHeight() < 650 && formatProfileName.contains(CODEC_PROFILE_HIGH) && DISPLAY_ASPECT_16_9.equals(mi.getDisplayAspect()); } // SD - HIGH@HIGH / 4:3 @@ -96,29 +99,25 @@ public class PASAPOOLTransferToStep extends TransferStep { // profile=High // aspect=4:3 // >SELENIOPOOL_4_3 | \\10.170.100.21\media\SelenioPool\4_3 - private boolean isSD_HIGH_4_3(MediaInfo mi) { - String codecProfileName = mi.getCodecProfileName(); - if (codecProfileName != null) - codecProfileName = codecProfileName.toUpperCase(); - return mi.getHeight() < 650 && CODEC_PROFILE_HIGH.equals(codecProfileName) && DISPLAY_ASPECT_4_3.equals(mi.getDisplayAspect()); + private boolean isSD_HIGH_4_3(MediaArea mi) { + String formatProfileName = mi.getFormatProfileName(); + return mi.getHeight() < 650 && formatProfileName.contains(CODEC_PROFILE_HIGH) && DISPLAY_ASPECT_4_3.equals(mi.getDisplayAspect()); } // SD - MAIN/422@HIGH // height < 650 // profile=Main || profile=4:2:2 // >PEABLEBEACH | \\10.170.100.21\media\BeachPool - private boolean isSD_MAIN_422(MediaInfo mi) { - String codecProfileName = mi.getCodecProfileName(); - if (codecProfileName != null) - codecProfileName = codecProfileName.toUpperCase(); - return mi.getHeight() < 650 && (CODEC_PROFILE_MAIN.equals(codecProfileName) || CODEC_PROFILE_422.equals(mi.getCodecProfileName())); + private boolean isSD_MAIN_422(MediaArea mi) { + String formatProfileName = mi.getFormatProfileName(); + return mi.getHeight() < 650 && (formatProfileName.contains(CODEC_PROFILE_MAIN) || formatProfileName.contains(CODEC_PROFILE_422)); } protected void logMediaProfile() { Marker marker = MarkerManager.getMarker("MEDIAPROFILE"); try { Path filePath = Paths.get(sourceStoreUri.toString(true), sourceFileName); - MediaInfo mi = new MediaInfo(filePath); + MediaArea mi = new MediaArea(filePath); mi.process(); if (isHD(mi)) { diff --git a/server/user.jobengine.executors/amc/user/jobengine/server/steps/ReportServerStatusStep.java b/server/user.jobengine.executors/amc/user/jobengine/server/steps/ReportServerStatusStep.java new file mode 100644 index 00000000..3c0026b4 --- /dev/null +++ b/server/user.jobengine.executors/amc/user/jobengine/server/steps/ReportServerStatusStep.java @@ -0,0 +1,64 @@ +package user.jobengine.server.steps; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import user.commons.JobStatus; +import user.commons.MediaCubeMarker; +import user.commons.strings.FileSizeUtils; +import user.jobengine.server.IJobRuntime; + +public class ReportServerStatusStep extends JobStep { + private static final Logger logger = LogManager.getLogger(); + private static SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd hh:mm"); + + @StepEntry + public Object[] execute() throws Exception { + MediaCubeMarker marker = new MediaCubeMarker(); + + int jobCount = 0; + StringBuilder sb = new StringBuilder("Állapotjelentés:
"); + Map jobs = getEngine().getJobs(); + Set keys = jobs.keySet(); + jobCount = keys.size() - 1; + marker.setSubject(String.format("%s [%d futó folyamat] AMC MediaCube report", df.format(new Date()), jobCount)); + for (Long key : keys) { + IJobRuntime runtime = jobs.get(key); + if (JobStatus.SUSPENDED.equals(runtime.getStatus())) + sb.append(String.format("Felfüggesztve: %s %s %s %s %s
", df.format(runtime.getSubmitted()), df.format(runtime.getFinished()), + runtime.getStatus(), runtime.getRelated(), runtime.getDescription())); + } + + // sb.append("
"); + // float cpuTime = getCPUTime(); + // sb.append(String.format("Processzor használat: %.2f %%
", cpuTime == 0 ? cpuTime : cpuTime * 100 / 1000000000d)); + sb.append("
"); + sb.append(String.format("Szabad JVM memória: %s
", FileSizeUtils.sizeAsString(Runtime.getRuntime().freeMemory()))); + sb.append(String.format("Használt JVM memória: %s
", + FileSizeUtils.sizeAsString(Runtime.getRuntime().maxMemory() - Runtime.getRuntime().freeMemory()))); + sb.append(String.format("Maximum JVM memória: %s
", FileSizeUtils.sizeAsString(Runtime.getRuntime().maxMemory()))); + logger.info(marker, sb.toString()); + + return null; + } + + private long getCPUTime() { + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + long result = 0; + for (Long threadID : threadMXBean.getAllThreadIds()) { + ThreadInfo info = threadMXBean.getThreadInfo(threadID); + System.out.println("Thread name: " + info.getThreadName()); + System.out.println("Thread State: " + info.getThreadState()); + result += threadMXBean.getThreadCpuTime(threadID); + } + return result; + } +} diff --git a/server/user.jobengine.executors/amc/user/jobengine/server/steps/TransferStep.java b/server/user.jobengine.executors/amc/user/jobengine/server/steps/TransferStep.java index 39c338c3..d285f70d 100644 --- a/server/user.jobengine.executors/amc/user/jobengine/server/steps/TransferStep.java +++ b/server/user.jobengine.executors/amc/user/jobengine/server/steps/TransferStep.java @@ -3,10 +3,12 @@ package user.jobengine.server.steps; import java.nio.file.Path; import java.nio.file.Paths; +import org.apache.commons.net.ftp.FTPClient; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import user.commons.StoreUri; +import user.commons.remotestore.FtpDirectoryLister; import user.commons.remotestore.RemoteStoreProtocol; import user.jobengine.db.Store; @@ -77,18 +79,46 @@ public class TransferStep extends JobStep { String currentTargetFileName = targetFileName; boolean renameAfterCopy = false; - if (getTmpExtension() != null && RemoteStoreProtocol.LOCAL.equals(targetStoreUri.getProtocol())) { - currentTargetFileName += getTmpExtension(); - renameAfterCopy = true; + boolean renameAfterFTP = false; + if (getTmpExtension() != null) { + if (RemoteStoreProtocol.LOCAL.equals(targetStoreUri.getProtocol())) { + currentTargetFileName += getTmpExtension(); + renameAfterCopy = true; + } + Store targetStore = getManager().getStore(targetStoreUri.getStoreId()); + + if (RemoteStoreProtocol.FTP.equals(targetStoreUri.getProtocol()) && !"NEXIO1".equals(targetStore.getName()) + && !"NEXIO2".equals(targetStore.getName())) { + currentTargetFileName += getTmpExtension(); + renameAfterFTP = true; + } } sourceStoreUri.transferFrom(targetStoreUri, sourceFileName, currentTargetFileName); - logger.info(getMarker(), "Transfer of {} completed from {} to {}", sourceFileName, sourceStoreUri, targetStoreUri); + + logger.info(getMarker(), "Transfer of {} completed from {} to {}", sourceFileName, sourceStoreUri, currentTargetFileName); if (renameAfterCopy) { Path tmpTargetFile = Paths.get(targetStoreUri.toString(true), currentTargetFileName); Path targetFile = Paths.get(targetStoreUri.toString(true), targetFileName); - tmpTargetFile.toFile().renameTo(targetFile.toFile()); + try { + logger.info(getMarker(), "Renaming LOCAL file from {} to {} on {}", currentTargetFileName, targetFileName, sourceStoreUri); + tmpTargetFile.toFile().renameTo(targetFile.toFile()); + } catch (Exception e) { + logger.error(getMarker(), e.getMessage()); + } + } + if (renameAfterFTP) { + try { + FtpDirectoryLister lister = (FtpDirectoryLister) targetStoreUri.getLister(); + FTPClient client = lister.connect(); + logger.info(getMarker(), "Renaming FTP file from {} to {} on {}", currentTargetFileName, targetFileName, sourceStoreUri); + client.rename(currentTargetFileName, targetFileName); + } catch (Exception e) { + logger.error(getMarker(), e.getMessage()); + } finally { + targetStoreUri.cleanUp(); + } } } } diff --git a/server/user.jobengine.executors/config/config-mv.xml b/server/user.jobengine.executors/config/config-mv.xml index e0f67e2e..18c392dd 100644 --- a/server/user.jobengine.executors/config/config-mv.xml +++ b/server/user.jobengine.executors/config/config-mv.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/server/user.jobengine.executors/src/user/jobengine/server/steps/CancelableStep.java b/server/user.jobengine.executors/src/user/jobengine/server/steps/CancelableStep.java index b01bf0a2..9979d22e 100644 --- a/server/user.jobengine.executors/src/user/jobengine/server/steps/CancelableStep.java +++ b/server/user.jobengine.executors/src/user/jobengine/server/steps/CancelableStep.java @@ -21,17 +21,17 @@ public class CancelableStep extends JobStep { // ftpTest(); - if (param == 0) - Thread.sleep(5000); - if (param == 1) - throw new Exception("Error teszt"); + // if (param == 0) + // Thread.sleep(5000); + // if (param == 1) + // throw new Exception("Error teszt"); Marker marker = MarkerManager.getMarker("MEDIAPROFILE"); for (int i = 0; i < count; i++) { if (getJobRuntime().isWaitingCancel()) break; - Thread.sleep(500); + Thread.sleep(200); int progress = (i + 1) * 100 / count; setProgress(progress); logger.info(marker, "{}", i); diff --git a/server/user.jobengine.executors/src/user/jobengine/server/steps/CleanupMountedLocationStep.java b/server/user.jobengine.executors/src/user/jobengine/server/steps/CleanupMountedLocationStep.java index 4e6f10d1..0cd2e364 100644 --- a/server/user.jobengine.executors/src/user/jobengine/server/steps/CleanupMountedLocationStep.java +++ b/server/user.jobengine.executors/src/user/jobengine/server/steps/CleanupMountedLocationStep.java @@ -246,9 +246,11 @@ public class CleanupMountedLocationStep extends JobStep implements FileVisitor

0) { + if (!isArchived(filePath)) { + logger.error(marker, "A(z) {} anyag törlésre van kijelölve, de nem található az archívumban.", filePath); + return; + } } if (removeFiles(filePath, killDateFiles)) diff --git a/server/user.jobengine.executors/src/user/jobengine/server/steps/TSMRestoreStep.java b/server/user.jobengine.executors/src/user/jobengine/server/steps/TSMRestoreStep.java index 5b410055..6b40e3b7 100644 --- a/server/user.jobengine.executors/src/user/jobengine/server/steps/TSMRestoreStep.java +++ b/server/user.jobengine.executors/src/user/jobengine/server/steps/TSMRestoreStep.java @@ -2,7 +2,9 @@ package user.jobengine.server.steps; import java.io.IOException; import java.nio.file.Paths; +import java.text.Normalizer; import java.util.List; +import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; @@ -28,6 +30,8 @@ import user.jobengine.server.IJobEngine; import user.jobengine.server.IJobRuntime; public class TSMRestoreStep extends JobStep { + private static final String DOT = "."; + public static final Pattern DIACRITICS_AND_FRIENDS = Pattern.compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+"); private static final Logger logger = LogManager.getLogger(); private IItemManager manager; private StoreUri targetUri; @@ -60,6 +64,9 @@ public class TSMRestoreStep extends JobStep { marker = jobRuntime.getSessionMarker(); setAndCheck(mediaCubeMedia, targetPath, targetNamePattern, localRetrievePath, globalRetrievePath, jobEngine); String targetFileName = String.format(targetNamePattern, sourceFileName); + //20210129 + //targetFileName = getMaximizedFileName(mediaCubeMedia, targetFileName, 120); + Timecode timecode = new Timecode(mediaCubeMedia.getLength(), Type.PAL); try { String details = String.format("%s (%s)", sourceFileName, timecode.toString()); @@ -99,6 +106,24 @@ public class TSMRestoreStep extends JobStep { return null; } + private String getMaximizedFileName(Media mediaCubeMedia, String targetFileName, int limit) { + String name = targetFileName; + String extension = ""; + if (name.contains(DOT)) { + extension = DOT + name.substring(name.lastIndexOf(DOT) + 1); + name = name.substring(0, name.lastIndexOf(DOT)); + } + String typeName = Normalizer.normalize(mediaCubeMedia.getItemType().getName(), Normalizer.Form.NFD); + typeName = DIACRITICS_AND_FRIENDS.matcher(typeName).replaceAll(""); + typeName = typeName.replace(" ", "_"); + + int allowedSize = limit - typeName.length() - 1 - extension.length(); + if (name.length() > allowedSize) + name = name.substring(0, allowedSize); + + return String.format("%s_%s%s", name, typeName, extension); + } + private String getSourceFileName(Media mediaCubeMedia, Store store) { List mediaFiles = mediaCubeMedia.getMediaFiles(); if (mediaFiles == null) diff --git a/server/user.jobengine.executors/src/user/jobengine/server/steps/TestForkCancelableStep.java b/server/user.jobengine.executors/src/user/jobengine/server/steps/TestForkCancelableStep.java index e622b543..28e66d3f 100644 --- a/server/user.jobengine.executors/src/user/jobengine/server/steps/TestForkCancelableStep.java +++ b/server/user.jobengine.executors/src/user/jobengine/server/steps/TestForkCancelableStep.java @@ -12,7 +12,7 @@ import user.jobengine.server.IJobRuntime; public class TestForkCancelableStep extends JobStep { private static final String CHILD_TEMPLATE = "cancelable.xml"; private static final Logger logger = LogManager.getLogger(); - int count = 10; + int count = 5; @StepEntry public Object[] execute(IJobEngine jobEngine, IJobRuntime jobRuntime) throws Exception { diff --git a/server/user.jobengine.osgi.commons/META-INF/MANIFEST.MF b/server/user.jobengine.osgi.commons/META-INF/MANIFEST.MF index 3ef3e4ac..f59caf2a 100644 --- a/server/user.jobengine.osgi.commons/META-INF/MANIFEST.MF +++ b/server/user.jobengine.osgi.commons/META-INF/MANIFEST.MF @@ -9,6 +9,7 @@ Import-Package: com.fasterxml.jackson.annotation;version="2.4.5", com.fasterxml.jackson.databind.util;version="2.4.5", com.fasterxml.jackson.datatype.joda;version="2.4.5", com.fasterxml.jackson.jaxrs.json;version="2.4.5", + com.sun.jna, io.humble.video, io.humble.video.awt, junit.framework, @@ -29,6 +30,7 @@ Export-Package: user.commons, user.commons.ftp, user.commons.harris, user.commons.logging, + user.commons.mediaarea, user.commons.mediatool, user.commons.morpheus, user.commons.nexio, diff --git a/server/user.jobengine.osgi.commons/pom.xml b/server/user.jobengine.osgi.commons/pom.xml index c02e675f..04d98e2d 100644 --- a/server/user.jobengine.osgi.commons/pom.xml +++ b/server/user.jobengine.osgi.commons/pom.xml @@ -11,4 +11,36 @@ user.jobengine.osgi.commons eclipse-plugin 1.0.0 + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + + copy-jar + install + + copy-resources + + + ${project.basedir}}/../-product/target/deploy + + + target + + ${project.artifactId}_${project.version}.jar + + + + + + + + + + \ No newline at end of file diff --git a/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/InfoKind.java b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/InfoKind.java new file mode 100644 index 00000000..f9d82732 --- /dev/null +++ b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/InfoKind.java @@ -0,0 +1,39 @@ +package user.commons.mediaarea; + +/** + * + */ +public enum InfoKind { + /** + * Unique name of parameter. + */ + Name, + /** + * Value of parameter. + */ + Text, + /** + * Unique name of measure unit of parameter. + */ + Measure, Options, + /** + * Translated name of parameter. + */ + Name_Text, + /** + * Translated name of measure unit. + */ + Measure_Text, + /** + * More information about the parameter. + */ + Info, + /** + * How this parameter is supported, could be N (No), B (Beta), R (Read only), W (Read/Write). + */ + HowTo, + /** + * Domain of this piece of information. + */ + Domain; +} diff --git a/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/InformParser.java b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/InformParser.java new file mode 100644 index 00000000..725c1ed5 --- /dev/null +++ b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/InformParser.java @@ -0,0 +1,88 @@ +package user.commons.mediaarea; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Simple parser to adapt inform response to meta objects. The default MediaInfo inform call returns data in the form
+ * + *

+ * General
+Complete name                            : /path/to/input/file.ext
+Format                                   : Windows Media
+...
+
+Video
+ID                                       : 1
+Format                                   : FMT
+Codec ID                                 : CODECID
+...
+
+Audio
+ID                                       : 2
+Format                                   : MP3
+...
+ * 
+ * 
+ */ +public class InformParser { + + static final String DELIM = ":"; + + static MediaMetadata createMM(String type, BufferedReader r, Map pairs) throws IOException { + pairs.clear(); + String meta; + while ((meta = r.readLine()) != null && !meta.isEmpty()) { + int idx = meta.indexOf(DELIM); + if (idx == -1) { + throw new IllegalStateException("Parser received unexpected data, should contain ':'. Type=" + type + " line=" + meta); + } + String key = meta.substring(0, idx).trim(); + String value = meta.substring(++idx).trim(); + pairs.put(key, value); + } + return new MediaMetadata(type, pairs); + } + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + System.out.println("Usage: java -jar .jar [FILE]..."); + System.exit(1); + } + for (String arg : args) { + try (MediaInfo mi = new MediaInfo(arg)) { + Map results = InformParser.parse(mi.inform()); + System.out.println("Inform " + arg + "\n" + results + "\n"); + } + } + + } + + /** + * + * @param inform + * A MediaInfo formatted inform string + * @return Map containing parsed meta data. + */ + public static Map parse(String inform) { + LinkedHashMap results = new LinkedHashMap<>(); + try { + BufferedReader reader = new BufferedReader(new StringReader(inform)); + String line; + Map pairs = new LinkedHashMap<>(); + // first read should always be the type + while ((line = reader.readLine()) != null) { + if (!line.isEmpty()) { + results.put(line, createMM(line, reader, pairs)); + } + } + } catch (IOException ex) { + // shouldn't happen since we're reading from a String + // TODO add logging? + } + return results; + } +} diff --git a/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/InformResult.java b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/InformResult.java new file mode 100644 index 00000000..103b6c9f --- /dev/null +++ b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/InformResult.java @@ -0,0 +1,56 @@ +package user.commons.mediaarea; + +import java.io.File; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; + +/** + * Wrapper to perform JAXB Serialization + */ +@XmlRootElement +public class InformResult { + @XmlTransient + private static final JAXBContext CTX; + + static { + JAXBContext ctx; + try { + ctx = JAXBContext.newInstance(InformResult.class); + } catch (JAXBException ex) { + // should never happen + ctx = null; + } + CTX = ctx; + } + + public static InformResult getResult(String file) throws JAXBException { + return (InformResult) CTX.createUnmarshaller().unmarshal(new File(file)); + } + + public static void saveResult(InformResult ir, String filename) throws JAXBException { + Marshaller m = CTX.createMarshaller(); + m.marshal(ir, new File(filename)); + } + + @XmlElement + LinkedHashMap results = new LinkedHashMap<>(); + + public InformResult() { + } + + public InformResult(Map map) { + results.putAll(map); + } + + public LinkedHashMap getResults() { + return results; + } + +} diff --git a/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/LibMediaInfo.java b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/LibMediaInfo.java new file mode 100644 index 00000000..f3c220e9 --- /dev/null +++ b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/LibMediaInfo.java @@ -0,0 +1,57 @@ +package user.commons.mediaarea; + +import static java.util.Collections.singletonMap; + +import java.lang.reflect.Method; + +import com.sun.jna.FunctionMapper; +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.NativeLibrary; +import com.sun.jna.Pointer; +import com.sun.jna.WString; + +/** + * JNA Wrapper for MediaInfo. + */ +public interface LibMediaInfo extends Library { + + static final String LIB_NAME = "mediainfo"; + + final LibMediaInfo INSTANCE = (LibMediaInfo) Native.loadLibrary(LIB_NAME, LibMediaInfo.class, + singletonMap(OPTION_FUNCTION_MAPPER, (FunctionMapper) (NativeLibrary lib, Method method) -> "MediaInfo_" + method.getName())); + + /* Closes the file upon completion */ + void Close(Pointer Handle); + + int Count_Get(Pointer Handle, int StreamKind, int StreamNumber); + + /* Deconstructor */ + void Delete(Pointer Handle); + + WString Get(Pointer Handle, int StreamKind, int StreamNumber, WString parameter, int infoKind, int searchKind); + + WString GetI(Pointer Handle, int StreamKind, int StreamNumber, int parameterIndex, int infoKind); + + /* return information in various ways */ + WString Inform(Pointer Handle, int Reserved); + + /* Constructor */ + Pointer New(); + + /* Opens a file for inspection */ + int Open(Pointer Handle, WString file); + + int Open_Buffer_Continue(Pointer handle, byte[] buffer, int size); + + long Open_Buffer_Continue_GoTo_Get(Pointer handle); + + /* release buffer resources (close) */ + int Open_Buffer_Finalize(Pointer handle); + + /* Opens a buffered read for the specified length beginning at offset */ + int Open_Buffer_Init(Pointer handle, long length, long offset); + + /* Set options */ + WString Option(Pointer Handle, WString option, WString value); +} diff --git a/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/MediaArea.java b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/MediaArea.java new file mode 100644 index 00000000..1c8bdd96 --- /dev/null +++ b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/MediaArea.java @@ -0,0 +1,41 @@ +package user.commons.mediaarea; + +import java.nio.file.Path; + +public class MediaArea { + + private Path filePath; + private int height; + private String displayAspect; + private String formatProfile = ""; + + public MediaArea(Path filePath) { + this.filePath = filePath; + + } + + public String getDisplayAspect() { + return displayAspect; + } + + public String getFormatProfileName() { + return formatProfile; + } + + public int getHeight() { + return height; + } + + public void process() { + //auto closable + try (MediaInfo mi = new MediaInfo(filePath.toAbsolutePath().toString())) { + height = Integer.parseInt(mi.get(StreamKind.Video, 0, "Height")); + displayAspect = mi.get(StreamKind.Video, 0, "DisplayAspectRatio/String"); + formatProfile = mi.get(StreamKind.Video, 0, "Format_Profile").toUpperCase(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + } + +} diff --git a/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/MediaInfo.java b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/MediaInfo.java new file mode 100644 index 00000000..e08e9771 --- /dev/null +++ b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/MediaInfo.java @@ -0,0 +1,222 @@ +package user.commons.mediaarea; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; + +import com.sun.jna.Pointer; +import com.sun.jna.WString; + +/** + * Class to wrap calls to LibMediaInfo via JNA. This class takes either a file path or a SeekableByteChannel and calls to LibMediaInfo to generate meta info. + * This class must be closed to properly release Native resources. It implements AutoCloseable to allow use try with resources. + */ +public class MediaInfo implements AutoCloseable { + + public static void setXMLOutput(MediaInfo m) { + m.option("Inform", "XML"); + } + + private Pointer pointer; + + private MediaInfo() { + pointer = LibMediaInfo.INSTANCE.New(); + } + + public MediaInfo(SeekableByteChannel channel) throws IOException { + this(); + // open, read and finalize the data in the buffer + long len = channel.size(); + final byte[] buff = new byte[64 * 1024]; + final ByteBuffer dst = ByteBuffer.wrap(buff); + int read; + LibMediaInfo.INSTANCE.Open_Buffer_Init(pointer, len, 0L); + while ((read = channel.read(dst)) != -1) { + int status = LibMediaInfo.INSTANCE.Open_Buffer_Continue(pointer, buff, read); + if ((status & Status.Finalized.val) == Status.Finalized.val) { + break; + } + long seekPos = LibMediaInfo.INSTANCE.Open_Buffer_Continue_GoTo_Get(pointer); + if (seekPos != -1) { + channel.position(seekPos); + LibMediaInfo.INSTANCE.Open_Buffer_Init(pointer, len, seekPos); + } + dst.clear(); + } + LibMediaInfo.INSTANCE.Open_Buffer_Finalize(pointer); + } + + public MediaInfo(String fileName) throws IOException { + this(); + int opened = LibMediaInfo.INSTANCE.Open(pointer, new WString(fileName)); + if (opened == 0) { + throw new IOException("Unable to open file with libmediainfo: " + fileName); + } + } + + /** + * Closes the underlying file handle, and releases the native instance. + * + * @throws Exception + */ + @Override + public void close() throws Exception { + if (pointer != null) { + LibMediaInfo.INSTANCE.Close(pointer); + LibMediaInfo.INSTANCE.Delete(pointer); + pointer = null; + } + } + + /** + * Get a piece of information about a file (parameter is an integer). + * + * + * @param streamKind + * Kind of Stream (general, video, audio...) + * @param streamNumber + * Stream number in Kind of Stream (first, second...) + * @param parameterIndex + * Parameter you are looking for in the Stream (Codec, width, bitrate...), in integer format (first parameter, second parameter...) + * @return information requested, empty string if not found + */ + public String get(StreamKind streamKind, int streamNumber, int parameterIndex) { + return get(streamKind, streamNumber, parameterIndex, InfoKind.Text); + } + + /** + * Get a piece of information about a file (parameter is an integer). + * + * + * @param streamKind + * Kind of Stream (general, video, audio...) + * @param streamNumber + * Stream number in Kind of Stream (first, second...) + * @param parameterIndex + * Parameter you are looking for in the Stream (Codec, width, bitrate...), in integer format (first parameter, second parameter...) + * @param infoKind + * Kind of information you want about the parameter (the text, the measure, the help...) + * @return a string about information you search, an empty string if there is a problem + */ + public String get(StreamKind streamKind, int streamNumber, int parameterIndex, InfoKind infoKind) { + return LibMediaInfo.INSTANCE.GetI(pointer, streamKind.ordinal(), streamNumber, parameterIndex, infoKind.ordinal()).toString(); + } + + /** + * Get a piece of information about a file (parameter is a string). + * + * @param streamKind + * Kind of Stream (general, video, audio...) + * @param streamNumber + * Stream number in Kind of Stream (first, second...) + * @param parameter + * Parameter you are looking for in the Stream (Codec, width, bitrate...), in string format ("Codec", "Width"...) + * @return a string about information you search, an empty string if there is a problem + */ + public String get(StreamKind streamKind, int streamNumber, String parameter) { + return get(streamKind, streamNumber, parameter, InfoKind.Text, InfoKind.Name); + } + + /** + * Get a piece of information about a file (parameter is a string). + * + * @param streamKind + * Kind of Stream (general, video, audio...) + * @param streamNumber + * Stream number in Kind of Stream (first, second...) + * @param parameter + * Parameter you are looking for in the Stream (Codec, width, bitrate...), in string format ("Codec", "Width"...) + * @param infoKind + * Kind of information you want about the parameter (the text, the measure, the help...) + * @return information requested, empty string if not found + */ + public String get(StreamKind streamKind, int streamNumber, String parameter, InfoKind infoKind) { + return get(streamKind, streamNumber, parameter, infoKind, InfoKind.Name); + } + + /** + * Get a piece of information about a file (parameter is a string). + * + * @param streamKind + * Kind of Stream (general, video, audio...) + * @param streamNumber + * Stream number in Kind of Stream (first, second...) + * @param parameter + * Parameter you are looking for in the Stream (Codec, width, bitrate...), in string format ("Codec", "Width"...) + * @param infoKind + * Kind of information you want about the parameter (the text, the measure, the help...) + * @param searchKind + * Where to look for the parameter + * @return a string about information you search, an empty string if there is a problem + */ + public String get(StreamKind streamKind, int streamNumber, String parameter, InfoKind infoKind, InfoKind searchKind) { + return LibMediaInfo.INSTANCE.Get(pointer, streamKind.ordinal(), streamNumber, new WString(parameter), infoKind.ordinal(), searchKind.ordinal()) + .toString(); + } + + /** + * Count of Streams of a Stream kind (StreamNumber not filled), or count of piece of information in this Stream. + * + * + * @param streamKind + * Kind of Stream (general, video, audio...) + * @return number of Streams of the given Stream kind + */ + public int getCount(StreamKind streamKind) { + //We should use NativeLong for -1, but it fails on 64-bit + //int Count_Get(Pointer Handle, int StreamKind, NativeLong StreamNumber); + //return MediaInfoDLL_Internal.INSTANCE.Count_Get(Handle, StreamKind.ordinal(), -1); + //so we use slower Get() with a character string + String streamCount = get(streamKind, 0, "StreamCount"); + if (streamCount == null || streamCount.length() == 0) { + return 0; + } + return Integer.parseInt(streamCount); + } + + /** + * Count of Streams of a Stream kind in the Stream Number. + * + * @param streamKind + * Kind of Stream (general, video, audio...) + * @param streamNumber + * Stream number in this kind of Stream (first, second...) + * @return number of Streams of the given Stream kind + */ + public int getCount(StreamKind streamKind, int streamNumber) { + return LibMediaInfo.INSTANCE.Count_Get(pointer, streamKind.ordinal(), streamNumber); + } + + /** + * Get all details about a file. + * + * @return All details about a file in one string + */ + public String inform() { + return LibMediaInfo.INSTANCE.Inform(pointer, 0).toString(); + } + + /** + * Configure or get information about MediaInfo. + * + * @param option + * The name of option + * @return Depends on the option: by default "" (nothing) means No, other means Yes + */ + public String option(String option) { + return option(option, ""); + } + + /** + * Configure or get information about MediaInfo. + * + * @param option + * The name of option + * @param value + * The value of option + * @return Depends on the option: by default "" (nothing) means No, other means Yes + */ + public String option(String option, String value) { + return LibMediaInfo.INSTANCE.Option(pointer, new WString(option), new WString(value)).toString(); + } +} diff --git a/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/MediaMetadata.java b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/MediaMetadata.java new file mode 100644 index 00000000..fae0580d --- /dev/null +++ b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/MediaMetadata.java @@ -0,0 +1,64 @@ +package user.commons.mediaarea; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + +/** + * Simple data object to hold MediaInfo inform results. + */ +@XmlType +public final class MediaMetadata { + @XmlElement + String type; + + @XmlElement + Map metaData; + + /* JAXB */ + MediaMetadata() { + } + + public MediaMetadata(String type, Map metaData) { + this.type = type; + this.metaData = Collections.unmodifiableMap(new LinkedHashMap<>(metaData)); + } + + /** + * The meta data information for this type. The underlying implementation is an ordered map, but order may not be maintained if the object is constructed + * with an unordered Map. + * + * @return Unmodifiable meta map + */ + public Map getMetaData() { + return metaData; + } + + /** + * The meta info type (General, Video, Audio, etc) + * + * @return + */ + public String getType() { + return type; + } + + /** + * Returns a single meta data value from the meta map. + * + * @param key + * @return value if present or null + */ + public String getValue(final String key) { + return metaData.get(key); + } + + @Override + public String toString() { + return "MediaMetadata{" + "type=" + type + ", metaData=" + metaData + '}'; + } + +} diff --git a/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/Status.java b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/Status.java new file mode 100644 index 00000000..077b074f --- /dev/null +++ b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/Status.java @@ -0,0 +1,14 @@ +package user.commons.mediaarea; + +/** + * Status codes when reading from a buffer. + */ +public enum Status { + None(0x00), Accepted(0x01), Filled(0x02), Updated(0x04), Finalized(0x08); + + public final int val; + + Status(int val) { + this.val = val; + } +} diff --git a/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/StreamKind.java b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/StreamKind.java new file mode 100644 index 00000000..4bd1895e --- /dev/null +++ b/server/user.jobengine.osgi.commons/src/user/commons/mediaarea/StreamKind.java @@ -0,0 +1,8 @@ +package user.commons.mediaarea; + +/** + * Enumeration of the StreamKind. + */ +public enum StreamKind { + General, Video, Audio, Text, Other, Image, Menu; +} diff --git a/server/user.jobengine.osgi.commons/src/user/commons/strings/FileSizeUtils.java b/server/user.jobengine.osgi.commons/src/user/commons/strings/FileSizeUtils.java new file mode 100644 index 00000000..f37435f1 --- /dev/null +++ b/server/user.jobengine.osgi.commons/src/user/commons/strings/FileSizeUtils.java @@ -0,0 +1,31 @@ +package user.commons.strings; + +import java.text.DecimalFormat; + +public class FileSizeUtils { + private static final long K = 1024; + private static final long M = K * K; + private static final long G = M * K; + private static final long T = G * K; + + private static String format(final long value, final long divider, final String unit) { + final double result = divider > 1 ? (double) value / (double) divider : (double) value; + return new DecimalFormat("#,##0.#").format(result) + " " + unit; + } + + public static String sizeAsString(final long value) { + final long[] dividers = new long[] { T, G, M, K, 1 }; + final String[] units = new String[] { "TB", "GB", "MB", "KB", "B" }; + if (value < 1) + throw new IllegalArgumentException("Invalid file size: " + value); + String result = null; + for (int i = 0; i < dividers.length; i++) { + final long divider = dividers[i]; + if (value >= divider) { + result = format(value, divider, units[i]); + break; + } + } + return result; + } +} diff --git a/server/user.jobengine.osgi.db/migrations/scripts/026_insert_itemtype_demo_nyers.sql b/server/user.jobengine.osgi.db/migrations/scripts/026_insert_itemtype_demo_nyers.sql deleted file mode 100644 index 66858867..00000000 --- a/server/user.jobengine.osgi.db/migrations/scripts/026_insert_itemtype_demo_nyers.sql +++ /dev/null @@ -1,11 +0,0 @@ --- // Insert DEMO nyers itemtype --- Migration SQL that makes the change goes here. - -INSERT INTO ITEMTYPE (NAME) VALUES ('DEMO nyers') -@ - --- //@UNDO --- SQL to undo the change goes here. - -DELETE FROM ITEMTYPE WHERE NAME='DEMO nyers' -@ \ No newline at end of file diff --git a/server/user.jobengine.osgi.db/migrations/scripts/026_insert_itemtype_muszter_demo.sql b/server/user.jobengine.osgi.db/migrations/scripts/026_insert_itemtype_muszter_demo.sql new file mode 100644 index 00000000..3dbe459a --- /dev/null +++ b/server/user.jobengine.osgi.db/migrations/scripts/026_insert_itemtype_muszter_demo.sql @@ -0,0 +1,11 @@ +-- // Insert Muszetr DEMO itemtype +-- Migration SQL that makes the change goes here. + +INSERT INTO ITEMTYPE (NAME, DESCRIPTION, ISSTATIC) VALUES ('Muszter DEMO', 'Muszter DEMO', 'N') +@ + +-- //@UNDO +-- SQL to undo the change goes here. + +DELETE FROM ITEMTYPE WHERE NAME='Muszter DEMO' +@ diff --git a/server/user.jobengine.osgi.db/migrations/scripts/029_alter_PASA_storeuri_to_FTP.sql b/server/user.jobengine.osgi.db/migrations/scripts/029_alter_PASA_storeuri_to_FTP.sql new file mode 100644 index 00000000..6d51e9c4 --- /dev/null +++ b/server/user.jobengine.osgi.db/migrations/scripts/029_alter_PASA_storeuri_to_FTP.sql @@ -0,0 +1,52 @@ +-- +-- Copyright 2010-2016 the original author or authors. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +-- // Alter PASA STOREURI change to FTP protocol +-- Migration SQL that makes the change goes here. + +update storeuri set protocol='FTP', URI='10.170.100.21', username='10.170.100.21\pbadmin', password='Txhkkmot52', rootpath='MEDIA_FS/PasaPool' where storeid in (select id from store where name = 'PASAPOOL'); +@ +update storeuri set protocol='FTP', URI='10.170.100.21', username='10.170.100.21\pbadmin', password='Txhkkmot52', rootpath='MEDIA_FS/BeachPool' where storeid in (select id from store where name = 'PEABLEBEACH'); +@ +update storeuri set protocol='FTP', URI='10.170.100.21', username='10.170.100.21\pbadmin', password='Txhkkmot52', rootpath='MEDIA_FS/SelenioPool' where storeid in (select id from store where name = 'SELENIOPOOL'); +@ +update storeuri set protocol='FTP', URI='10.170.100.21', username='10.170.100.21\pbadmin', password='Txhkkmot52', rootpath='MEDIA_FS/SelenioPool/16_9' where storeid in (select id from store where name = 'SELENIOPOOL_16_9'); +@ +update storeuri set protocol='FTP', URI='10.170.100.21', username='10.170.100.21\pbadmin', password='Txhkkmot52', rootpath='MEDIA_FS/SelenioPool/16_9/Processiong' where storeid in (select id from store where name = 'SELENIOPOOL_16_9_PROCESSING'); +@ +update storeuri set protocol='FTP', URI='10.170.100.21', username='10.170.100.21\pbadmin', password='Txhkkmot52', rootpath='MEDIA_FS/SelenioPool/4_3' where storeid in (select id from store where name = 'SELENIOPOOL_4_3'); +@ +update storeuri set protocol='FTP', URI='10.170.100.21', username='10.170.100.21\pbadmin', password='Txhkkmot52', rootpath='MEDIA_FS/SelenioPool/4_3/Processiong' where storeid in (select id from store where name = 'SELENIOPOOL_4_3_PROCESSING'); +@ + +-- //@UNDO +-- SQL to undo the change goes here. + +update storeuri set protocol='LOCAL', URI='/mnt/PEABLEBEACH/PASAPOOL', username=null, password=null, rootpath=null where storeid in (select id from store where name = 'PASAPOOL'); +@ +update storeuri set protocol='LOCAL', URI='/mnt/PEABLEBEACH/BEACHPOOL',username=null, password=null, rootpath=null where storeid in (select id from store where name = 'PEABLEBEACH'); +@ +update storeuri set protocol='LOCAL', URI='/mnt/PEABLEBEACH/SELENIOPOOL', username=null, password=null, rootpath=null where storeid in (select id from store where name = 'SELENIOPOOL'); +@ +update storeuri set protocol='LOCAL', URI='/mnt/PEABLEBEACH/SELENIOPOOL/16_9', username=null, password=null, rootpath=null where storeid in (select id from store where name = 'SELENIOPOOL_16_9'); +@ +update storeuri set protocol='LOCAL', URI='/mnt/PEABLEBEACH/SELENIOPOOL/16_9/PROCESSING', username=null, password=null, rootpath=null where storeid in (select id from store where name = 'SELENIOPOOL_16_9_PROCESSING'); +@ +update storeuri set protocol='LOCAL', URI='/mnt/PEABLEBEACH/SELENIOPOOL/4_3', username=null, password=null, rootpath=null where storeid in (select id from store where name = 'SELENIOPOOL_4_3'); +@ +update storeuri set protocol='LOCAL', URI='/mnt/PEABLEBEACH/SELENIOPOOL/4_3/PROCESSING', username=null, password=null, rootpath=null where storeid in (select id from store where name = 'SELENIOPOOL_4_3_PROCESSING'); +@ + diff --git a/server/user.jobengine.osgi.db/migrations/scripts/030_insert_itemtype_musor_clean.sql b/server/user.jobengine.osgi.db/migrations/scripts/030_insert_itemtype_musor_clean.sql new file mode 100644 index 00000000..4ddc872e --- /dev/null +++ b/server/user.jobengine.osgi.db/migrations/scripts/030_insert_itemtype_musor_clean.sql @@ -0,0 +1,11 @@ +-- // Insert Musor CLEAN itemtype +-- Migration SQL that makes the change goes here. + +INSERT INTO ITEMTYPE (NAME, DESCRIPTION, ISSTATIC) VALUES ('Műsor CLEAN', 'Műsor CLEAN', 'N') +@ + +-- //@UNDO +-- SQL to undo the change goes here. + +DELETE FROM ITEMTYPE WHERE NAME='Műsor CLEAN' +@ \ No newline at end of file diff --git a/server/user.jobengine.osgi.db/pom.xml b/server/user.jobengine.osgi.db/pom.xml index d59dcf0e..ea2ba4e7 100644 --- a/server/user.jobengine.osgi.db/pom.xml +++ b/server/user.jobengine.osgi.db/pom.xml @@ -12,4 +12,36 @@ user.jobengine.osgi.db eclipse-plugin 1.0.0 + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + + copy-jar + install + + copy-resources + + + ${project.basedir}}/../-product/target/deploy + + + target + + ${project.artifactId}_${project.version}.jar + + + + + + + + + + \ No newline at end of file diff --git a/server/user.jobengine.osgi.server/pom.xml b/server/user.jobengine.osgi.server/pom.xml index 77c5d836..edfb0de3 100644 --- a/server/user.jobengine.osgi.server/pom.xml +++ b/server/user.jobengine.osgi.server/pom.xml @@ -48,4 +48,36 @@ + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + + copy-jar + install + + copy-resources + + + ${project.basedir}}/../-product/target/deploy + + + target + + ${project.artifactId}_${project.version}.jar + + + + + + + + + + \ No newline at end of file diff --git a/server/user.jobengine.osgi.server/src/user/jobengine/server/IJobEngine.java b/server/user.jobengine.osgi.server/src/user/jobengine/server/IJobEngine.java index 3f775fab..d6bb6057 100644 --- a/server/user.jobengine.osgi.server/src/user/jobengine/server/IJobEngine.java +++ b/server/user.jobengine.osgi.server/src/user/jobengine/server/IJobEngine.java @@ -112,6 +112,8 @@ public interface IJobEngine { void putOutputsToStack(IJobRuntime jobRuntime, Object[] outputs); + void reloadGracefully() throws Exception; + void removeFromExecutorQueue(IJobRuntime jobRuntime); void removeFromRunQueue(IJobRuntime jobRuntime); diff --git a/server/user.jobengine.osgi.server/src/user/jobengine/server/IJobStepExecutor.java b/server/user.jobengine.osgi.server/src/user/jobengine/server/IJobStepExecutor.java index 7ad2abb1..02dc8dd9 100644 --- a/server/user.jobengine.osgi.server/src/user/jobengine/server/IJobStepExecutor.java +++ b/server/user.jobengine.osgi.server/src/user/jobengine/server/IJobStepExecutor.java @@ -36,6 +36,10 @@ public interface IJobStepExecutor { void revoke(IJobRuntime jobRuntime); + void setJobEngine(IJobEngine jobEngine); + + void setMaxConcurrent(int maxConcurrent); + /** * V�grehajt� le�ll�t�sa. Minden sz�l le�ll�t�sra ker�l. */ @@ -44,7 +48,7 @@ public interface IJobStepExecutor { /** * A v�grehajt� elind�t�sa. */ - void startup(IJobEngine jobEngine); + void startup(); ClusteredJob steelJob() throws InterruptedException; diff --git a/server/user.jobengine.osgi.server/src/user/jobengine/server/JobEngine.java b/server/user.jobengine.osgi.server/src/user/jobengine/server/JobEngine.java index 0a05285e..0a46bbf6 100644 --- a/server/user.jobengine.osgi.server/src/user/jobengine/server/JobEngine.java +++ b/server/user.jobengine.osgi.server/src/user/jobengine/server/JobEngine.java @@ -283,12 +283,21 @@ public class JobEngine implements IJobEngine { @Override public void addStepExecutor(IJobStepExecutor executor) { //Class stepClass = executor.getStepClass(); + executor.setJobEngine(this); String unitName = executor.getStepUnitName(); if (!executors.containsKey(unitName)) { - logger.info("Executor registered {}", unitName); + logger.info("Executor now registered for {}", unitName); executors.put(unitName, executor); - } else - logger.debug("Executor already registered {}", unitName); + } else { + logger.info("Executor already registered for {}", unitName); + IJobStepExecutor stepExecutor = executors.get(unitName); + int currentMaxConcurrent = stepExecutor.getMaxConcurrent(); + int newMaxConcurrent = executor.getMaxConcurrent(); + if (currentMaxConcurrent != newMaxConcurrent) { + stepExecutor.setMaxConcurrent(newMaxConcurrent); + logger.info("Executor maxConcurrent changed from {} to {}", currentMaxConcurrent, newMaxConcurrent); + } + } } @@ -667,7 +676,7 @@ public class JobEngine implements IJobEngine { public void loadExecutors() { // TODO // shutdownExecutors(); - executors.clear(); + //executors.clear(); InputStream stream = null; try { String stepRoot = DirectoryUtils.normalize(System.getProperty(STEPSROOT), File.separator); @@ -884,6 +893,22 @@ public class JobEngine implements IJobEngine { } } + @Override + public void reloadGracefully() throws Exception { + if (schedulerService != null) + schedulerService.shutdown(); + + loadPrograms(); + loadExecutors(); + + //startupExecutors(); + + schedulerService = new SchedulerService(this); + schedulerService.startup(); + logger.info("JobEngine gracefully reloaded"); + isRunning = true; + } + @Override public void removeFromExecutorQueue(IJobRuntime jobRuntime) { for (IJobStepExecutor executor : executors.values()) @@ -1107,7 +1132,7 @@ public class JobEngine implements IJobEngine { private void startupExecutors() { for (IJobStepExecutor executor : executors.values()) - executor.startup(this); + executor.startup(); } @Override diff --git a/server/user.jobengine.osgi.server/src/user/jobengine/server/JobStepExecutor.java b/server/user.jobengine.osgi.server/src/user/jobengine/server/JobStepExecutor.java index a6be04f5..d2dd3a50 100644 --- a/server/user.jobengine.osgi.server/src/user/jobengine/server/JobStepExecutor.java +++ b/server/user.jobengine.osgi.server/src/user/jobengine/server/JobStepExecutor.java @@ -6,6 +6,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.PriorityBlockingQueue; @@ -41,6 +42,9 @@ public class JobStepExecutor implements IJobStepExecutor { try { Thread.sleep(IJobEngine.QUEUE_POLL_INTERVAL_MS); + if (jobEngine == null) + logger.info("JobEngine is null"); + if (jobEngine.isWorker() && isRemote) { //a worker is csak azokat akarja vegrehajtani ClusteredJob job = jobEngine.getRemoteEngine().getRemoteJob(getStepUnitName()); @@ -76,12 +80,11 @@ public class JobStepExecutor implements IJobStepExecutor { } if (jobRuntime != null) { - long submitted = jobRuntime.getSubmitted().getTime(); - long current = System.currentTimeMillis(); - boolean timeout = current - submitted > WAIT_FOR_REMOTE; - //ha remote, de nem jelentkezik senki, akkor helyi vegrehajtas if (isRemote) { + long submitted = jobRuntime.getSubmitted().getTime(); + long current = System.currentTimeMillis(); + boolean timeout = current - submitted > WAIT_FOR_REMOTE; if (timeout) { logger.info("Remote JobStep timed out, processing locally."); } else { @@ -95,7 +98,11 @@ public class JobStepExecutor implements IJobStepExecutor { logger.info("Executing locally {}", jobRuntime.getId()); //jobRuntime.setDescription(PROCESSING_LOCALLY); Object[] inputs = jobEngine.getInputsFromStack(jobRuntime); - runStepObject(jobRuntime, inputs); + IJobStep stepObject = runStepObject(jobRuntime, inputs); + if (stepObject.isTest()) { + logger.trace("Finishing test worker"); + break; + } } if (shutdown) { @@ -117,15 +124,22 @@ public class JobStepExecutor implements IJobStepExecutor { jobRuntime = null; step = null; } + if (retireUnusedWorker(this)) { + break; + + } + } } } - private void runStepObject(IJobRuntime jobRuntime, Object[] inputs) throws Throwable { + private IJobStep runStepObject(IJobRuntime jobRuntime, Object[] inputs) throws Throwable { + IJobStep step = null; + jobRuntime.setStatus(JobStatus.EXECUTING); jobRuntime.NotifyUpdate(); - IJobStep step = createStepObject(); + step = createStepObject(); if (step == null) throw new Exception("Step object is null"); logger.debug("{} executing", jobRuntime); @@ -139,6 +153,7 @@ public class JobStepExecutor implements IJobStepExecutor { // jobEngine.sendMessage(new JobStepCompletedMessage(jobRuntime.getId(), outputs)); // } jobEngine.sendMessage(new JobStepCompletedMessage(jobRuntime.getId(), outputs)); + return step; } public void shutdown() { @@ -156,7 +171,7 @@ public class JobStepExecutor implements IJobStepExecutor { private Logger logger; private PriorityBlockingQueue queue; - private List workers; + private List workers = Collections.synchronizedList(new ArrayList()); protected IJobEngine jobEngine; private CountDownLatch barrier; private Class stepClass; @@ -180,6 +195,21 @@ public class JobStepExecutor implements IJobStepExecutor { create(className, maxConcurrent, false); } + private void addWorkers(int count) { + barrier = new CountDownLatch(count); + + for (int index = 0; index < count; index++) { + Worker worker = new Worker(); + worker.start(); + workers.add(worker); + } + + try { + barrier.await(); + } catch (Exception e) { + } + } + @Override public void changePriority(IJobRuntime runtime) { if (queue != null && runtime != null) { @@ -220,12 +250,6 @@ public class JobStepExecutor implements IJobStepExecutor { queue = new PriorityBlockingQueue(); this.maxConcurrent = maxConcurrent; - if (maxConcurrent > 0) { - barrier = new CountDownLatch(maxConcurrent); - workers = new ArrayList(); - for (int index = 0; index < maxConcurrent; index++) - workers.add(new Worker()); - } } protected IJobStep createStepObject() throws Exception { @@ -259,7 +283,7 @@ public class JobStepExecutor implements IJobStepExecutor { } @Override - public int getMaxConcurrent() { + public synchronized int getMaxConcurrent() { return maxConcurrent; } @@ -312,31 +336,55 @@ public class JobStepExecutor implements IJobStepExecutor { return result; } + protected synchronized boolean retireUnusedWorker(Worker worker) { + boolean result = false; + if (getMaxConcurrent() < workers.size()) { + workers.remove(worker); + logger.info("Worker {} is retired, max {} current {}", getStepUnitName(), getMaxConcurrent(), workers.size()); + result = true; + } + + return result; + } + @Override public void revoke(IJobRuntime jobRuntime) { queue.remove(jobRuntime); } @Override - public void shutdown() { - for (Worker w : workers) - w.shutdown(); + public void setJobEngine(IJobEngine jobEngine) { + this.jobEngine = jobEngine; } @Override - public void startup(IJobEngine jobEngine) { - this.jobEngine = jobEngine; - if (workers != null) { - for (Worker w : workers) - w.start(); - } - if (barrier != null) { - try { - barrier.await(); - } catch (Exception e) { - } - //barrier.reset(); + public synchronized void setMaxConcurrent(int maxConcurrent) { + if (this.maxConcurrent == maxConcurrent) + return; + + if (this.maxConcurrent < maxConcurrent) { + int diff = maxConcurrent - this.maxConcurrent; + addWorkers(diff); } + + this.maxConcurrent = maxConcurrent; + } + + @Override + public void shutdown() { + if (workers == null) + return; + + workers.forEach(worker -> worker.shutdown()); + workers.clear(); + } + + @Override + public void startup() { + if (maxConcurrent == 0) + return; + + addWorkers(maxConcurrent); } @Override @@ -368,7 +416,6 @@ public class JobStepExecutor implements IJobStepExecutor { @Override public void waitShutdown() { - for (Worker w : workers) - w.waitShutdown(); + workers.forEach(worker -> worker.waitShutdown()); } } diff --git a/server/user.jobengine.osgi.server/src/user/jobengine/server/steps/IJobStep.java b/server/user.jobengine.osgi.server/src/user/jobengine/server/steps/IJobStep.java index e84eb5de..f3c8fde3 100644 --- a/server/user.jobengine.osgi.server/src/user/jobengine/server/steps/IJobStep.java +++ b/server/user.jobengine.osgi.server/src/user/jobengine/server/steps/IJobStep.java @@ -9,6 +9,7 @@ public interface IJobStep { void cleanup(); - Object[] run(IJobEngine jobEngine, IJobRuntime jobRuntime, Object[] inputs) throws Throwable; + boolean isTest(); + Object[] run(IJobEngine jobEngine, IJobRuntime jobRuntime, Object[] inputs) throws Throwable; } diff --git a/server/user.jobengine.osgi.server/src/user/jobengine/server/steps/JobStep.java b/server/user.jobengine.osgi.server/src/user/jobengine/server/steps/JobStep.java index 42de2048..79f19de7 100644 --- a/server/user.jobengine.osgi.server/src/user/jobengine/server/steps/JobStep.java +++ b/server/user.jobengine.osgi.server/src/user/jobengine/server/steps/JobStep.java @@ -143,6 +143,11 @@ public class JobStep implements IJobStep { return (MediaCubeMarker) jobRuntime.getSessionMarker(); } + @Override + public boolean isTest() { + return false; + } + @Override public Object[] run(IJobEngine jobEngine, IJobRuntime jobRuntime, Object[] inputs) throws Throwable { this.jobEngine = jobEngine; diff --git a/server/user.jobengine.osgi.server/test/log4j2-test.xml b/server/user.jobengine.osgi.server/test/log4j2-test.xml new file mode 100644 index 00000000..ad428dc7 --- /dev/null +++ b/server/user.jobengine.osgi.server/test/log4j2-test.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/server/user.jobengine.osgi.server/test/user/jobengine/server/JobStepExecutorTest.java b/server/user.jobengine.osgi.server/test/user/jobengine/server/JobStepExecutorTest.java index 19efc467..6f3441e8 100644 --- a/server/user.jobengine.osgi.server/test/user/jobengine/server/JobStepExecutorTest.java +++ b/server/user.jobengine.osgi.server/test/user/jobengine/server/JobStepExecutorTest.java @@ -37,20 +37,25 @@ public class JobStepExecutorTest { }; jobStep = new IJobStep() { @Override - public Object[] run(IJobEngine jobEngine, IJobRuntime jobRuntime, Object[] inputs) throws Exception { - called[0] = true; - if (threadCounter != null) - threadCounter.increment(); - return null; + public boolean canContinue() { + return false; } @Override - public boolean canContinue() { + public void cleanup() { + } + + @Override + public boolean isTest() { return false; } @Override - public void cleanup() { + public Object[] run(IJobEngine jobEngine, IJobRuntime jobRuntime, Object[] inputs) throws Exception { + called[0] = true; + if (threadCounter != null) + threadCounter.increment(); + return null; } }; @@ -62,7 +67,7 @@ public class JobStepExecutorTest { } }; - sut.startup(jobEngine); + sut.startup(); jobRuntime1 = new JobRuntime(jobEngine, new Program()); jobRuntime1.pushToStack("var1"); @@ -81,40 +86,15 @@ public class JobStepExecutorTest { } @Test - public void testSendJobCompletedMessage() { + public void testConstructor_Dynamic_ExistingClass() throws Exception { // Fixture - messageCounter = new ThreadCounter(2); + System.setProperty("jobengine.jobsteps.root", "c:\\$Work\\USER\\JobEngine\\plugins"); // Exercise - sut.submit(jobRuntime1); - sut.submit(jobRuntime2); + sut = new JobStepExecutor("user.jobengine.server.steps.JobStepDyn", 1); // Verify - messageCounter.waitFinish(); - assertEquals(2, messages.size()); - assertEquals(JobStepCompletedMessage.class, messages.get(0).getClass()); - assertEquals(1, messages.get(0).getJobId()); - assertEquals(JobStepCompletedMessage.class, messages.get(1).getClass()); - assertEquals(2, messages.get(1).getJobId()); - } - - @Test - public void testRunStep() { - // Fixture - threadCounter = new ThreadCounter(1); - - // Exercise - sut.submit(jobRuntime1); - - // Validate - threadCounter.waitFinish(); - assertTrue(called[0]); - } - - @Test(expected = NullPointerException.class) - public void testSubmit_NullParameter() { - // Exercise - sut.submit(null); + assertTrue(sut.getStepClass() != null); } @Test(expected = RuntimeException.class) @@ -122,18 +102,6 @@ public class JobStepExecutorTest { new JobStepExecutor("user.jobengine.server.steps.JobStepDyn", 1); } - @Test - public void testConstructor_Dynamic_ExistingClass() throws Exception { - // Fixture - System.setProperty("jobengine.jobsteps.root", "c:\\$Work\\USER\\JobEngine\\plugins"); - - // Exercise - sut = new JobStepExecutor("user.jobengine.server.steps.JobStepDyn", 1); - - // Verify - assertTrue(sut.getStepClass() != null); - } - @Test public void testDynamicExecutor() throws Exception { // Fixture @@ -145,7 +113,7 @@ public class JobStepExecutorTest { jobRuntime1.pushToStack(2); System.setProperty("jobengine.jobsteps.root", "c:\\$Work\\USER\\JobEngine\\plugins"); sut = new JobStepExecutor("user.jobengine.server.steps.JobStepDyn", 1); - sut.startup(jobEngine); + sut.startup(); messageCounter = new ThreadCounter(1); // Exercise @@ -159,4 +127,41 @@ public class JobStepExecutorTest { //check stack } + @Test + public void testRunStep() { + // Fixture + threadCounter = new ThreadCounter(1); + + // Exercise + sut.submit(jobRuntime1); + + // Validate + threadCounter.waitFinish(); + assertTrue(called[0]); + } + + @Test + public void testSendJobCompletedMessage() { + // Fixture + messageCounter = new ThreadCounter(2); + + // Exercise + sut.submit(jobRuntime1); + sut.submit(jobRuntime2); + + // Verify + messageCounter.waitFinish(); + assertEquals(2, messages.size()); + assertEquals(JobStepCompletedMessage.class, messages.get(0).getClass()); + assertEquals(1, messages.get(0).getJobId()); + assertEquals(JobStepCompletedMessage.class, messages.get(1).getClass()); + assertEquals(2, messages.get(1).getJobId()); + } + + @Test(expected = NullPointerException.class) + public void testSubmit_NullParameter() { + // Exercise + sut.submit(null); + } + } diff --git a/server/user.jobengine.osgi.server/test/user/jobengine/server/JobStepExecutorTest1.java b/server/user.jobengine.osgi.server/test/user/jobengine/server/JobStepExecutorTest1.java new file mode 100644 index 00000000..b5aea6dd --- /dev/null +++ b/server/user.jobengine.osgi.server/test/user/jobengine/server/JobStepExecutorTest1.java @@ -0,0 +1,142 @@ +package user.jobengine.server; + +import static org.junit.Assert.assertEquals; + +import java.sql.Timestamp; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import user.jobengine.server.steps.IJobStep; + +public class JobStepExecutorTest1 { + @BeforeClass + public static void beforeClass() { + //System.setProperty("org.apache.logging.log4j.simplelog.StatusLogger.level", "TRACE"); + } + + @Before + public void setup() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testTicketBase() throws Exception { + // Fixture + int expectedJobCount = 3; + IJobEngine jobEngine = new JobEngine(); + final AtomicInteger stepCallCount = new AtomicInteger(); + final CountDownLatch stepStartBarrier = new CountDownLatch(expectedJobCount); + + final IJobStep jobStep = new IJobStep() { + @Override + public boolean canContinue() { + return true; + } + + @Override + public void cleanup() { + } + + @Override + public boolean isTest() { + return true; + } + + @Override + public Object[] run(IJobEngine jobEngine, IJobRuntime jobRuntime, Object[] inputs) throws Exception { + stepCallCount.getAndIncrement(); + stepStartBarrier.countDown(); + return null; + } + }; + + final IJobStepExecutor sut = new JobStepExecutor(IJobStep.class, expectedJobCount) { + @Override + protected IJobStep createStepObject() throws InstantiationException, IllegalAccessException { + return jobStep; + } + + }; + sut.setJobEngine(jobEngine); + sut.startup(); + + // Exercise + for (int i = 0; i < expectedJobCount; i++) { + JobRuntime jobRuntime = new JobRuntime(jobEngine, new Program()); + jobRuntime.setSubmitted(new Timestamp(System.currentTimeMillis())); + jobRuntime.pushToStack(0); + jobRuntime.saveStack(); + sut.submit(jobRuntime); + } + stepStartBarrier.await(); + // Verify + assertEquals(expectedJobCount, stepCallCount.get()); + } + + @Test + public void testTicketIncrement() throws Exception { + // Fixture + int expectedJobCount = 3; + IJobEngine jobEngine = new JobEngine(); + final int[] stepCallCount = { 0 }; + final CountDownLatch stepStartBarrier = new CountDownLatch(expectedJobCount); + final IJobStep jobStep = new IJobStep() { + @Override + public boolean canContinue() { + return true; + } + + @Override + public void cleanup() { + } + + @Override + public boolean isTest() { + return true; + } + + @Override + public Object[] run(IJobEngine jobEngine, IJobRuntime jobRuntime, Object[] inputs) throws Exception { + + stepCallCount[0]++; + stepStartBarrier.countDown(); + + return null; + } + + }; + + final IJobStepExecutor sut = new JobStepExecutor(IJobStep.class, 1) { + @Override + protected IJobStep createStepObject() throws InstantiationException, IllegalAccessException { + return jobStep; + } + + }; + sut.startup(); + + // Exercise + + sut.setMaxConcurrent(sut.getMaxConcurrent() + 1); + + for (int i = 0; i < expectedJobCount; i++) { + JobRuntime jobRuntime = new JobRuntime(jobEngine, new Program()); + jobRuntime.setSubmitted(new Timestamp(System.currentTimeMillis())); + jobRuntime.pushToStack(0); + sut.submit(jobRuntime); + } + stepStartBarrier.await(); + + // Verify + assertEquals(expectedJobCount, stepCallCount[0]); + } + +} diff --git a/server/user.jobengine.osgi.services/pom.xml b/server/user.jobengine.osgi.services/pom.xml index 787546d8..d0b367b5 100644 --- a/server/user.jobengine.osgi.services/pom.xml +++ b/server/user.jobengine.osgi.services/pom.xml @@ -44,29 +44,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + - org.codehaus.mojo - exec-maven-plugin - 1.5.0 + org.apache.maven.plugins + maven-resources-plugin + 2.7 - install + copy-jar install - exec + copy-resources - echo - - yyy - + ${project.basedir}}/../-product/target/deploy + + + target + + ${project.artifactId}_${project.version}.jar + + + - diff --git a/server/user.mediacube.gui/pages/joblist.zul b/server/user.mediacube.gui/pages/joblist.zul index 480bbcb4..900880a4 100644 --- a/server/user.mediacube.gui/pages/joblist.zul +++ b/server/user.mediacube.gui/pages/joblist.zul @@ -30,6 +30,7 @@ + diff --git a/server/user.mediacube.gui/pom.xml b/server/user.mediacube.gui/pom.xml index 73ceef6c..4cef5f7d 100644 --- a/server/user.mediacube.gui/pom.xml +++ b/server/user.mediacube.gui/pom.xml @@ -11,4 +11,37 @@ user.mediacube.gui eclipse-plugin 1.0.0 - \ No newline at end of file + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + + copy-jar + install + + copy-resources + + + ${project.basedir}}/../-product/target/deploy + + + target + + ${project.artifactId}_${project.version}.jar + + + + + + + + + + + + \ No newline at end of file diff --git a/server/user.mediacube.gui/resources/i3-label_hu.properties b/server/user.mediacube.gui/resources/i3-label_hu.properties index b0e183c6..e54983be 100644 --- a/server/user.mediacube.gui/resources/i3-label_hu.properties +++ b/server/user.mediacube.gui/resources/i3-label_hu.properties @@ -1,4 +1,4 @@ -version=2.7.0 +version=2.7.1 footer=2016-2020 © Copyright User Rendszerház Kft. login_info=Információ diff --git a/server/user.mediacube.gui/src/user/jobengine/zk/model/FileSizeConverter.java b/server/user.mediacube.gui/src/user/jobengine/zk/model/FileSizeConverter.java index 09183ca2..97685bfd 100644 --- a/server/user.mediacube.gui/src/user/jobengine/zk/model/FileSizeConverter.java +++ b/server/user.mediacube.gui/src/user/jobengine/zk/model/FileSizeConverter.java @@ -1,20 +1,21 @@ package user.jobengine.zk.model; -import org.apache.commons.io.FileUtils; import org.zkoss.bind.BindContext; import org.zkoss.bind.Converter; import org.zkoss.zul.Label; -public class FileSizeConverter implements Converter { +import user.commons.strings.FileSizeUtils; - @Override - public String coerceToUi(Long fileSize, Label paramC, BindContext context) { - return FileUtils.byteCountToDisplaySize(fileSize); - } +public class FileSizeConverter implements Converter { @Override public Long coerceToBean(String paramU, Label paramC, BindContext paramBindContext) { // TODO Auto-generated method stub return null; } + + @Override + public String coerceToUi(Long fileSize, Label paramC, BindContext context) { + return FileSizeUtils.sizeAsString(fileSize); + } } diff --git a/server/user.mediacube.gui/src/user/jobengine/zk/model/JobListModel.java b/server/user.mediacube.gui/src/user/jobengine/zk/model/JobListModel.java index d45e5ccf..ef49696f 100644 --- a/server/user.mediacube.gui/src/user/jobengine/zk/model/JobListModel.java +++ b/server/user.mediacube.gui/src/user/jobengine/zk/model/JobListModel.java @@ -333,6 +333,15 @@ public class JobListModel extends AsyncBaseModel implements IJobChangedListener t.start(); } + @Command + public void reload() { + try { + jobEngine.reloadGracefully(); + } catch (Exception e) { + Messagebox.show(e.getMessage(), "Hiba!", Messagebox.OK, Messagebox.ERROR); + } + } + @Command @NotifyChange("searchStatus") public void resetSearchRelated() { diff --git a/server/user.mediacube.metadata/pom.xml b/server/user.mediacube.metadata/pom.xml index e8c132cf..bf09ee71 100644 --- a/server/user.mediacube.metadata/pom.xml +++ b/server/user.mediacube.metadata/pom.xml @@ -11,5 +11,35 @@ user.mediacube.metadata eclipse-plugin 1.0.0 + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + + copy-jar + install + + copy-resources + + + ${project.basedir}}/../-product/target/deploy + + + target + + ${project.artifactId}_${project.version}.jar + + + + + + + + + \ No newline at end of file diff --git a/server/user.tsm.client/pom.xml b/server/user.tsm.client/pom.xml index e81dac19..e228ea6d 100644 --- a/server/user.tsm.client/pom.xml +++ b/server/user.tsm.client/pom.xml @@ -12,4 +12,35 @@ user.tsm.client eclipse-plugin 1.2.0 + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + + copy-jar + install + + copy-resources + + + ${project.basedir}}/../-product/target/deploy + + + target + + ${project.artifactId}_${project.version}.jar + + + + + + + + + + \ No newline at end of file -- 2.54.0