.editorconfig000066400000000000000000000006371516100455000135360ustar00rootroot00000000000000# EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] # Change these settings to your own preference indent_style = space indent_size = 4 # We recommend you to keep these unchanged end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false .gitignore000066400000000000000000000003721516100455000130450ustar00rootroot00000000000000/build /node_modules /*.log /generated /ramdisk-settings.json .idea/workspace.xml .idea/tasks.xml .idea/profiles_settings.xml .idea/inspectionProfiles/Project_Default.xml .idea/inspectionProfiles/profiles_settings.xml node_modules/.yarn-integrity .idea/000077500000000000000000000000001516100455000120335ustar00rootroot00000000000000.idea/codeStyleSettings.xml000066400000000000000000000004251516100455000162320ustar00rootroot00000000000000 .idea/modules.xml000066400000000000000000000004121516100455000142220ustar00rootroot00000000000000 .idea/ramdisk.iml000066400000000000000000000005201516100455000141650ustar00rootroot00000000000000 .idea/vcs.xml000066400000000000000000000002471516100455000133530ustar00rootroot00000000000000 .npmignore000066400000000000000000000002221516100455000130460ustar00rootroot00000000000000/.idea /artifacts /build /test /node_modules /*.iml /*.ipr /*.iws /.travis.yml /.scrutinizer.yml /Gruntfile.js /*.lock *.log /corifeus-boot.json .travis.yml000066400000000000000000000017661516100455000131760ustar00rootroot00000000000000language: node_js sudo: required cache: npm: false node_js: - lts/* env: matrix: - CXX=g++-4.8 global: secure: 0K7XKLpX0QWoI+dTey16SGssHaX55WWz6twIF4VxOmqenRzRjXyU1lgI8RTLnjwBm7rHRfabzTVs0DosbEvCnleGc6t/FiC3otG7zY/MaX/Ak4UGWY6GpRrne3iBtrRVN+2i3h0OHf/wiFhIik4Yo5iRWiTVRGxULKKkQBFKYuipLqAviN6NIUjdMX92Rz53GEPv8gNgh0ezl8u1MAeR8fQL+hrQLelvuckffpR8bRDXSnwTnblnuKm50/5b1Eq/ap+e7s+C2coJYlL+yCmqcW2KZOI5kmEmahVgS2DDzswzMTN4QJxkiJqOKaCyfCcaMsgl/jnPBBlS8sysHmhWJvZrS7oocfA8a3FjW0C9yc8f9V9zR5XssV3/LAgpqUK9URie8l/G4qU/vFFHp/f7FOBLpTp8BsqZ5HsIIaVO0CFHoxJ69B0rWzaxbNhteNqcmisJbJRZdQYHQq1ZSnjBi60Yrds2nCzQwI7odQA9Arx9SdqH96A3qS4UMQ4SCbHMG+2NXfY21JZA1fzdAb8nuCRcq/Q6KYBujYz9QVUsYwP5k7Mr/7+5Zx2ySVMSyfD9dxnb1bqa+4ZeuzY7gAUx/ORKVMZ8WM9ct2Ytrccyc6KQHmkfxInnJqZlR77CdJU7JPMlhSGPQJx3Euybd5PgFWcDAQuB51oMSw9Dl6yFTVU= addons: apt: sources: - ubuntu-toolchain-r-test packages: - dbus - gcc-4.8 - g++-4.8 - libglib2.0-dev before_script: - npm install -g node-gyp - npm install -g grunt-cli npm Gruntfile.js000066400000000000000000000007471516100455000133600ustar00rootroot00000000000000module.exports = (grunt) => { const builder = require(`corifeus-builder`); const loader = new builder.loader(grunt); loader.js({ replacer: { type: 'p3x', npmio: true, }, config: { 'clean': { 'generated': [ 'generated' ] } } }); const defaults = [] grunt.registerTask('default', defaults.concat(builder.config.task.build.js)); } LICENSE000066400000000000000000000023411516100455000120600ustar00rootroot00000000000000 @license p3x-ramdisk v2020.10.129 💾 Linux RAM disk persistent with Systemd timer, service and suspend https://corifeus.com/ramdisk Copyright (c) 2020 Patrik Laszlo / P3X / Corifeus and contributors. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. README.md000066400000000000000000000274551516100455000123470ustar00rootroot00000000000000[//]: #@corifeus-header [![NPM](https://nodei.co/npm/p3x-ramdisk.png?downloads=true&downloadRank=true)](https://www.npmjs.com/package/p3x-ramdisk/) [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://paypal.me/patrikx3) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Corifeus @ Facebook](https://img.shields.io/badge/Facebook-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) [![Build Status](https://api.travis-ci.com/patrikx3/ramdisk.svg?branch=master)](https://travis-ci.com/patrikx3/ramdisk) [![Uptime Robot ratio (30 days)](https://img.shields.io/uptimerobot/ratio/m780749701-41bcade28c1ea8154eda7cca.svg)](https://uptimerobot.patrikx3.com/) # 💾 Linux RAM disk persistent with Systemd timer, service and suspend v2020.10.129 **Bugs are evident™ - MATRIX️** ### NodeJs LTS Version Requirement ```txt >=12.13.0 ``` ### Built on NodeJs ```txt v14.15.0 ``` The ```async``` and ```await``` keywords are required. Only the latest LTS variant is supported. Install NodeJs: https://nodejs.org/en/download/package-manager/ # Description [//]: #@corifeus-header:end ## Breaking change [readme](artifacts/readme/breaking-change.md) # Install ```bash sudo npm install -g p3x-ramdisk --unsafe-perm=true --allow-root ``` ## IntelliJ Speed Based on: http://sheroz.com/pages/blog/improving-development-performance-moving-intellij-idea-cache-ramdisk.html ![http://sheroz.com/pages/blog/improving-development-performance-moving-intellij-idea-cache-ramdisk.html](http://cdn.corifeus.com/git/ramdisk/artifacts/original-idea.png "After all these tricks I tried to open my current project with IntelliJ IDEA 12 …") ```text After all these tricks I tried to open my current project with IntelliJ IDEA 12 … Wow!!! It is fantastic! Works like a … sword!!! Why sword?! I don’t know exactly. May be because of Darcula theme. This is just first thought what came to my mind seeing the overall results. The Intellij IDEA 12 now works as a lightsaber sword, as a weapon of a Jedi Knight, which you can trust in Java world! ``` ## Features * Requires tmpfs, bash, fstab, rsync, memory :) * Usually, all requirements are available in many Unix flavors * SystemD * Service * Timer * Suspend * RAM disk to HDD * Linux for sure, easy to extend for Unix, BSD, macOS * Should might need some tuning, but the functions are there, I just only tested in Linux. ## Intel Optane I think my ramdisk is faster. Ciao!!! (: # Use case Speed up IntelliJ and development Node. (Tons of files.) The result is that the development is many folds faster. No waiting at all. # Changelog * [Since **v2019.2.1**, BREAKING CHANGELOG](artifacts/readme/breaking-change.md#v201921) # Install ## Warning Do not use the ```$USER``` variable, use the actual username, like ```p3x-robot```. ## So actual install ```text patrikx3@workstation ~ $ p3x-ramdisk install --help Usage: install [options] Install a p3x-ramdisk Options: -h, --help output usage information -r, --rampath [path] The path of the ram disk, default /home/{{USER}}/ramdisk -p, --persistent [path] The path of the ram persistent, default /home/{{USER}}/ramdisk-persistent -g, --gid [group] The gid, it you omit it is the current user -t, --timer [minutes] The timer in minutes, minimum about 10 minutes, Default is 20 minutes, the best -s, --size [megabytes] Ramdisk in size of megabytes, default is 4096 megabytes ``` # IMPORTANT Trash is disabled in GNOME with p3x-ramdisk. It will ask for confirm to delete data. You might have it in the previous save. It is possible to enable trash bin, but for me is not important now, so I disabled, memory is not cheap. # Setup **Use GitHub for info, NPM hides wide strings.** https://github.com/patrikx3/ramdisk ```bash # of course your data will never be deleted, # double persistence (current, previouse, saves every 20 minutes) # load on boot, plus at shutdown and suspend it saves sudo npm install -g p3x-ramdisk --unsafe-perm=true --allow-root # install # if you need less/more memory, add -s 1024 or even more, 10GB is good :) sudo p3x-ramdisk install {{USER}} # Get the output, add to /etc/fstab echo "tmpfs /home/{{USER}}/ramdisk tmpfs gid=10000,uid=10000,size=4096M 0 0" | sudo tee -a /etc/fstab sudo mount -a # you should verify the ramdisk is existing now, you might have to reboot # on linux it can show your settings, like below: df -h # if there is an error, you can sudo p3x-ramdisk stop {{USER}} # if all good # STARTUP THE RAMDISK PERSISTENT sudo p3x-ramdisk start {{USER}} # you can work like here (this a symlink, so you can't accidentally delete # so next time boot, it will re-create the symlink ...) # /home/{{USER}}/ramdisk/p3x-persistence # SOME DEBUG p3x-ramdisk watch {{USER}} # to trigger a savs p3x-ramdisk save {{USER}} # you don't need usually to save # the default is 20 minutes # the systemd service saves on suspend and shutdown # to stop the services # removes sync, so the ramdisk files will be unavailable, # only be in /home/username/ramdisk-persistent/current sudo p3x-ramdisk stop {{USER}} # your duplicate copies are ls -all /home/{{USER}}/ramdisk-persistent/current/ ls -all /home/{{USER}}/ramdisk-persistent/previous/ # you are done # the default use case is to speed up working with IntelliJ and my projects to ramdisk # if you just want persistent folder and that's all # there is a special folder, .p3x-ramdisk-link # everything there is linked into /home/{{USER}} # if there is nothing in .p3x-ramdisk-link # no linking is. to test it, you might not need it. ``` ## Linkin' in /home ```bash ### //LINKING:START # LINKING - IS NOT REQUIRED, but is good as a sword :) p3x-ramdisk save {{USER}} sudo p3x-ramdisk stop {{USER}} mkdir -p /home/{{USER}}/ramdisk-persistent/current/.p3x-ramdisk-link cp -avr /home/{{USER}}/.IntelliJIdea2019.2 /home/{{USER}}/ramdisk-persistent/current/.p3x-ramdisk-link # backup mkdir -p /home/{{USER}}/backup mv /home/{{USER}}/.IntelliJIdea2019.2 /home/{{USER}}/backup/ # need to delete the originals, since they become symlinks rm -rf /home/{{USER}}/.IntelliJIdea2019.2 ln -s /home/{{USER}}/ramdisk-persistent/current/.p3x-ramdisk-link/.IntelliJIdea2019.2 /home/{{USER}}/.IntelliJIdea2019.2 sudo p3x-ramdisk start {{USER}} ### //LINKING:END ``` # Output ## Install ```text patrikx3@laptop:~/ramdisk/.p3x-ramdik-persistence/content/.p3x-ramdisk-link/Projects/patrikx3/ramdisk$ sudo p3x-ramdisk install patrikx3 -s 6144 2018-05-08 00:30:08: terminal install 2018-05-08 00:30:08: terminal copy 2018-05-08 00:30:08: terminal suspend 2018-05-08 00:30:08: terminal reload services 2018-05-08 00:30:08: terminal install done Settings: { "rampath": "ramdisk", "persistent": "ramdisk-persistent", "uid": "patrikx3", "uidNumber": 10000, "gid": "patrikx3", "timer": 20, "size": "4096", "home": "/home/patrikx3", "script": "/home/patrikx3/.p3x-ramdisk" } Final commands: -------------------------- 1) You only have to do it once, if you haven't done it before echo "tmpfs /home/patrikx3/ramdisk tmpfs gid=10000,uid=10000,size=4096M 0 0" | sudo tee -a /etc/fstab sudo mount -a -------------------------- 2) verify that ramdisk is working, see it here df -h -------------------------- 3) if everything is ok, start the persistent ramdisk sudo p3x-ramdisk start {{USER}} patrikx3@laptop:~/ramdisk/.p3x-ramdik-persistence/content/.p3x-ramdisk-link/Projects/patrikx3/ramdisk$ ``` ## Watching the RAM disk ```bash p3x-ramdisk watch {{USER}} ``` ```text Filesystem Size Used Avail Use% Mounted on tmpfs 4,0G 2,0G 2,1G 50% /home/patrikx3/ramdisk total used free shared buff/cache available Mem: 31G 3,3G 18G 2,0G 9,8G 25G Swap: 8,0G 0B 8,0G Load: 2018-05-27 09:01:49 2018-05-27 09:01:56 0 minutes 7 seconds Save: 2018-05-27 09:20:00 2018-05-27 09:20:16 0 minutes 16 seconds 2018-05-27 09:20:00: timer save, ramdisk to current 2018-05-27 09:20:00: timer save /home/patrikx3/ramdisk/.p3x-ramdik-persistence/content to /home/patrikx3/ramdisk-persistent/current 2018-05-27 09:20:16: timer saved 2018-05-27 09:20:16: timer save done 2018-05-27 09:20:16: timer 0 minutes 16 seconds 5/27/2018, 9:24:45 AM | Persistence 20 minutes | Watch 1 second ``` # LOGS ``` /home/{{USER}}/ramdisk-persistent/ramdisk-persistent.log ``` ``` /home/{{USER}}/ramdisk-persistent/update-at-load.log ``` ``` /home/{{USER}}/ramdisk-persistent/update-at-save.log ``` ## LOG info ```text 2018-05-06 02:57:37: boot loading 2018-05-06 02:57:37: boot load /home/patrikx3/ramdisk-persistent/current to /home/patrikx3/ramdisk/.p3x-ramdik-persistence/content 2018-05-06 02:57:47: boot loaded 2018-05-06 02:57:47: boot link 2018-05-06 02:57:47: boot link /home/patrikx3/ramdisk/.p3x-ramdisk-persistence/content/.IntelliJIdea2018.3 to /home/patrikx3/.IntelliJIdea2018.3 2018-05-06 02:57:47: boot link /home/patrikx3/ramdisk/.p3x-ramdisk-persistence/content/Projects to /home/patrikx3/Projects 2018-05-06 02:57:47: boot link done 2018-05-06 02:57:47: boot 0 minutes 10 seconds 2018-05-06 02:57:47: timer save 2018-05-06 02:57:47: timer save, current to previous 2018-05-06 02:57:47: timer save /home/patrikx3/ramdisk-persistent/current to /home/patrikx3/ramdisk-persistent/previous 2018-05-06 02:57:50: timer saved 2018-05-06 02:57:50: timer save, ramdisk to current 2018-05-06 02:57:50: timer save /home/patrikx3/ramdisk/.p3x-ramdisk-persistence/content to /home/patrikx3/ramdisk-persistent/current 2018-05-06 02:57:53: timer saved 2018-05-06 02:57:53: timer save done 2018-05-06 02:57:53: timer 0 minutes 6 seconds ``` ## LOG Update ```text 2018-05-06 03:31:51 2018-05-06 03:31:57 0 minutes 6 seconds ``` # Thunder ramdisk persistence ```text patrikx3@workstation ~/ramdisk-persistent/current/.p3x-ramdisk-link $ ll total 32 drwxr-xr-x 8 patrikx3 patrikx3 4096 May 7 13:04 ./ drwxr-xr-x 3 patrikx3 patrikx3 4096 May 7 13:02 ../ drwxr-xr-x 4 patrikx3 patrikx3 4096 Apr 25 17:51 .IntelliJIdea2018.3/ patrikx3@workstation ~/ramdisk-persistent/current/.p3x-ramdisk-link $ ``` [//]: #@corifeus-footer --- 🙏 This is an open-source project. Star this repository, if you like it, or even donate to maintain the servers and the development. Thank you so much! Possible, this server, rarely, is down, please, hang on for 15-30 minutes and the server will be back up. All my domains ([patrikx3.com](https://patrikx3.com) and [corifeus.com](https://corifeus.com)) could have minor errors, since I am developing in my free time. However, it is usually stable. **Note about versioning:** Versions are cut in Major.Minor.Patch schema. Major is always the current year. Minor is either 4 (January - June) or 10 (July - December). Patch is incremental by every build. If there is a breaking change, it should be noted in the readme. --- [**P3X-RAMDISK**](https://corifeus.com/ramdisk) Build v2020.10.129 [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZVM4V6HVZJW6) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Like Corifeus @ Facebook](https://img.shields.io/badge/LIKE-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) ## P3X Sponsor [IntelliJ - The most intelligent Java IDE](https://www.jetbrains.com/?from=patrikx3) [![JetBrains](https://cdn.corifeus.com/assets/svg/jetbrains-logo.svg)](https://www.jetbrains.com/?from=patrikx3) [//]: #@corifeus-footer:end artifacts/000077500000000000000000000000001516100455000130335ustar00rootroot00000000000000artifacts/original-idea.png000066400000000000000000003616311516100455000162570ustar00rootroot00000000000000PNG  IHDRl/=sBITOtEXtSoftwaregnome-screenshot> IDATx}@S8/$`<$D9A%ҒjM bt8wٺK_?N^;MzJ[IiIUC JDIE@@ Ĉ~y<B!n^.B! (B!' !0"B~ B (B!' !0"B~ B (B!' !0"B~ BQ>]8d,^H!n`z u dE ,yd_Cֳ@!n5qsB{گ]wE;zHc)Nu3=}}}x_B s+x%4DYPE& @D@YD;0rMUB;VDs!7-tGM &fi a99{e))6B!tR%΂H.S\޲pnȂ-}Br3^W "^ȵ3d׭]7;Ws {O~A*(̻7IOKM.i" z?75Xk|Z^OXleeLmw|W>$ߐ}9/?5)ɟ裒;g[ b^Bvskl.ٹfX{aZ0~{X,V_۽}x<׻5^wv8:~ Oug =Q 7Gݻ nnm;eUOnxb~x!Ҍ~#wnCB( ^&^ޣ)瞮г&tϑt747uAj/ %|1r ='pݑa~{wsd~xSz.\p?7XJ?+w׊e\ocxo_K䇦O66YV`e=uzҳ# ϟ]WU,W*]mڴ`wȓ_//^s j/[{颤zzz^ZJZ騪G韤6_mpx!憦K63{F`^ѳ_tn Y|r/tO_{z{KP*gU|=1{ehQ]oGA౔FM=]&)6f/ݾS? d͛3s2N(`6zznO`IYUpk9^a3{{{2QЏ~mHGFyѕ].rAɗ{t.̘QTW?A.!3|>x` "~U5H.X(<7x ^ ='=2^Q~ Xp<_>?ճYE*2" "f VРؘy!|%,!/1qc'>_>jд5ۗ䉄!~{!ӖֶJG}OZ܉AyuZkhdz{{;9Pl}},vo@x҂ǹӴbx(z#7ٯ}dcc'><I?Bl9aeyAȌC/ڗo./}nϣ+  |oNyr rR$._߷߳n|b͂b# qw,SX-g;KKx.\*E$iJNO3?3o]QO<Ⱦr--gػ~swvwSfn\ؼ(G?2ՙ$k:-q^x v"(^ `|cOom7G[ۊ4dm~<ÿZwOS߫eЏ=7luuuQV1Xx`wdc9`.\0;hd;Q@`Ύ[[EGQD*/\mѵ`vum, D+ Mgmk&6lźԬ;~qs^ܴi $ >pƌ_=[ ^+Ϧ: q.zZZrjL{ >:~l/soq`\ՒqvtjK;(m*5i w 5ÿ'~aZfS;_<(]==>nzj#(DӳFЉ D&߿?1!?֟hSq2-x&vwwܨY!pJhpUQW4\}N;>6\-_{{L^kmy?u]Yo7@rE ZZ/qtT֖g ƷO|8AA,^{±_y<dF^򥉣 5.gfy9UUfD{+Lώ rK? {^.xoo6Y w>NS] &B7;WV0=V;Œ!|K{~l6kK{߽R+sם]k+%3z:?Lժ̞6b?jOA#q1s秼H'_!32?jd~7u5@g=NdNw\<O6s -m/A\% fziAznPhi5mv\L_]8uݳ;x{۹ô' X?KDFtw&H_kǴwtt̋7~|;+jNx___wf p\.pjE͉kvf\mTV_Psku'sC=E.W"ak ߼n Z\g&m_?u0,-KKX,]1?x~#Kv00|clvw9{tOEw={=&wquaZoAӚb# Ӧ/ _r(]w̞7Ke>_)4qL ٷ32<,00pNO.5)wxx~O0M]*\ bFgt-{޻OkG7 B2[GGƕ-ȻE// \幚Af!S[pj^Ԝ/SWON~/iaW5&Ǔ򰂜M~Pbj㉋;8xbyf$ss^Û>}vutѨ7+6̋W>bmcJ#ه^}Hs;MfxOe) .,x)tuuE͉Nr].o8:2< :u65|/i 6SA:ek4b|MiQbLX:9g;,gYbC״|qȵ{imYu}'K{nN@&i7[kwѳ/:y\ӹka3C|rpOx/ _oc!O4ѳ#wOnw:5;Smnm{ֶSg^6]tY3YKK ;wroy7?||6k_p>\lY7gp]JKU9Muxʫ NGK|{{{٬6 #xwjҟ>4>/> 3^ EwtWY?߽``_=k -O#On!tw:]=D_h jf}BǛ2suCq/^-H{>_vsoH{}UUdj+)I+i\.}H._ugtiтKw} ZWW|irp8;mHDG5+%/Zog:AȌe.Ys?C÷B  'ATXg1E䟀ě/4mrvܙD;0s :{ȝsMӝ!зy?c`NoX3)pKAtP__@rcS(rrݗEAlB.SQϺ=,B_Լiyeaa*n X,Vxz!L; AQ b|BWSD?PB!/?BA!QBODB!?aE!A!QBODB!?aE!A!QBODB!?W/e@!%(B!' !0"B~ B)N!w!0"B~ B (B!' !0"B~ B (B!' !0"B~ B (B!' !0"B~ B (B!' !0"B~ B (B!' !0"B~ B (B!' !0"B~ B (B!' !0"B~ B (B!' !0"B~ B (B!' !0"B~ B (B!' /0郝f''Mk ']fʋgDSd @gmɻ4/j(ז.^iΡg ⲔnOK^x*+bn(g(G~ጎ8ެOr\]}s@8-T'%\16O^p鷏׋Ѥk%V(S1ICiiFC*ACj煲'' 5/h͛}u:Wưǻ\5|n(A>NtQYzͧI&r_պyw#J kΚⳮy4XW|9d,΍w|nMs{+ϳw/6l]7?|P9G_յs w# q{ypz?xlʳm>s1sg*Bb8pE \8-NUW]M{j5ΔʲfQ$ Q|uܵjuFKe-bgϛ,e!n(L:IWYh 5#j6cM\fvd.B:>In-Wzp9$8sI+0(Y\bX""JVp79jtYG&%W7DǶYs{")1lץ戸+ r@DN eCqNX649bS%\Npתe +y\e+E׹ W.X+/#X ʔXϹo7.޸r\DkVWbyX;Q{8NkJΘ=n+rWu-]EsW.J_ E%㚋Wy\WbSWRigR1"c颚rO jG:YW\r=vOXO9Z]iN^BJg:xfYiOyX =+=,g;gn,:J"e nD+Ez8Nw"Y[ @YYdz.m.q _-bCC/^*ڢw"W=w.2}Xm\(<^^T \kU:E1x%F>tdW7u@qͮXnD$rmF)\b9Fu*:4w]Xh>MOJc2 U`xE"aG,ǰ%oF?).lv55D\T4@Y莌huBv IDAT Dn*OIn4%Ag;@ڳ$#( n6Dd n#m9+1总VLHN}+JuQwl -rKeByN]Z:tVV1JnnlKCD7Z7:qt]EvV A4prD!PkxB\bZ],Y//.-_݉.?Nm6Y0Suc"y.e+mv78Nfpf#fӱ܏:+NfjGTynV0FeBhp|ynkMYOƆrcσ+v;i).r˗e*I>kAܸr֖v.^L(]쎥= }ˬ+m')(6#|鶔+ :~A65wyS!P8,ݬ.dVg.,E>w"e!.ck|vۣl e#x\W@N/YxݢI#/1Fg!܎r+F)ٝ!r+{*Ov;! V7{heI4KN1* bU#rwZl`wJXㅄ-`w J偽 "Ɍ9yVru,zV+lpqY;m| Ȇr䔫C{Z] OIs:xxQ!ӕ,Z!SRn'` {nDxJD>.3b؂繢R8DQ߽lwB$>M;j:Q0A,knd"2΢S[B8|eLm *fƔˀE/]@hJ[rF.(a[3r+G=^,A'$D ڢ"q 㔨5l+~Jʺ\\^ZcwRo kY 1xBVW"lH_(-Rs9 X ,c.-*ⲹ}5hdv9IF՞0%%EW)& 0g5suZP,eLW̎DeB"`ّߨnE&.aԈ(tf.6RWBƬ+1;;\+VmZ? ?.1L[v/A)冥#(;.,7G y5,VdBJ\H.f٢82DV3W.illFU ]Z3' 5%:vscM4 &Fp9,'Xqm <5YͧlpCdbXKԥfQ!2|/~V(䫘K%;R9n6JJ1 2AʤVm,LJ:KKRo܃RU|lM lA™6]Đ;]JN1?C4ut3Lϋ!ؤ[՜o՝*lA'E!!QBODB!?aE!A!QBODB!?aE!A!QBODB!?aE!A!QBODB!?aE!A!QBODB!?aE!A!-Ѷk7U{j.PḆ)I춨>Nxh sݿ.ySoP#M_7 ڃZ!!m^P$ݩnӅWdbN ;; -dkWmWMᬹCeCp:h'UARMftر6sHiY_ަ|46i 6k4zyCnw۹C{\;tᵮΙp?6Y]>P$<zg&{$4@zkW%+>Dk(CS΍n>ii6yy?ؿVѳ%Yv41YZONLy?V$9mg&''Z՟x=W%oW'S#g*v'΍ɫ6xGHRm{W^?J1#y)eƶm۶mŚM522].d :}_I Iްj3{#:8W{ '`9 |7hӰm%{v䙀;5G!۞{rl& o ZL&; Z#8mOfn_9] d 6hFc0zJtfn%DY4z򑉻N18NS1 XE3QK^+42X3:#d[7$bfPnznP[,=q֧քF.JTz}@ӱl#C7xn9.q~A5&ڰ#6j Z`h@ff$ 6-ڞvֆ$^M0r46:kRCK՚(=:3w! jɍ?j$3d_1)7פ }z^\?jAH3)Iuz[2YoKl MOU1;2sK^Ӷ≨&r]58@uskB]2fFSA yӔa]daryR*,,dHjV0ք &[swŘ*$%1QSh!ȟZv2'uH A'%@b:cQ׌&MO ˷N41ycuXl5sYF7lh\*Zh54,?Se[3ߍP* u F)% nB_L1Ȕ2RHz3Pi8h:c^ 7 JQ0f.-@H)!@PT8}l9-#M=8{4H`@z )8-90xe^L~Ì21LFDTcaA&$0lb(6)ѓѬ-(1 D$x$1$ RH8llQB8*(髅z##T*Di!//oYH0m.haES&2u RoI<lCQ@ގᥱl Oп)!g ?Qeea@f=PP(h!f_^iAxbLl6$E ` 96z$h"eX*)PuC̸!X.at,2{ED&pdq*P;L@bƙדGei/ ZeR`krE)$ 0iyR қpMNkYm^3zJ9BF[*ńdtȷƌ~pȌ2)Y5)$CKĠ1N@J).i+eQ#I >N d w:\:]*i^.wAmB@JI PӘŴ)m,Mv& @.U2bd<e* /ȧSec1bwkO>)tAN&Ɍo\F#MUR{lR0h@$ITb &hR0v1IQz^z}a`1cFEA[tzh4]HanO #6Jr$/``PN͠58b(1X ^ٿX1n c,ؿ_c3&!ٔ1i4@+3U6:o<P{x ZPxmJ@1l.J&u;`1i2 0Ak(4{X7s'$0 ,}w .H1+*81W8 LY;G0wa6Nۤ 1RLM gu\;bczitï"fN)lNŸJ6Fc03@6CanNHS2H3c(+0I33Ö :-+4 6J;s xm=,ڼ|c=cbLFLBb) )aRbڷ;MM"o:4vTlڪY5:!Kl:囲|5e @c"dv? Qiar$c X:0?/g׳N Y)+UBZ Z=2U;uO CHT[QOۗ.#99G4fU C\ƁiFw$''/_gp#Fy:6b,ΒMTC_XѪ}&b'vgJIPg&IY6ISanNNc43K&mڝ0c2 z~p$&/';Wn~;K%P%Ĥe`* 36 >c-6G!F3:&ZzٔF3#Ӑ#KZ&p[ `JS^~q7`J&d+;)oY?wu^|+VrdAR4uߦL0ҷs59; BHKdlJRhlFʬ,JTMƜZ ),cߦo@1PPM|Fo e@!3J IDAT%B (B!' !0"B~ B (B!' !0"B~ B (B!' !0"B~ jyU%2LIƟ=}c!TҥKJҥqCXG.Jqsp^aIu7@J ii}Bi\ߧV~)EZu?͕ζ}<?@_w]gYl/ݕm^I~MϓB)S_݊^=gPY,K [:%L;f|?&vB![4ٌͣӚ:!_Mz04@x6]q#qٞ/w8Cy4o8m9vm_yGg2%H3179 Yd b F7!xhZOqE{CY-}C3z*BoAR:h4iCg v}9r.u]_=h>W' L-}9S5o窲]C[=e MƱMt|jάgC!(,Tv\i}5+h#?c;|ʰ NCǻUv>a`Zv]I4:4Iƹ_f>26!Sy5-Cwpp@ϸ#u/}[oA)@w[ij 8;ͥB`vnpj}D=AP@Plww}[+n 87QczE.ax:bڼoJ\ĸ -wO۵$o|0BoA,KgG dh<4g!8 Yc>;" }tםM$rv^[5j=3}bAeS{ܥ,)0r`XdG[ki1az6%mp΃h`Yx,cd[א6/;2W{BTfȩGJNYdT!j"hoM_NfnαɚO~6Z'q|Ҹ8{S"0\-vD?8*(%cp=Eq L)Cz%ЙTiW<Otފ߂o<DxJXov_n[Y U7v=~mx4cyGs=&qOe 1ۃ'M"O2Q96`œ (%| 0%HvᒸQ?|3O81]78׾tG -Kr;qgE͆]MIO7P,sVoo D+^ɥ¹@}V;e S\:v`mYWt5nwV׵Na3gd&3͗-ES/607.cY]QxTInKSvs^L 4ye%-l1@{>|itj/!5¶DQ8mr=&_ol9b))UR"xw&X|?,ʻ;N4ų[ -0#7 93tr/$*B n'.{V˔P<@멃'@Z٨@ >L/K0c#]"=$ <DZ M1bOMRERhfy>"K&]Z'}O5 hMrϱ,P+:@40$#!/OEQ$ʡHQHHzPvy +hN.DZ,MRyc=( g' y%oW%QR^bQJ,hw[tgî;6TW8+81k;}mYvw:\,+sV'}N3l;.z.}mtVq&%MoQT+fgXWUX#bǙ6W;+. t5p9+nr@qnДlɂX+6TW85w 'ʎ NYQS{mM3 wUطk gEM@k+Ng ŤZܴ 7+++k(wn,tMBSvmv97a ^y~w5<v@ _!i>\VB V պNNV-9k/Ufa\_V%0N/:|?C/jѐK뮚g7o\t߸~~0k_MG{Sj?ZhӅ~E{OV-e0c\͋Z_vaFE+ _:ݘn|7п=W>Cy7 2+~z s+wgw޹ꁟ2+]u0ӯ'c~n\ӏ=?wB7t+t~7Oaϟ|Gn}?]Wxw0s]7zzw2ku0nW>0pH}hœ?ifŊǟ|o0 øѓB;VJsykF?~/ECV.,C#8U~0(4MӴhTQ,%坥9K,ִ 28JjoZr`h}'<[N4.Km4l.`lFxbZZ]vI9h>q'ТzWǎ;N޻wU+i=t[JVs()aN^/g7a=(дkQd@nOn uD:mk0gEdZ]j.3 V+k^4 FIQz@`fDyJ)z|I4πJ3r0(GC9?x$R^9dfFa߁5%E%[l?Z"dm˲ K,ťDT H’_ i] 'Z,{3Nϑ-$,DjTEYK'QuBH)g06y&OK94yrݞ.>a-,ٸsbȆ!#V XrrrӅX K[v kQmY67Օ z{Gʅc?4 @|c zH%-%֎D1Mc;R@ "MU (.:lG'Ϡ@ ;4s?O88s߆:8 : ʜK %zq!1=Vh@̐/p.@ ^PE b @  ^ս w*$ c ?C M3F IR4#uͯSzy WZ{緃㖌h7=׵gꑞ '>ٮgV:KL9=W>mirvM+^~_:WFm+c;_Zq64o+ړ2)l{?|ߟ-n]i ?z*C6gJ|sO~ I\#!\^H~_1!g0D ğ7o Ce?x}WrΏm9}Rỿ`+Μ}+o=7scn<=h߀YS ߺ:Jnyszoxs /t 5GoFVHY~{@ǿh%y67 y|}=׮xóaތZPc+uo]]d?q.4KKX;X‹SXTӵfyGFPH=^h:fpM>TD5@F^j7yL).Σnc%h@otrKڦʂׂAQ֬w*TcU D=%(Q=$y.c&ѼΑ~ѫnEbtQ#Idp9بGs;1,_γ<|y敫7Įn‚{zo~r>Sz- }Xuu޺o=wEg[G .쎽7[f%Y9 R_;9dG=og.m:;$>[ۉs]|{><*w~ݞeX]|y8+}g?XPPGn}ro|뽁Bh2ٳXyC?yk gO߽K[z{~l9E}c}Wˬkm{gY_~a0x{[s~q)`WegO ]Y089 ߌ_)|_A _aKɪz/Օ^Hlx;Iv =]hy̸l~E.FEOUri]kBmYY텔 L ۸8tMFcHÆaC{,[)4aQ+/ZZɚKIANFoӭi ?^ U{ 0XV׍@}~öa$N{h͕O 01-4"m7گӯF[7ۆhj ŌmWkڢ=7ow=#]av4?n0Ձ:ocN7~b̈́M7ta{`*[bb0Xdcc׼v!nn сa)^_Z+hq);>eS]ȶƮmJ{}H1HZoa$>ݤ w||8q0nuu\fF 2b|F}<ڢ >@zEQ !MwS_i]â~ΈvӴÛ&9EsAcY*F"=a(P{ `9F8eǬFs$/=b@7})%8ͳf*JH240Bp .#hyoײ]zbε<w(Z0oz׷_ݻ+A|O0w&g n^]`&GϞ6X7!_(]3OvG-[>xOќCCmtJ^݂/|r#}-({'Y)gr>{i{'DAMco6w<+ ;o~nC`⽱΁yŝwnwbf2'Q٤ 0,}+1)(^Ud%E0-jh3bZr @y"N}D8Ec?`StF$ifÇ뺎w*S]+ߵwm+t7Xgtf;Z:?%l.xħj\UPnXćU ކW|ٮzd9m7+&z猼=k/\N{Gy ߺy~oAwe,ed^r5{uP\oL:[`I1~˶Ѣx)8vf㠈O1W`,uF.C5i%|{<2o+! J%W Go`xa0A Iu60!UI&O{EU7M)xŮ|4;{0''k7Ĭ}W?&.\.{G=gY`|V|(Z EP{H_9-~Gܺ+V|:cH=~;!fX-ŋ[/^E[]SG},cn΅]>pVȯooc~3'Q??7e1Coڇ?0o{]<ʽo#)KsspE*;Dn =q僁xFwf=h3Y+͌xs3b10a"t %2 %/4++{XO0L9(/HG IDATvaI?2GdP Dh9]bitpVC>Le:!iI$ $єGڹP I$y9MQf'oxgͰ]1?5q}B>"IbEL_ {Fb|f 1wq˔?m|dz6y9#$ Yּ~HAQQB^Yc!(l9ҔϢƗ fÒOTi) sT.wU@0\zÏh(8L4fD! q,K/{EX"iLB^iH!Iޢ2 rJ%p,24E 1)A ERI._&)@W2ᗔxeB^EU3,S$EQ$AڐESE$#5^q DZ Eb *WR$Rܤ]!>f_R$1=MD$2y7A{i$O@X@W%i$H6,j>AI?Cy&$'˲ E W x )faAM%=>"K$2>)ChңfT,)^9 hQ 8@a;FR?!=$ <DZ M1)Vh ?dMd5u1ǯ"͎%gLpL L`&Gg/t?YV#|jQ᫆a\[h0ˊ7]5 迺BahkɫO/k5 0Z_(_vaFEf#jE.#Gmo>rQqUE6o5 0_)zᤸɚKaU5M:6O&¦iB\[jBb5ټtUtMhM];~fMKן6{l5øvxeYդ'o7MzǸRѸ~jɪkccڸ~Qj꿰iQ e‡/3=Yc*+ynik>\6hKdOV=XP{0ʪ%/+~akTc隂i5*6,ԛZYɪE2Yd\+~Y:|jQ^øZW)N(`YpgH0kpv ݎGQ\zEQ !Mwd3:EG}^L6q<R G1sS:`Kj7z87H>;s r.GQ`AcD@(bIq-jKP;Mc?<ω. rR `9F8e>U Y#/y@ð:@pLmasmh3R0;LpFQȦ,f A? `x1Atrbx2mhFyXea:Y(dMDfjC 2y&M {GBǟnE X(h{D1le̐):(+M\1%Ncu]cSu3`%{s>IT@׃9ziQEyEIeO.Qd* f)(^Ud%Ed.&JqH7kI^*lcX))LզM^8#*Q &ln=&L觴Ois+26'>lHI:Ms3cK c``tY Ō₳&s6ɩ͠%igBjQn@=E0C^I:@{Xff. JCsޙ=\IJ! jPqc^)}s0KHR@dHh=H&!ϜcU%);`#h!F*0;ydWB#e̵EQnMɩe2IlZȬ]Wv*(:AQv'6i(-7+8nπ$+nG@IfkC>oFw [rFόC&֑3%UGY 3Q3?v^v .r`dqڃ6$"efh Q҃yT;@Pjvv,ogXO̱9R t󒏟娏'U躎)Oy^<4!FMxH|%x$%_E`GiGސK]",xu 0pѲte>wL&GaϊAKqԮFVfȞ)g/ s0]t#$:cm!lw*XZ*PMVi?h՛1R">/=/7}R)2?OR)^J}?L@疴>@|bѠ̹ P7tg ?DK"ù@|.@/G b @  AA@ 2 h<}Cu鬨ٰLku|@wU87>ӕk;cCYQyWc\,ƹykkWtǁee.k;vmtVl;6||W ^[{bPٺڵ3 *7 /-Kǹh{Ԗ=6t"fW#,+pkβ2gay0ZW]6\u_u3=mj*2}ixc|g;Z ڗ:㥵gqƙs/pw:^^]5t]q 5NgEMaqE _9:7|EA(xw{wNJ֍?\uÔe}h=ExwqD8 `NW=&?~³/: 64kxg٪\\S;lZmy)rmE9' s%'ʷF["ꩃϻ"NbŒh9XXHܡWW' :yl+`fbh #/?xٞ5'nhh{CG9v)\e`@mhIX-Um<7ksQzVUiK/Fɯ&g93_k{e׆d:?h@)=O@|jgzs]s|f^xkl٦j6K6”Z0/7oII"E _DÝKQIQZXNVe|T{sgJ b_u=/;vرwɁD-N;ggƁQ^.T: ( Hd/UZ80U6uH m Mxib- #_I9%4)J4+텕8Eբ鿽kVD`|, DFln-߫`w7E 9:[:rr@LmnOhM/"w&-f-D<XK/zډmBUE _DssjEt~{CS,K"~X,y]S9gVn=ɹsTj&/ZWĻm91eYK_4t"9kZ񼼼^rϱwV'0,7p&-t5h:xQEl5C>*$:Քp{gRXObiǒhho jUmX-{N ߾Yt:z5P3rKHSCtѥ -S٥tIq_-%ӷ2M@V4nɶW~gr-9g+ء֗&d#`/ 0 t5]i: w]n ʢN 0,t+8j!J,jkX$l,jq(*%\ @,1f6YZ=0EY===v-ns1LkN^B%4쮿e.-lB:˶n-m<6|V gNt4+-mk%T)?tU߽ew{mBk\kU|J.^J,VT= },MgZUr$M;|y0w U@ ʺdod|JKI-8lwfv2M@V?.3e缾;T?*QN8Dzb?c&H~j3.Dst۞M-\|\uٵYVް땶@ہծpVTow珨xew7'"ͧNj:^ٱfmMY1+)uUb)(x nhu]9$)$i̫4fD!,B^6$Y9js0Æ񧆤E(ND)$)kV= M4E'0ZK.Dz,CQ'#xՀfAhX"e̙p#Q?n/6NlcF4 $AC~MINxY y] A/G Pv `Gf2Pu$A K 07~͔1Y$)$A1wKXehv))}PIfMCfiJ^.+34E$+g:b4ESfdĴ4c,eȍ{($)s԰7$EQ$Ŋ"0$AA ~}}./JJ!& $zĀZe#\6i# IDATW aӢa Ქuak`a\-6븰& k5C隂R%' LMraSAaPl a,~ռ)5dyl$ =YS‡W.j)~٢TS-1WldբӽBYkIe/\2ѪECǒ&qi\dZtu٢!-I*.+~uԑe×n2ɨ~jQISҵIÌ5;99OZ ٢Kr -͝fYEnD"Vk$ D"1t'ځ"yNМGƦtAcHғ8ͳv(!if=AL4ppMYAVr0 Q3uꀥ (ɚBem~sSImCU8wIj4|FЦmQmVL, EUUUU^$?184K4ȭ?drq,ݱ`)u,s1\p]*;Øzh *ftrώiQ] )^qs/GF1y!A_83`6v[f'SǞxFyІ9hҬBeiv꺪n#MF4(dtsOQdRv#]W2}#K@LgNTW\#݊$WV~!JKY_HbdںQY՚_skmxUEyxv6|_躎Ǧu`~SFV\Dc6VvO0~epP_lل,fj !.eɯkEx %os'R4Q6qPnDQa IybGk7r8Ec?`StF$i)8&Jq67-iiOi,$Vd f 9VOJ})t4 {2ATfȱ˭RǑ>1QT:# L'h1 +Wݯ\:Z/wĠ$[9u1/R:%ES,YСCĝt]KA_@0}(pДzƬ7HT%yhy/J Ə/@W"GM,K덒= D>AX4v?ӬðhTׄ;l\dI]A"C3emT_*^+{3qJL X3Mx ҆*p"6OghhH&}TuNqɮlQ&Aupd#`/)!  MAGu b mՄ7BjQn@.&kC0԰š$X3:ͫڳ9{8Xspbޥ lݳ3Oh’{-ފ]⸥}Zzl* n>w ų"OR2-u\4cKv*ϑ2`|<8I01nٞZځ8"`aA ax\ "f#I:G@e`H  M0g$88R.Yk1y9҇6+("3dC`?,n)El(9?Sq]f?I'Ov\`y;Ò~-Fy䠋n#o[h <ɍ_RF). 0-Ov3[j;Z`vA$B@ӚE1Z]q[sg!Ä(p`YH\8X?3DnD$MM4nMy566ƈY#/_nk6FW&fAbLt4g4#XCy)Ҋeę g} jJ0VV8@F t@hJ!uŧB;%MGm@ O$\@ "J *!(@ dѻWvy>TlO>׋4lV+ShZAs*uE^.@R䪞?kjf=܂|_3\tx^}yTK}̮tͧs?;uœ·ܭ}[:S3k5`}XICafA M+=xVᵬ]Rc K(ٜjcOW^H4=CHFY CQpF)NbLɦHRd9B5:ϒvBUWf\%ElI@Vե[`ա˱fc[=qjehi5eH`88i5|V58Ѱu>qkv$8vϚ5|OKsћ|4;(MSJ;?}?}sΟ_)/iΝo~Z+;|8Ͽ/8M"?y?_)~܋g/^WZWrjeySMlK<ꑁ#WkʫQF[.Qj{站7Y lO͞ejjʩx%Lo"MMߔY_+W'ApоLlmꡁᓷIUiR+?oȿMCۖDdOdmQm%f ZΌڥC2K\keI4yzpk%');[%L m7s;]XRd}rxScvqbӶ s@\?~Ҁy}#ǒ_8ѿobnN='Fϙ|^%V^SV4.3{vWý&ڹclaN;#M^8wFύ ОyYte~2e5N~޽{g7,fq?rz[e(P۱@־Bώ[`v09?'I^F^%z%</ ov 0Iz UD(,0T' ׬,+4$aJ^&8xaV2*$, 1B(@(f,2@`L hi*+,+Enjv1 U8ٵF*t(,ɢIeeEE%.cZyRaI*0?A^ԉՙB4vcȕKK.͝AHBSs.9!ŘyidD֠5ʆ2BCmcnΉ25#֏/Uf̈́hJPJfKJm m٤nfrF} bs^}uomJ6˺ иN$㹷c,zVސO\腱&ܕ2d3,|;ihp_+˳4Zr'ges]SnK4bN^ڎ %Sj0j)s ÚOkҒkVg0JR9w)eد*{m ˛ dνf)SS9{;7nAOoo`7w}̍קY?,nİP(ܹ"lvlk黋xS5e ʚjB0T%ުWjnlPb ԬrdBK:9?akjD>ziK%m}'ơ&|< ]:K[[t#OY j*ƾڶc" f0v\eVJC{a59íZYҾң]iv]HP]|FhD1GeRE*XpǬ|sBk?X4w©S'=dٛGO} ;KMޚ+z{X=>q>Y:$..q˳ ٩}3vtҥ/>2p@T\gnXzRhd{0wv|< x.;v t`£,6Y:}1dAŌ2ڔ׊*N&\8a)4@ V @O *!(@ ,@ v)C<v!hiu$~80%AslS}XO6@,|yĭxQ-X~@ <K䨪Y0UH)6+8-hZ#Q+1&@xӊ=4ʜf9F\L 1`L et}ȑZ&E|/HCќI|^sAʑ-Q-j p )$RbMdJ )f!Vŀʮ ?J)tSZ9H&2]gk̞Vՙ=kۡ M:ucM+ ,eԨUsV,N#,W]o.T+oj^+y!ɗTzh[Ug]UsWUjI^>Wrh@}zYj-}ViDiҪaMteRD*$В1qX2Ġ6:9Z+ d4C5 i5 ! D DIcH۰!J@6F]PqA5Y@ E%߱TlODU(Zn&N"[4Շ$oH'*8óOxS⪲#D W{V2(@,)ՔU9f7>Vi=@c5JBtwcTӒHPȱ,d<~+lf?>ni䡩bE/s"K6!Bu2ƞnYZ\:+`h%Rwqѷn@k]J(y8ABYt]|Jrj[B础Mh}gݱ?}SI^άݫb6M`Y_g'Z|KtxS/&-#c}b&vQ(u{hXў%Y~Qxp/貎'yy =Jλ+=E\:z Jƣ"G9Vڤ ֫{FGGGwOE@c9 /!%[yAxA6+{%iK/($ Y& lGKy';a`gAph( <^ e,@lD*p /i($,9$ Y;.pzuMlO~U\Q߱%$v5Q%I`)Ds׹Uy_$MWdYDA2%! .;w7Osүӯ?H4wN4M3M[i~OvҴv7_|G)͝;~/ izo:LJv}⾡]'3Ç2{S7|o|L>+*͞ޕi͞޵i۾+i G64 }Wt']nz IDAT~==Ф*V}S*n:~3m-Cw]C齋\^Hoش+SBKgOg9,u&uWD+mf~oj@wnW[ZѷedR[惥'2,ZJgǷ:{&<9T[7]/Nl8ꡁy˷҅Cm3 ;Ի k5[j6 ٪C۪{YA/A/N,'7 .3ϤLJ*ԁMKBŴ᪰_zo@V%o<>,cx@Ņtaj_#>9<|/U{EcGJuSSάThz@u[z$Yl.k%A&ƋvGZ-,SbQ.=g6lB` X.\nNзc stl>8{'IM7&.P 3!<0#bE%@ E^YXI qB%.#H#c^ QYܘm]aMd+d6eQw$I( bYqbE$KJY 79:SOh3uH:)9:3oVl5'ek~CӨ-69"=M} /z ҹ۳y;+d\Y/V'ޞMwz տ6?wKG?LN ECyXňӝ0%_͚_:t{A4n#%)VVl/ |]2iǷ9-"[IN79AFl9T'jG{ Z#Ad /TM6\闥(vo%Sc÷ S<}HPpcۉlՆ_1^;m̞>ty6;2;o['F.'zX:C) ;;k{Kgw5"b\w hEDѢ`i2GmU `{d偘aW<n)SB{@ 'aE@ UBQ@ VIw(vJ(3 &^ҜؾJS W8zF'Թ:@cE$TcYXNTS]GB¨uKv΄nc*IuS2,7@ xp:@~QīDB8RÙб1 SRnK[2%œl)ȔdW C0[^"TrZqh'R4K@IyVY^e#'xh0Ioٔb*m5 oXjL51JHbrrN[g rjU(IUiRC"vj@ <(qP*UAz_ydgy,,Ml&0DnʭJb9W 9,JkER}O؉K33<Т9dTYd/M<+T+IhɺNmS 9j0,QjO``ׅKS YjV1-C[pD qdV ?ĪRQiOS$b*UyO-Yq&TaĘmS! &0JqSV̸Jd%J;a%SmC9uteΡ8bFU EX4]"Gcٖ ^t(#Z$K,@j'Rl D1iW0q" &8m3DilV5+-]NS&hNhf~8wX\ʬFƙXp8dU(yA8f5ǔh( $-C-+ ooL uk7w|oxZ_a?~Ro?#4Mv3OIS_|}@{ GvM#͞6q~6M4}v|Ś S ͓.K4]yrx4==i;&..iz;?4MӋ?.7uizCWWRV'MUszC&.ʼnMYtӷ4M.ڶl]ILip~b}OU+PjN\|U3|{ǯޫ&?{#W2ZpmCǧ4MgϏTW/6~Rʼno;py!M Yu7|zmM'Mӛ'$VuZ2==k*:֢\ɹ!'a%9XTVry~nEغcwƻ_n 0s܎|i?|z#,޹~}{[! ] b:*E8N %m1XA5-A8Izg:%^YI ʮ8 /-}PgqS,C@9Yrd2bPu[Y 0b׭A'IRF Qٮ3XAwI'8)K玓ӿ4GYuC׋ r'M #k`ѵN@K灃>qO5.ng7s~p );67i43W. *f]OLi4' Eв.;/4Hbsƒ(VPRfb,+RevBVݲOIURdKRIBfk6ZTKV6\H+IY BNVBl)*(mm$j MS LU6U^E֪)Y5U0B@ɖ|dUFySMYDNQSH0\SiJ$(hby* BlǏFǔtQq Ѳ% JvC]T"J b~W~qkxX}}h#[Co]P2xrc*#N/-w;8 xfv-QlKJ{GRbG;{]aDL0k1 =[G0 &h'J A@X%{?@ ["J *!(@ ,@ J"J *!(@ ,@ J"J *!(@ ,@ J"J *!(@ ,@ J"J *bM\8c ,q83CW=__SOd Q_}_[ ؑXD+Ze&%ĕ9ﺐB]kf |$'El 2ۍMhw0u$%.e/bVՕYկKZwE_ec"[ :ΓRH@gQpq"`||cз^O\|?p[q`]YΒS ] ޸^EVǁ3(A䚡h:V-v_&*ڼ(>sɠEE?A^E}3HqLɪPqj)8U*84eW8[ 4]M5C Sa|[Bo-[6-O'^Sp珯vtԥsBOߎ,N yV/^ĉsfw7~ڏ/=7܈=[G^){frrk_cIBI|3A$Ƭn" {R1RVf?{/)Agn;S' ;/㙹rSP4'F4Et-%S(S!)vM?f&jv W<#E7B1хT#0iU\ѳ@=d E?-SbbOS`LɖU*S3)Ȧ%3HRd98V-WZ@I(fT 0QE* |Б ͎Vw,iפ"2\*4,aIB+EG!PŰMo %[3b@KEGHS(I`uۖȚUwb@ղ-Yjv&kHmQ%(As\{QH|C)@A+F61VU}8Ѱu9j0iqН䋒ͩ!RQs'H7=;mlvj^>п4Mۧ7 N4Moʲzd`B#CCo  M4]:m]o.T7PSW{ŝ;%4&M4/i/_X?z7_}}iJ;~i/7iMwWi՟~s??q~|s;?E8M?})u&..d7ɬ.NluvՉX5GUM?oJ;(şo-mUΞ^˙wMM.Vڱ٫ S ޅ\N+8p^͓./];D| im?1}d /];'6Иo]4p| muvUT[mj߶}5t|zC Me;9$ٳ\>2a<׵h%n^v͞vj  s۵<>T$6]nܜdK{LJOϦm͓ 7m4p$Dzi^O>2l~UuxDqd_Yfn9!N3t$%G}C,`͏tUu͍lWM*q J4 `ޅswxv?]ƞ+'_;Lc%n]ϝygs+ 47-[w oS/,oEc2 A%a N|g謶2d"[0xT|jS | r?iQaC)Ze^ 6vx1_ٚ V)+@Т*3 CϋDH{ IDAT+y#.f$)VEVP%4 {I+0tE&+d%ju8Ge8!FaR^8BN3r[YkA(20؏ t(,ɢ1{EI&KɖnIDW̓FG @c`GqbeCH3T''J)\REV-לU8?ބ'uxNAqKDU@(lM<3gđ?{zdl)/{[Fa|#{.ؿSy5@P(_w; h~meU]) W [ߒcɫA{I c8q+HX`gZK&6 %bU[7^mʼE4|cKdžo x&s])']kP^cUSm_5'$-9tEjA($Ȃ̳xW!rU:\K +NGc?qA$uJw5Vkg'3q(; r-ÉDE@/2D=8Ijw޽;{|;vsgő-\rwhg̍EBP}غc;wqA$ah9 \zY(@jy~n۱pmr˻dZy-p?*~\R=]p5ϫGդBoJS%I//e*' 1b*{ueP(I|N8aĮt4o1ʼneJh4Չ rT52qS,C@U[..t@VhVPmω|d[8iCHI|d=Ji`D  ʓA{tsl۱}Pݠ$ɒw eּeˊTY0ВeI h& 0Ff+O$ҕD/ee5یT3a K˷S'NBOvQ?:`H ё0ۧPqP߱鱗v\9u^>^>a|t6XYehb%o=RiۈB4Q)Ǔ$v=~!+܈t3?3=}aߟsMQ,kW%*)2g% l`mvQ$QlShE-;RrH @\Ѯ\YYe$9մλ5Rަ)JE&jW 훠dJZ3%e]EA3"hEZxhNebCU B ˲-\r훕UR WD.peǺ7|aƌoLO^59٭FP[z篜f=ReZֺ@5ƶޘj6ի{FGgpڙcƹ[0kW'Y؎pJe;{ı3pxeW8wc):=GGwOvc^ٳ{tttϫS'~W~^س{w$o}_ۓLwhbWw4v^ؽ{ʀܦŅ[+FGw1unǎf?{=wFwOՊI%,`eމn:0wk&[Vۣ+O?472쎑K[d|/lZC}7s}[3ǗWźMdƙgd]m񼥥|ƾG3[oC}އC>(f,}]uσp_'>+o<=G+3s{2o7>_?*hU$A{{xy+ꉟ^G~=]z?HKtG*"׭}!L;K>v߯P[?[B7m uq7jpqF!n6!:x9!!N!ЌpE!f(B!4#DB$B'QBhF8"B3I!N!ЌpE!f(B!4#DB$B'QBhF8"B3I!N!ЌpE!f(B!4#DBu_]z &B^(B!4#DB$B'QBhF8"B3I!N!ЌpE!ft '£t-C1eɷܥ+nV2f%_nX|j!uk7,qs[!Fu 'QPܺf9NoSUhiֈ麮o|+vr}O1?x`|T|^+++o U[EV4oڊPSQq ؓNk垭qn(BZ7ڿ)Bʪ(YṬUlh+`:m7HIP%K%!R*L,BKB^(<QH۶_)U .üNXaرL"A5a2F2-uSi44˔XG]uUTPF@@J B24 7)BB4+m#)ʪ֝(u'DW9|; $̉Db?ԏ/}`UT>Yp}DEp#YrR Ev/Z*@$2f9^(@g?wy]  τYUK_xO>Mﵢegy?j)׋ C@iQ>yל {5n(R5)WC"19jU.?}q3'7i-/tv~f4MuԉS4ƅkM4kK'Wg~ݤi7V׷]:;pr4ƹM4勫/VO͟nqn~Fxԅ+WV-^^[:1T1O?vigַʥՕ6+Ǘֶn`ܱMl,ϟ83Rz\nʥs'Y6M7zf5[oH^c'< p\riiW/>R'NpZO֩ /5M4¿;}ա tQz*X,BKI߈ GEEkPW5t $+T}zH~闲޻}Ǜd}xwb$|VO)U/*[2N-zl/yxUaݭqHNLTE! Lm/@Vy]{@7QӤ\41h@Ù$ tˆ4qLV_ die! jyn/pEZZDDSe>!DJ¼Ң[ T33b"羙}wj$s̰*˚1aMqwBY:5-znysk"=իid;R$R߬zz&SDž"-')T{ew z4o_q}._, Eď&]]au]xv{Pb D~?jz+Ջ2+(a:\kD^>y..h3=>q _hEDan9"AnTz#xS47"(=a~v/9&"׿7Bw;MTEDxCQ-q&B \BhF8"B3I!N!Ќ`R3+Ej7.T~]_UDlT|p2MY1_I(C3)71BxB^=~s3 $z %b] B|M_a{?q',>u:c.[ѵ ڧ`dX%O_U]>&/J pgx0zd)Չ罧r7!X=@ϻO}GhVfABw7V(Nñ,<&JQ5ʕ,iCC4G,j =C * Ru =U)@t' y`(+`(4Xf K 7Qo$3 7] yK[fP/kF/\F޴œq9{]S.cO5BO2_y(՝G j(Ķ#cgYߧ;rIXL^Rnw{YNXf9Qo;MIm9,%~2興sVi,ZINᾚ}^=^_%Kv\{(B _ʀ/B FB5M4N[ZxTsTۗ3<8kP i#5ͥ6Z\~ifsKז0dxSO[wymb?3s,ry5Ze֬`/-˛6MseN?a't [?WWi^8HH_xׇb5:?\__Z V,qg p6\пX<ټzwG[򥵥ǖؚXf啅~qͶ4Ѿꈈl.T4Hl/ ߣr49U5,SZ07n$fW.dciWldZ\Uw }S5̻jDSߢGq\ ?&!?{ ~\Ū|9QD:>|Gˌ:槨(75Y|D%5Ux-q>£w pֺyRD8w9Xfsm>kjKzGD)̊O~,F7,J0y%.3 M+ j"^h k)pE_@/4tP& <6g:2خ9VSG(],.U5"ShrG!tT@!^`ܖh-T嗺3RW-z&B8|w.B!4#DB$B'QBhF8"B3I!N!ЌpE!ft/[@!e(B!4#DB$B'QBhF8"B3I!N!ЌpE!f(B!4#DB$B'QBhF8"B3I!N!ЌpE!f(B!4#DB$B'QBhF8"B3I!N!ЌpE!f(B!4Nehp'P&uқQΓn,<&0ۆB(VXBpE1J)3Z/纪Ԥʀ38C:Ԍ׻a3ENLF8J;C4GpD(F)MR'fA;aeyZd$M=NZx=)cG4ܱXv5d[de"f9QP%bΘC<0y㪌^Ǯ0 /]E!uQy0ܤ.*w#dCنsNy0VJ>Duph{>n>X;4t+#|Qev{.623~H+#Sƹ0U(t' Jז$st#w,,7bl`Z: yhd$ɮ)Ql1Pv*sBI0;LsgNZ4wf퇛O\dmF4Mqnis[sKkWiז?.?yvW96Ν\\jsC4[ζ\xzԅᅗWvJ<l-K0wR4N^daܥ.+ hپvzMtAOY.𕵥Av/_l+"\\j6O<9hMsyeqaM{kÍ~fnim{)v-?4MqCin.?5(XW3j.;6\ *jY?sbi'NlNtf4cu[+'7.,]mZ=5wf_Ź۝ھxznPV\n[ʅKkMHZ]6߳Nol./,|raekwO872hmܙNgFRo5:$g{יF{h n,wm4gNm\:;2Z64wbU:qEP5U'*QPuUMS-qeX$rfiJ ] ҊRUe+0 ۲ +yymYB+): BKiPiQdڭA%E`QtÏTf۽zkf GB0fMax4 J48+kHR@a))LPV`u/]5LSB-)kQYet8=f[#²LkSw$ r"BdYI]2}8A=\fvnkOհ:*ҎzI>*DWtMsb,ɩ^! &iB+f ";.\3K@`%hZ CҊ M]:€jZSxE`r'-I* @Ժ&G;BwL@09=N](YԍKC u+m3iiM D,(g-%nGH&-A5ģX=ȊC gc{d4a9AhUc+'( 4]:geSV#PN~/"Ɠ+^BM’qD!u=ErMXt/^j llߓE^6k%u&J ^I@3-BFOE0& fG隑ø*!^EdYAE(F/@т- ny`s^ycs#DJ½~US5e(D5w#R#Н/m@Fo34"K꺖8zw#Tcz Rtoر$T7nx(;@Q[Բ77L*HZ ,ɣTm853mi&Q_/ UEgQJp#u`TRJIY`  ;*nSirp`$;Qp`I]ۍ6Y$iy,'Y13, wQnzIysZw~< aTخ~$e?ּ"܎R(GRҬ([%3 (@ y@ɑ㗠(M|k^:Vk?ˉe$ 뚰^ d[FYֵꄁMc}eVb0 @:UcyR1%gRPRal@K;R9~flk D5<闑my9(2ުuxi.mF/<8t%,c'H]KkX݃n/@xBO_ZSn/\*?h;!ϔs*ANXGtס9+kK'WKkWi3sKkMs7/̟Y4My~avg6iSsKi˫,֖89 m.Ν4s 燶ZY<6wif{m/_8y85ZY<4±VO-m7Oϝ4Ms4/?xf{խ63s.\i.,.,Z=զ~KW&ܙi--|~|[ K g/m7M\xzԅ+'Oqn~ar3$mte m).nsyR 8=KS{6ԛ3'ƛ`m~A6]l.?ls:PiϜ=<(Zgtv~qu˻o[y~aܥ6/l pl3'tbim{ZY?{i6:GyFzC,r&'WwW:әI҉ϴ 4W.-/`G^>0sC%jdg<)=2eRP ?qgvdæ^J+Vd#*Ğ$k>㰎m3iiM9ڈt:#d@D5޿':ZqfFF=O57Ma'H?o FhP/#]'Q^Ѝ2ωnSrcأUspj:XV7-0fQ[N,.3Ü0$X͢z:7 Be*b/4RJ 'XgRᆖEi} l{C<9* fY唥T2 8u]MkiY}y\QQ'2ۇd/* „Q9(V=cUV)P'ݫh'm]t,Ëm&aY 2f .ɔݠoA_|@4JΔM/@=amsdx ވ)/^jBW)'it4KjMR폲3q<8F2MaSq Jҙ81HTME1ѻWJq m%MʑC&j/0 gn:pL**cC'rJ~:-'e==ԙLDwҝYpQDoر$T7;칎{4+9:Rp)yfɃ{z> ~PX& n _"t\N%i.H\f̴WE7+:kETUMUDٻc"q %N_4,ŵjh՝/m@FEENO J f6Z BT]\l5- 6Eث$SBVBTBCo4j` q'.h:Du-5'ptTZ=j^n{{Qϱ9"%^OZv0S-~kȧNh[6cDUUhKjf,9_-0!JY@{LF$eԖ9M# IDATkOJ.T5wm3Fۣ1z$ I t'& Znۃ#yY4V8Z|d,ۜyB4]hUU<9M/k(v%8M ѝulN%!AСFp,H^ 2(ff؍1nx(k;9 ;loʽR*,$RJP姝_M:o4W.Y8}7*6Ov=VrS?Hl,/,ݎy~qq,vx֍?r ọmo4Eq8_8RFbq޾[qӝh$h.@[!D3e\D`)T)2.<ƼJ]313fKbcipnx?*#Sƹ0B*Wk0Jnl:-Cp(+#Bpθ~֝WYB5B4f9,BK09㦿os26 ~ld~R!.u2YGc Sj]dHO&⵵\&buG(d3u"m86-4:hkF ~G*mĵ=)Ft`8D}ߋciZ5@Hk'`|a#܍oҨO ++V uCKp05{]J#:2dQI]O:A*L0b -oR0g{C#x Vimq۾ʦI]Gby^no{)7_r^ؽw^os^}xĀ@7эu]A|4c4VgǓZ3Y5|5JCJ  abfd*Y}K{Ezs9P%ȶ#*WY^v 9{n2D^At]:QtCu?mA ݁`jvp7bh:p| U)uU~lAb K,ÙdNdJ (34 labn"`Վ~P슁U3gtcjVa/F߫-o{ڷ~{qd"Yl{̦Ń@쎣pXs`ެw~ 3Opw/y1M_x}0lї;r3ߝ;UF@Q}ʭT‘e ,CS$V_O 80TUu1duԁzK_GO~G]VtLt*u^f!vŠ K|gk!t'I!#B3I!N!ЌpE!f(B!4#DB$B'QBhF8"B3I!N!ЌpE!f(B!4#DB$B'QBhF8"B3I!ѡ'QYD)RA~3JJ\MNojnPIVߜ@p&6W2ǃ,QIm͈S:6x2I&pv!::Ԍg7Y{L܉m5Yx%'DDTC/wTT@`7wjw TR (;{][ A @]M) !T +{݌ZiVMm۟#1= } ὔ*.gEZEȺ>.7j%Hʔ$H{'P'a-hevT8n;6(*ljLavTQ$ֱ͐vVj4<;|2A@QMJkT`^08ϡD`)#81M+ԡZi '@fgy`Э!K7ឮ3qƘex}Exi0@%qwS!8c-]۲L3ËBDzLSpn% BS@zgsƸDe. ME)BA @1aGH?Z>YJ*KWy)THQPipF=h2Gݳ) M^U(i0 ((i@5=:ÊЍ~`f !2 @&ׅb1ÑA3&FP4mX<= nQocdg$;ۏN7{_ևm:8;?=: '] 'oo?: [;Gb6OWn_cճ;G`0|py#ΏzuɃ1u|"vm٣o= ˧w7OlL|hgԉ{W^ogg{r0(^ٴ-l.z[uq-Z'Or:9}rQ线W'\>? Ovz;;['[M=?=>]峇;O.Z_Fyr1\|Idwsi'M$A>\<<r0x~}e{w7N5[;O(0.~sझռnS>tg50 &ꌇ㽻[-[Sdg5j?Mf]q~xAWn5 xvw|xeڦy;:㻻V8 |UNQp1 I_??juuzteBÃ㽻GgWm>ν aӦTDiQ<R˪N'YVn85E%7Qf ZO8ոLbyieiK7K$yZ," \?)BU^VJ )bݏCj1fa'*jՌc;N;D0%#(ă:gD *p ~e#AZ{* BPU B@|'Cϋs@HлSVעêΆ@猨;*LKXP&Bʪ2R(Ӽ4= [qI Yn0@@%HLGw9L՚!0g 3abYF}Tdqfz?ʀqEVqV!\B$d(&R8i0vݴeJgE*èSg=13RWKT12@ hߏ*f((X:3P5*Z-0yiTs!QT !Oe^IT4Ort5٬]ݳ:=f c #Ō40T&}H1E'rVY6Nڗ EjdM@緈b4z">m1q|q+Alw0#-܋]<>RE$v5£0+4ѩP+.T%LcLw4) bS"ĩE0T{«PVM7=֖]R׺06Ja`UI璄*8v֪|![Q0gEPXIUN9!+IXp0Uw4 b` @xz uvQQ&;ad9Hȓз˃غk0&\YQnTTOrT~Aʋ$^хiǩA "fJC5]jPSw#*u괳`G, 4\2 [af& I f$"*K*2bsܣKM80_f0c;Qjۊo5o1G( *P&u0-oKDU9l"d1e4|i})xBUY R Q QVi*<:%BR}=X]PR/AN2MUNQ- b}$3m?r _!(aanO.̭*+UQv7!Y3LnXԑRUjQ2!# Z(NY $[JLJ -0Sg4 ke愷fGo_JhThR!TU ]Q29B(:<QZvHp5\[7Z.&oBw(C 4tX2&P*^ EMf ,%2)Xa=uʲDjxIdQk9^plGPfJ`)'v80'M35rmqחqU*˅bi0;g7>(D@2x)ꂻq.WX  LBҸAly9>&vi rۤ! 71Oe-JuÍ~ط- yp&TU3ڳ:Iki7q]QVW&e,?0釦e"fM LFCjO̭RhYi%B~͖H]&(6H1$sH7L˵~K9Nh3j BVeln YM P4j.ך3Ugv,BH7|rRb~XLG QHIr ;}l3 7tgY couA-Wc+؁29Ldj0/0CWk[~)[Å2|{Yq9Z5sc6;ֺ}t,Z"ɛ㝝:īz/ W׬ƛ=_Nҧ7rǢ*c͚s*Y2JDsXy ccO*^;U5K& PqC^of|L >zxs_ ed^*~`mod77 qfƽf2AK?6fW!TOCuy˼+o D"HJǹD" IT"H$D"H$+rIY-#B:!cLrSEba# sp7qCj, s) 7n_mE'ŵZ dV҂=W.ZIWQcc1mJK{k$PŰ^61+^y`B){#BIZ KSךW~]F}]Ž{W_-I@IbkK}KHر2*]ZNi%)WJlf8zVǹHgT-C$~~K䫊 wF=f /e5 N n)"rQ9s7jl US 4 NY iMbQˢХ@ԩ4Ol'j=#٦֡)!סP{k$ ˷BlϪhͭ=6Om.:;ڈpzpĆE]ڎ\>nG51vu-7͑.?j<~_^=m\uEܥv Onm>z:x[&œݭuns[ͭ8,)Νހ 3tǏJnEz:cU57AOŏ&tvTfϔ2+ե0m$4ƐQVKHe^045xV:`lYیjt$9w+hf-ZΆnLRVjH}u*lR-[%Oh_RuM$"8k8ղ>iѩסݦ䳌v+)"YhEc10_2=iP0HDn PU k]Y0vYP ZQ$N=!0.Tvq2ǑkCX6P]{{=} |wcODYR@%r?,L[ % oi3ۥjeֈ.n5٩KHkJ=j@ЭM۴=/*]5שׁ( h- :4tѵ$;sծś\(6Ljvˊ6 =?cF_}genA.䳌vM*Vy@fiE:+N'N,L;$Sc8/Ohu泄vA?V8SAc~PPDp!$RH۱,JE0zxʵtWazX֑\6φV5DQHUТ~,v2rٿCCa@ QQ<Y \6f [KM a%YPVP KHk.pP Weڵ6KkBKH0gi;w<4!:A^dԯRD1-i*5yzsFXm"%ؖC , vl26^?(qiI[˛ngS4΅bt΍+r[Q-&Gk;Rbqj8 i@fEPFct$\6h@ x;Bͻ&3SJ~/=7yzl=\ptw:٣1/p)ԛNg/=z"$$7q[wo=>έ"`گlw#E``|OkƿK`7|uSb(,|-*V{*.Jb>˦Kl%ߝ/MY ܏|~J29?K"yy`A:n}ϖ[_"rH$dED"D%D"Y9J$D"7DS;=B!eH._I[!`ccbmvR&Ve&£}oԺ"O- LZ~cL >%^P%6vE`1t4I*.Y S;SjS>M )}FXхrM&ƬCDZ # wI9"24VOf4&[7[0IpI"]tӗB[_agYQ8KNȟaVtFʸ$&hӜ4){QmdieYC%pBۋfِԥ7f(Dݴ9r?˲,4ן!eYE 6 픹lbmW=B"ǩ8ϲ,˒A7 % FWU JXo䖸юEU끤-@QMkc @ Mej}=-ٖ BOԳҾ2 TJQ%o{)eY*E8-EYz\mu@TNdP,(K`nY0`+pajOi0mR tԑQ8dI%͹Zqύt7*FM9/04!BTہohPD&P UYڏFL)8'G؉bkaDTqL^\ږ*8#VYHF&ُ*@Buc"@ l{JBa#X",/P04"Q6)"OJ9&V!včGDvnGۚ"r5sX B2p DU!<=uQVdPvTS$nZI;. k]i%RUWf N(F M lB7[A3b e%ʲ,7( HB -uKjUYAYl7ɄRC̶l47vqșf A(( g6CPBIxO%o7ٙdcQB:9ڭ5o> n3WiF_lpqoz~8_7!A7պ:^(|5YÓCv-uwG_'bA0nm>4So';[\ [[n] xux:OQ\FoPPxnʀjzSkWn8eTNvvZPY{~9:{۬K89l 7w܉jt !͊ !dAB,O٠!Sui&cbF&Q~ZQK[(*eȎü$iEqrwըKhFx2mPjV竕hQahJTA׹eWJݰ,[ j8;d2ϫVM\S\+7I1{\Dq AR)Iv+ cXؑ2^{3SNM&UU>sܣKMLqC0`mߊڻDpEH"/!u i" Zcjl\Y}X^= !TU^_d[JFM.P#Tæ.g̙K+J!TI;˾R"/LI% p HG-tJ 'iΨl$Xae,B%CZWQJ8 3QĎKVmvLn|Ӿn rgShTC_\/7IxZ"i"tQAO48ED$ue X,X6re Mw[Rk*j I3 a^!T3\[eQ(}'ѝ(rֵn!nQ!cVN^_uA5~ӰihHe(5JF 0?}cF?rf@Ƕ(bE7Q]' W!F W!4 sIG-rHؚmfTۓᘆ UEHQ3zXLt89C/[j"BQ;@a?cwsWI!'78k}CӲ @Zq{Vv2;#°%1-Bى5MByuDizqV:qB-5[O.oIM{g؁efvu[T'ﲸſn W-\?jI~v ǽL_˧{Co W'[;ҡѲ٣^o2A䶸f >Kyr|_*HlVC^?*^kO|z|^Ww-7jM8~q.7;ԮOAi|G$b^դ.13b Je"$IتNd7DEJ'D^R.?{fɹUPJ 5\@3.'Xo0%q]tM9(ƹX[1J01\꒍z׶ߛIP%f8w ":gL!VTܤo.V\(½ I+"(&2nNӎ*"B)!gn}:Ζ>#ca0FWETW$%@bcn;aљKl-΅~g;*AĦG`jc|Y[ y@dhfܜ*}]M˦VRNmapJ͠vdqMJNSQutJTҧHzx2Q1߅)nZ}c33t R׷*M&X&P.U[Tp}j*ĭ?n] daV`pqswn=?\l?:k'zR8~tz5 O6aoI]̓Miqw<`pk+{'w_ ޔ#ΏzۏN'm^_yr1զ]<|j08xi{'uiG;[u j8jsoQqq宍o=loww&DqWn|p*b1/e#zWy% 4gz*QЏ̐Aʋ$^'Үg Էثsζ5m';}v&n`DET7I-UmSjG N3ix.P4!+IXp0Uw4 b,%6Un,M3t`ǭ &|=hNfE@UO UU})xI,(+4DE6 Q-tJ!㉒Tb8A_O3MTU^hTQ0fQ%M?G]Q ,%UmJ!Lq%ȓtʵ4Ba8ݼHha%fXͰ(wh Ȫʲn2Mo`/ƿU& %A2joUq꬝ VuJD܏g:(kg:1$GhM:O/1P.s; gkIDr`=FԱhjnXZhf9b^hń`ZM(cص!trHRL D0B(ȱIWشcќ~h6!^X2)RjDz^|ۼ$kG.%2>4++RbeFN s"M/=(%W\Tt;X&(#7$8ЧBP7Lg~iqMdToO{uRJă.0SnjIř 34Qٴ{hV7.V9xEm22M7iTu]1o/+SqL\ۍ{+*L[AfRW^OQ^zR׿B"Cܧ7^b>&^TE<Ė|* I$ AWGʷ*X?ɿm$*:bpZ~_mA~ʿn#$78W"H$K$D"rH$dE$*H$ɊIT"H$D"H$+"'QD"HVDND"D%D"Y9J$D"rH$dE$*H$ɊIT"H$D"H$+"'QD"HVDND"D%D"Y9J$D"rH$dEn0~?~~˛އ??ӿ^{N۩D5+231Ibc#TD%23ȗ\禟gBeGu;S4 fӕ"w kggvzܷ?b| IDAT)_?_ޟCV"HnLw=~Qˏ^| 7/mm ?:u/"כh?񹲰"2'IU&KL3癄!4JP{D(ffr+(O5e3;kk:͉,D"Y<}߿E\>{ޯg_xv;{ON{\z/>]___[[_Gz\__[[zfɹCH7(RBh<Yڜ7c,2L`adŸgPgQJh]vJ)!ITI )Ҟ1UPQ@: @z%BS M 0)ƙI (B՞ FrU"N>P.V\@8J av42y?ϗQ7?|~^;>q~x~?|)|}އ{AYS8ͽ׿߽zᵗZ"Gfw>({k7`m}:

~[7^>߹s|}Me{wׇOևwtzSe.3 ) UiiYUU?F:#Ĭj(gi}|嫏笡D #P0}Q9uMS@Qu%PUw*+UQJ 'iOQREA{Bb2RLWzŜQ^TgkMIdqB(:<QzB IםU6,JEהژd%$ _򗟬w|E~po~|~_|PGMoAǿO/YvEzU)8C"H禿}߿gpPϏ_|Lw??Lٿqe~ǥz'//>|s}ݰ ݿT=N~eg^j8Fnʘ!4TESRَp)a%eaMa>rl2~",lBWttJԚ9@gJ"<5)0}Kg1"n`yhQ+}c7&={o:P ?`I0!3l}52f%IQ P&~^۫{ wv/ ??{ޘ{O{Ɣa}8O>{8frH$U`u H$[ܱH"H$D"H$+"'QD"HVDND"D%D"Y9J$D"rH$dE$*H$ɊIT"H$D"H$+"'QD"HVDND"D%D"Y9J$D"rH$dE$*H$ɊIT"H$D"H$+"'QD"HVDND"D%D"YLWA v ψߪXA;BOvJrJ#Bsv~#6UegbdbF4}DqR4s0\9RKa:T!NoYd[/iWC8 /hB0pvgZeqڴW*뚂Tcݧ >n($66ja6yp_UC 5q<ۀ-3^À=uud)gÛ2RBx}'ٕ"YӴ R23ފI}enKljIEcB06bc2h &Y`1nÊQJ05q=[gt,48% :)˵"ʸo0F)thkd5߯Ze`0/8ĊDf5wS9]c`Dv11L@YFZ FPTY23:d@qK,C19F.j1oR*ðn*rL0vnpǦ?3` luքZAYFnRAr݌vO>4[M|2]n< l< GۏN`0=}Sn<~`py; .6O9_=?l9{uwb0~.ݽaƧG{ݣ`p;<ݍò.~sऱn輶ígu'wzst1ͽ˳'{OΚ#9\<{ZqoXnv]ӽ7w^w>S}R׮l|iC],hŜoퟜ?:|> Wۭ? G‚n6|pkYwG=ں.deV`p9l)]*1 Nkf从7PhBԁt^(֛^, `0D􍃧MHo *=3lzAf Id2?!MD㙂 @AUOQeu{=Z3$B`#ּq(U׻7rBXX ivjjN%z2lHIh*xU $[P`H *P0Ws=_:-Y1AUUܠiRMR x4UAPs*^q>>7<3ּÔ',!JGnUUA*X dN &2`~A<"Ɂ/c R#8Ao,фtJ <2r!JIO'⍦|^ ˒&鬘*Q?itipbV8q5ǺE4wkÓhD4ym+Kj4Un'kB0ugUB4MEf ZW"h&!`:1\s h<@06cd&@Mw4Q7]ďP"iu`JU7+$V=D֟$9ꍥcPвG` ڴ_y3+S@Rɤ|LZOEt"/OU?iOTUx0&FP>JPDGHWIA>ѴLӏJ `\<'~PHNɌm Tf!$6= \:]?B:^"Oњ,f'4+(jx6jDH̤6~ ~^IURrCpFzVdYR.cYPq6JM Z(rv3hV_3794 @)xTH5T( +/aM+ːs^ʡXOc/x$|:Ʉy+/)E~$R|1@H_(N(E%P:hZe@<xX>qPo6nZ$ KSWLCA)h œR:2(@ ~hrP@}H<$K (ʊ7ĭȞd@jAQ/L=Thbp+>5(M9ĒpDih#7COthz%I# SW p Z| 0} %#a'̷٨"%M 2aYyzM&H`(,I %f>d"dXnW)ۭxHhJiVDəh&rZ} s0Ob 4Aw8oraM&p"= S5mīc / d8$A9M=ݳ_2D I9_Tn-%v (zn-en_  pЬ֥"R (nQinjg=c ߴÜO (…pv7 tk͛KP.,d6ΜLWrfBC<(a1k"Ĺ$ݩL35xIO $MmfʺMPM=R;5La4s&)B3WΗ6_=hg7u,o$|zJˬ3lTe1ۏ_fk}TO31δb=<৺i}LL$4gPݍIfg7-4V޵4ϳ;7M2oilx`ҺMNdOR;8ir͜I !Ps}قOOj s tMX)Zh'X`'E¤Pti<3*kC>)uD9NכTs{<_ f9nVTAj . 4O#xImMǤMO55ĿvjjBMr2S9%œ=7=PC>vD2# nIdRo(3]jIg5 tNqEL!Wh@M|E|J E$IXX$|>)ȏ2xB 0 ޳ͻ`6|7/}ڴowfS!Ye%o)i) FW4}ѡ>wMpm;=اZ3Mx'N>g\ E|ͪPD$˒SV̄}p$вp,P2fcYeY THdP^L|h0"d"'KxjB"#` r xAYq(9d^%QPSQ zi_8;ٗM(,K>)T_lTC@!VdYeI&'C~EQdYC<~v%92EQdI g80C? w?q7޾qÚ5klظy{MׯYa=?]on6~׬YM~~=OQڟs3m'r'6x78>I%ZHE}>yRݫIc^Rm-@&' LSIcJJ( EcMr&,\/]Onj&IQOZ'zT?D}_ac+3qPȞ&^t~xٍͪͯ+6L)mwAJqͦUJ)zʛ~6o?i妝'YvgSs__%J顟a(sWW)[zLօR(](j/ܻnWS(ՎwHCcPnAt_wڱ~u7QJ?.7Jx}}u>ן+hnXF}/|ᆯ?wҗsCnzjߨ6^/$?m|a _Qث? Ioz|jx o۴4~kƟ ӷ~תR:M+ZFE7okt]W_ =+\i[u=*ͫVi)[~pkJ}u(Z_K=W׭ _:6:^ur՝½WzݺOifz߰iT{F ^ޭܱ*}{CXe_sBPI3Wk:Ʈm7chh<%ÀMur˪6G',GVW:RQ&սsM9Db?(9)v FI˥@;Uz)y@!iY D_㄁# nNlK4Tj퍽{VܶvyNSDai$SN %!τ_Vsf3yҼix2A_45QIXNAԄ_[eB6)ПN@,)D3 YHӉlύ_%wu\+U߻x~պ??O?Ώw> /޷mVNt|]kq̈́Tl^'|{ĮkN_t0bm_U clM<;5 $L:i#Jfd<$Gtq4]x©lVzQɝUY5+*!7(G yҙIA69-)ăL(LK}w}r©}kp~ ~>ϝݟ]cM]߱+w]{mOU9r5V8.fI?MBOE*H*JX ?miUfIf* v~ e{?Umŭ|QY{G/%ߗV̰itZ"H~)%'7"fڼ63B6 M r\.7ynżGZ6 ʊR@jh(}eos*ǒXh"$f4= Ti @$N;WDG5̜C~b6ycbeln]ڿ` A?uK4/R.bg޾e-+HbO@?~`Y{L @[ޡ]2|W8|[_ͷߵeڲXgz} N(7齍r擡٩ZA @*Ոe zP,R $śEހ,/ )MQ<x2WjM+|&xJf}Oe';Vɤ*򄹯8l*3'6^LbqݻVV-[([yLϧ"w>܉s]{>9j'rubQVUDIs|Ǔ%( 3u nXOIO앥|jԔ$)zZ;AU;R|W;h[}cH$vAߍkg\9wӹD> P<=o /Kc|8"44M R4MR0 d}x(d c|`4d8Vd 4M>/A}6ħi2r0˄ @t|g]P‘xHnQœв%A-~#gq'X~K+{O?wZ_A5@86R"dXnWw`0^ Ϭ ʊS>T~/7C_ M_$Lh-J^SUEA7_L x溊7AIvׯHS0ޯ*D?r*w?x{O^E-}8wCxK]O'\˔c͗Y}-}3vO\Rf '"@<> @P ljnx)Rr0uU/H2>k4/n3/Mv=!*rR'bw/0>K䝟UnYb|x >̼szQ'3b_sjR__wٳ]3n]klb&Znן=ڱ{RcKܺa\y,3sھၬv 2s'zV6utjtuCo_';w|7/ѽK=5߿R`|]z0TruaZj:ҞVg#9]}_mf$I lD^&RS5P4<Z6>1&t_eO6L(?ng!w?qC%wnWn:E/S8- p0ռD9ۅ9~FP8o(B!t B B-QBhlmO'ۿ~3B!_Ww:@!b>A:ܾa \."B rEs;r+ػ#g pK엿cA{,[.(FVOB֞'r𗿌*^4B!>7 {w{wre{w{vJڊ]F6GOܑC_=wP>}͍k5n>y]!Иy=uV{wVvݑZz9 YNv{S$B謙Ģ0 =ޮ®-u-郍x;⌫!B#ݷzEP:Xp!u#lFo?~CjFW})r۲\4 ?w=yZyB}ߝB-{!й(B!@DB B-QBh0"B A!Z !aE!(B!@DB B-QBh0"B A!Z !aE!(B茢lSv  PBPav`Ev&202 C9a1sQUaAaR``jck܊bE. 0i٤I&- XqR0 `E( EAbPJ ncm㩋gXeGtn :-jhtD'E bRJ 8lL#r,cg)蜅A!t #i CU2R%Jg.Nɑ V8icxB 0B& Slhpű\3,C7ֺfR$S^I !t-רf#ڇݶ8żFF ,B(0"N%BbJXZѥI @T*j C A!t*UjhbkH:^&\r:; 5 Z(\A!th5ZъI:ȨFUy, `g"{Ʋ eR蜄A!tjXtBu tVjtFK9L21Z;scl,p XIDBPh͢5 t&-Tt!v5ʠr6l1vXsѹ(B(5 TjtD##eb'.05 [dE'+:v3aN0"NBEA3iY#:H-KyBe@3v')XssQ_PxjU-=|1(w7r0m|6aE2 T͢E5ZhFspR@Fwx[m3N;p0""IanV-Xt贙a,*#Er3m0sQF݄J jD-1rcL  fY[.jax@.:aE`_:!dl,$:9^%+b#t3{Wow]\mo܆#@tVRQJl-(NW-D225 |}R hG +gZ쬝tVƲV;yYD7iF+5jdD#%\e^EF7]-l{ `])br R2Z!-[6;vjgygQ F@$;Ί-Vɵ;X\t>  `QRE 2RF ыn56vW;8d9ً\mR\t %âE5M:UUj]`,  #:y-n{Q:x#(:`F@(5,,ZɨAGQ.w8 1+Ԏ-nẝlFPtv IA7A7\:Q˖3.r`c w5 ۹E-lE-l`A7iդE WpT=V4GV[u l+2pw#t. +B0IńȈNF`RP߽Y5MjaVX'GSN9DBsBh=_;U#e>{:m `ΐ‰dDX,k.ia:`E͉InͤJ IDATՉSU`cxu9ٮN0M0 A!tr_ EtTMZILzDeIx{zZ-l;,b7ئ:*ݻ=z 2z3Rl܉t$yV`]jDl喴g x}zΎjf2~wټy^{WQ̈́J:9^JV. @[jχj.j;a6Q4˲?s֭oː.׮oD"qUWNE)X5jX%pSW:T-XM/w:egl\t ЙcY;3H$ÇYR;W_;˯y睏=5\sKzmL)jL5Yjٺ_f3)PZ9Xv l t b>W;x_}ݰȺKFs/x}=Sg IG :T ZcN;S6Xz4Xrm.:BQ΄}=#>{キ͹#KXQ3ɨ^9n+Go>ۥZϿmSR̈́r5&]t{g:[%bjg06gEN_~}ÅA[u+oLa-V#NꩧNY'R` E iQpbǒ֤,=VS+z\y](0"tzVg>z٦qXJ/[/ru:8%Ut&9'xWv`1,0,-,Z)tb΢,,]]d;ց[:BK=s=zwrauv]DzR @(KV]w5M;Ӆ<0P#lAFt2TԊ`/m(0 UTIg]v٢a.n@00 e{kI#GrիWsZFj*FG7)`RxBgɺ]΂i™-'BgQͲ{ܹs޽{cwWN{ 0̵W,qظA9XjbZ2 `qu +30[o9cH(TMR5rdb%K6 `c!(Yu8nvr]h0"4?r9N{ocyE yt 鰹{WÚ8ѷ,B)KZ/_ӹjŒ^[r3^kmZ5jQ:UPx?iQ;5[ ,naݭ]0"4'?я~C6ﲥҮ֚I8)T;%]-ع-,R˸v;?P`}b%=β%k-,nT^zo>3A԰br 2 ,|bΔ-85`{ڸ.'Pt IB^}Շ~WzO\ vжh߬XZ sZz@aX,bJ[%FhY&XX*JnV;k T7\(a9l^0:vR E0 ͆R+?y˗uaZ C{a۳"!mavXQ쾭/;Z5eV(vswbq?W-( höA9rigxu5 ɱE W ̀]\v …AB?|k_{[x^ueQFh%䏕v}$tY듽w VW5jWyoYf9.Ql l%KW~G.2 cR[r2@>| D3fC)TMh2qr -s<[%IAd;ZN'fägBA_vs|eύ,7,Z6N1#ꁡ0}/hq6PcZza`aaZ[[:;;'>ڴ,kѢECCCùwK.רVX,+ ., 6[PjXuBJ:.NPhG;b'dqB.aE Jٳsk?x/J)nutv#>:Z5;?~6B; t:;;;EQ6mʎ)m{+imlR?:p,7JTjd*9VxR1h3h9Xm㙥.N.] @hJiX~O>,%kz/ lks,̑~XaJ-ŝ<81~sI7Yt8<7ݣi(vuu;v׹wV]w0 Ɩ+) 35,\:)i#<"g`(1G*֒v[iw[8E8 0::'_|ѣ@|\%@zĖ6'e՚E#őbV,]QJ? ǫtSK֧J)˲===b0RFe g9;BN\PݢE52R%racu ¨A_|Owژv'`ʻ)eY,N6IO>T+{q9g^ NJ9¨EyYx9\Z[9϶ڙ.P0 ?yRt׮;FFX0v%6;Wg}EgPAUn璮 +i5K6B8;փ(ÀEoXfRZ)D)&-\թZӶ'lq2pJ^>w86j.'PQtd2<%} Ow{Y35*넂CXxdUV1j^;4\oȥꒊnnϡNj-]8Jrb9}= sq q6,0S{(nQÄIK=V%#eb15ncatt23qй(8p~뭷6]rYwnY˔pYXѲ>a$#a^|o\a+V?}P ˲ve_}iucq,RtQE+& rJh.i,S4wu .tmX<6D Z9l%8ӊȗHZ1NyĞSްC׬@xpءT*iEɛyK֏9]wLOOkW@kH5N(2P~i'89ֻj>xAo]zK!j]X駟 aI?T`"pv_NqdsۡupJm {l)u A׷CD)%"֣Ƃ+055uNV`2CXQ+zd=P׌:@p#DZp|^qYA`Wu6օ%Igy&Mӑr0X! p8 P2D1!ʔmG[L~tZc($8qT|w8o0D1(LU=LOH[b6D Nz@' `;NaขiC@_p WzR A@x!*#8"d\h4:FFsZRM22شbS>Z@NMzﲢDPz 6D

Ks90;9Բ^ Q233j +(vf\xI_ z7..1s.H.%L |À A*v<)9X-~]qppWdCԺhǣ(*p륪&P#s\jb5ۉ ? )ojm1p:bJLnzs"b7*{; 3 3[iN7-qKnh/{^^fdH9LzDxVhR)80ш +y;NL]leY~[j4?q@J42 Sj&12׌hnDG8.}VHK 6@-. QWD!,B}K˗׺q'O"+.z @TD|c[^0׉QV0ΚaIyEG~j]CiJD : 5bNj_-)`RQcEE 8:$)jY? QrСc(,| IDATǎ!Bѕ 1p֍82{ڑ+vh`?Or:No޶4N5!24ߧͻ NfT0|'vcs_&&&^q@ $̴ӌLkeK@0H =3>2O#P2h3Բ~ օĉc@ ,4z1hi"y5Qovn~,k#xNT6O?B"(NjiF3-q@aT:]וeEߋ=elZFGQTE#{k;.^vҊf/ՆY+0"Ă+@a2=;W^eQL;J;Iz}A='LSMvBĴ{u%."*EjY{a RdY}X"ھ}ۑhJ_2d!Ƒ5# dqU $=2Vod|ϛ-}2W.=3|;Luё8dIpA੧z_ݍLS LVb ڼ[Q fRbe]@vW͆uAZڵkvv+B2m`ٻlpɋnReӆ +X~/!r! x޼RJ)_`e_LLލWµngu#eߙ$ >Ys4-ԉ xj_0TA<="GE``hZ֫fZLMM=CZˇD)(5\}!Y/zI? Y-z@&P1-/^a4ʹ6y"2Ƹ|/< P( bO6•C%6%z*oX#<˿+VxsІbez :&uK2 P-] $-]jY Qgy衇v;CHpJD@$SڜID" `qm<~~jDŢR);{c6_a8??j45 QH$Յr~B!YlaqƃʵkwOOODy?H D̄Ǜ{.Ǯz4R.Ez}dpZ !BmQk+MEOԺQ1G*ȷ7@)3jSN3a/r6 X 5>Uq2?O@ԩSZ w Gp^Xȋp3/nEYHG$3a +KO?D (CP(LV(9gƀN~1Ey%CJX֫g_/y''KVW IVuJSWGʾ+6 9SG7AJe``1&p]W !f^'c~>p= 3aԘz~ĉ|1\'>Q,Jq[O{W,wj'eA@"WW "҆1@ɵ!qɯ>zK'aOʯ^jsDXw}$kyGsݼ8煂hZM*%O00DDƓBrJ Q\NL#6md@^Q {zdr>/8H lzlZ0 /<;/^/m823Ї @8}\ @ϞG_q`u?{6klS#DF_^#E9BQJَSE*xz r5(DA/3͘fzz:)\ $)}*Ez_ Xq0Lr VZ"|w/9IW ݲ~I3LD{ϛ"#XsW)c |Gbs̶8/onZdJy^?]j7oE,|^'!5/j<9rjl\bJgMju3j%IK9ʀ&p8̇#ϱ䱊.GGC-+Q뼢{?Ύ o0j\)9]W2I҆Gs;{/i"NBʏ^_OLLݮ2!8_t饗^9jZnj3L5zr% Z+mB:DQP'NBw 05  Si`鹡Ie>6Ḑ?'N \j0'Z"` +#g JOwz>rt"bT*Mq_a kݸqSO=uw>#s*ߏ2e˖}oG?zUW14=u޽{񉉉ÇOOMM5fw`s4Ti8SM==EafZyvV30S/!K%ZeC:hmvAGwn[;\9^Ùy"nsIKTѕF +6 ~U'4T2@ J4rnꪫk{=yl$Z)p^xMlru3[rCCC_~9dY6;;{~o7g;+ʌ#`8U>DQ1qV|Sus|Iv`\VrXDhJgyV- Ժq4G&^j]q`MJހ^r$X'! uO6}3([l[{^VkZYiW.+J>7%I)GGG}sfgM5n^ 8!b#L`p:3S{*'TcZԡ۟!90,+^~0}%Hn[bC:c:ٴߗl2$GL#9W$SK*0P .9#@/g@IQ,xΝk׮]h+>8,2ʴ& PԪ8Aul$IBDYu??g>7pCPi(] N|x (,٤ x~Yc!b83RfDѢ'?mT!CF*w58cȚG1}j#o\ )]ZeG:!6m?0ppՍ' :QVƵN4ӈaztTcq@d -4+ {LϵcySoB0&y!DEÃv_>m۶+,o³!"2G绑7]vm?_*FFF "5{L;P&,2~<&ogGÖesss~zqݷTgkE ݧdi2"'KepK϶ءTe?=ut.W\f"*Vkǎy^6{=ydzY+BBD4>>o;{[6_wϳ{g׽}{W:{###7t+fFƼ!f2vRO52C3Բ^+FqW]Vuޓ(I3iMMJq `ٗWDv~͈`xǞ9Q//pww믿S* ~k;Ν;[>#?Ν;>l6$)Jg2U2ƌu]Ym8vﻻS/x8 ؼyҥVj%ѢW7}RM(bY Q<$X~-[n.yr|#3Sp1WT| +仦MK&?<>~mv2Ʈʾ>='&&&&&n/LO8sٳ󼱱ZKDw:}ڑMnZ뙙Fyц7&u`2l͠Haѓ$],W\V7x5\^?~g?m<|˺բ5D C1s"iG_"66.[1x˦NG]xSOm۶ȑ#Q˖-袋֬YdɒjZ.y&/oX &֝fdKWeR^iuƽX7RO3eӲyNJ9<<<<<\,qipͪa$4y- "Τ@P26 {*Rpc(FrV\<7|twu׽89/~߽e˖ <}&)]c6H)Ι*WU'"ݻ]rÖ3Ӎ1,=ow=}w9 Բk]7r#Md!$Wd#h>L:L0Dj_p]=r&ٞO>T,CyL2}? |w]7Og'oٽ{}gtoݧ}\q &?9_f5\#ZkCjzʛsF;|PX3yۮaxp}-cCԺ ??Ly%cBpXɜ>({''EO 83#hCᚡGV } Yrk-z0o}ovNtkˆ91QO8~lB/%gJ杊\)89]zdwvsŊZpfZQ/U0I*zE_-W!$Ӊ25#UC]d}`=QOջs͹OL= L|`H6XpY?joHyI8P|i 85y;3fT6Ռ&\bϿw޹p/=|7*8'JNBetib5p CV I@@ ,zC%h6P@2P A`)C/%gQg:P*:fq &ϼs`j׮cδ }weV5co?u)CDT\PiXaڐ7-yR ܴZ̉7GL3YwghL5zؐyq<ReTRi@P2t: 9CE ,Lu8M@iSƚ C,<|'F -fӐq AfMk?tJehawffLm ]Z!"ggn]W*I=ˁJ9^D~# İZ:G;&cL0EYikٙN/RjB8N7[xca08 QaCuC 9ƙiG+C|\3|;_k[|˲DRVԧ>5>>>_v 8DC G5K.̉={<զ].`/B;z/9U cj@ 1_ _2D z 0ghcWĂsfڷr?} lZ(>ر!lV8b`I3 @}[ ;I/˞k인cdH):>$jy`+3"/{ieL!" ]D ʼҖ,#n=0ŭū'~en6D1{?EQɗFAi \Yt%e$J3C9 b'a8@_fU%p;q㜣|w`aSnU fɣZ)$cIfRE A WW8cQ|k;=D^UW]>a:3Q뼲o߾|3Q9mY?bM3_ !L2&Oq8*(S$'&b [ɣ[Ned~⥃%/ɌV12chE/zEW͐_!  9/?2C\Ch]} {ww؊\z#lZ珹O}S;wG[6.! 8\=ɉ$iEO|'d8$p" \Rгx$j?yu+,*bm(6コv$(;Ղ'""@M;Nϓ)1 U TSJ =? 6iOв.6DDo8_r +|68Q(8`Yb݉e/]:cccXφuغu?0 9[-^;RɌ+ &j2M=ɋ$ `Q-9wcLp^6.k|W=eT< V%S͞'Ys0eZh0c5%/4nwbdyc3e^ёIld6(91;0:趣 vݺЇ|u!B{zZ?ۿ}vr!y\ȯ0V͞!b W }ga0ӎj\\cyWnv3ِ \9t㚑%1Q%g2$8+;Ղ[O op&ꋺ,1`2T^2Sm/LO73R2(eFޢEVXqWm\:z;t>hX:;%_cNd;}<Rm:qd*szJI"e(3nbw|l|SOe;.,Q'{jEi7:Q6qK\TJ+GDD2DLL)Ze:e伿 NOi.]B\וR !Y|W^v 'u!j}4Nm^I>T Wz;NLL?K/eY/bC:ΖK^= nCLVYاZ>m۞}=ݧ_p׍TURq/ @EnGi=ka<݌պS^/[(U<.m^Z]5T^Rd/kGEo|u1"!jYnlZoy RpGg A#JaTAġ_y^)EHOJΤ,MG2@i2ƄNz׻;k׮={޽{zz:5:6OD`x6(*Q1Ƙniz׭'!QfEwHlg ðT*|-˲!jU ҕLZ'FACeྠ1LnYݗ:g7'hڻغ2߷^vc8qҤ6e&d:S-PD+TQ(N !Ua2LRAJ/IsiIvN|=>s^{]aۧNbJ.`liu>Z42 c&qDƣ0 -Z~ƍbq|||lllhhٳ/ONN6CܶWv]vݺuBWO8q").RhmJ)ʡtνo8p`hhhǎKy x QcKv{7P%A|RJ[VsQCy$Ӟ,?RUZHj=lF`MX\a۶P(X533ORܲewEQVhaf΀mUl&tvvx V\f}'2@#(7dr wrrr```fF0>&De/NPbR!:fb=)N#x:߭Yhqddȑ#s90jKʄu7!@Rvx*ZQ϶lJףGlbQ~"`=P0 dV~ώrV|׮]C=|@˲ctm!mik߽݊uaT:u?qL^g,BǢIǚ]>D "QoE.%p,JI( LPZOTPRپt:}zرcf3|'v$]jѕjpfv'xСCwl(===ehqFf4.dYD*] kS@\OH\i J)AXR&DezDBӞkE\Jj6&)!E 7&.F @+';nۿ~'NnԛI cR]L~frh_ԩS?=ܓNlB4\DFBPfK Y֬8xvj``̙36m0waBHArW[<\m?.s!Xd6PlrKEvs=W\[V޲óTF@GMB|[W~ctرGy;|GwJ!P4B)lJQG‘Js!]ӧOK6l0QX:2==r+Y߶+1*uJ /̊uDxfznJ=p;칱۶HK.hoL:̢Qwݾ޸+1==_;Ή'd{CԂ/JH2$HBkB|mJZ D.dkZ+2==/a QcKeE"4Bp1+2=iAGB ,:SJDbg41߶ْWC+\FwP*RMqظP_iLTZIHփhG|n_.=ϋRj!t-BhQ\yalύy} E.a 2ӹƲwaڝ]f k:t(%JCRB J&Y_KmZ%7@\J@ke!n˶ioh:ZjLTR3jJ% q[ .z,rW뻓쾡Fַ(;}߿"b< \h7#ȳF7u2ɷzk݋.dƻ3nNdwngVv$x$#j JkϦߞ0^ Elʴa5W=@_rXk[ʧ#GK͙@&Un,\ח\׍R* J<91^ Y\bR }_}ᇿo'IN PkMQ~m+m.dO w_PXoLdS\PJQ {n̯g)dVI7T 1 w[`-Eb?5 %7t˧v[֯_b vsи<"rΛٳgϜ9GFF(я~>h*D1Jk z،BERB8-r{cL1!j,{wرS /[޳ufT"de#&W z5yp:_\e 35DL$[nݱc֭[֚s^,8qvRJ !qSD"cǎ;wJ)+ݻo߾B{ul;WHб(\d<[0t=b\~7.3kKWƲwu].]:x+2<<\*/\yлsmWW:a[lҜ"Х$)?, /~{,KQTƢ(j=U5y}l۶nӟ?SFf___t;ѮoX"( 0%Z#ݓ۶mIPX"'~DQܶm߾iӦիW'VNߺX|mx ųSb=`v܎KhiŅ`k6\˿;zQ}3 48AyR*nh48|~Æ Jŋo`oooP/'bqcovsoeԷ-=kҜLs練O_]ƃ>hz1!j,{T*Lzgv:޸qmݶ{͛7R0JLK38?=4^.8Ji bgu,g@ )돃gׯh!Ԝソ%R;vLNN;wnpp_vP(ضCtk_nSOֵ-/Q f.4j]n?kG.LO2t ci:h|d%8 hǏ?~s΅a-쎤ttb{ \MVG@=[h6L~>Hy[o5 åx~TVdvu7+yJ?n)/2 08_8}rTKy~P(tuuś 3!j|dq1&(J$(fZ8yÇb\Vzlj%R"9X12Q-񾾾x^xO|%ʅJٶEѣGHB3gTUB6PJɅBB6r'˭\r'ofX6<$a,"uѨT*juzzzxxٳ###JVUfod2/}DRJeY85,QahR˲ݻ{^\̔x 8uuumݺu۷om3a|Lx:333===999===55oݼyg?Y۶׌5xť?ꫯAs~&rBa͚5|>H8c0>d&D c,Z !8AaH ^dmw_@WwL&l<6OfW„a\+ J<kSe%ղx=W#W3 Wńaa,2 0E2!jadB0 0Ʉaa, Q0 X$aH&D 0 cLa"5 0E2!jadB0 0}d؝eIENDB`artifacts/readme/000077500000000000000000000000001516100455000142705ustar00rootroot00000000000000artifacts/readme/breaking-change.md000066400000000000000000000072721516100455000176270ustar00rootroot00000000000000[//]: #@corifeus-header # 💾 Linux RAM disk persistent with Systemd timer, service and suspend [//]: #@corifeus-header:end # Breaking change ## v2019.2.1 * The ramdisk works with multiple different users * Migration * `sudo p3x-ramdisk stop` * Install the latest version * `sudo p3x-ramdisk install $USER` * `sudo p3x-ramdisk start $USER` * What actually changed, is that every command has a parameter `` eg. * `sudo p3x-ramdisk install $USER` * `sudo p3x-ramdisk start $USER` * `sudo p3x-ramdisk stop $USER` * `sudo p3x-ramdisk link $USER` * `sudo p3x-ramdisk load $USER` * `sudo p3x-ramdisk save $USER` * `sudo p3x-ramdisk status $USER` * `sudo p3x-ramdisk watch $USER` ## v1.1 This is only important if you have an older version (1.0.x-y) and/or you use linked folders (like IntelliJ). It will never change anymore, but sometimes I can delete accidentally my data from the ramdisk, so I refactored the name instead of: ```text /home/user/ramdisk/persistence ``` now it is called ```text /home/user/ramdisk/.p3x-ramdisk-persistence ``` (hidden by default) and if you accidentally deleted, it will be auto-recreate (this is always visible - a symlink) ```text /home/user/ramdisk/p3x-persistence ``` If you are linked to IntelliJ for example, you have to recreate the symlink in your home. For example, migration looks like this: ```bash # exit intellij if you have linked sudo p3x-ramdisk stop rm -rf ~/ramdisk/persistence sudo npm i -g p3x-ramdisk sudo p3x-ramdisk install $USER sudo p3x-ramdisk start # so you are already linked into p3x-ramdisk, here # (if you are not linked, do not delete and # do not execute below, only if the intellij # data is linked and you are sure it is a # symlink ) ll ~/.IntelliJIdea2018.1 ``` Shows: ```text lrwxrwxrwx 1 patrikx3 patrikx3 94 Apr 14 19:34 /home/patrikx3/.IntelliJIdea2018.1 -> /home/patrikx3/ramdisk/.p3x-ramdisk-persistence/content/.p3x-ramdisk-link/.IntelliJIdea2018.1/ ``` **MAKE SURE YOU EXIT FROM INTELLIJ.** If you are sure it is a link, you can recreate like: ```bash rm ~/.IntelliJIdea2018.1 ln -s ~/ramdisk/.p3x-ramdisk-persistence/content/.p3x-ramdisk-link/.IntelliJIdea2018.1/ ~ ``` Now you are safe. [//]: #@corifeus-footer --- 🙏 This is an open-source project. Star this repository, if you like it, or even donate to maintain the servers and the development. Thank you so much! Possible, this server, rarely, is down, please, hang on for 15-30 minutes and the server will be back up. All my domains ([patrikx3.com](https://patrikx3.com) and [corifeus.com](https://corifeus.com)) could have minor errors, since I am developing in my free time. However, it is usually stable. **Note about versioning:** Versions are cut in Major.Minor.Patch schema. Major is always the current year. Minor is either 4 (January - June) or 10 (July - December). Patch is incremental by every build. If there is a breaking change, it should be noted in the readme. --- [**P3X-RAMDISK**](https://corifeus.com/ramdisk) Build v2020.10.129 [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZVM4V6HVZJW6) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Like Corifeus @ Facebook](https://img.shields.io/badge/LIKE-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) ## P3X Sponsor [IntelliJ - The most intelligent Java IDE](https://www.jetbrains.com/?from=patrikx3) [![JetBrains](https://cdn.corifeus.com/assets/svg/jetbrains-logo.svg)](https://www.jetbrains.com/?from=patrikx3) [//]: #@corifeus-footer:end bin/000077500000000000000000000000001516100455000116235ustar00rootroot00000000000000bin/p3x-ramdisk.js000077500000000000000000000024461516100455000143340ustar00rootroot00000000000000#!/usr/bin/env node //const os = require('os'); //const process = require('process'); //const cores = os.cpus().length < 4 ? 4 : os.cpus().length; //process.env.UV_THREADPOOL_SIZE = cores; //console.debug(`P3X sets UV_THREADPOOL_SIZE to ${cores} thread pool`) if (!require('fs').existsSync(`${__dirname}/../node_modules`)) { require('child_process').execSync(`cd ${__dirname}/.. && npm install --only=prod`, { stdio: 'inherit' }); } const commander = require('commander'); const utils = require('corifeus-utils'); const pkg = require('../package.json'); const mz = require('mz'); const start = async() => { // command // required // [command] optional // [command ...] variable options commander .version(pkg.version) .usage('[command]') .action((command, options) => { console.error(`unknown command: ${command}`) }) ; require('../src/command/install') require('../src/command/start') require('../src/command/load') require('../src/command/watch') require('../src/command/save') require('../src/command/stop') require('../src/command/link') require('../src/command/status') commander.parse(process.argv); if (!process.argv.slice(2).length) { commander.outputHelp(); } } start();package.json000066400000000000000000000024401516100455000133410ustar00rootroot00000000000000{ "name": "p3x-ramdisk", "version": "2020.10.129", "description": "💾 Linux RAM disk persistent with Systemd timer, service and suspend", "main": "Gruntfile.js", "corifeus": { "prefix": "p3x-", "type": "p3x", "publish": true, "code": "Vader", "nodejs": "v14.15.0", "opencollective": false, "reponame": "ramdisk", "build": true }, "bin": { "p3x-ramdisk": "bin/p3x-ramdisk.js" }, "scripts": { "test": "grunt " }, "repository": { "type": "git", "url": "git+https://github.com/patrikx3/ramdisk.git" }, "keywords": [ "linux", "systemd", "ramdisk", "ram", "disk", "suspend", "timer", "service", "unix" ], "author": "Patrik Laszlo ", "license": "MIT", "bugs": { "url": "https://github.com/patrikx3/ramdisk/issues" }, "homepage": "https://corifeus.com/ramdisk", "dependencies": { "clear": "^0.1.0", "commander": "^6.2.0", "corifeus-utils": "^2020.10.136", "lodash": "^4.17.20" }, "engines": { "node": ">=12.13.0" }, "devDependencies": { "corifeus-builder": "^2020.10.144" } }ramdisk.iml000066400000000000000000000006241516100455000132120ustar00rootroot00000000000000 src/000077500000000000000000000000001516100455000116425ustar00rootroot00000000000000src/command/000077500000000000000000000000001516100455000132605ustar00rootroot00000000000000src/command/install.js000066400000000000000000000022331516100455000152640ustar00rootroot00000000000000const commander = require('commander'); const ramdisk = require('../index'); // install commander .command('install ') .option('-r, --rampath [path]', 'The path of the ram disk, default /home/{{USER}}/ramdisk') .option('-p, --persistent [path]', 'The path of the ram persistent, default /home/{{USER}}/ramdisk-persistent') // .option('-u, --uid ', 'The username, it you omit it is the current user') .option('-g, --gid [group]', 'The gid, it you omit it is the current user') .option('-t, --timer [minutes]', 'The timer in minutes, minimum about 10 minutes, Default is 20 minutes, the best') .option('-s, --size [megabytes]', `Ramdisk in size of megabytes, default is ${ramdisk.defaults.ramdisk.size} megabytes`) // .option('-h, --home [type]', 'Home path, defaults to /home/uid') .description(` Install a p3x-ramdisk `) // .option('-d, --dry', 'Do not actually remove packages, just show what it does') .action(async function (uid, options) { try { await ramdisk.install(uid, options); } catch (e) { console.error(e); process.exit(1); } }) ; src/command/link.js000066400000000000000000000010071516100455000145510ustar00rootroot00000000000000const commander = require('commander'); // install commander .command('link ') .description(` Link from the ramdisk to the home `) // .option('-d, --dry', 'Do not actually remove packages, just show what it does') .action(async function (uid, options) { const link = require('../index').link; try { await link({ uid: uid }); } catch (e) { console.error(e.message); process.exit(1); } }) ; src/command/load.js000066400000000000000000000007701516100455000145410ustar00rootroot00000000000000const commander = require('commander'); // install commander .command('load ') .description(` Load a p3x-ramdisk `) // .option('-d, --dry', 'Do not actually remove packages, just show what it does') .action(async function (uid, options) { const load = require('../index').load; try { await load({ uid: uid }); } catch (e) { console.error(e.message); process.exit(1); } }) ; src/command/save.js000066400000000000000000000007721516100455000145620ustar00rootroot00000000000000const commander = require('commander'); // install commander .command('save ') .description(` Save the p3x-ramdisk `) // .option('-d, --dry', 'Do not actually remove packages, just show what it does') .action(async function (uid, options) { const save = require('../index').save; try { await save({ uid: uid }); } catch (e) { console.error(e.message); process.exit(1); } }) ; src/command/start.js000066400000000000000000000007751516100455000147640ustar00rootroot00000000000000const commander = require('commander'); // install commander .command('start ') .description(` Start a p3x-ramdisk `) // .option('-d, --dry', 'Do not actually remove packages, just show what it does') .action(async function (uid, options) { const start = require('../index').start; try { await start({ uid: uid }); } catch (e) { console.error(e.message); process.exit(1); } }) ; src/command/status.js000066400000000000000000000006251516100455000151440ustar00rootroot00000000000000const commander = require('commander'); // install commander .command('status ') .description(` Status of p3x-ramdisk `) .action(async function (uid, options) { const status = require('../index').status; try { await status({uid: uid}, options); } catch (e) { console.error(e.message); process.exit(1); } }) ; src/command/stop.js000066400000000000000000000007701516100455000146070ustar00rootroot00000000000000const commander = require('commander'); // install commander .command('stop ') .description(` Stop a p3x-ramdisk `) // .option('-d, --dry', 'Do not actually remove packages, just show what it does') .action(async function (uid, options) { const stop = require('../index').stop; try { await stop({ uid: uid }); } catch (e) { console.error(e.message); process.exit(1); } }) ; src/command/watch.js000066400000000000000000000011131516100455000147200ustar00rootroot00000000000000const commander = require('commander'); // install commander .command('watch ') .description(` Watch the p3x-ramdisk `) .option('-w, --watch [milliseconds]', 'The time for watching, default is 1000 milliseconds') // .option('-d, --dry', 'Do not actually remove packages, just show what it does') .action(async function (uid, options) { const watch = require('../index').watch; try { await watch({uid: uid}, options); } catch (e) { console.error(e.message); process.exit(1); } }) ; src/index.js000066400000000000000000000166601516100455000133200ustar00rootroot00000000000000const mz = require('mz'); const path = require('path'); const utils = require('corifeus-utils'); const fsx = require('fs-extra'); const _ = require('lodash'); const clear = require('clear'); const ms = require('ms'); const settingsFileFun = (opts) => { const {uid} = opts return `/etc/p3x-ramdisk-${uid}.json`; } const defaults = { ramdisk: { size: 2048, }, timer: { save: 20, } }; const requireRoot = () => { if (process.getuid && process.getuid() === 0) { return true; } throw new Error('you are not root! please sudo!') } const getGeneratedDir = (homedir = process.env.HOME) => { //return path.resolve(`${__dirname}/../generated`); return path.resolve(`${homedir}/.p3x-ramdisk`) } const getSettings = (opts) => { const settings = require(settingsFileFun(opts)); settings.generatedDir = getGeneratedDir(settings.home); settings.program = `${path.resolve(settings.generatedDir )}/p3x-ramdisk.sh`; return settings; } const defaultTerminal = async (opts, command, doesRequireRoot = false) => { if (doesRequireRoot) { requireRoot(); } const settings = getSettings(opts); let commandExec = `${settings.program} ${command}`; await utils.childProcess.exec(commandExec, true) } const load = async (opts) => { await defaultTerminal(opts, 'load') } const link = async (opts) => { await defaultTerminal(opts, 'link') } const save = async (opts) => { await defaultTerminal(opts, 'save') } const status = async (opts) => { requireRoot(); let commandExec = ` sudo service p3x-ramdisk-${opts.uid} status sudo systemctl status p3x-ramdisk-timer-${opts.uid}.timer `; await utils.childProcess.exec(commandExec, true) } const watch = async (mainOpts, options) => { const settings = getSettings(mainOpts); options.watch = Number(options.watch) || 1000; const watch = ms(options.watch, {long: true}); const timer = ms(settings.timer * 1000 * 60, {long: true}); return new Promise(async () => { const show = async () => { const df = await utils.childProcess.exec(`df -h | grep -e ${settings.home}/${settings.rampath} -e Size`); const free = await utils.childProcess.exec(`free -h`); const loadLogFile = `${settings.home}/${settings.persistent}/update-at-load.log`; let loadLog; if (await mz.fs.exists(loadLogFile)) { loadLog = await mz.fs.readFile(loadLogFile); } else { loadLog = 'Load is not done.' } const saveLogFile = `${settings.home}/${settings.persistent}/update-at-save.log`; let saveLog; if (await mz.fs.exists(saveLogFile)) { saveLog = await mz.fs.readFile(saveLogFile); } else { saveLog = 'Save is not done.' } const logFile = `${settings.home}/${settings.persistent}/ramdisk-persistent.log`; let log; if (await mz.fs.exists(logFile)) { log = await utils.childProcess.exec(`tail -n 5 ${logFile}`); } else { log = 'Log is not done.' } clear() console.log(`${df.stdout.trim()} ${free.stdout} Load: ${loadLog.toString().trim().split('\n').join(' ')} Save: ${saveLog.toString().trim().split('\n').join(' ')} ${log.stdout.trim()} ${new Date().toLocaleString()} | Persistence ${timer} | Watch ${watch}`); }; show(); setInterval(show, options.watch) }) } const start = async (opts) => { await defaultTerminal(opts, 'start', true) } const stop = async (opts) => { await defaultTerminal(opts, 'stop', true) } const install = async (uid, options) => { requireRoot(); // .option('-r, --rampath [type]', 'The path of the ram disk') // .option('-p, --persistent [type]', 'The path of the ram persistent') // .option('-u, --uid [type]', 'The username, it you omit it is the current user') // .option('-g, --gid [type]', 'The gid, it you omit it is the current user') // .option('-t, --timer [type]', 'The timer in minutes, minimum about 10 minutes, 20 is the best') // .option('-s, --size [type]', 'Ramdisk in size of MegaBYTEs, default is 8192') // .option('-h, --home [type]', 'Home path, defaults to /home/uid') // uid - current user default // gid - default uid // timer -- 20 min // rampath - default /home/uid/ramdisk // persistent - default /home/uid/ramdisk-persistent // home - default /home/uid/ // sciripts - is given const userid = require('./userid'); const homedir = (await utils.childProcess.exec(`sudo -H -u ${uid} -i eval 'echo $HOME'`)).stdout.trim(); options = options || {}; const generateOptions = { rampath: options.rampath || 'ramdisk', persistent: options.persistent || 'ramdisk-persistent', uid: uid, uidNumber: userid.uid(uid), gidNumber: userid.gid(uid), gid: options.gid || uid, timer: options.timer || defaults.timer.save, size: options.size || defaults.ramdisk.size, home: homedir, } const generatedDir = getGeneratedDir(homedir) await fsx.emptyDir(generatedDir); generateOptions.script = generatedDir; const origin = `${__dirname}/../templates`; const files = await mz.fs.readdir(origin); _.templateSettings.interpolate = /{{([\s\S]+?)}}/g; await utils.fs.ensureFile(settingsFileFun(generateOptions), JSON.stringify(generateOptions, null, 4), true) await files.forEachAsync(async (file) => { const generatedFile = path.basename(file, '.hbs'); const buffer = await mz.fs.readFile(`${origin}/${file}`); const string = buffer.toString(); const generatedString = _.template(string)(generateOptions); let useredFile = generatedFile for (let type of ['.service', '.timer']) { if (useredFile.endsWith(type)) { useredFile = useredFile.substring(0, useredFile.length - type.length) useredFile += '-' + generateOptions.uid + type } } await utils.fs.ensureFile(`${generatedDir}/${useredFile}`, generatedString, true); }) const program = `${path.resolve(generateOptions.script)}/p3x-ramdisk.sh`; let command = `chown -R ${generateOptions.uidNumber}:${generateOptions.gidNumber} ${generatedDir} chmod u+x ${program} ${program} install `; //console.log(command) await utils.childProcess.exec(command, true) console.log(` Settings: ${JSON.stringify(generateOptions, null, 4)} Final commands: -------------------------- 1) You only have to do it once, if you haven't done it before echo "tmpfs ${generateOptions.home}/${generateOptions.rampath} tmpfs gid=${userid.gid(generateOptions.gid)},uid=${userid.uid(generateOptions.uid)},size=${generateOptions.size}M 0 0" | sudo tee -a /etc/fstab sudo mount -a -------------------------- 2) verify that ramdisk is working, see it here df -h -------------------------- 3) if everything is ok, start the persistent ramdisk sudo p3x-ramdisk start `) } module.exports.install = install; module.exports.load = load; module.exports.save = save; module.exports.start = start; module.exports.stop = stop; module.exports.watch = watch; module.exports.link = link; module.exports.status = status; module.exports.requireRoot = requireRoot; module.exports.defaults = defaults; src/userid.js000066400000000000000000000004731516100455000134770ustar00rootroot00000000000000const {execSync} = require('child_process') module.exports.uid = (username) => { const output = execSync(`id -u ${username}`).toString().trim() return parseInt(output) } module.exports.gid = (username) => { const output = execSync(`id -g ${username}`).toString().trim() return parseInt(output) } templates/000077500000000000000000000000001516100455000130515ustar00rootroot00000000000000templates/p3x-ramdisk-timer.service.hbs000066400000000000000000000004211516100455000204630ustar00rootroot00000000000000[Unit] Description=P3X ramdisk {{ uid }} persistent timer After=p3x-ramdisk-{{ uid }}.target [Service] Type=oneshot ExecStart={{ script }}/p3x-ramdisk.sh timer save TimeoutStopSec=infinity TimeoutStartSec=infinity TimeoutSec=infinity [Install] WantedBy=multi-user.target templates/p3x-ramdisk-timer.timer.hbs000066400000000000000000000003221516100455000201430ustar00rootroot00000000000000[Unit] Description=P3X ramdisk {{ uid }} persistent timer After=p3x-ramdisk-{{ uid }}.target [Timer] OnCalendar=*:0/{{ timer }} OnBootSec=0min #OnUnitActiveSec={{ timer }}min [Install] WantedBy=timers.target templates/p3x-ramdisk.service.hbs000066400000000000000000000004621516100455000173520ustar00rootroot00000000000000[Unit] Description=P3X ramdisk {{ uid }} persistent [Service] Type=oneshot RemainAfterExit=true ExecStartPre={{ script }}/p3x-ramdisk.sh boot load ExecStop={{ script }}/p3x-ramdisk.sh shutdown save TimeoutStopSec=infinity TimeoutStartSec=infinity TimeoutSec=infinity [Install] WantedBy=multi-user.target templates/p3x-ramdisk.sh000077500000000000000000000207531516100455000155610ustar00rootroot00000000000000#!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ME="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")" P3X_UID={{ uid }} P3X_UID_NUMBER={{ uidNumber }} P3X_GID={{ gid }} P3X_GID_NUMBER={{ gidNumber }} ROOT={{ home }} PERSISTENCE=$ROOT/{{ persistent }} RAMDISK=$ROOT/{{ rampath }} TYPE=$1 COMMAND=$2 if [ -z "$COMMAND" ]; then COMMAND=$TYPE TYPE=terminal fi TRASH_NAME=.Trash-$P3X_UID_NUMBER PERSISTENCE_LOG=$PERSISTENCE/ramdisk-persistent.log #PERSISTENCE_LOG_LOAD=$PERSISTENCE/ramdisk-persistent-load.log #PERSISTENCE_LOG_SAVE=$PERSISTENCE/ramdisk-persistent-save.log PERSISTENCE_CURRENT=$PERSISTENCE/current PERSISTENCE_PREVIOUS=$PERSISTENCE/previous PERSISTENCE_LOCK=$PERSISTENCE/persistent.lock PERSISTENCE_TRASH=$PERSISTENCE/$TRASH_NAME RAMDISK_TRASH=$RAMDISK/$TRASH_NAME RAMDISK_PERSISTENCE=$RAMDISK/.p3x-ramdisk-persistence RAMDISK_PERSISTENCE_LINKED=$RAMDISK/p3x-persistence RAMDISK_CONTENT=$RAMDISK_PERSISTENCE/content RAMDISK_CONTENT_LINK=$RAMDISK_CONTENT/.p3x-ramdisk-link RAMDISK_LOCK=$RAMDISK_PERSISTENCE/ramdisk.lock SUSPEND_ROOT=/lib/systemd/system-sleep SUSPEND_SCRIPT=$SUSPEND_ROOT/p3x-ramdisk-$P3X_UID.sh SYSTEMD=/etc/systemd/system function generate_persistence_link() { log "generate_persistance_link remove linked persistence" rm -rf $RAMDISK_PERSISTENCE_LINKED || true ln -s $RAMDISK_CONTENT $RAMDISK_PERSISTENCE_LINKED chown -h $P3X_UID_NUMBER:$P3X_GID_NUMBER $RAMDISK_PERSISTENCE_LINKED } function require_sudo() { if [[ $EUID -ne 0 ]]; then printf "You are not root!\n\n" exit 1 fi } function notify() { if hash notify-send 2>/dev/null; then notify-send P3X-RAMDISK $1 # else # date "$@" fi } function install() { require_sudo mkdir -p $PERSISTENCE_CURRENT mkdir -p $PERSISTENCE_PREVIOUS mkdir -p $PERSISTENCE_TRASH #mkdir -p $RAMDISK_CONTENT mkdir -p $RAMDISK_CONTENT_LINK printf "\n" >> $PERSISTENCE_LOG log "install" log "copy" cp $DIR/p3x-ramdisk-timer-$P3X_UID.service $SYSTEMD cp $DIR/p3x-ramdisk-timer-$P3X_UID.timer $SYSTEMD cp $DIR/p3x-ramdisk-$P3X_UID.service $SYSTEMD log suspend mkdir -p $SUSPEND_ROOT touch $SUSPEND_SCRIPT chmod u+x $SUSPEND_SCRIPT cat < $SUSPEND_SCRIPT #!/usr/bin/env bash ACTION=\$1 TYPE=\$2 LOG_DATA="\`date '+%Y-%m-%d %H:%M:%S'\`: SUSPEND \$TYPE \$ACTION" if [ "\$ACTION" == "pre" ]; then # Do the thing you want before suspend here, e.g.: sudo -u $P3X_UID printf "\n\$LOG_DATA Suspending" >> $PERSISTENCE_LOG $DIR/$ME suspend save elif [ "\$1" == "post" ]; then # Do the thing you want after resume here, e.g.: sudo -u $P3X_UID printf "\n\$LOG_DATA On again" >> $PERSISTENCE_LOG fi chown $P3X_UID_NUMBER:$P3X_GID_NUMBER $PERSISTENCE_LOG EOT log "reload services" systemctl daemon-reload log "install done" touch $PERSISTENCE_LOG # touch $PERSISTENCE_LOG_LOAD # touch $PERSISTENCE_LOG_SAVE fix_permissions } function start() { require_sudo install log "start" systemctl enable p3x-ramdisk-$P3X_UID systemctl enable p3x-ramdisk-timer-$P3X_UID.timer systemctl start p3x-ramdisk-$P3X_UID systemctl start p3x-ramdisk-timer-$P3X_UID.timer } function stop() { require_sudo printf "\n" >> $PERSISTENCE_LOG log "stop" systemctl stop p3x-ramdisk-timer-$P3X_UID.timer systemctl stop p3x-ramdisk-$P3X_UID systemctl disable p3x-ramdisk-$P3X_UID systemctl disable p3x-ramdisk-timer-$P3X_UID.timer if [ -f $SUSPEND_SCRIPT ]; then rm -rf $SUSPEND_SCRIPT fi if [ -f $SYSTEMD/p3x-ramdisk-$P3X_UID.service ]; then rm $SYSTEMD/p3x-ramdisk-$P3X_UID.service fi if [ -f $SYSTEMD/p3x-ramdisk-timer-$P3X_UID.timer ]; then rm $SYSTEMD/p3x-ramdisk-timer-$P3X_UID.timer fi if [ -f $SYSTEMD/p3x-ramdisk-timer-$P3X_UID.service ]; then rm $SYSTEMD/p3x-ramdisk-timer-$P3X_UID.service fi if [ -f $RAMDISK_LOCK ]; then rm $RAMDISK_LOCK fi } function fix_permissions() { chown $P3X_UID_NUMBER:$P3X_GID_NUMBER -R $RAMDISK chown $P3X_UID_NUMBER:$P3X_GID_NUMBER -R $PERSISTENCE # chown $P3X_UID_NUMBER:$P3X_GID_NUMBER $PERSISTENCE_LOG_LOAD # chown $P3X_UID_NUMBER:$P3X_GID_NUMBER $PERSISTENCE_LOG_SAVE } function log() { if [ -z ${2+x} ]; then false; else notify $1 fi LOG_DATA="`date '+%Y-%m-%d %H:%M:%S'`: $TYPE $1" echo $LOG_DATA echo $LOG_DATA >> $PERSISTENCE_LOG } function timer_start() { SECONDS=0 STARTED=`date '+%Y-%m-%d %H:%M:%S'` } function timer_end() { DURATION=$SECONDS PERSISTENCE_UPDATED_AT=$PERSISTENCE/$1 echo $STARTED > $PERSISTENCE_UPDATED_AT echo `date '+%Y-%m-%d %H:%M:%S'` >> $PERSISTENCE_UPDATED_AT DURATION_STRING="$(($DURATION / 60)) minutes $(($DURATION % 60)) seconds" echo $DURATION_STRING >> $PERSISTENCE_UPDATED_AT chown $P3X_UID_NUMBER:$P3X_GID_NUMBER $PERSISTENCE_UPDATED_AT log "$DURATION_STRING" } function load() { timer_start printf "\n" >> $PERSISTENCE_LOG log "loading" if [ -f $PERSISTENCE_LOCK ]; then log "has a lock at $PERSISTENCE_LOCK ! not loading! quitting..." true return fi if [ -f $RAMDISK_LOCK ]; then log "already loaded! quitting, existing ramdisk lock: $RAMDISK_LOCK" true return fi cd $PERSISTENCE_CURRENT #tar xvf $PERSISTENCE_COMPRESSED_CURRENT >$PERSISTENCE_LOG_LOAD log "load $PERSISTENCE_CURRENT to $RAMDISK_CONTENT" mkdir -p $RAMDISK_CONTENT cp -avr . $RAMDISK_CONTENT > /dev/null #$PERSISTENCE_LOG_LOAD #rsync -atvq --delete . $RAMDISK_CONTENT > /dev/null #$PERSISTENCE_LOG_LOAD log "loaded" true if ! [ -d $RAMDISK_TRASH ]; then ln -s $PERSISTENCE_TRASH $RAMDISK fi # truncate $PERSISTENCE_LOG_LOAD # chown $P3X_UID_NUMBER:$P3X_GID_NUMBER $PERSISTENCE_LOG_LOAD fix_permissions generate_persistence_link timer_end "update-at-load.log" echo `date '+%Y-%m-%d %H:%M:%S'` > $RAMDISK_LOCK } function truncate() { tail -n 10240 $1 > $1.1 mv $1.1 $1 } function internal_save() { FROM=$1 TO=$2 log "save $FROM to $TO" cd $FROM rsync --rsync-path="nice -n19 ionice -c3 rsync" -aq --delete . $TO > /dev/null #$PERSISTENCE_LOG_SAVE log "saved" # truncate $PERSISTENCE_LOG_SAVE } function save() { printf "\n" >> $PERSISTENCE_LOG timer_start log "save" if [ -f $RAMDISK_LOCK ]; then if [ -f $PERSISTENCE_LOCK ]; then log "has a lock at $PERSISTENCE_LOCK ! quitting..." true return fi touch $PERSISTENCE_LOCK log "save, current to previous" internal_save $PERSISTENCE_CURRENT $PERSISTENCE_PREVIOUS log "save, ramdisk to current" internal_save $RAMDISK_CONTENT $PERSISTENCE_CURRENT rm $PERSISTENCE_LOCK log "save done" true # cannot use it at save, it will change modification #fix_permissions truncate $PERSISTENCE_LOG timer_end "update-at-save.log" else log "not saving, not loaded" true fi } function link() { log "link" if [ -d "$RAMDISK_CONTENT_LINK" ]; then cd $RAMDISK_CONTENT_LINK shopt -s dotglob for d in */ ; do if [ "$d" = "*/" ]; then continue fi GENERATED_LINK="${ROOT}/${d}" DIR=${d%/*} log "link $RAMDISK_CONTENT_LINK/$DIR to $ROOT/$DIR" # delete actual directory, no slash! otherwise it shows not empty if [ -d "${ROOT}/${DIR}" ]; then unlink "${ROOT}/${DIR}" || true fi ln -s "${RAMDISK_CONTENT_LINK}/${DIR}" $ROOT done fi log "link done" } if [ "$COMMAND" == "load" ] then load exit fi if [ "$COMMAND" == "save" ] then save exit fi if [ "$COMMAND" == "link" ] then link exit fi if [ "$COMMAND" == "start" ] then start exit fi if [ "$COMMAND" == "stop" ] then stop exit fi if [ "$COMMAND" == "install" ] then install exit fi if [ "$COMMAND" == "watch" ] then WATCH1="df -h | grep -e " WATCH2=$RAMDISK WATCH3=" -e Size" watch $WATCH1$WATCH2$WATCH3 exit fi cat <&1 Eg: sudo $ME install sudo $ME start $ME watch $ME save $ME load Unknown type: {$TYPE} command: {$COMMAND} Requires 2 parameters: type: anything (like boot, timer, terminal), default is "terminal" command: install, start, stop, load, save, watch EOT test/000077500000000000000000000000001516100455000120325ustar00rootroot00000000000000test/scripts/000077500000000000000000000000001516100455000135215ustar00rootroot00000000000000test/scripts/test.js000077500000000000000000000006161516100455000150440ustar00rootroot00000000000000#!/usr/bin/env node const uid = 'patrikx3' const useridNative = require('userid') const userid = require('../../src/userid') const start = async () => { console.log('useridNative.uid(uid)', useridNative.uid(uid)) console.log('useridNative.gid(uid)', useridNative.gid(uid)) console.log('userid.uid(uid)', userid.uid(uid)) console.log('userid.gid(uid)', userid.gid(uid)) } start();