[
  {
    "path": ".dockerignore",
    "content": ".git/\n**/_old\n**/node_modules\n**/tmp\n**/vendor\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ntrim_trailing_whitespace = true\ninsert_final_newline = true\ncharset = utf-8\n\n# Default to tab indentation (narrow 2-column size)\nindent_style = tab\nindent_size = 2\n\n# Specific exceptions using space indentation\n[*.{json,yml}]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".eslintignore",
    "content": "node_modules/\nnode_modules_1/\nvendor/\ndist/\nbuild/\nclient/app/fonts/\n_old/\nfront/\nfront2/\n*.d.ts\n"
  },
  {
    "path": ".eslintrc.yml",
    "content": "env:\n  es6: true\n  node: true\nextends: 'eslint:recommended'\nparserOptions:\n  ecmaVersion: 2018\nrules:\n  indent:\n    - error\n    - tab\n    - SwitchCase: 1\n  linebreak-style:\n    - error\n    - unix\n  quotes:\n    - error\n    - double\n  semi:\n    - error\n    - always\n"
  },
  {
    "path": ".gcloudignore",
    "content": "#!include:.gitignore\n\n.git/\n"
  },
  {
    "path": ".github/workflows/build-test.yml",
    "content": "name: Node.js CI\n\non: [push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n    - uses: actions/setup-node@v1\n      with:\n        node-version: 10.x\n    - name: Install\n      run: |\n        touch config.hjson\n        npm ci\n        (cd test && npm ci)\n    - name: Test\n      run: |\n        (cd test && npm test)\n      env:\n        CI: true\n    - name: Lint\n      run: npm run lint\n"
  },
  {
    "path": ".gitignore",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n# Directory names that should not be tracked\n_old/\nbundle/\ndist/\nnode_modules/\nnode_modules_1/\nvendor/\ntmp/\nfront/static/\n\n# File extensions that should not be tracked\n*.bak\n*.log\n*.o\n*.out\n*.pem\n*.pub\n*.pp\n\n# Specific files to omit\n.env\nconfig.hjson\nback-octave/octave-host\nback-octave/bin/cwd/\nentrypoint/exit.js\nentrypoint/front_setup.js\n"
  },
  {
    "path": ".npmrc",
    "content": "# Please keep this in sync with all other .npmrc files\n# SEE: https://github.com/npm/feedback/discussions/864\ninstall-links=false\n"
  },
  {
    "path": "COPYING",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "Makefile",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\nSHELL := /bin/bash\nNODE = node\n\n# Read options from config file\n# \\n is replaced with \\1 because gnumake removes \\n from ${shell}. See: https://stackoverflow.com/q/54067438/1407170\nget_config = ${shell $(NODE) -e \"console.log(require('./shared').config.$(1))\" | tr '\\n' '\\1'}\nGIT_HOST      = $(call get_config,git.hostname)\nGIT_DIR       = $(call get_config,docker.gitdir)\nWORK_DIR      = $(call get_config,docker.cwd)\nOCTAVE_SUFFIX = $(call get_config,docker.images.octaveSuffix)\nFILES_SUFFIX  = $(call get_config,docker.images.filesystemSuffix)\nJSON_MAX_LEN  = $(call get_config,session.jsonMaxMessageLength)\nCGROUP_CONF   = $(call get_config,selinux.cgroup.conf)\n\n\ninstall-selinux-policy:\n\t# yum install -y selinux-policy-devel policycoreutils-sandbox selinux-policy-sandbox\n\tcd entrypoint/policy && make -f /usr/share/selinux/devel/Makefile octave_online.pp\n\tsemodule -i entrypoint/policy/octave_online.pp\n\trestorecon -R -v /usr/local/lib/octave\n\trestorecon -R -v /tmp\n\trestorecon -R -v /srv/oo\n\tsetenforce enforcing\n\techo \"For maximum security, make sure to put SELinux in enforcing mode by default in /etc/selinux/config.\"\n\ninstall-selinux-bin:\n\tcp entrypoint/back-selinux.js /usr/local/bin/oo-back-selinux\n\t# TODO: Put in /etc instead of /usr/lib?\n\tcp entrypoint/oo.service /usr/lib/systemd/system/oo.service\n\tsystemctl daemon-reload\n\techo \"$(CGROUP_CONF)\" | tr '\\1' '\\n' > /etc/cgconfig.d/oo.conf\n\techo \"Run `systemctl restart cgconfig.service` to load changes to cgroup configurations.\"\n\tsystemctl enable cgconfig\n\tln -sf $$PWD /usr/local/share/oo\n\ninstall-site-m:\n\techo \"This command is no longer supported. See containers/octave-pkg/Dockerfile.\"\n\nenable-graceful-shutdown:\n\tcp entrypoint/oo-no-restart.service /usr/lib/systemd/system/oo.service;\n\tsystemctl daemon-reload;\n\techo \"Tip 1: Consider removing entrypoint/exit.js if it could cause disruption\";\n\techo \"Tip 2: Consider sending SIGUSR1 to the server process to start a graceful shutdown\";\n\nlint:\n\tcd back-filesystem && npm run lint\n\tcd back-master && npm run lint\n\tcd shared && npm run lint\n\tcd utils-auth && npm run lint\n\nclean:\n\tif [[ -e bundle ]]; then rm -rf bundle; fi\n"
  },
  {
    "path": "README.md",
    "content": "Octave Online Server\n====================\n\nThis repository contains the full stack of code to run Octave Online Server, the infrastructure that powers [Octave Online](https://octave-online.net).\n\n[![Node.js CI](https://github.com/octave-online/octave-online-server/workflows/Node.js%20CI/badge.svg)](https://github.com/octave-online/octave-online-server/actions)\n\n## High-Level Overview\n\nThere are three separate components of Octave Online Server:\n\n1. **Client**: Code that runs in the browser.\n2. **Front Server**: Authentication, client session handling.\n3. **Back Server**: File I/O, Octave process handling.\n\n*Communication:* The Client and Front Server communicate primarily with WebSockets via [socket.io](https://socket.io); the Front Server and Back Server communicate primarily with [Redis PubSub](https://redis.io/topics/pubsub).  User account information is stored in [MongoDB](https://www.mongodb.com) and is accessed primarily from the Front Server.  User files are stored in [Git on the Server](https://git-scm.com/book/en/v1/Git-on-the-Server) and are accessed primarily from the Back Server.\n\n*Scaling:* Front Servers and Back Servers can be scaled independently (in general, you need more Back Servers than Front Servers).  It is also possible to run both the Front Server and the Back Server on the same computer.\n\n*Languages:* All code is written with JavaScript technologies, although for historical reasons, the three components use different flavors of JavaScript.  The Client uses ES5; the Front Server uses TypeScript; and the Back Server uses ES6.\n\n## Quick Start\n\nRead [containers/README.md](containers/README.md) for details on running a containerized version of all of Octave Online Server for use with trusted users.  This is the fastest way to get off the ground.\n\n## Manual Installation\n\n*Note:* Octave Online Server has a lot of moving parts.  It is recommended that you feel comfortable with basic system administration before attempting an installation.\n\nFor more details on operating each of the three components, see the respective README files:\n\n- [back-master/README.md](back-master/README.md) (back server)\n- [front/README.md](front/README.md) (front server)\n- [client/README.md](client/README.md) (client)\n\nThere are also a few more directories for other components:\n\n- [back-filesystem/README.md](back-filesystem/README.md) for filesystem I/O on the back server\n- [back-octave/README.md](back-octave/README.md) for GNU Octave bindings for the back server\n- [entrypoint/README.md](entrypoint/README.md) for helper scripts to run Octave Online Server\n- [shared/README.md](shared/README.md) for code shared by multiple components\n- [utils-admin/README.md](utils-admin/README.md) for an optional admin panel\n- [utils-auth/README.md](utils-auth/README.md) for an optional standalone user authentication service\n\nEvery subdirectory of the top-level Octave Online Server directory has a README file that explains what the contents of the directory is for.\n\n### Prerequisites\n\n[Required] *Operating System:* Octave Online Server is built and tested exclusively on GNU/Linux.  It is recommended that you use CentOS 8, although other modern distributions should work also.  Most of Octave Online Server should work on macOS, but this has not been tested.\n\n[Required] *Node.js:* Octave Online Server is built and tested with Node.js LTS version 10.  This is the default version on CentOS 8.\n\n\t# Install Node.js 10.x LTS on CentOS 8:\n\t$ sudo yum install nodejs\n\n[Required] *Redis:* Install and run a local Redis instance.  Enable expiration events in `redis.conf`:\n\n\t$ sudo yum install redis\n\t$ sudo emacs redis.conf\n\t# Search for \"notify-keyspace-events\"\n\t# Set the value to \"Ex\"\n\nAlthough it is possible to use a third-party hosted Redis instance, this is not recommended because Redis latency is amplified due to its central role in the Octave Online Server architecture.\n\n[Recommended] *Git Server:* In order to persist user files between sessions, you need to set up a Git file server.  It boils down to a server, which could be the current server, with a low-privileged user usually named \"git\".  For more information, see [Git on the Server](https://git-scm.com/book/en/v1/Git-on-the-Server).  Also see [back-filesystem/README.md](back-filesystem/README.md) for instructions on how to configure a Git file server for Octave Online Server.\n\n[Recommended] *MongoDB:* Install and run a MongoDB instance.  Unlike Redis, MongoDB is not as central of a piece in the infrastructure, so it is possible to use a remotely hosted MongoDB if you do not want to host it locally.  My experience is that it takes some time to correctly configure a fast and secure MongoDB installation.  Keep in mind that MongoDB will contain personally identifiable information for user accounts.\n\n[Recommended] *Email SaaS:* If you want Octave Online Server to be able to send transactional emails, such as for email-based login, you need a [Mailgun](https://www.mailgun.com) or [Postmark](https://postmarkapp.com) account.  Mailgun has a free tier that should cover most experimental and low-traffic usage.\n\n[Recommended] *ReCAPTCHA:* Certain actions, such as when email is sent, require a CAPTCHA to prevent abuse. You should register for a [ReCAPTCHA](https://www.google.com/recaptcha/) v2 Checkbox and put your credentials into your config.hjson file.\n\n[Optional] *Google Analytics:* For aggregated statistics about traffic to your site, you can enable [Google Analytics](https://www.google.com/analytics/) integration.\n\n[Optional] *Nginx:* For better performance with serving static files and easier HTTPS setup, I recommend installing and configuring [Nginx](https://www.nginx.com).  However, this is not an essential piece, and it can be done after the rest of the infrastructure is up and running.\n\n### Configuration File\n\nRead `config_defaults.hjson` to learn more about the array of settings available for Octave Online Server.  When ready, copy `config.sample.hjson` into `config.hjson`, and fill in the required details.  Your own `config.hjson` is ignored by source control.\n\n### Installing Depencencies and Building\n\nIn each of the five directories containing Node.js projects, go in and run `npm install`:\n\n\t$ (cd shared && npm install)\n\t$ (cd back-filesystem && npm install)\n\t$ (cd back-master && npm install)\n\t$ (cd front && npm install)\n\t$ (cd client && npm install)\n\nYou also need to install the Bower (client-side) dependencies for the client project:\n\n\t$ (cd client && npm run bower install)\n\nFinally, build the client and front server projects (the back server runs without needing to be built):\n\n\t$ (cd client && npm run grunt)\n\t$ (cd front && npm run grunt)\n\n### Configuring GNU Octave\n\nOctave Online Server requires a special version of GNU Octave, which needs to be built.  *This is a required step.*  For more information, see [back-master/README.md](back-master/README.md).\n\n### Running Octave Online Server\n\nTo run the code manually, just open up two terminals and run each of the following two commands:\n\n\t$ (cd back-master && DEBUG=* node app.js)\n\t$ (cd front && node app.js)\n\nTo run the code as a service, you can install the systemd service provided in this repository and enable the code to be automatically run at startup; see *entrypoint/oo.service* and `make install-selinux-bin`.\n\n**Tip:** When debugging, you can modify your hosts file (on macOS, /private/etc/hosts) to create a stable URL that you can add to your Google developer console to allow Google services to work.\n\n## Contributing\n\nYou are welcome to send pull requests for consideration for addition to Octave Online Server.  Pull requests are not guaranteed to be accepted; if in doubt, you should open an issue to discuss your idea before spending time writing your pull request.\n\n### Contributor License Agreement\n\nLike many projects distributed with copyleft licenses such as AGPL, contributors to Octave Online Server must sign a Contributor License Agreement (CLA).  The terms of the [Octave Online CLA](https://cla-assistant.io/octave-online/octave-online-server) are taken from [The Apache Software Foundation CLA](https://www.apache.org/licenses/contributor-agreements.html).  Having a CLA in place enables Octave Online Server to be distributed with alternate licensing schemes, including commercial licenses that help keep the project afloat.\n\n### Style\n\nIf in doubt on style, follow the convention of the file you are editing.\n\n**Wrapping and Indentation:** Use tab indentation, unless in a file format such as *.yml* that requires space indentation.  There is no limit on line length.  This gives you full control to configure your editor to your desired width and tab size.\n\n**Naming:** In general, use camelCase for variable names and MACRO_CASE for constants.  Prefix private members with an underscore (`_`).\n\n**Quotes:** Use double-quoted strings, unless you are in a context where you need a different quotation style, such as backtick strings in JavaScript.\n\n**Internationalization (i18n/l10n):** If possible, all new UI strings should be extracted into *en.yaml* so that they can be translated.  For more details, see [front/locales/README.md](front/locales/README.md).\n\n**Upstream/Downstream:** Throughout the code, there are comments and function names indicating \"upstream\" and \"downstream\".  \"Upstream\", or \"U\", means toward the Octave process, away from the client.  \"Downstream\", or \"D\", means toward the client, away from the Octave process.  So, for example, a message sent from the back server to the front server is considered downstream, and a message sent from the back server to the Octave process is considered upstream.\n\n**ECMAScript Versions:** JavaScript code in the *client* project should conform to the ECMAScript 5 standard, in order to have broad browser support.  JavaScript in all other projects can use the latest ECMAScript standard supported by Node.js 6.x LTS.  By design, all JavaScript code in Octave Online Server server should be able to be run natively without transcompilation to a different ECMAScript version.\n\n### Linting\n\nThe *eslint* tool will catch most style and compatibility issues in JavaScript files.  Execute `npm run lint` in the top-level directory to check for errors.  If your code does not pass *eslint*, you will also trigger a Travis failure on GitHub.\n\n### Manual Testing\n\nDue to the complexity of Octave Online Server, there is not currently an automated test suite.  As a contributor, you are expected to perform some manual testing to ensure that your feature does not accidentally break something else in Octave Online Server.\n\nHere are some critical user journeys that test a fairly wide cross-section of the code base.  **Please make sure that all of these journeys continue working after your change.**\n\n1. The core file editor\n\t1. Sign in if necessary\n\t1. Create a new file\n\t1. Open the new file in the editor and make some changes\n\t1. The file should appear dirty (unsaved): its name should be italic and underlined\n\t1. Save the file; it should no longer appear dirty\n\t1. Press the \"Refresh Files\" button; your file should go away and reappear a few seconds later with the same changes you had made\n\t1. Click the following the buttons in the file toolbar, and make sure they behave as expected:\n\t\t- \"Download File\"\n\t\t- \"Print File\"\n\t\t- \"Toggle Word Wrap\"\n\t\t- \"Save File\" (make some changes first)\n\t\t- \"Run Script\"\n\t1. Create a file named `.octaverc` with the following content:\n\t```rcx = 5;```\n\t1. Run the `exit` command, then click the reconnect link\n\t1. Once the workspace loads, check that the variable `rcx` exists and has value 5\n1. Collaborative workspaces\n\t1. Sign in if necessary\n\t1. Open the side bar menu and enable workspace sharing if necessary\n\t1. Open the sharing link in another window\n\t1. Repeat all of the steps from the \"core file editor\" journey, mixed between the two windows, and make sure that all state gets updated as expected\n1. Plotting and image processing\n\t1. In the main command prompt, make some standard plots like `sombrero()` and `fplot(@sin, [-pi pi])`, and ensure they appear as expected\n\t1. Open the plot window.  You should be able to scroll through your plots.  Ensure that the two download buttons work as expected (download as PNG and as SVG)\n\t1. Sign in if necessary\n\t1. Download a full-color image from [PNGNQ](http://pngnq.sourceforge.net/pngnqsamples.html); I usually use mandrill.png\n\t1. Drag the PNG file onto the file list until it turns yellow; drop the file to upload it\n\t1. Select the file in the list; make sure \"Download File\" and \"Rename File\" work\n\t1. Click the \"DELETE File\" button to delete the file\n\t1. Upload the file again, this time using the \"Upload file\" button in the file list toolbar\n\t1. In the command prompt, run the following command: `imshow(imread(\"mandrill.png\"))`; you should see the full-color image appear in the console output window (there is a surprisingly large amount of code that is needed to make this happen)\n1. Buckets and Projects\n\t1. Sign in if necessary\n\t1. Create or upload multiple files if you don't already have files in your workspace\n\t1. Open a script file that runs by itself (not a function file)\n\t1. Click the \"Share File in new Bucket\" button\n\t1. Play around with the options, adding new files and selecting a main file\n\t1. Click \"Create Bucket\"\n\t1. Ensure that the bucket creates successfully and that the main file runs\n\t1. Click the bucket ID in the title bar. An information panel should appear with details about the bucket. Make sure they look correct\n\t1. Click the pencil icon to edit the shortlink; change it to \"test999\"\n\t1. Go to \"octav.onl/test999\"; the redirect should work and the same bucket page should open\n\t1. Click \"Fork This Bucket\"\n\t1. Enter \"test999\" as the custom URL in the \"Create Project\" screen. Click \"Create Project\"; a second or two later, you should see an alert box saying that there is a duplicate link\n\t1. Change the shortlink so that it contains illegal characters, like \"$\". Click \"Create Project\" again; a second or two later, you should see an alert saying that the shortlink has invalid characters\n\t1. Close the \"Customize Project\" dialog box, then reopen it by clicking \"Fork This Bucket\" again\n\t1. This time, click \"Create Project\" without changing any other settings. Your browser should refresh into the new project\n\t1. Ensure that you can edit and save files in the project\n\t1. Open the info panel for the project by clicking it in the title bar. You should see the bucket name under \"Forked From\"\n\t1. Close the info bar, then open the side bar menu\n\t1. Find the bucket and project you created; ensure that the timestamps is correct\n\t1. Save the links to both the bucket and the project\n\t1. Press the \"⌫\" button to delete the bucket and then the project\n\t1. Once deleted, go back to the bucket and the project with the links you saved, and ensure that the bucket and project are actaully deleted\n1. Small interpreter features\n\t1. Run a few lines of code and then run `clc`; it should clear all output from the console window\n\t1. Run `doc fplot`; it should produce a working link\n\t1. Run `char(randi(256, 1000, 1)' .- 1)`; it should print a nonsense string with a lot of replacement characters\n\t1. Run `O = urlread(\"http://example.com\")`; it should finish without error and print the HTML content of that page\n\t1. Run `O = urlread(\"https://example.com\")`; it should print the same HTML as the previous line (http vs https)\n\t1. Run `O = urlread(\"http://cnn.com\")`; it should print an error saying that the domain is not in the whitelist (unless you added that domain to your custom whitelist)\n\t1. Run `ping`; you should see a response like \"Ping time: 75ms\"\n\t1. Run `pause; disp(\"done\")`; you should see a message \"press enter to continue\".  Press enter, and then you should see \"done\" printed out to the console\n1. Octave feature coverage\n\t1. Run `gf`; you should see a message \"Run 'pkg load communications' to use 'gf'\"\n\t1. Run `pkg load communications` and then `help gf`; you should get a help page\n\t1. Run `audioread(\"dummy.wav\")`; you should get an error that the file does not exist (but you should NOT get an error that says libsndfile was not installed)\n\t1. Run `fork`; you should see a message \"error: 'fork' undefined near line 1, column 1\"\n1. Student / instructor features\n\t1. Create two accounts if you do not already have two accounts\n\t1. In one account, add a string to the `instructor` field in mongodb; for example, `\"test-course\"`\n\t1. Sign in to Octave Online Server using the other account\n\t1. Run `enroll(\"test-course\")` and follow the onscreen instructions\n\t1. Sign out and sign into the first account, the one with the instructor field\n\t1. Ensure that the student is listed in the menu bar\n\t1. Sign out and back into the student account\n\t1. Open the menu and try disabling sharing; it should deny permission\n\t1. Run `enroll(\"default\")` and follow the onscreen instructions\n\t1. Open the menu and try disabling sharing again; it should work this time\n1. Network connection and reconnecting to a session\n\t1. Open your Octave Online Server as a guest user (not signed in)\n\t1. Type `x = 5` and press Enter, followed by `x` and Enter, to ensure that the variable is set correctly\n\t1. Terminate (Ctrl-C) your front server process and quickly restart it\n\t1. The loading animation should appear on the browser window, and the animation should go away once the front server has finished restarting.  In addition, the phrase \"Connection lost.  Attempting to reconnect...\" should be printed to the console window.  When the server reconnects, the prompt should activate\n\t1. Type `x` and Enter; the variable should still have the value 5\n\t1. Type `exit`; it should say \"Octave Exited. Message: Shell Exited\", and you should get a link that says \"Click Here to Reconnect\"\n\t1. Terminate (Ctrl-C) your front server process and quickly restart it\n\t1. The loading animation should appear on the browser window, and the animation should go away once the front server has finished restarting.  However, you should NOT get the \"Connection lost\" message printed to the console, and you should NOT get an active prompt automatically after the animation goes away\n\t1. Press the \"Click Here to Reconnect\" button; you should now get an active command prompt.  Run a command or two to make sure the session is working normally\n\t1. For an exhaustive test, repeate this section as (i) a signed-in user, (ii) a session with sharing enabled, and (iii) a bucket session.\n1. Reconnecting to and expiring collaborative workspaces\n\t1. Sign in to a user that has sharing enabled\n\t1. *Ensure that no one else is viewing the user's workspace* (for example, there should be no red cursors at the command prompt)\n\t1. Set a variable like `x = 99`\n\t1. Reload the browser window; it should be the same session.  Check that `x` is still `99`\n\t1. Close the browser window without exiting explicitly\n\t1. Wait for `config.redis.expire.timeout` milliseconds to ellapse, then open up a new tab for that user; it should be a new session.  Check that `x` is no longer set to `99`\n1. GUI: Flexbox panels and CSS\n\t1. Hover over the border between panels; a slider should appear.  Drag the slider around to resize the panels\n\t1. Open the menu and click \"Change/Reset Layout\"; the panel sizes should reset to the defaults\n\t1. Open the menu and click \"Change Theme\"; you should get a dark theme.  Clicking the button again should change the theme back\n1. GUI: Function arguments and filenames\n\t1. Run the command `edit demo_fn.m`; it should create a new file with that name and open it in the editor\n\t1. Enter the following content for that file:\n\t```\n\tfunction [o] = demo_fn(x)\n\to = x*2;\n\tendfunction\n\t```\n\t1. Click the \"Run\" button.  You should get a prompt asking you for the value of x.  Enter a value such as 3.  You should now see `ans = 6` in the console output window\n\t1. Press Command+R or Control+R.  The same prompt should appear\n\t1. Attempt to create another new file with the same name, `demo_fn.m`, using the \"Create empty file\" button.  You should not be able to create a file with that name since it already exists\n1. GUI: Command prompt features\n\t1. Type `fpl` into the prompt box, then hit TAB.  You should get a menu of auto-completions like `fplot`\n\t1. Run several commands, such as `x=1` then `x=2` then `x=3`.  Press the up arrow.  You should be able to scroll through your command history\n\t1. You should see \"x\" in the Vars menu.  Click on the x.  A dialog should open telling you the current value of x\n\t1. Within the command output panel, click on command text, to the right of the \"octave:#>\".  That command should appear in the URL bar\n\t1. Reload the page.  The command you clicked (the one now in the URL) should be automatically executed after the page loads\n1. GUI: Legal and account management\n\t1. Open the side bar menu.  Click on \"Privacy Policy and EULA\".  A dialog should open showing that content\n\t1. Make sure you are signed in\n\t1. Click \"Change Password\".  Follow the instructions to change the password\n\t1. Sign out and sign back in using your new password to make sure it worked\n1. GUI: Folders\n\t1. Use the \"Create empty file\" button to create a file named \"dir1/foo.m\".  It should create a file in that subdirectory, \"dir1\", shown in the file list panel\n\t1. Enter the command `cd dir1`; you should now be changed into that directory and there should be a small window reminding you in the top left of the console output window\n1. Pushing the limits: File Size\n\t1. Make sure you are *not* signed in\n\t1. Run the following command line; it should finish without any errors:\n\t```A = rand(500); save A.mat; load A.mat```\n\t1. Run the following command line; it should produce the error \"load: failed to load matrix constant\", due to hitting the 20 MB file size limit per workspace:\n\t```A = rand(5000); save A.mat; load A.mat```\n1. Pushing the limits: Message Size\n\t1. Run the following command line; it should finish without any errors and produce a busy line plot:\n\t```plot(rand(100));```\n\t1. Run the following command line; it should produce the error \"Warning: Suppressed a large plot\", due to hitting the 1 MB limit on message size and therefore plot size:\n\t```plot(rand(300));```\n1. Pushing the limits: Countdown / Time Limit\n\t1. Run the following command:\n\t```pause(12)```\n\t1. When the \"Add 15 Seconds\" link appears, click it\n\t1. Ensure that the time runs out after 12 seconds from the original entry of the command\n1. Pushing the limits: Payload and signals\n\t1. Run the following command:\n\t```x = 0; while(true), x += 1, end```\n\t1. The variable `x` should get to somewhere between 1500 and 2000 before being paused for payload\n\t1. Click the \"Resume Execution\" button, and `x` should climb by approximately the same amount\n\t1. Click the x button to stop execution.  There may be a bit more output, but you should soon be returned to the command prompt\n\t1. Repeat the above steps, but instead of clicking the x button, wait for the payload timeout to finish on its own and return you to the command prompt\n\nTip: A community member like you could implement an automated end-to-end test suite.  If this is your area of expertise, please open an issue and engage!\n\n## License\n\nOctave Online Server is licensed under the [GNU Affero General Public License](https://en.wikipedia.org/wiki/Affero_General_Public_License).\n\n> Octave Online Server is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n>\n> Octave Online Server is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.\n\nA copy of the license can be found in COPYING.\n\nNote: You may contact webmaster@octave-online.net to inquire about other options for licensing Octave Online Server.\n"
  },
  {
    "path": "back-filesystem/.eslintrc.yml",
    "content": ""
  },
  {
    "path": "back-filesystem/.npmrc",
    "content": "# Please keep this in sync with all other .npmrc files\n# SEE: https://github.com/npm/feedback/discussions/864\ninstall-links=false\n"
  },
  {
    "path": "back-filesystem/README.md",
    "content": "Octave Online Server: Back Server, Filesystem Utilities\n=======================================================\n\nThis directory contains the source code dealing with the filesystem for the Octave Online Server back server.  It also contains scripts for interacting with the Git file server.\n\n## Git File Server\n\nThe subdirectory *git* contains files for running a Git file server for Octave Online Server.\n\n*gitd.service* is a systemd service that enables the Git daemon with the repository root at */srv/oo/git*, which is expected to contain *repos* and *buckets* subdirectories that are read/write to the *git* user.\n\n*create-repo.service* is a systemd service that runs a tiny server for creating empty repositories.  It has the same path expectations as *gitd.service*.  It invokes the path */usr/local/bin/create-repo-service*, which is expected to be a copy of *create-repo-service.js* from this project.\n\n**CAUTION:** Neither *gitd.service* nor *create-repo.service* require any authentication.  You should therefore run these services behind a firewall.\n\n## History\n\nWhen using the SELinux backend, the code in this directory is run in the main event loop (Node.js process) along with the *back-master* code.  When the Docker backend is used, however, this code runs inside of a Docker container, while *back-master* runs outside of a Docker container.\n\nThis separation was done in order to make file permissions work correctly.  However, this separation is one reason why the Docker implementation is not able to handle as many concurrent sessions as the SELinux implementation.\n"
  },
  {
    "path": "back-filesystem/app.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\n// This is the entrypoint for a standalone version of back-filesystem, used by the Docker mode but NOT the SELinux mode.\n\nconst messenger = new (require(\"@oo/shared\").StdioMessenger)();\nconst log = require(\"@oo/shared\").logger(\"app\");\nconst FilesController = require(\"./src/controller\");\n\n// Customize options on the logger\nrequire(\"./src/logger\");\n\n// Read command-line arguments\nconst GIT_DIR = process.argv[2];\nconst WORK_DIR = process.argv[3];\nlog.info(\"Dirs:\", GIT_DIR, WORK_DIR);\n\n// Make an instance of controller\nvar controller = new FilesController(GIT_DIR, WORK_DIR, \"\");\n\n// Set up the STDIO messenger instance so we can talk to the master\nmessenger._log = require(\"@oo/shared\").logger(\"messenger\");\nmessenger.setReadStream(process.stdin);\nmessenger.setWriteStream(process.stdout);\nmessenger.on(\"message\", (name, content) => {\n\tcontroller.receiveMessage(name, content);\n});\ncontroller.on(\"message\", (name, content) => {\n\tmessenger.sendMessage(name, content);\n});\nmessenger.on(\"error\", (err) => {\n\tlog.error(\"messenger:\", err);\n});\n\n// Send acknowledgement message downstream\nmessenger.sendMessage(\"ack\", true);\n\n\n"
  },
  {
    "path": "back-filesystem/git/create-repo-service.js",
    "content": "#!/usr/bin/env node\n\n/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\n/* eslint-disable no-console */\n\nconst child_process = require(\"child_process\");\nconst fs = require(\"fs\");\nconst http = require(\"http\");\nconst path = require(\"path\");\nconst url = require(\"url\");\n\nif (process.argv.length !== 4) {\n\tconsole.error(\"Usage: node create-repo-service.js /path/to/git/root PORT\");\n\tprocess.exit(1);\n}\n\nconst gitRoot = process.argv[2];\nconst port = parseInt(process.argv[3]);\n\nhttp.createServer((req, res) => {\n\tconst { query } = url.parse(req.url, true);\n\tconst isoTime = new Date().toISOString();\n\tif ([\"buckets\", \"repos\"].indexOf(query.type) === -1) {\n\t\tres.writeHead(400, \"Invalid type\");\n\t\tconsole.log(`create-repo-service: ${isoTime} Invalid type`);\n\t\treturn res.end();\n\t}\n\tif (!query.name || !/^[\\w]+$/.test(query.name)) {\n\t\tres.writeHead(400, \"Invalid name\");\n\t\tconsole.log(`create-repo-service: ${isoTime} Invalid name`);\n\t\treturn res.end();\n\t}\n\tconst bareRepoPath = path.join(gitRoot, query.type, query.name + \".git\");\n\tfs.stat(bareRepoPath, (err) => {\n\t\tconst exists = !err;\n\t\tlet process;\n\t\tif (query.action === \"delete\") {\n\t\t\tif (exists) {\n\t\t\t\tprocess = child_process.spawn(\"rm\", [\"-rf\", bareRepoPath]);\n\t\t\t} else {\n\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/plain\" });\n\t\t\t\tres.end(\"Already Deleted\\n\");\n\t\t\t\tconsole.log(`create-repo-service: ${isoTime} Already Deleted: ${bareRepoPath}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t} else {\n\t\t\tif (exists) {\n\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/plain\" });\n\t\t\t\tres.end(\"Already Created\\n\");\n\t\t\t\tconsole.log(`create-repo-service: ${isoTime} Already Created: ${bareRepoPath}`);\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\tprocess = child_process.spawn(\"git\", [\"init\", \"--bare\", bareRepoPath]);\n\t\t\t}\n\t\t}\n\t\tlet resData = Buffer.alloc(0);\n\t\tprocess.stdout.on(\"data\", (chunk) => {\n\t\t\tresData = Buffer.concat([resData, chunk]);\n\t\t});\n\t\tprocess.stderr.on(\"data\", (chunk) => {\n\t\t\tresData = Buffer.concat([resData, chunk]);\n\t\t});\n\t\tprocess.on(\"exit\", (code /* , signal */) => {\n\t\t\tconst operation = (query.action === \"delete\") ? \"Delete\" : \"Init\";\n\t\t\tif (code === 0) {\n\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/plain\" });\n\t\t\t\tres.write(`${operation} Success\\n`);\n\t\t\t\tconsole.log(`create-repo-service: ${isoTime} ${operation} Success: ${bareRepoPath}`);\n\t\t\t} else {\n\t\t\t\tres.writeHead(500, { \"Content-Type\": \"text/plain\" });\n\t\t\t\tres.write(`${operation} Error\\n`);\n\t\t\t\tconsole.log(`create-repo-service: ${isoTime} ${operation} Error: ${bareRepoPath}`);\n\t\t\t\tconsole.log(`create-repo-service: ${resData.toString(\"utf-8\")}`);\n\t\t\t}\n\t\t\tres.write(resData);\n\t\t\tres.end();\n\t\t});\n\t});\n}).listen(port);\n\nconsole.log(\"create-repo-service: Listening on port\", port);\n"
  },
  {
    "path": "back-filesystem/git/create-repo.service",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n###############################################################\n# NOTE: This systemd service file is here for reference only; #\n# it is not currently being used in Octave Online Server.     #\n###############################################################\n\n[Unit]\nDescription=Service to create and delete bare git repos\n\n[Service]\nExecStart=/usr/local/bin/create-repo-service /srv/oo/git 3003\nEnvironment=NODE_ENV=production\n\nRestart=always\n\nStandardOutput=syslog\nStandardError=syslog\n\nUser=git\nGroup=git\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "back-filesystem/git/gitd.service",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n###############################################################\n# NOTE: This systemd service file is here for reference only; #\n# it is not currently being used in Octave Online Server.     #\n###############################################################\n\n[Unit]\nDescription=Start Read-Write Git Daemon\n\n[Service]\nExecStart=/usr/bin/git -c daemon.receivepack=true daemon --verbose --reuseaddr --export-all --base-path=/srv/oo/git /srv/oo/git\n\nRestart=always\nRestartSec=500ms\n\nStandardOutput=syslog\nStandardError=syslog\nSyslogIdentifier=git-daemon\n\nUser=git\nGroup=git\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "back-filesystem/package.json",
    "content": "{\n  \"name\": \"@oo/files\",\n  \"version\": \"0.0.0\",\n  \"description\": \"Reads and writes to an Octave session working directory\",\n  \"main\": \"app.js\",\n  \"scripts\": {},\n  \"author\": \"Octave Online LLC\",\n  \"license\": \"AGPL-3.0\",\n  \"private\": true,\n  \"engines\": {\n    \"node\": \"18.x\"\n  },\n  \"dependencies\": {\n    \"@oo/shared\": \"file:../shared\",\n    \"async\": \"^1.5.2\",\n    \"charset-detector\": \"0.0.2\",\n    \"debug-logger\": \"^0.4.1\",\n    \"iconv\": \"^3\",\n    \"mime\": \"^1.3.4\",\n    \"socketio-file-upload\": \"^0.4.4\",\n    \"sprintf-js\": \"^1.0.3\"\n  }\n}\n"
  },
  {
    "path": "back-filesystem/src/controller.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst async = require(\"async\");\nconst GitUtil = require(\"./git-util\");\nconst WorkingUtil = require(\"./working-util\");\nconst path = require(\"path\");\nconst SocketIOFileUploadServer = require(\"socketio-file-upload\");\nconst config = require(\"@oo/shared\").config;\nconst FakeSocket = require(\"./fake-socket\");\nconst logger = require(\"@oo/shared\").logger;\nconst EventEmitter = require(\"events\");\n\nclass FilesController extends EventEmitter {\n\tconstructor(gitDir, workDir, logMemo) {\n\t\tsuper();\n\t\tthis._log = logger(`files-controller:${logMemo}`);\n\t\tthis._mlog = logger(`files-controller:${logMemo}:minor`);\n\n\t\tthis.gitUtil = new GitUtil(gitDir, logMemo);\n\t\tthis.workingUtil = new WorkingUtil(workDir, logMemo);\n\t\tthis.workDir = workDir;\n\t\tthis.user = null;\n\t\tthis.bucketId = null;\n\t\tthis.ready = false;\n\t\tthis.destroyed = false;\n\n\t\tthis.fakeSocket = new FakeSocket();\n\t\tthis.fakeSocket.on(\"_emit\", this._sendMessage.bind(this));\n\t\tthis._setupUploader();\n\t}\n\n\t// Returns whether Git operations are safe to perform.\n\t// Check this.ready for whether file operations (without git) are safe.\n\t_isInitialized() {\n\t\treturn this.ready && (this.user !== null || this.bucketId !== null);\n\t}\n\n\t// Returns whether either \"user-info\" or \"bucket-info\" has been called\n\t_isSetUp() {\n\t\treturn this.user || this.bucketId || this.ready;\n\t}\n\n\treceiveMessage(name, content) {\n\t\tswitch (name) {\n\t\t\tcase \"user-info\": {\n\t\t\t\tif (this._isSetUp()) {\n\t\t\t\t\tthis._log.error(\"user-info called, but already set up:\", content);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.user = content.user;\n\t\t\t\tif (this.user) {\n\t\t\t\t\tthis._log.info(\"Received user:\", this.user.consoleText);\n\t\t\t\t\tthis._legalTime = content.legalTime; // FIXME: For backwards compatibility\n\t\t\t\t} else {\n\t\t\t\t\tthis._log.info(\"No user this session\");\n\t\t\t\t\tthis.ready = true;\n\t\t\t\t\tthis._sendMessage(\"files-ready\", {});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tasync.waterfall([\n\t\t\t\t\t(_next) => {\n\t\t\t\t\t\tthis.gitUtil.initialize(this.user, this.workDir, _next);\n\t\t\t\t\t},\n\t\t\t\t\t(results, _next) => {\n\t\t\t\t\t\tthis.workingUtil.hasOctaverc(_next);\n\t\t\t\t\t},\n\t\t\t\t\t(hasOctaverc, _next) => {\n\t\t\t\t\t\tthis.ready = true;\n\t\t\t\t\t\tthis._sendMessage(\"files-ready\", { hasOctaverc });\n\t\t\t\t\t\t_next(null);\n\t\t\t\t\t},\n\t\t\t\t\t(_next) => {\n\t\t\t\t\t\tthis.workingUtil.listAll(_next);\n\t\t\t\t\t}\n\t\t\t\t], (err, fileData) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\tif (/unable to write file/.test(err.message)) {\n\t\t\t\t\t\t\treturn this._fail(\"filelist\", \"warn\", `Whoops! You are currently exceeding your space limit of ${config.docker.diskQuotaKiB} KiB.\\nPlease open a support ticket and we will help you resolve the\\nissue. Sorry for the inconvenience!`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (this._logError(\"initialize\", err)) {\n\t\t\t\t\t\t\t\tthis._fail(\"filelist\", \"warn\", \"Unable to load your files from the server: please try again.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis._mlog.debug(\"User successfully initialized\");\n\t\t\t\t\tthis._sendMessage(\"filelist\", {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tlegalTime: this._legalTime, // FIXME: for backwards compatibility\n\t\t\t\t\t\tfiles: fileData,\n\t\t\t\t\t\trefresh: false\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"bucket-info\": {\n\t\t\t\tif (this._isSetUp()) {\n\t\t\t\t\tthis._log.error(\"bucket-info called, but already set up:\", content);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.bucketId = content.id;\n\t\t\t\tthis._legalTime = content.legalTime; // FIXME: For backwards compatibility\n\t\t\t\t// If content.readonly is false, this request is for a project or for creating the bucket.  If content.readonly is true, this request is for reading from the bucket.\n\t\t\t\tthis._log.info(\"Received bucket:\", this.bucketId, content.readonly);\n\t\t\t\tasync.waterfall([\n\t\t\t\t\t(_next) => {\n\t\t\t\t\t\tthis.gitUtil.initializeBucket(this.bucketId, this.workDir, content.readonly, _next);\n\t\t\t\t\t},\n\t\t\t\t\t(results, _next) => {\n\t\t\t\t\t\tthis.workingUtil.hasOctaverc(_next);\n\t\t\t\t\t},\n\t\t\t\t\t(hasOctaverc, _next) => {\n\t\t\t\t\t\tthis.ready = true;\n\t\t\t\t\t\tthis._sendMessage(\"files-ready\", { hasOctaverc });\n\t\t\t\t\t\t_next(null);\n\t\t\t\t\t},\n\t\t\t\t\t(_next) => {\n\t\t\t\t\t\tthis.workingUtil.listAll(_next);\n\t\t\t\t\t}\n\t\t\t\t], (err, fileData) => {\n\t\t\t\t\tif (err) return this._logError(\"bucket\", err);\n\t\t\t\t\tthis._mlog.debug(\"Bucket successfully initialized\");\n\t\t\t\t\tthis._sendMessage(\"filelist\", {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tlegalTime: this._legalTime, // FIXME: for backwards compatibility\n\t\t\t\t\t\tfiles: fileData,\n\t\t\t\t\t\trefresh: false\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"list\": {\n\t\t\t\tif (!this.ready) return this._mlog.warn(\"list: not ready\");\n\t\t\t\tthis._mlog.debug(\"Listing files...\");\n\t\t\t\tasync.waterfall([\n\t\t\t\t\t(_next) => {\n\t\t\t\t\t\tthis.workingUtil.listAll(_next);\n\t\t\t\t\t}\n\t\t\t\t], (err, fileData) => {\n\t\t\t\t\tif (err) return this._logError(\"list\", err);\n\t\t\t\t\tthis._log.debug(\"Files successfully listed\");\n\t\t\t\t\tthis._sendMessage(\"filelist\", {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tlegalTime: this._legalTime, // FIXME: for backwards compatibility\n\t\t\t\t\t\tfiles: fileData,\n\t\t\t\t\t\trefresh: false\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"refresh\": {\n\t\t\t\tif (!this._isInitialized()) return this._mlog.warn(\"refresh: not initialized\");\n\t\t\t\tthis._mlog.debug(\"Refreshing files...\");\n\t\t\t\tasync.waterfall([\n\t\t\t\t\t(_next) => {\n\t\t\t\t\t\tthis.gitUtil.pullPush(\"Scripted user file commit\", _next);\n\t\t\t\t\t},\n\t\t\t\t\t(results, _next) => {\n\t\t\t\t\t\tthis.workingUtil.listAll(_next);\n\t\t\t\t\t}\n\t\t\t\t], (err, fileData) => {\n\t\t\t\t\tif (err) return this._logError(\"refresh\", err);\n\t\t\t\t\tthis._log.debug(\"Files successfully refreshed\");\n\t\t\t\t\tthis._sendMessage(\"filelist\", {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tfiles: fileData,\n\t\t\t\t\t\trefresh: true\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"commit\": {\n\t\t\t\tif (!this._isInitialized()) return this._fail(\"committed\", \"warn\", \"Not initialized\");\n\t\t\t\t// NOTE: In a readonly repository (buckets), this is a no-op.\n\t\t\t\tconst comment = content.comment;\n\t\t\t\tif (!comment) return this._fail(\"committed\", \"warn\", \"Empty comment:\", comment);\n\t\t\t\tthis._mlog.debug(\"Committing files...\");\n\t\t\t\tasync.waterfall([\n\t\t\t\t\t(_next) => {\n\t\t\t\t\t\tthis.gitUtil.pullPush(comment, _next);\n\t\t\t\t\t}\n\t\t\t\t], (err) => {\n\t\t\t\t\tif (err) return this._fail(\"committed\", \"warn\", err);\n\t\t\t\t\tthis._log.debug(\"Files successfully committed (except for readonly)\");\n\t\t\t\t\treturn this._sendMessage(\"committed\", { success: true });\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"save\": {\n\t\t\t\tif (!this.ready) return this._fail(\"save\", \"warn\", \"Not ready\");\n\t\t\t\tconst filename = content.filename;\n\t\t\t\tconst value = content.content;\n\t\t\t\tthis._mlog.debug(\"Saving file:\", filename);\n\t\t\t\tif (!filename) return this._fail(\"saved\", \"warn\", \"Empty file name:\", filename, value);\n\t\t\t\tasync.waterfall([\n\t\t\t\t\t(_next) => {\n\t\t\t\t\t\tthis.workingUtil.saveFile(filename, value, _next);\n\t\t\t\t\t}\n\t\t\t\t], (err, md5sum) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\tif (/ENOSPC/.test(err.message)) return this._fail(\"saved\", \"warn\", `Whoops, you reached your space limit (${config.docker.diskQuotaKiB} KiB).\\nYou should free up space to ensure that changes you make get committed.\\nRunning the command \"system('rm octave-workspace')\" might help.`);\n\t\t\t\t\t\telse return this._fail(\"saved\", \"error\", err);\n\t\t\t\t\t}\n\t\t\t\t\tthis._log.debug(\"File successfully saved\");\n\t\t\t\t\treturn this._sendMessage(\"saved\", {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tfilename,\n\t\t\t\t\t\tmd5sum\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"rename\": {\n\t\t\t\tif (!this.ready) return this._fail(\"rename\", \"warn\", \"Not ready\");\n\t\t\t\tconst oldname = content.filename;\n\t\t\t\tconst newname = content.newname;\n\t\t\t\tif (!oldname || !newname) return this._fail(\"renamed\", \"warn\", \"Empty file name or new name:\", oldname, newname);\n\t\t\t\tthis._mlog.debug(\"Renaming file:\", oldname, newname);\n\t\t\t\tasync.waterfall([\n\t\t\t\t\t(_next) => {\n\t\t\t\t\t\tthis.workingUtil.renameFile(oldname, newname, _next);\n\t\t\t\t\t}\n\t\t\t\t], (err) => {\n\t\t\t\t\tif (err) return this._fail(\"renamed\", \"error\", err);\n\t\t\t\t\tthis._log.debug(\"File successfully renamed\");\n\t\t\t\t\treturn this._sendMessage(\"renamed\", { oldname, newname, success: true });\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"delete\": {\n\t\t\t\tif (!this.ready) return this._fail(\"delete\", \"warn\", \"Not ready\");\n\t\t\t\tconst filename = content.filename;\n\t\t\t\tif (!filename) return this._fail(\"deleted\", \"warn\", \"Empty file name:\", filename);\n\t\t\t\tthis._mlog.debug(\"Deleting file:\", filename);\n\t\t\t\tasync.waterfall([\n\t\t\t\t\t(_next) => {\n\t\t\t\t\t\tthis.workingUtil.deleteFile(filename, _next);\n\t\t\t\t\t}\n\t\t\t\t], (err) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\tif (/ENOENT/.test(err.message)) return this._fail(\"deleted\", \"warn\", `Whoops, the file ${filename} does not exist any more.\\nTry pressing the \"refresh files\" button in the file manager toolbar.`);\n\t\t\t\t\t\telse return this._fail(\"deleted\", \"error\", err);\n\t\t\t\t\t}\n\t\t\t\t\tthis._log.debug(\"File successfully deleted\");\n\t\t\t\t\treturn this._sendMessage(\"deleted\", {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tfilename\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"binary\": {\n\t\t\t\tif (!this.ready) return this._mlog.warn(\"binary: not ready\");\n\t\t\t\tconst filename = content.filename;\n\t\t\t\tif (!filename) return this._fail(\"binary\", \"warn\", \"Empty file name:\", filename);\n\t\t\t\tthis._mlog.debug(\"Loading binary file:\", filename);\n\t\t\t\tasync.waterfall([\n\t\t\t\t\t(_next) => {\n\t\t\t\t\t\tthis.workingUtil.readBinary(filename, _next);\n\t\t\t\t\t}\n\t\t\t\t], (err, base64data, mime) => {\n\t\t\t\t\tif (err) return this._fail(\"binary\", \"error\", err);\n\t\t\t\t\tthis._log.debug(\"File successfully loaded\");\n\t\t\t\t\treturn this._sendMessage(\"binary\", {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tfilename,\n\t\t\t\t\t\tbase64data,\n\t\t\t\t\t\tmime\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"read-delete-binary\": {\n\t\t\t\tif (!this.ready) return this._mlog.warn(\"read-delete-binary: not ready\");\n\t\t\t\tconst filename = content.filename;\n\t\t\t\tif (!filename) return this._fail(\"deleted-binary\", \"warn\", \"Empty file name:\", filename);\n\t\t\t\tthis._mlog.debug(\"Loading and deleting binary file:\", filename);\n\t\t\t\tasync.series([\n\t\t\t\t\t(_next) => {\n\t\t\t\t\t\tthis.workingUtil.readBinary(filename, _next);\n\t\t\t\t\t}, (_next) => {\n\t\t\t\t\t\tthis.workingUtil.deleteFile(filename, _next);\n\t\t\t\t\t}\n\t\t\t\t], (err, results) => {\n\t\t\t\t\tif (err) return this._fail(\"deleted-binary\", \"error\", err);\n\t\t\t\t\tconst base64data = results[0][0];\n\t\t\t\t\tconst mime = results[0][1];\n\t\t\t\t\tthis._log.debug(\"File successfully loaded and deleted\");\n\t\t\t\t\treturn this._sendMessage(\"deleted-binary\", {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tfilename,\n\t\t\t\t\t\tbase64data,\n\t\t\t\t\t\tmime\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"multi-binary\": {\n\t\t\t\tif (!this.ready) return this._mlog.warn(\"multi-binary: not ready\");\n\t\t\t\tconst filenames = content.filenames;\n\t\t\t\tconst responseName = \"multi-binary:\" + content.id;\n\t\t\t\tif (!Array.isArray(filenames)) return this._fail(responseName, \"warn\", \"Invalid filename array:\", filenames);\n\t\t\t\tthis._mlog.debug(\"Loading multiple files\", responseName, filenames);\n\t\t\t\tasync.map(filenames, (filename, _next) => {\n\t\t\t\t\tasync.waterfall([\n\t\t\t\t\t\t(__next) => {\n\t\t\t\t\t\t\tthis.workingUtil.readBinary(filename, __next);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(base64data, mime, __next) => {\n\t\t\t\t\t\t\t__next(null, base64data);\n\t\t\t\t\t\t}\n\t\t\t\t\t], _next);\n\t\t\t\t}, (err, results) => {\n\t\t\t\t\tif (err) return this._fail(responseName, \"error\", err);\n\t\t\t\t\tthis._mlog.trace(\"Files finished loading\", responseName);\n\t\t\t\t\treturn this._sendMessage(responseName, {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tresults\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"save-multi-binary\": {\n\t\t\t\tif (!this.ready) return this._mlog.warn(\"save-multi-binary: not ready\");\n\t\t\t\tconst filenames = content.filenames;\n\t\t\t\tconst base64datas = content.base64datas;\n\t\t\t\tconst responseName = \"multi-binary-saved:\" + content.id;\n\t\t\t\tif (!Array.isArray(filenames) || !Array.isArray(base64datas) || filenames.length !== base64datas.length) return this._fail(responseName, \"warn\", \"Invalid array:\", filenames, base64datas);\n\t\t\t\tthis._mlog.debug(\"Writing multiple files:\", responseName, filenames);\n\t\t\t\tasync.times(filenames.length, (i, _next) => {\n\t\t\t\t\tconst buffer = new Buffer(base64datas[i], \"base64\");\n\t\t\t\t\tthis.workingUtil.saveFile(filenames[i], buffer, _next);\n\t\t\t\t}, (err, results) => {\n\t\t\t\t\tif (err) return this._fail(responseName, \"error\", err);\n\t\t\t\t\tthis._mlog.trace(\"Files finished writing\", responseName);\n\t\t\t\t\treturn this._sendMessage(responseName, {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tresults\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Send remaining messages to the fakeSocket\n\t\t\tdefault: {\n\t\t\t\tthis.fakeSocket.trigger(name, content);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tdestroy() {\n\t\tthis.destroyed = true;\n\t}\n\n\t_logError(context, err) {\n\t\tif (this.destroyed) {\n\t\t\tthis._mlog.trace(\"Ignoring git error:\", context, err);\n\t\t\treturn false;\n\t\t} else {\n\t\t\tthis._log.error(context, err);\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Send messages downstream\n\t_sendMessage(name, content) {\n\t\tthis.emit(\"message\", name, content);\n\t}\n\n\t// Log and send failure messages\n\t_fail() {\n\t\tlet args = Array.prototype.slice.call(arguments, 2);\n\t\tlet messageString = args.join(\" \");\n\t\tthis._log[arguments[1]].apply(this, args);\n\t\tthis._sendMessage(arguments[0], { success: false, message: messageString });\n\t}\n\n\t// Set up SIOFU\n\t_setupUploader() {\n\t\tconst uploader = new SocketIOFileUploadServer();\n\t\tuploader.dir = this.workDir;\n\t\tuploader.emitChunkFail = true;\n\t\tuploader.on(\"saved\", (event) => {\n\t\t\tconst filename = path.basename(event.file.pathName);\n\t\t\tthis.workingUtil.getFileInfo(filename, (err, fileInfo) => {\n\t\t\t\tif (err) return this._log.warn(err);\n\t\t\t\tif (!fileInfo) return this._fail(\"saved\", \"warn\", \"Your file uploaded, but it will not appear in the list due to an illegal file name.\");\n\t\t\t\tthis._log.debug(\"File successfully uploaded\");\n\t\t\t\treturn this._sendMessage(\"fileadd\", fileInfo);\n\t\t\t});\n\t\t});\n\t\tuploader.on(\"error\", (event) => {\n\t\t\tif (/ENOSPC/.test(event.error.message)) return this._fail(\"saved\", \"debug\", `Uploading ${event.file.name}:\\nIf your file is large and causes you to exceed your space limit\\n(${config.docker.diskQuotaKiB} KiB), the file may be incomplete.`);\n\t\t\tthis._log.error(\"siofu:\", event);\n\t\t});\n\t\tuploader.listen(this.fakeSocket);\n\t}\n}\n\nmodule.exports = FilesController;\n"
  },
  {
    "path": "back-filesystem/src/fake-socket.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst EventEmitter = require(\"events\");\n\n// Object to replicate the API of a server-side Socket.IO socket instance.\n// \n// To trigger all local listeners with an emulated socket message, call\n//   myFakeSocket.trigger(name,data)\n// \n// To listen for when local methods want to send a message, listen for\n// the \"_emit\" event on your FakeSocket instance.\n\nclass FakeSocket extends EventEmitter {\n\tconstructor() {\n\t\tsuper();\n\t}\n}\n\n// Change around some of the methods\nconst oldEmit = FakeSocket.prototype.emit;\nFakeSocket.prototype.trigger = oldEmit;\nFakeSocket.prototype.emit = function(){\n\tconst args = Array.prototype.slice.call(arguments);\n\targs.unshift(\"_emit\");\n\toldEmit.apply(this, args);\n};\n\nmodule.exports = FakeSocket;\n"
  },
  {
    "path": "back-filesystem/src/git-util.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst async = require(\"async\");\nconst child_process = require(\"child_process\");\nconst logger = require(\"@oo/shared\").logger;\nconst config = require(\"@oo/shared\").config;\nconst silent = require(\"@oo/shared\").silent;\n\nclass GitUtil {\n\tconstructor(gitDir, logMemo) {\n\t\tthis._log = logger(`git-util:${logMemo}`);\n\t\tthis._mlog = logger(`git-util:${logMemo}:minor`);\n\t\tthis.execOptions = { cwd: gitDir };\n\t\tthis.readonly = false;\n\t\tthis._initialized = false;\n\t\t// TODO: Prevent multiple git operations from taking place simultaneously.\n\t}\n\n\tinitialize(user, workDir, next) {\n\t\tif (this._initialized) {\n\t\t\tthis._log.error(\"Initializing a repository for a user that was already initialized\");\n\t\t\treturn;\n\t\t}\n\t\tconst remote = this._userToRemote(user);\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\tthis._createUserOnRemote(user, _next);\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._initialize(remote, workDir, _next);\n\t\t\t}\n\t\t], next);\n\t}\n\n\tinitializeBucket(bucketId, workDir, readonly, next) {\n\t\tif (this._initialized) {\n\t\t\tthis._log.error(\"Initializing a repository for a bucket that was already initialized\");\n\t\t\treturn;\n\t\t}\n\t\tthis.readonly = readonly;\n\t\tconst remote = this._bucketToRemote(bucketId);\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\tif (!readonly) {\n\t\t\t\t\tthis._createBucketOnRemote(bucketId, _next);\n\t\t\t\t} else {\n\t\t\t\t\t_next(null);\n\t\t\t\t}\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._initialize(remote, workDir, _next);\n\t\t\t}\n\t\t], next);\n\t}\n\n\t_initialize(remote, workDir, next) {\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.trace(\"Running git init...\");\n\t\t\t\tchild_process.execFile(\"git\", [\"--git-dir=.\", `--work-tree=${workDir}`, \"init\"], this.execOptions, _next);\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\t// May 2018: Do not log email-based Git URL\n\t\t\t\tconst idx = remote.indexOf(\"_\");\n\t\t\t\tconst safeOrigin = (idx === -1) ? remote : remote.substr(0, idx) + \"_…\";\n\t\t\t\tthis._mlog.info(\"Setting origin:\", safeOrigin);\n\t\t\t\tchild_process.execFile(\"git\", [\"remote\", \"add\", \"origin\", remote], this.execOptions, _next);\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._pull(_next);\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._initialized = true;\n\t\t\t\t_next(null);\n\t\t\t}\n\t\t], next);\n\t}\n\n\tpullPush(message, next) {\n\t\tif (!this._initialized) {\n\t\t\t// Trying to sync the repo before it has been initialized; do not attempt to push, because no local changes are possible.\n\t\t\treturn next(null);\n\t\t}\n\t\tif (this.readonly) {\n\t\t\treturn this._pull(next);\n\t\t} else {\n\t\t\treturn this._pullPush(message, next);\n\t\t}\n\t}\n\n\t_pullPush(message, next) {\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.debug(\"Preparing to pull-push...\");\n\t\t\t\t_next();\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._commit(message, _next);\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\t// Perform a shallow clone to avoid wasting time and resources downloading old refs from the server\n\t\t\t\t// This command can fail silently for the case when the remote repo is empty\n\t\t\t\tchild_process.execFile(\"git\", [\"fetch\", \"--depth=1\"], this.execOptions, silent(/no matching remote head/, _next));\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\t// Resolve merge conflicts by committing all the conflicts into the repository, and let the user manually fix the conflict next time the log in.\n\t\t\t\t// This command can fail silently for the case when origin/master does not exist\n\t\t\t\tconst mergeArgs = config.git.supportsAllowUnrelatedHistories ? [\"merge\", \"--no-commit\", \"--allow-unrelated-histories\", \"origin/master\"] : [\"merge\", \"--no-commit\", \"origin/master\"];\n\t\t\t\tchild_process.execFile(\"git\", this._gitConfigArgs().concat(mergeArgs), this.execOptions, silent(/fix conflicts|not something we can merge/, _next).stdout);\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._commit(\"Scripted merge\", _next);\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\t// Push the changes up\n\t\t\t\t// This command can fail silently for the case when the local branch \"master\" is empty\n\t\t\t\tchild_process.execFile(\"git\", [\"push\", \"origin\", \"master\"], this.execOptions, silent(/src refspec master does not match any/, _next));\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.debug(\"Finished pull-push\");\n\t\t\t\t_next(null);\n\t\t\t}\n\t\t], next);\n\t}\n\n\t_pull(next) {\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.debug(\"Preparing to pull...\");\n\t\t\t\t_next();\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\t// Perform a shallow clone to avoid wasting time and resources downloading old refs from the server\n\t\t\t\t// This command can fail silently for the case when the remote repo is empty\n\t\t\t\tchild_process.execFile(\"git\", [\"fetch\", \"--depth=1\"], this.execOptions, silent(/no matching remote head/, _next));\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tchild_process.execFile(\"git\", this._gitConfigArgs().concat([\"merge\", \"origin/master\"]), this.execOptions, silent(/not something we can merge/, _next));\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.debug(\"Finished pull\");\n\t\t\t\t_next();\n\t\t\t}\n\t\t], next);\n\t}\n\n\t_commit(message, next) {\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\tchild_process.execFile(\"git\", [\"add\", \"--all\"], this.execOptions, _next);\n\t\t\t},\n\t\t\t// Remove the following check since the new \"capped file system\" should take care of this for us\n\t\t\t// (_next) => {\n\t\t\t// \t// Do not commit files greater than 1MB in size\n\t\t\t// \tchild_process.exec(\"find . -size +1M -type f -exec git reset {} \\\\;\", this.execOptions, _next);\n\t\t\t// },\n\t\t\t(_next) => {\n\t\t\t\t// This command can safely fail silently for the case when there are no files to commit (in that case, the error is empty)\n\t\t\t\t// Note that specifying --author here does not seem to work; I have to do -c ... instead\n\t\t\t\tchild_process.execFile(\"git\", this._gitConfigArgs().concat([\"commit\", \"-m\", message]), this.execOptions, silent(/nothing to commit/, _next).stdout);\n\t\t\t}\n\t\t], next);\n\t}\n\n\t_createUserOnRemote(user, next) {\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.debug(\"Preparing remote repo...\");\n\t\t\t\t_next();\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tfetch(`http://${config.git.hostname}:${config.git.createRepoPort}/?` + new URLSearchParams({\n\t\t\t\t\ttype: \"repos\",\n\t\t\t\t\tname: user.parametrized\n\t\t\t\t})).then((response) => {\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\treturn _next(new Error(\"Not 2xx response\", { cause: response }));\n\t\t\t\t\t}\n\t\t\t\t\t_next();\n\t\t\t\t}).catch(_next);\n\t\t\t}\n\t\t], next);\n\t}\n\n\t_createBucketOnRemote(bucketId, next) {\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.debug(\"Preparing remote bucket...\");\n\t\t\t\t_next();\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tfetch(`http://${config.git.hostname}:${config.git.createRepoPort}/?` + new URLSearchParams({\n\t\t\t\t\ttype: \"buckets\",\n\t\t\t\t\tname: bucketId\n\t\t\t\t})).then((response) => {\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\treturn _next(new Error(\"Not 2xx response\", { cause: response }));\n\t\t\t\t\t}\n\t\t\t\t\t_next();\n\t\t\t\t}).catch(_next);\n\t\t\t}\n\t\t], next);\n\t}\n\n\t_userToRemote(user) {\n\t\treturn `git://${config.git.hostname}:${config.git.gitDaemonPort}/repos/${user.parametrized}.git`;\n\t}\n\n\t_bucketToRemote(bucketId) {\n\t\treturn `git://${config.git.hostname}:${config.git.gitDaemonPort}/buckets/${bucketId}.git`;\n\t}\n\n\t_gitConfigArgs() {\n\t\treturn [\"-c\", `user.name=\"${config.git.author.name}\"`, \"-c\", `user.email=\"${config.git.author.email}\"`];\n\t}\n}\n\nmodule.exports = GitUtil;\n"
  },
  {
    "path": "back-filesystem/src/logger.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst logger = require(\"@oo/shared\").logger;\n\n// Enable all log levels by default\nlogger.debug.enable(\"*\");\n\n// Customize formatArgs\n// Based on https://github.com/visionmedia/debug/blob/master/node.js\nlogger.debug.formatArgs = function formatArgs() {\n\tvar args = arguments;\n\tvar useColors = this.useColors;\n\tvar name = this.namespace;\n\n\tif (useColors) {\n\t\tvar c = this.color;\n\n\t\targs[0] = \"  \\u001b[3\" + c + \";1m\" + name + \" \"\t+ \"\\u001b[0m\"\t+ args[0] + \"\\u001b[3\" + c + \"m\" + \" +\" + logger.debug.humanize(this.diff) + \"\\u001b[0m\";\n\t} else {\n\t\targs[0] = name + \" \" + args[0];\n\t}\n\treturn args;\n};\n"
  },
  {
    "path": "back-filesystem/src/mime.types",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n\n# Additional MIME Types used by the Octave Online application\n\n# GNU Octave Types\n# If these haven't ben defined before, then they are now!\ntext/x-octave\tm\napplication/x-octave-data\tmat\ntext/plain\toctaverc\n"
  },
  {
    "path": "back-filesystem/src/working-util.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst async = require(\"async\");\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst mime = require(\"mime\");\nconst charsetDetector = require(\"charset-detector\");\nconst Iconv = require(\"iconv\").Iconv;\nconst logger = require(\"@oo/shared\").logger;\nconst config = require(\"@oo/shared\").config;\nconst crypto = require(\"crypto\");\n\n// Load extra MIME types\nmime.load(path.join(__dirname, \"mime.types\"));\n\nconst ACCEPTABLE_MIME_REGEX = /^(text\\/.*)$/;\nconst UNACCEPTABLE_FILENAME_REGEX = /^(\\..*|octave-\\w+)$/;\n\nclass WorkingUtil {\n\tconstructor(workDir, logMemo) {\n\t\tthis._log = logger(`working-util:${logMemo}`);\n\t\tthis._mlog = logger(`working-util:${logMemo}:minor`);\n\t\tthis.cwd = workDir;\n\t}\n\n\tlistAll(next) {\n\t\tasync.waterfall([\n\t\t\t(_next) => {\n\t\t\t\tthis._recursiveReaddir(this.cwd, 0, _next);\n\t\t\t},\n\t\t\t(fileInfos, _next) => {\n\t\t\t\tconst dict = {};\n\t\t\t\tfileInfos.forEach((fileInfo) => {\n\t\t\t\t\tif (!fileInfo) return;\n\t\t\t\t\tlet filename = fileInfo.filename;\n\t\t\t\t\tdelete fileInfo.filename;\n\t\t\t\t\tdict[filename] = fileInfo;\n\t\t\t\t});\n\t\t\t\t_next(null, dict);\n\t\t\t}\n\t\t], next);\n\t}\n\n\thasOctaverc(next) {\n\t\tfs.access(path.join(this.cwd, \".octaverc\"), (err) => {\n\t\t\tif (err) {\n\t\t\t\tthis._mlog.trace(\".octaverc does not exist\");\n\t\t\t\treturn next(null, false);\n\t\t\t} else {\n\t\t\t\tthis._mlog.trace(\".octaverc exists\");\n\t\t\t\treturn next(null, true);\n\t\t\t}\n\t\t});\n\t}\n\n\t_recursiveReaddir(directory, depth, next) {\n\t\t// Don't recurse more than 3 levels deep\n\t\tif (depth > 3) {\n\t\t\treturn next(null, []);\n\t\t}\n\n\t\tasync.waterfall([\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.trace(\"Entering directory\", directory);\n\t\t\t\tfs.readdir(directory, _next);\n\t\t\t},\n\t\t\t(files, _next) => {\n\t\t\t\tasync.map(files, (filename, __next) => {\n\t\t\t\t\tlet pathname = path.join(directory, filename);\n\t\t\t\t\tlet relname = path.relative(this.cwd, pathname);\n\t\t\t\t\t// TODO: Can the following be removed?\n\t\t\t\t\tif (pathname === path.join(this.cwd, \".git\")) {\n\t\t\t\t\t\tthis._mlog.debug(\"Skipping .git in _recursiveReaddir\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tasync.waterfall([\n\t\t\t\t\t\t(___next) => {\n\t\t\t\t\t\t\t// lstat to prevent following symlinks\n\t\t\t\t\t\t\tfs.lstat(pathname, ___next);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(stats, ___next) => {\n\t\t\t\t\t\t\tthis._mlog.trace(\"Got lstats\", relname, stats.isDirectory(), stats.isFile());\n\t\t\t\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\t\t\t\treturn this._recursiveReaddir(pathname, depth+1, ___next);\n\t\t\t\t\t\t\t} else if (stats.isFile()) {\n\t\t\t\t\t\t\t\treturn this._getFileInfo(filename, pathname, relname, stats, ___next);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn ___next(null, []);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t], __next);\n\t\t\t\t}, (err, results) => {\n\t\t\t\t\tif (err) return _next(err);\n\t\t\t\t\tthis._mlog.trace(\"Leaving directory\", directory);\n\t\t\t\t\t_next(null, Array.prototype.concat.apply([], results));\n\t\t\t\t});\n\t\t\t}\n\t\t], next);\n\t}\n\n\t// Endpoint for standalone getFileInfo (used by SIOFU)\n\tgetFileInfo(filename, next) {\n\t\tlet pathname = this._safePath(filename);\n\t\tlet relname = path.relative(this.cwd, pathname);\n\t\tasync.waterfall([\n\t\t\t(_next) => {\n\t\t\t\tfs.lstat(pathname, _next);\n\t\t\t},\n\t\t\t(stats, _next) => {\n\t\t\t\tthis._getFileInfo(filename, pathname, relname, stats, _next);\n\t\t\t},\n\t\t\t(arr, _next) => {\n\t\t\t\t_next(null, arr[0]);\n\t\t\t}\n\t\t], next);\n\t}\n\n\t_getFileInfo(filename, pathname, relname, stats, next) {\n\t\tthis._mlog.trace(\"Getting file info for\", relname);\n\t\tlet _mime = mime.lookup(filename);\n\t\tif (ACCEPTABLE_MIME_REGEX.test(_mime)) {\n\t\t\tif (stats.size > config.session.textFileSizeLimit) {\n\t\t\t\t// This file is too big.  Do not perform any further processing on this file.\n\t\t\t\t// FIXME: Show a nice message to the end user to let them know why their file isn't being loaded\n\t\t\t\tthis._log.debug(\"Skipping text file that is too big:\", stats.size, filename);\n\t\t\t\tnext(null, [{\n\t\t\t\t\tfilename: relname,\n\t\t\t\t\tisText: false\n\t\t\t\t}]);\n\t\t\t} else {\n\t\t\t\tfs.readFile(pathname, (err, buf) => {\n\t\t\t\t\tif (err) return next(err);\n\t\t\t\t\tbuf = this._convertCharset(buf);\n\t\t\t\t\tnext(null, [{\n\t\t\t\t\t\tfilename: relname,\n\t\t\t\t\t\tisText: true,\n\t\t\t\t\t\tcontent: buf.toString(\"base64\")\n\t\t\t\t\t}]);\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (!UNACCEPTABLE_FILENAME_REGEX.test(filename)) {\n\t\t\tnext(null, [{\n\t\t\t\tfilename: relname,\n\t\t\t\tisText: false\n\t\t\t}]);\n\t\t} else {\n\t\t\tnext(null, []);\n\t\t}\n\t}\n\n\t_convertCharset(buf) {\n\t\tvar encoding;\n\n\t\t// Detect and attempt to convert charset\n\t\tif (buf.length > 0) {\n\t\t\ttry {\n\t\t\t\tlet charsetResults = charsetDetector(buf);\n\t\t\t\tthis._log.debug(\"Top charset match:\", charsetResults?.[0]);\n\t\t\t\tencoding = charsetResults?.[0]?.charsetName || \"UTF-8\";\n\t\t\t\tif (encoding !== \"UTF-8\"){\n\t\t\t\t\tthis._log.trace(\"Converting charset:\", encoding);\n\t\t\t\t\tbuf = new Iconv(encoding, \"UTF-8\").convert(buf);\n\t\t\t\t}\n\t\t\t} catch(err) {\n\t\t\t\tthis._log.warn(\"Could not convert encoding:\", encoding);\n\t\t\t}\n\t\t}\n\n\t\t// Convert line endings\n\t\t// TODO: Is there a better way than converting to a string here?\n\t\tbuf = new Buffer(buf.toString(\"utf8\").replace(/\\r\\n/g, \"\\n\"));\n\n\t\treturn buf;\n\t}\n\n\tsaveFile(filename, value, next) {\n\t\t// Create backup of file in memory in case there are any I/O errors\n\t\tasync.waterfall([\n\t\t\t(_next) => {\n\t\t\t\tlet dirname = path.dirname(this._safePath(filename));\n\t\t\t\t// Callback to a function to remove/normalize extra arguments\n\t\t\t\tfs.mkdir(dirname, { recursive: true }, (e) => _next(e));\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tfs.readFile(\n\t\t\t\t\tthis._safePath(filename),\n\t\t\t\t\t(err, buf) => {\n\t\t\t\t\t\tif (!err) return _next(null, buf);\n\t\t\t\t\t\tif (/ENOENT/.test(err.message)) {\n\t\t\t\t\t\t\tthis._log.info(\"Creating new file:\", filename);\n\t\t\t\t\t\t\treturn _next(null, new Buffer(0));\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn _next(err);\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t},\n\t\t\t(buf, _next) => {\n\t\t\t\tfs.writeFile(\n\t\t\t\t\tthis._safePath(filename),\n\t\t\t\t\tvalue,\n\t\t\t\t\t(err) => {\n\t\t\t\t\t\t_next(null, buf, err);\n\t\t\t\t\t});\n\t\t\t},\n\t\t\t(buf, err, _next) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tfs.writeFile(\n\t\t\t\t\t\tthis._safePath(filename),\n\t\t\t\t\t\tbuf,\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t_next(err);\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tasync.nextTick(() => {\n\t\t\t\t\t\t_next(null);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\treturn _next(null, crypto.createHash(\"md5\").update(value, \"utf-8\").digest(\"hex\"));\n\t\t\t}\n\t\t], next);\n\t}\n\n\trenameFile(oldname, newname, next) {\n\t\tfs.rename(\n\t\t\tthis._safePath(oldname),\n\t\t\tthis._safePath(newname),\n\t\t\tnext);\n\t}\n\n\tdeleteFile(filename, next) {\n\t\tfs.unlink(\n\t\t\tthis._safePath(filename),\n\t\t\tnext);\n\t}\n\n\treadBinary(filename, next) {\n\t\tasync.waterfall([\n\t\t\t(_next) => {\n\t\t\t\tfs.readFile(this._safePath(filename), _next);\n\t\t\t},\n\t\t\t(buf, _next) => {\n\t\t\t\tconst base64data = buf.toString(\"base64\");\n\t\t\t\tconst _mime = mime.lookup(filename);\n\t\t\t\t_next(null, base64data, _mime);\n\t\t\t}\n\t\t], next);\n\t}\n\n\t_safePath(filename) {\n\t\t// Filenames must descend from the working directory.  Forbids filenames starting with '..' or similar.\n\t\tlet candidate = path.join(this.cwd, filename);\n\t\tif (candidate.substring(0, this.cwd.length) !== this.cwd) {\n\t\t\tthis._log.warn(\"Processed a fishy filename:\", filename);\n\t\t\treturn path.join(this.cwd, \"octave-workspace\"); // an arbitrary legal path\n\t\t}\n\t\treturn candidate;\n\t}\n}\n\nmodule.exports = WorkingUtil;\n"
  },
  {
    "path": "back-master/.eslintrc.yml",
    "content": ""
  },
  {
    "path": "back-master/.npmrc",
    "content": "# Please keep this in sync with all other .npmrc files\n# SEE: https://github.com/npm/feedback/discussions/864\ninstall-links=false\n"
  },
  {
    "path": "back-master/README.md",
    "content": "Octave Online Server: Back Server\n=================================\n\nThis directory contains the source code for the Octave Online Server back server.  This is the \"master\" process that communicates with the downstream user via Redis and with the GNU Octave subprocess.  Commands dealing with the filesystem are in the *back-filesystem* directory parallel to this directory.\n\n## Setup\n\nThere are three modes for the back server.\n\n1. SELinux: Fast, and able to handle many concurrent sessions.\n2. Unsafe: Fast and easy, but not recommended with untrusted users. Every Octave process is run without any sandboxing or resource limitations.\n\nPreviously, a third mode, \"docker\", worked by creating a new Docker container for each session. This is no longer supported. Instead, if running with trusted users, you can use a single Docker container and Unsafe mode.\n\n### Option 1: SELinux\n\nEnsure that you are running on CentOS or another distribution of Linux that supports SELinux.  SELinux should come pre-installed on CentOS.\n\nMake and build Octave from source.  Follow a procedure similar to the one put forth in *dockerfiles/build-octave.dockerfile*.\n\nRun `sudo yum install -y selinux-policy-devel policycoreutils-sandbox selinux-policy-sandbox libcgroup-tools`\n\nEnsure that Node.js is installed and the dependencies are downloaded for the shared project:\n\n\t$ (cd shared && npm install)\n\nRun all of the following make commands from the projects directory.\n\n- `sudo make install-cgroup`\n- `sudo make install-selinux-policy`\n- `sudo make install-selinux-bin`\n- `sudo make install-site-m`\n\n### Option 2: Unsafe\n\nFollow the Option 2 instructions to build and install Octave from source.  Stop before installing selinux-policy-devel and other selinux packages.\n\n## Running the Back Server\n\n### Debugging\n\nGo to the *back-master* directory and run `DEBUG=* node app.js` to start the back server.  The `DEBUG=*` enables debug logging.\n\n### Production\n\n`node app.js` can be run directly, but consider using `oo.service` in the *entrypoint* directory parallel to this directory.\n\n## Stopping the Back Server\n\nBy default, after the back server receives a successful maintenance request, it will wait for all sessions to close, and then cleanup and exit with code 0.\n\nIf you wish to cause the back server process to gracefully release all sessions without exiting, you can send the signal SIGUSR1 to the process.\n\n## To-do list\n\n- Update /usr/bin/sandbox according to https://github.com/SELinuxProject/selinux/commit/0f4620d6111838ce78bf5a591bb80c99c9d88730\n- If using RHEL, the line \"Defaults requiretty\" must be commented out.\n"
  },
  {
    "path": "back-master/app.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst log = require(\"@oo/shared\").logger(\"app\");\nconst mlog = require(\"@oo/shared\").logger(\"app:minor\");\nconst hostname = require(\"@oo/shared\").hostname();\nconst MessageTranslator = require(\"./src/message-translator\");\nconst RedisMessenger = require(\"@oo/shared\").RedisMessenger;\nconst SessionManager = require(\"./src/session-manager\");\nconst config = require(\"@oo/shared\").config;\nconst gcStats = (require(\"@sematext/gc-stats\"))();\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst async = require(\"async\");\nconst http = require(\"http\");\n\nprocess.stdout.write(\"Process ID: \" + process.pid + \"\\n\");\nprocess.stderr.write(\"Process ID: \" + process.pid + \"\\n\");\nlog.info(\"Process ID:\", process.pid);\nlog.info(\"Hostname:\", hostname);\nlog.log(process.env);\n\nvar sessionManager, mainImpl, personality;\nif (fs.existsSync(config.rackspace.personality_filename)) {\n\tpersonality = JSON.parse(fs.readFileSync(config.rackspace.personality_filename, \"utf8\"));\n\tlog.info(\"Personality:\", personality.flavor, personality);\n\tsessionManager = new SessionManager(true);\n\tmainImpl = require(\"./src/main-flavor\");\n} else if (process.env[\"OO_FLAVOR_OVERRIDE\"]) {\n\tpersonality = { flavor: process.env[\"OO_FLAVOR_OVERRIDE\"] };\n\tlog.info(\"Flavor override:\", personality.flavor);\n\tsessionManager = new SessionManager(true);\n\tmainImpl = require(\"./src/main-flavor\");\n} else {\n\tlog.info(\"No personality file found\");\n\tpersonality = null;\n\tsessionManager = new SessionManager(false);\n\tmainImpl = require(\"./src/main-pool\");\n}\n\nlet sessionLogDirCount = 0;\nfunction makeSessionLogDir(tokens) {\n\tif (tokens.length === config.worker.sessionLogs.depth) {\n\t\tconst dirname = path.join(config.worker.logDir, config.worker.sessionLogs.subdir, ...tokens);\n\t\tif (sessionLogDirCount % 1000 === 0) {\n\t\t\tmlog.trace(dirname);\n\t\t}\n\t\tsessionLogDirCount++;\n\t\tfs.mkdirSync(dirname, { recursive: true });\n\t} else {\n\t\tfor (let a of \"0123456789abcdef\") {\n\t\t\tmakeSessionLogDir(tokens.concat([a]));\n\t\t}\n\t}\n}\nlog.info(\"Creating session log dirs…\");\nmakeSessionLogDir([]);\nlog.info(sessionLogDirCount, \"dirs touched\");\n\nconst redisInputHandler = new RedisMessenger().subscribeToInput();\nconst redisDestroyDHandler = new RedisMessenger().subscribeToDestroyD();\nconst redisExpireHandler = new RedisMessenger().subscribeToExpired();\nconst redisScriptHandler = new RedisMessenger().enableSessCodeScriptsSync();\nconst redisMessenger = new RedisMessenger();\nconst translator = new MessageTranslator();\n\nredisInputHandler.on(\"message\", translator.fromDownstream.bind(translator));\nsessionManager.on(\"message\", translator.fromUpstream.bind(translator));\n\ntranslator.on(\"for-upstream\", (sessCode, name, getData) => {\n\tconst session = sessionManager.get(sessCode);\n\n\t// Stop processing this message if it doesn't have to do with a session running on this node.\n\tif (!session) return;\n\n\t// Now we can safely continue.  The following method will download the data from Redis.\n\tsession.enqueueMessage(name, getData);\n});\n\ntranslator.on(\"for-downstream\", (sessCode, name, content) => {\n\tlog.trace(\"Sending Downstream:\", sessCode, name);\n\tredisMessenger.output(sessCode, name, content);\n});\n\ntranslator.on(\"destroy\", (sessCode, reason) => {\n\tlog.debug(\"Received Destroy:\", sessCode);\n\tsessionManager.destroy(sessCode, reason);\n});\n\ntranslator.on(\"ping\", (code) => {\n\t// Not currently used\n\tlog.debug(\"Received Ping:\", code);\n\tredisMessenger.output(code, \"pong\", { hostname });\n});\n\nredisDestroyDHandler.on(\"destroy-d\", (sessCode, reason) => {\n\tif (!sessionManager.get(sessCode)) return;\n\tlog.info(\"Received Destroy-D:\", sessCode, reason);\n\tsessionManager.destroy(sessCode, reason);\n});\n\nredisExpireHandler.on(\"expired\", (sessCode, channel) => {\n\tif (!sessionManager.get(sessCode)) return;\n\tlog.info(\"Received Expire:\", sessCode, channel);\n\tsessionManager.destroy(sessCode, \"Octave Session Expired (downstream)\");\n});\n\nsessionManager.on(\"touch\", (sessCode, start) => {\n\tredisMessenger.touchOutput(sessCode);\n\tif (personality) {\n\t\tredisMessenger.output(sessCode, \"oo.touch-flavor\", {\n\t\t\tstart,\n\t\t\tcurrent: new Date().valueOf(),\n\t\t\tflavor: personality.flavor\n\t\t});\n\t}\n});\n\nsessionManager.on(\"live\", (sessCode) => {\n\tredisMessenger.setLive(sessCode);\n});\n\nsessionManager.on(\"destroy-u\", (sessCode, reason) => {\n\tlog.info(\"Sending Destroy-U:\", reason, sessCode);\n\tredisMessenger.destroyU(sessCode, reason);\n});\n\ngcStats.on(\"stats\", (stats) => {\n\tmlog.trace(`Garbage Collected (type ${stats.gctype}, ${stats.pause/1e6} ms)`);\n});\n\nconst healthServer = http.createServer((req, res) => {\n\tif (sessionManager.isHealthy()) {\n\t\tres.writeHead(200);\n\t} else {\n\t\tres.writeHead(503);\n\t}\n\tres.end();\n}).listen(config.gcp.health_check_port);\n\nmainImpl.start({\n\tsessionManager,\n\tredisScriptHandler,\n\tredisMessenger,\n\tpersonality\n}, (err) => {\n\tlog.error(\"Main-impl ended\", err);\n\tdoExit();\n});\n\nfunction doExit() {\n\tsessionManager.terminate(\"Server Maintenance\");\n\tmainImpl.doExit();\n\n\tsetTimeout(() => {\n\t\tredisInputHandler.close();\n\t\tredisDestroyDHandler.close();\n\t\tredisExpireHandler.close();\n\t\tredisScriptHandler.close();\n\t\tredisMessenger.close();\n\t\thealthServer.close();\n\t}, 5000);\n}\n\nfunction doGracefulExit() {\n\tlog.info(\"RECEIVED SIGUSR1.  Will not accept any further sessions.\");\n\tmainImpl.doExit();\n\tsessionManager.disablePool();\n\tasync.series([\n\t\t(_next) => {\n\t\t\tasync.whilst(\n\t\t\t\t() => { return sessionManager.numActiveSessions() > 0; },\n\t\t\t\t(next) => { setTimeout(next, config.maintenance.pauseDuration); },\n\t\t\t\t_next\n\t\t\t);\n\t\t},\n\t\t(_next) => {\n\t\t\tlog.info(\"All user sessions are closed. Send SIGINT to fully terminate.\");\n\t\t\t_next(null);\n\t\t}\n\t], (err) => {\n\t\tif (err) log.error(\"Error during graceful exit:\", err);\n\t});\n}\n\nfunction doFastExit() {\n\tlog.info(\"RECEIVED SIGNAL.  Starting exit procedure.\");\n\tdoExit();\n}\n\nprocess.on(\"SIGINT\", doFastExit);\nprocess.on(\"SIGHUP\", doFastExit);\nprocess.on(\"SIGTERM\", doFastExit);\nprocess.on(\"SIGUSR1\", doGracefulExit);\n\n//const heapdump = require(\"heapdump\");\n//setInterval(() => { heapdump.writeSnapshot(\"/srv/oo/logs/heap/\" + hostname + \".\" + process.pid + \".\" + Date.now() + \".heapsnapshot\"); }, 30000);\n"
  },
  {
    "path": "back-master/package.json",
    "content": "{\n  \"name\": \"@oo/back\",\n  \"version\": \"0.0.0\",\n  \"description\": \"Spawns Octave sessions and passes messages\",\n  \"main\": \"app.js\",\n  \"scripts\": {},\n  \"author\": \"Octave Online LLC\",\n  \"license\": \"AGPL-3.0\",\n  \"private\": true,\n  \"engines\": {\n    \"node\": \"18.x\"\n  },\n  \"dependencies\": {\n    \"@oo/shared\": \"file:../shared\",\n    \"@sematext/gc-stats\": \"^1.5.8\",\n    \"async\": \"^1.5.2\",\n    \"base-x\": \"^3.0.8\",\n    \"ps-tree\": \"^1.2.0\",\n    \"temp\": \"^0.8.4\",\n    \"tmp\": \"0.0.28\",\n    \"uuid\": \"^2.0.1\"\n  }\n}\n"
  },
  {
    "path": "back-master/src/capped-file-system.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst async = require(\"async\");\nconst temp = require(\"temp\");\nconst child_process = require(\"child_process\");\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst logger = require(\"@oo/shared\").logger;\nconst OnlineOffline = require(\"@oo/shared\").OnlineOffline;\nconst config = require(\"@oo/shared\").config;\n\n// This file is based on http://souptonuts.sourceforge.net/quota_tutorial.html\n\nconst IMG_FILE_NAME = \"img.ext3\";\nconst IMG_MNT_DIR = \"mnt\";\nconst IMG_DATA_DIR = \"data\";\n\nclass CappedFileSystem extends OnlineOffline {\n\tconstructor(sessCode, size) {\n\t\tsuper();\n\t\tthis.sessCode = sessCode;\n\t\tthis._log = logger(\"capped-file-system:\" + sessCode);\n\t\tthis._mlog = logger(\"capped-file-system:\" + sessCode + \":minor\");\n\n\t\tthis._size = size;\n\t}\n\n\t_doCreate(next) {\n\t\tthis._cleanups = [];\n\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.trace(\"Making temp dir...\");\n\t\t\t\ttemp.mkdir(\"oo-\", (err, tmpdir) => {\n\t\t\t\t\tif (tmpdir) this._tmpdir = tmpdir;\n\t\t\t\t\tif (!err) this._cleanups.unshift((__next) => {\n\t\t\t\t\t\tthis._mlog.trace(\"Removing temp dir...\");\n\t\t\t\t\t\tfs.rmdir(tmpdir, __next);\n\t\t\t\t\t});\n\t\t\t\t\t_next(err);\n\t\t\t\t});\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.debug(\"Created temp dir:\", this._tmpdir);\n\t\t\t\tthis._mlog.trace(\"Allocating space for filesystem...\");\n\t\t\t\tconst imgFileName = path.join(this._tmpdir, IMG_FILE_NAME);\n\t\t\t\t// eslint-disable-next-line no-unused-vars\n\t\t\t\tchild_process.execFile(\"dd\", [\"if=/dev/zero\", `of=${imgFileName}`, \"bs=1k\", `count=${this._size}`], (err, stdout, stderr) => {\n\t\t\t\t\tif (!err) this._cleanups.unshift((__next) => {\n\t\t\t\t\t\tthis._mlog.trace(\"Removing file system...\");\n\t\t\t\t\t\tfs.unlink(imgFileName, __next);\n\t\t\t\t\t});\n\t\t\t\t\t_next(err);\n\t\t\t\t});\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.trace(\"Formatting file system...\");\n\t\t\t\tconst imgFileName = path.join(this._tmpdir, IMG_FILE_NAME);\n\t\t\t\tchild_process.execFile(\"mkfs\", [\"-t\", \"ext3\", \"-q\", imgFileName, \"-F\"], (err, stdout, stderr) => {\n\t\t\t\t\tif (stderr) err = new Error(stderr);\n\t\t\t\t\t_next(err);\n\t\t\t\t});\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.trace(\"Creating mount directory...\");\n\t\t\t\tconst imgMntDir = path.join(this._tmpdir, IMG_MNT_DIR);\n\t\t\t\tfs.mkdir(imgMntDir, 0o700, (err) => {\n\t\t\t\t\tif (!err) this._cleanups.unshift((__next) => {\n\t\t\t\t\t\tthis._mlog.trace(\"Removing mount directory...\");\n\t\t\t\t\t\tfs.rmdir(imgMntDir, __next);\n\t\t\t\t\t});\n\t\t\t\t\t_next(err);\n\t\t\t\t});\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.trace(\"Mounting file system...\");\n\t\t\t\tconst imgFileName = path.join(this._tmpdir, IMG_FILE_NAME);\n\t\t\t\tconst imgMntDir = path.join(this._tmpdir, IMG_MNT_DIR);\n\t\t\t\tchild_process.execFile(\"sudo\", [\"mount\", \"-o\", \"loop,rw\", imgFileName, imgMntDir], (err, stdout, stderr) => {\n\t\t\t\t\tif (stderr) err = new Error(stderr);\n\t\t\t\t\tif (!err) this._cleanups.unshift((__next) => {\n\t\t\t\t\t\tthis._mlog.trace(\"Unmounting file system...\");\n\t\t\t\t\t\tchild_process.execFile(\"sudo\", [\"umount\", imgMntDir], __next);\n\t\t\t\t\t});\n\t\t\t\t\t_next(err);\n\t\t\t\t});\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.trace(\"Claiming ownership of file system root...\");\n\t\t\t\tconst imgMntDir = path.join(this._tmpdir, IMG_MNT_DIR);\n\t\t\t\tchild_process.execFile(\"sudo\", [\"chown\", config.worker.uid+\":\"+config.worker.uid, imgMntDir], (err, stdout, stderr) => {\n\t\t\t\t\tif (stderr) err = new Error(stderr);\n\t\t\t\t\t_next(err);\n\t\t\t\t});\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.trace(\"Creating data directory...\");\n\t\t\t\tconst imgDataDir = path.join(this._tmpdir, IMG_MNT_DIR, IMG_DATA_DIR);\n\t\t\t\tfs.mkdir(imgDataDir, 0o700, (err) => {\n\t\t\t\t\t// Cleanup function not necessary here because the directory resides in the guest filesystem\n\t\t\t\t\t_next(err);\n\t\t\t\t});\n\t\t\t}\n\t\t], (err) => {\n\t\t\tconst imgDataDir = path.join(this._tmpdir, IMG_MNT_DIR, IMG_DATA_DIR);\n\t\t\tthis.dir = imgDataDir;\n\t\t\treturn next(err, imgDataDir);\n\t\t});\n\t}\n\n\t_doDestroy(next) {\n\t\tasync.series(this._cleanups, (err) => {\n\t\t\tif (err) return next(err);\n\t\t\tthis._enabled = false;\n\t\t\tthis._cleanups = null;\n\t\t\tthis._tmpdir = null;\n\t\t\tthis.dir = null;\n\t\t\treturn next(null);\n\t\t});\n\t}\n}\n\n/// An alternative to CappedFileSystem when sudo is unavailable.\n/// This class does *not* limit file sizes.\nclass TmpWorkDirectory extends OnlineOffline {\n\tconstructor(sessCode) {\n\t\tsuper();\n\t\tthis.sessCode = sessCode;\n\t\tthis._mlog = logger(\"tmp-work-directory:\" + sessCode + \":minor\");\n\t}\n\n\t_doCreate(next) {\n\t\tthis._cleanups = [];\n\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\tthis._mlog.trace(\"Making directory...\");\n\t\t\t\ttemp.mkdir(\"oo-\", (err, tmpdir) => {\n\t\t\t\t\tif (tmpdir) this.dir = tmpdir;\n\t\t\t\t\tif (!err) this._cleanups.unshift((__next) => {\n\t\t\t\t\t\tthis._mlog.trace(\"Removing directory...\");\n\t\t\t\t\t\tchild_process.execFile(\"rm\", [\"-rf\", tmpdir], __next);\n\t\t\t\t\t});\n\t\t\t\t\tthis._mlog.debug(\"Created directory:\", this.dir);\n\t\t\t\t\t_next(err);\n\t\t\t\t});\n\t\t\t},\n\t\t], (err) => {\n\t\t\treturn next(err, this.dir);\n\t\t});\n\t}\n\n\t_doDestroy(next) {\n\t\tasync.series(this._cleanups, (err) => {\n\t\t\tif (err) return next(err);\n\t\t\tthis._enabled = false;\n\t\t\tthis._cleanups = null;\n\t\t\tthis.dir = null;\n\t\t\treturn next(null);\n\t\t});\n\t}\n}\n\nmodule.exports = {\n\tCappedFileSystem,\n\tTmpWorkDirectory\n};\n"
  },
  {
    "path": "back-master/src/docker-handler.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst async = require(\"async\");\nconst child_process = require(\"child_process\");\nconst logger = require(\"@oo/shared\").logger;\nconst StdioMessenger = require(\"@oo/shared\").StdioMessenger;\n\nclass DockerHandler extends StdioMessenger {\n\tconstructor(sessCode, dockerImage) {\n\t\tsuper();\n\t\tthis._log = logger(`docker-handler:${dockerImage}:${sessCode}`);\n\t\tthis.sessCode = sessCode;\n\t}\n\n\t_doCreate(next, dockerArgs) {\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\t// Create the session\n\t\t\t\tthis._spwn = child_process.spawn(\"docker\", dockerArgs);\n\t\t\t\tthis._log.trace(\"Docker args:\", dockerArgs.join(\" \"));\n\t\t\t\tthis._log.debug(\"Launched process with ID:\", this._spwn.pid);\n\n\t\t\t\t// Create stderr listener\n\t\t\t\tthis._spwn.stderr.on(\"data\", this._handleLog.bind(this));\n\n\t\t\t\t// Create exit listener\n\t\t\t\tthis._spwn.on(\"exit\", this._handleExit.bind(this));\n\n\t\t\t\t// Listen to main read stream\n\t\t\t\tthis.setReadStream(this._spwn.stdout);\n\n\t\t\t\t// Wait until we get an acknowledgement before continuing.  Two conditions: receipt of the acknowledgement message, and premature exit.\n\t\t\t\tvar ack = false;\n\t\t\t\tthis.once(\"message\", (name /*,  content */) => {\n\t\t\t\t\tif (ack) return;\n\t\t\t\t\tack = true;\n\n\t\t\t\t\t// Error if the message is docker-exit\n\t\t\t\t\tif (name === \"docker-exit\") return _next(new Error(\"Process exited prematurely\"));\n\n\t\t\t\t\t// Don't enable the write stream until down here because we don't want to write messages to the child's STDIN until we've acknowledged that it is online\n\t\t\t\t\tthis.setWriteStream(this._spwn.stdin);\n\t\t\t\t\t_next(null);\n\t\t\t\t});\n\t\t\t}\n\t\t], (err) => {\n\t\t\tif (err) return next(err);\n\t\t\tthis._log.debug(\"Finished creating\");\n\t\t\treturn next(null);\n\t\t});\n\t}\n\n\t_doDestroy(next) {\n\t\t// Since the child process is actually the docker client and not the daemon, the SIGKILL will never get forwarded to the actual octave host process.  We need to delegate the task to docker.\n\t\tchild_process.execFile(\"docker\", [\"stop\", \"-t\", 0, this._dockerName], (err, stdout, stderr) => {\n\t\t// child_process.execFile(\"docker\", [\"rm\", \"-f\", this._dockerName], (err, stdout, stderr) => {\n\t\t\tif (err) this._log.warn(err, stderr);\n\t\t\tthis._log.debug(\"Finished destroying\");\n\t\t\treturn next(null);\n\t\t});\n\t}\n\n\tsignal(name) {\n\t\tif (this._state !== \"ONLINE\") return this._log.warn(\"Will not send SIGINT to child process: process not online\");\n\n\t\t// Although the child process is actually the docker client and not the daemon, the client will forward simple signals like SIGINT to the actual octave host process.\n\t\tthis._spwn.kill(name);\n\t\tthis._log.debug(\"Sent \" + name + \" to child process\");\n\t}\n\n\t_handleLog(data) {\n\t\t// Log message to console\n\t\tdata.toString().trim().split(\"\\n\").forEach((line) => {\n\t\t\tthis._log.log(line);\n\t\t});\n\n\t\t// Special handling of certain messages\n\t\t// TODO: Make this message get sent from host.c instead of from here\n\t\tif (/Process exited with status 0, signal 9/.test(data)) {\n\t\t\tthis.emit(\"message\", \"octave-killed\");\n\t\t}\n\t}\n\n\t_handleExit(code, signal) {\n\t\tthis._log.debug(\"Docker Exit:\", code, signal);\n\t\tthis.emit(\"message\", \"docker-exit\", { code, signal });\n\t}\n}\n\nmodule.exports = DockerHandler;\n"
  },
  {
    "path": "back-master/src/main-flavor.js",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// This file contains the main loop for flavor (dedicated) servers.\n\nconst log = require(\"@oo/shared\").logger(\"main-flavor\");\nconst mlog = require(\"@oo/shared\").logger(\"main-flavor:minor\");\nconst config = require(\"@oo/shared\").config;\nconst config2 = require(\"@oo/shared\").config2;\nconst RedisMessenger = require(\"@oo/shared\").RedisMessenger;\nconst MaintenanceRequestFlavorManager = require(\"./maintenance-request-manager\");\nconst runMaintenance = require(\"./maintenance\");\nconst async = require(\"async\");\nconst rackOperations = require(\"@oo/shared/rack/operations\");\n\nvar redisFlavorStatusHandler,\n\tflavorStatusManager;\n\nvar ACCEPT_CONS = true;\nvar statusTimer;\n\nfunction start(globals, next) {\n\tasync.parallel([\n\t\t(_next) => {\n\t\t\tstartConnectionAcceptLoop(globals, _next);\n\t\t},\n\t\t(_next) => {\n\t\t\tstartLifetimeLoop(globals, _next);\n\t\t}\n\t], next);\n}\n\nfunction startConnectionAcceptLoop(globals, next) {\n\tlet { sessionManager, redisScriptHandler, personality } = globals;\n\tasync.during(\n\t\t(_next) => {\n\t\t\tif (!ACCEPT_CONS) return _next(null, false);\n\t\t\tasync.waterfall([\n\t\t\t\t(__next) => {\n\t\t\t\t\tif (sessionManager.canAcceptNewSessions()) {\n\t\t\t\t\t\tredisScriptHandler.getSessCodeFlavor(personality.flavor, (err, sessCode, content) => {\n\t\t\t\t\t\t\tif (err) log.error(\"Error getting sessCode:\", err);\n\t\t\t\t\t\t\t__next(null, sessCode, content);\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\t\t__next(null, null, null);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t(sessCode, content, __next) => {\n\t\t\t\t\tif (sessCode) {\n\t\t\t\t\t\tasync.waterfall([\n\t\t\t\t\t\t\t(___next) => {\n\t\t\t\t\t\t\t\tlog.info(\"Received Session:\", sessCode);\n\t\t\t\t\t\t\t\tcontent.tier = \"_maxima\";\n\t\t\t\t\t\t\t\tsessionManager.attach(sessCode, content);\n\t\t\t\t\t\t\t\tsessionManager.disablePool();\n\t\t\t\t\t\t\t\tflavorStatusManager.stop();\n\t\t\t\t\t\t\t\tflavorStatusManager.ignoreAll();\n\t\t\t\t\t\t\t\tclearTimeout(statusTimer);\n\t\t\t\t\t\t\t\tsessionManager.once(\"destroy-done\", (_sessCode) => {\n\t\t\t\t\t\t\t\t\t___next(null, _sessCode);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t(_sessCode, ___next) => {\n\t\t\t\t\t\t\t\tif (sessionManager.numActiveSessions() !== 0) {\n\t\t\t\t\t\t\t\t\tlog.error(\"Active sessions when session was closed\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (sessCode !== _sessCode) {\n\t\t\t\t\t\t\t\t\tlog.error(\"sessCode changed:\", sessCode, _sessCode);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsessionManager.terminate(\"Server Maintenance\");\n\t\t\t\t\t\t\t\tsetTimeout(___next, config.maintenance.pauseDuration);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t(___next) => {\n\t\t\t\t\t\t\t\trunMaintenance(___next);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t], (err) => {\n\t\t\t\t\t\t\t__next(err, false);\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\t__next(null, true);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t], _next);\n\t\t},\n\t\t(_next) => {\n\t\t\tlet delay = Math.floor(config.worker.clockInterval.min + Math.random()*(config.worker.clockInterval.max-config.worker.clockInterval.min));\n\t\t\tsetTimeout(_next, delay);\n\t\t},\n\t\t(err) => {\n\t\t\tmlog.info(\"Connection-accepting loop ended\");\n\t\t\treturn next(err);\n\t\t}\n\t);\n}\n\nfunction startLifetimeLoop(globals, next) {\n\tlet { sessionManager, redisMessenger, personality } = globals;\n\tconst flavorConfig = config2.flavor(personality.flavor);\n\tflavorStatusManager = new MaintenanceRequestFlavorManager(flavorConfig.defaultClusterSize);\n\tredisFlavorStatusHandler = new RedisMessenger().subscribeToFlavorStatus(personality.flavor);\n\tredisFlavorStatusHandler.on(\"flavor-status\", (...args) => {\n\t\tflavorStatusManager.onMessage(...args);\n\t});\n\tflavorStatusManager.on(\"request-maintenance\", (...args) => {\n\t\tredisMessenger.requestFlavorStatus(personality.flavor, ...args);\n\t});\n\tflavorStatusManager.on(\"reply-to-maintenance-request\", (...args) => {\n\t\tredisMessenger.replyToFlavorStatus(personality.flavor, ...args);\n\t});\n\n\t// In a flavor cluster, when we get approval from peers, we remove ourself from the cluster permanently in order to save on idle time costs. New servers will be added to the cluster when needed (on demand).\n\tasync.forever(\n\t\t(_next) => {\n\t\t\tasync.series([\n\t\t\t\t(__next) => {\n\t\t\t\t\tstatusTimer = setTimeout(__next, flavorConfig.idleTime);\n\t\t\t\t},\n\t\t\t\t(__next) => {\n\t\t\t\t\tflavorStatusManager.beginRequesting(flavorConfig.statusInterval);\n\t\t\t\t\tflavorStatusManager.once(\"maintenance-accepted\", __next);\n\t\t\t\t},\n\t\t\t\t(__next) => {\n\t\t\t\t\tsessionManager.disablePool();\n\t\t\t\t\tif (sessionManager.numActiveSessions() !== 0) {\n\t\t\t\t\t\tlog.warn(\"Active sessions when flavor maintenance was approved\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tsessionManager.terminate(\"Server Maintenance\");\n\t\t\t\t\tstatusTimer = setTimeout(__next, config.maintenance.pauseDuration);\n\t\t\t\t},\n\t\t\t\t(__next) => {\n\t\t\t\t\t// TODO: Move this call somewhere it could be configurable.\n\t\t\t\t\trackOperations.deleteSelf(personality, __next);\n\t\t\t\t}\n\t\t\t], (err) => {\n\t\t\t\t_next(err);\n\t\t\t});\n\t\t},\n\t\t(err) => {\n\t\t\tlog.info(\"Lifetime loop ended\");\n\t\t\treturn next(err);\n\t\t}\n\t);\n}\n\nfunction doExit() {\n\tclearTimeout(statusTimer);\n\tflavorStatusManager.stop();\n\tACCEPT_CONS = false;\n\n\tsetTimeout(() => {\n\t\tredisFlavorStatusHandler.close();\n\t}, 5000);\n}\n\nmodule.exports = {\n\tstart,\n\tdoExit\n};\n"
  },
  {
    "path": "back-master/src/main-pool.js",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// This file contains the main loop for normal pool servers.\n\nconst log = require(\"@oo/shared\").logger(\"main-pool\");\nconst config = require(\"@oo/shared\").config;\nconst RedisMessenger = require(\"@oo/shared\").RedisMessenger;\nconst MaintenanceRequestFlavorManager = require(\"./maintenance-request-manager\");\nconst runMaintenance = require(\"./maintenance\");\nconst async = require(\"async\");\n\nconst redisMaintenanceHandler = new RedisMessenger().subscribeToRebootRequests();\nconst maintenanceRequestManager = new MaintenanceRequestFlavorManager();\n\nvar ACCEPT_CONS = true;\nvar maintenanceTimer;\n\n// Main loops\nfunction start(globals, next) {\n\tasync.parallel([\n\t\t(_next) => {\n\t\t\tstartConnectionAcceptLoop(globals, _next);\n\t\t},\n\t\t(_next) => {\n\t\t\tstartMaintenanceLoop(globals, _next);\n\t\t}\n\t], next);\n}\n\nfunction startConnectionAcceptLoop(globals, next) {\n\tlet { sessionManager, redisScriptHandler } = globals;\n\tasync.forever(\n\t\t(_next) => {\n\t\t\tif (!ACCEPT_CONS) return;\n\t\t\tasync.waterfall([\n\t\t\t\t(__next) => {\n\t\t\t\t\tlet fraction;\n\t\t\t\t\tif (config.worker.clockStrategy === \"random\") {\n\t\t\t\t\t\tfraction = Math.random();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfraction = sessionManager.usagePercent();\n\t\t\t\t\t}\n\t\t\t\t\tlet delay = Math.floor(config.worker.clockInterval.min + fraction * (config.worker.clockInterval.max-config.worker.clockInterval.min));\n\t\t\t\t\tsetTimeout(__next, delay);\n\t\t\t\t},\n\t\t\t\t(__next) => {\n\t\t\t\t\tif (sessionManager.canAcceptNewSessions()) {\n\t\t\t\t\t\tredisScriptHandler.getSessCode((err, sessCode, content) => {\n\t\t\t\t\t\t\tif (err) log.error(\"Error getting sessCode:\", err);\n\t\t\t\t\t\t\t__next(null, sessCode, content);\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\t\t__next(null, null, null);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t(sessCode, content, __next) => {\n\t\t\t\t\tif (sessCode) {\n\t\t\t\t\t\tlog.info(\"Received Session:\", sessCode);\n\t\t\t\t\t\tsessionManager.attach(sessCode, content);\n\t\t\t\t\t}\n\n\t\t\t\t\t__next(null);\n\t\t\t\t}\n\t\t\t], _next);\n\t\t},\n\t\t(err) => {\n\t\t\tlog.error(\"Connection-accepting loop ended\");\n\t\t\treturn next(err);\n\t\t}\n\t);\n}\n\nfunction startMaintenanceLoop(globals, next) {\n\tlet { sessionManager, redisMessenger } = globals;\n\tredisMaintenanceHandler.on(\"reboot-request\", maintenanceRequestManager.onMessage.bind(maintenanceRequestManager));\n\tmaintenanceRequestManager.on(\"request-maintenance\", redisMessenger.requestReboot.bind(redisMessenger));\n\tmaintenanceRequestManager.on(\"reply-to-maintenance-request\", redisMessenger.replyToRebootRequest.bind(redisMessenger));\n\tasync.forever(\n\t\t(_next) => {\n\t\t\tasync.series([\n\t\t\t\t(__next) => {\n\t\t\t\t\tmaintenanceTimer = setTimeout(__next, config.maintenance.interval);\n\t\t\t\t},\n\t\t\t\t(__next) => {\n\t\t\t\t\tmaintenanceRequestManager.beginRequesting(config.maintenance.requestInterval);\n\t\t\t\t\tmaintenanceRequestManager.once(\"maintenance-accepted\", __next);\n\t\t\t\t},\n\t\t\t\t(__next) => {\n\t\t\t\t\tsessionManager.disablePool();\n\t\t\t\t\tasync.whilst(\n\t\t\t\t\t\t() => { return sessionManager.numActiveSessions() > 0; },\n\t\t\t\t\t\t(__next) => { maintenanceTimer = setTimeout(__next, config.maintenance.pauseDuration); },\n\t\t\t\t\t\t__next\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\t(__next) => {\n\t\t\t\t\tsessionManager.terminate();\n\t\t\t\t\tmaintenanceTimer = setTimeout(__next, config.maintenance.pauseDuration);\n\t\t\t\t},\n\t\t\t\t(__next) => {\n\t\t\t\t\trunMaintenance(__next);\n\t\t\t\t},\n\t\t\t\t(__next) => {\n\t\t\t\t\tsessionManager.restart();\n\t\t\t\t\tmaintenanceTimer = setTimeout(__next, config.maintenance.pauseDuration);\n\t\t\t\t},\n\t\t\t\t(__next) => {\n\t\t\t\t\tmaintenanceRequestManager.reset();\n\t\t\t\t\t__next();\n\t\t\t\t}\n\t\t\t], (err) => {\n\t\t\t\t_next(err);\n\t\t\t});\n\t\t},\n\t\t(err) => {\n\t\t\tlog.error(\"Maintenance loop ended\");\n\t\t\treturn next(err);\n\t\t}\n\t);\n}\n\nfunction doExit() {\n\tclearTimeout(maintenanceTimer);\n\tmaintenanceRequestManager.stop();\n\tACCEPT_CONS = false;\n\n\tsetTimeout(() => {\n\t\tredisMaintenanceHandler.close();\n\t}, 5000);\n}\n\nmodule.exports = {\n\tstart,\n\tdoExit\n};\n"
  },
  {
    "path": "back-master/src/maintenance-request-manager.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst uuid = require(\"uuid\");\nconst EventEmitter = require(\"events\");\nconst log = require(\"@oo/shared\").logger(\"maint-req-manager\");\nconst config = require(\"@oo/shared\").config;\n\nclass MaintenanceRequestFlavorManager extends EventEmitter {\n\tconstructor(clusterSize) {\n\t\tsuper();\n\t\tif (clusterSize) {\n\t\t\tthis._forFlavor = true;\n\t\t\tthis._clusterSize = clusterSize;\n\t\t\tthis._priority = Math.random();\n\t\t\tlog.debug(\"Flavor Priority:\", this._priority);\n\t\t}\n\t\tthis.reset();\n\t}\n\n\treset() {\n\t\tthis._responses = {};\n\t\tthis._ignoreAll = false;\n\t\tif (!this._forFlavor) {\n\t\t\tthis._priority = 0;\n\t\t}\n\t}\n\n\tignoreAll() {\n\t\tthis._ignoreAll = true;\n\t}\n\n\tbeginRequesting(intervalTime) {\n\t\tlog.info(\"Beginning maintenance requests\");\n\t\tthis.reset();\n\t\tthis._requestInterval = setInterval(this._requestMaintenance.bind(this), intervalTime);\n\t}\n\n\tonMessage(id, isRequest, message) {\n\t\t// Has the server has removed itself from the actively responding cluster?\n\t\tif (this._ignoreAll) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet isOwnRequest = Object.keys(this._responses).indexOf(id) !== -1;\n\n\t\tif (isRequest && !isOwnRequest) {\n\t\t\t// Reply to the maintenance request.  Reply \"yes\" only if the requester's priority is higher than our own priority.\n\t\t\tlet response = (message.priority > this._priority);\n\t\t\tthis.emit(\"reply-to-maintenance-request\", id, response);\n\t\t} else if (!isRequest && isOwnRequest) {\n\t\t\t// Someone replied to our own maintenance request.\n\t\t\tthis._responses[id].push(message.response);\n\t\t}\n\t}\n\n\tstop() {\n\t\tif (this._requestInterval) {\n\t\t\tclearInterval(this._requestInterval);\n\t\t}\n\t\tthis._requestInterval = null;\n\t}\n\n\t_requestMaintenance() {\n\t\tlet id = uuid.v4();\n\t\tif (!this._forFlavor) {\n\t\t\t// In flavor status mode, keep a fixed priority at all times. This creates stability and uniqueness that is easy to reason about.\n\t\t\t// In maintenance request mode, make the priority grow as time goes on. This means older servers get first priority to maintenance.\n\t\t\tthis._priority += 1;\n\t\t}\n\t\tthis._responses[id] = [];\n\t\tthis.emit(\"request-maintenance\", id, this._priority);\n\t\tlog.trace(\"Sent maintenance request:\", id, this._priority);\n\n\t\tsetTimeout(() => {\n\t\t\t// Count the number of yeses and nos.\n\t\t\tlet numYes = this._responses[id].reduce((s,v) => {\n\t\t\t\treturn s + (v ? 1 : 0);\n\t\t\t}, 0);\n\t\t\tlet numNo = this._responses[id].length - numYes;\n\n\t\t\tlet success;\n\t\t\tif (this._forFlavor) {\n\t\t\t\t// Policy: Guarantee at least _clusterSize online nodes\n\t\t\t\tsuccess = numYes >= this._clusterSize;\n\t\t\t} else {\n\t\t\t\t// Policy: Guarantee at least minNodesInCluster online nodes and no more than maxNodesInMaintenance maintenance nodes.\n\t\t\t\tsuccess = numNo < config.maintenance.maxNodesInMaintenance && numYes >= config.maintenance.minNodesInCluster;\n\t\t\t}\n\n\t\t\t// Were we successful?\n\t\t\tif (success) {\n\t\t\t\tlog.info(\"Maintenance request was approved\");\n\t\t\t\tthis.emit(\"maintenance-accepted\");\n\t\t\t\tthis._priority = Number.MAX_VALUE;\n\t\t\t\tclearInterval(this._requestInterval);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlog.trace(\"Maintenance request failed; trying again:\", id);\n\t\t\t}\n\n\t\t\t// Dereference responses array\n\t\t\tdelete this._responses[id];\n\t\t}, config.maintenance.responseWaitTime);\n\t}\n}\n\nmodule.exports = MaintenanceRequestFlavorManager;\n"
  },
  {
    "path": "back-master/src/maintenance.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst child_process = require(\"child_process\");\nconst log = require(\"@oo/shared\").logger(\"maintenance\");\nconst config = require(\"@oo/shared\").config;\n\nfunction runMaintenance(next) {\n\tlog.info(\"Starting Maintenance Routine\");\n\n\tswitch (config.session.implementation) {\n\t\tcase \"docker\": {\n\t\t\tlet MAINTENANCE_COMMAND = \"docker ps -a --filter \\\"status=exited\\\" --filter \\\"ancestor=oo/\" + config.docker.images.octaveSuffix + \"\\\" | cut -c -12 | xargs -n 1 docker rm -f; docker ps -a --filter \\\"status=exited\\\" --filter \\\"ancestor=oo/\" + config.docker.images.filesystemSuffix + \"\\\" | cut -c -12 | xargs -n 1 docker rm -f\";\n\t\t\tlog.trace(\"Running command:\", MAINTENANCE_COMMAND);\n\t\t\tchild_process.exec(MAINTENANCE_COMMAND, (err, stdout, stderr) => {\n\t\t\t\tlog.info(\"Finished Maintenance Routine\");\n\t\t\t\tif (err) log.warn(err);\n\t\t\t\tlog.trace(stdout);\n\t\t\t\tlog.trace(stderr);\n\t\t\t\tnext();\n\t\t\t});\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"selinux\":\n\t\tcase \"unsafe\":\n\t\t\t// Exit this process and let the daemon running it clean up and restart\n\t\t\tprocess.exit(0);\n\t\t\tbreak;\n\n\t\tdefault: {\n\t\t\tlog.error(\"Please provide a maintenance command for your implementation\");\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nmodule.exports = runMaintenance;\n"
  },
  {
    "path": "back-master/src/message-translator.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst EventEmitter = require(\"events\");\nconst log = require(\"@oo/shared\").logger(\"message-translator\");\nconst crypto = require(\"crypto\");\nconst uuid = require(\"uuid\");\nconst config = require(\"@oo/shared\").config;\n\n// This class \"translates\" messages between the older format expected by the downstream front end and the newer format expected by the upstream back ends (Octave host and file manager).\n\nclass MessageTranslator extends EventEmitter {\n\n\t// When the upstream back ends send us a message:\n\tfromUpstream(sessCode, name, content) {\n\t\tswitch(name) {\n\t\t\t// MESSAGES NEEDING TRANSLATION:\n\n\t\t\t// \"request-input\" is the newer version of \"prompt\", which provided a line number as an integer.  The line number needs to be extracted via regex for backwards compatibility.\n\t\t\tcase \"request-input\": {\n\t\t\t\tlet match = content.match(/^octave:(\\d+)>\\s+$/);\n\t\t\t\tlet line_number = -1;\n\t\t\t\tif (match) {\n\t\t\t\t\tline_number = parseInt(match[1]);\n\t\t\t\t} else {\n\t\t\t\t\tthis._forDownstream(sessCode, \"data\", {\n\t\t\t\t\t\ttype: \"stdout\",\n\t\t\t\t\t\tdata: content\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tthis._forDownstream(sessCode, \"prompt\", { line_number, prompt: content });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// \"out\" and \"err\" need to be translated to \"data\" events\n\t\t\tcase \"out\": {\n\t\t\t\tthis._forDownstream(sessCode, \"data\", {\n\t\t\t\t\ttype: \"stdout\",\n\t\t\t\t\tdata: content\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"err\": {\n\t\t\t\t// Send the error text doenstream\n\t\t\t\tthis._forDownstream(sessCode, \"data\", {\n\t\t\t\t\ttype: \"stderr\",\n\t\t\t\t\tdata: content\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// We need only the \"ws\" part of the \"set-workspace\" message\n\t\t\tcase \"set-workspace\": {\n\t\t\t\tthis._forDownstream(sessCode, \"workspace\", {\n\t\t\t\t\tvars: content.ws\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// The new \"show-static-plot\" needs to be broken into \"plotd\" (plot data) and \"plote\" (plot finished)\n\t\t\tcase \"show-static-plot\": {\n\t\t\t\tlet id = uuid.v4();\n\t\t\t\tthis._forDownstream(sessCode, \"plotd\", {\n\t\t\t\t\tid: id,\n\t\t\t\t\tcontent: content.content\n\t\t\t\t});\n\t\t\t\tthis._forDownstream(sessCode, \"plote\", {\n\t\t\t\t\tid: id,\n\t\t\t\t\tmd5: crypto.createHash(\"md5\").update(content.content).digest(\"hex\"),\n\t\t\t\t\tcommand_number: content.command_number\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// \"clc\" control command:\n\t\t\tcase \"clear-screen\": {\n\t\t\t\tthis._forDownstream(sessCode, \"ctrl\", {\n\t\t\t\t\tcommand: \"clc\"\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// \"doc\" control command:\n\t\t\tcase \"show-doc\": {\n\t\t\t\tvar url = content ? `https://octave.sourceforge.io/list_functions.php?q=${encodeURIComponent(content)}` : \"https://octave.sourceforge.io/octave/overview.html\";\n\t\t\t\tthis._forDownstream(sessCode, \"ctrl\", {\n\t\t\t\t\tcommand: `url=${url}`\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// When come other command was suppressed due to length:\n\t\t\tcase \"message-too-long\": {\n\t\t\t\tif (content.name === \"show-static-plot\") {\n\t\t\t\t\tlog.trace(\"Plot message too long:\", content);\n\t\t\t\t\tthis._forDownstream(sessCode, \"data\", {\n\t\t\t\t\t\ttype: \"stderr\",\n\t\t\t\t\t\tdata: `Warning: Suppressed a large plot (${content.length} bytes).\\nMaximum allowable length is ${content.max_length} bytes.\\nTip: Try running \"clf\".  If that does not work,\\ntry generating a rasterized plot (e.g., imagesc)\\ninstead of a vector plot.\\n`\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tlog.warn(\"Unknown message too long:\", content);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// The \"exit\" event from octave_link:\n\t\t\tcase \"exit\": {\n\t\t\t\tthis._forDownstream(sessCode, \"data\", {\n\t\t\t\t\ttype: \"exit\",\n\t\t\t\t\tcode: content\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// The \"exit\" event from the child process:\n\t\t\tcase \"docker-exit\":\n\t\t\tcase \"process-exit\": {\n\t\t\t\tthis.emit(\"destroy\", sessCode, \"Shell Exited\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// The event for when the Octave process is killed:\n\t\t\tcase \"octave-killed\": {\n\t\t\t\tthis._forDownstream(sessCode, \"data\", {\n\t\t\t\t\ttype: \"stderr\",\n\t\t\t\t\tdata: \"Error: Octave process killed.\\nYou may have been using too much memory.\\nYour memory cap is: \" + config.docker.memoryShares + \"\\n\"\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Turn \"destroy\" into \"destroy\" on this instance\n\t\t\tcase \"destroy\": {\n\t\t\t\tthis.emit(\"destroy\", sessCode, content);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Filesystem events: if any of them fail, do not let the events bubble up, but show their error messages on stderr\n\t\t\tcase \"saved\":\n\t\t\tcase \"renamed\":\n\t\t\tcase \"deleted\": {\n\t\t\t\t// FIXME: The rendering of error messages should occur on the client side, not here.\n\t\t\t\tif (content && !content.success) {\n\t\t\t\t\tthis._forDownstream(sessCode, \"data\", {\n\t\t\t\t\t\ttype: \"stderr\",\n\t\t\t\t\t\tdata: content.message+\"\\n\"\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t} else {\n\t\t\t\t\tthis._forDownstream(sessCode, name, content);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// File list event (change name from \"filelist\" to \"user\")\n\t\t\tcase \"filelist\": {\n\t\t\t\tthis._forDownstream(sessCode, \"user\", content);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// MESSAGES THAT CAN BE IGNORED:\n\t\t\tcase \"ack\":\n\t\t\tcase \"set-history\": {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// REMAINING MESSAGES:\n\t\t\tdefault: {\n\t\t\t\tthis._forDownstream(sessCode, name, content);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// When the downstream client sends us a message:\n\tfromDownstream(sessCode, name, getData) {\n\t\tswitch(name) {\n\t\t\t// MESSAGES NEEDING TRANSLATION:\n\n\t\t\t// \"data\" needs to be translated to the \"cmd\" event (which is a synonym for \"request-input-answer\")\n\t\t\tcase \"data\":\n\t\t\t\tthis._forUpstream(sessCode, \"cmd\", getData);\n\t\t\t\tbreak;\n\n\t\t\t// Translate \"signal\" to \"interrupt\"\n\t\t\tcase \"signal\":\n\t\t\t\tthis._forUpstream(sessCode, \"interrupt\", getData);\n\t\t\t\tbreak;\n\n\t\t\t// Emit ping/pong as an event\n\t\t\tcase \"oo.ping\":\n\t\t\t\tthis.emit(\"ping\", sessCode, getData);\n\t\t\t\tbreak;\n\t\t\tcase \"oo.pong\":\n\t\t\t\tthis.emit(\"pong\", sessCode, getData);\n\t\t\t\tbreak;\n\n\t\t\t// MESSAGES THAT CAN BE IGNORED:\n\t\t\tcase \"init\":\n\t\t\tcase \"ot.cursor\":\n\t\t\tcase \"ot.change\":\n\t\t\tcase \"oo.reconnect\":\n\t\t\t\tbreak;\n\n\t\t\t// REMAINING MESSAGES:\n\t\t\tdefault:\n\t\t\t\tthis._forUpstream(sessCode, name, getData);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t_forDownstream(sessCode, name, content) {\n\t\tthis.emit(\"for-downstream\", sessCode, name, content);\n\t}\n\n\t_forUpstream(sessCode, name, getData) {\n\t\tthis.emit(\"for-upstream\", sessCode, name, getData);\n\t}\n}\n\nmodule.exports = MessageTranslator;\n"
  },
  {
    "path": "back-master/src/octave-session.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\n// The code in this file is shared among all implementations of the Octave session.  See session-impl.js for examples of implementations.\n\nconst logger = require(\"@oo/shared\").logger;\nconst OnlineOffline = require(\"@oo/shared\").OnlineOffline;\nconst config = require(\"@oo/shared\").config;\nconst config2 = require(\"@oo/shared\").config2;\nconst timeLimit = require(\"@oo/shared\").timeLimit;\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst async = require(\"async\");\nconst url = require(\"url\");\nconst http = require(\"http\");\nconst https = require(\"https\");\nconst querystring = require(\"querystring\");\nconst uuid = require(\"uuid\");\nconst RedisQueue = require(\"@oo/shared\").RedisQueue;\nconst base58 = require(\"base-x\")(\"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\");\nconst temp = require(\"temp\");\nconst onceMessage = require(\"@oo/shared\").onceMessage;\nconst child_process = require(\"child_process\");\n\nclass OctaveSession extends OnlineOffline {\n\tconstructor(sessCode, options) {\n\t\tsuper();\n\t\tthis.sessCode = sessCode;\n\t\tthis._options = options;\n\t\tthis._log = logger(\"octave-session:\" + sessCode);\n\t\tthis._mlog = logger(\"octave-session:\" + sessCode + \":minor\");\n\n\t\tthis._log.debug(\"Tier:\", this._options.tier);\n\n\t\tthis._extraTime = 0;\n\t\tthis._countdownExtraTime = config2.tier(this._options.tier)[\"session.countdownExtraTime\"];\n\t\tthis._countdownRequestTime = config2.tier(this._options.tier)[\"session.countdownRequestTime\"];\n\n\t\tthis._legalTime = config.session.legalTime.guest;\n\t\tthis._payloadLimit = config.session.payloadLimit.guest;\n\t\tthis._resetPayload();\n\n\t\tthis._plotPngStore = {};\n\t\tthis._plotSvgStore = {};\n\n\t\tthis._suppressedWarningsStore = {};\n\n\t\tthis._redisQueue = new RedisQueue(sessCode);\n\t\tthis._redisQueue.on(\"message\", this.sendMessage.bind(this));\n\n\t\tthis._throttleCounter = 0;\n\t\tthis._throttleTime = process.hrtime();\n\n\t\tthis.on(\"error\", this._handleError.bind(this));\n\t}\n\n\t_doCreate(next) {\n\t\tlet subdir = path.join(config.worker.logDir, config.worker.sessionLogs.subdir);\n\t\tfor (let i=0; i<config.worker.sessionLogs.depth; i++) {\n\t\t\tsubdir = path.join(subdir, this.sessCode[i]);\n\t\t}\n\t\tlet sessionLogPath = path.join(subdir, `${this.sessCode}.log`);\n\t\tthis._mlog.trace(\"Storing session log in:\", sessionLogPath);\n\t\tthis._sessionLogStream = fs.createWriteStream(sessionLogPath);\n\t\tthis._doCreateImpl(next);\n\t}\n\n\t_doDestroy(next, reason) {\n\t\tthis._mlog.trace(\"Starting Destroy Procedure:\", reason);\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\tif (this._countdownTimer) clearTimeout(this._countdownTimer);\n\t\t\t\tif (this._timewarnTimer) clearTimeout(this._timewarnTimer);\n\t\t\t\tif (this._timeoutTimer) clearTimeout(this._timeoutTimer);\n\t\t\t\tif (this._autoCommitTimer) clearInterval(this._autoCommitTimer);\n\t\t\t\tif (this._payloadInterruptTimer) clearTimeout(this._payloadInterruptTimer);\n\t\t\t\t_next(null);\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tthis._doDestroyImpl(_next, reason);\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tif (this._sessionLogStream) this._sessionLogStream.end(reason);\n\t\t\t\t_next(null);\n\t\t\t}\n\t\t], next);\n\t}\n\n\tinterrupt() {\n\t\tthis._signal(\"SIGINT\");\n\t}\n\n\tenqueueMessage(name, getData) {\n\t\tthis._redisQueue.enqueueMessage(name, getData);\n\t}\n\n\t// COUNTDOWN METHODS: For interrupting the Octave kernel after a fixed number of seconds to ensure a fair distribution of CPU time.\n\t// Use an interval to signal Octave once after the first timeout and then repeatedly after that, until the kernel sends us a \"request-input\" event to signal that it is done processing commands.\n\t_startCountdown() {\n\t\tif (this._countdownTimer) return;\n\t\tif (this._state !== \"ONLINE\") return;\n\n\t\tthis._countdownTimer = setTimeout(this._onCountdownEnd.bind(this), this._legalTime);\n\t}\n\t_endCountdown() {\n\t\tif (this._countdownTimer) {\n\t\t\tclearTimeout(this._countdownTimer);\n\t\t\tthis._countdownTimer = null;\n\t\t}\n\t}\n\t_onCountdownEnd() {\n\t\tif (new Date().valueOf() - this._extraTime < this._countdownRequestTime + config.session.countdownRequestTimeBuffer) {\n\t\t\t// Add 15 seconds and don't send an interrupt signal\n\t\t\tthis._log.trace(\"Extending countdown with extra time\");\n\t\t\tthis._countdownTimer = setTimeout(this._onCountdownEnd.bind(this), this._countdownExtraTime);\n\t\t} else {\n\t\t\t// Send an interrupt signal now and again in 5 seconds\n\t\t\tthis._log.trace(\"Interrupting execution due to countdown\");\n\t\t\tthis.interrupt();\n\t\t\tthis.emit(\"message\", \"err\", \"!!! OUT OF TIME !!!\\n\");\n\t\t\tthis._countdownTimer = setTimeout(this._onCountdownEnd.bind(this), 5000);\n\t\t}\n\t}\n\t_addTime() {\n\t\t// This method gets called when the user clicks the \"Add 15 Seconds\" button on the front end.\n\t\tthis._extraTime = new Date().valueOf();\n\t}\n\n\t// TIMEOUT METHODS: For killing the Octave kernel after a fixed number of seconds to clear server resources when the client is inactive.\n\tresetTimeout() {\n\t\tif (this._state !== \"ONLINE\") return;\n\t\tif (this._timewarnTimer) clearTimeout(this._timewarnTimer);\n\t\tif (this._timeoutTimer) clearTimeout(this._timeoutTimer);\n\t\tconst timewarnTime = config2.tier(this._options.tier)[\"session.timewarnTime\"];\n\t\tconst timeoutTime = config2.tier(this._options.tier)[\"session.timeoutTime\"];\n\t\tthis._mlog.trace(\"Resetting timeout:\", timewarnTime, timeoutTime);\n\t\tthis._timewarnTimer = setTimeout(() => {\n\t\t\tthis.emit(\"message\", \"err\", config.session.timewarnMessage+\"\\n\");\n\t\t}, timewarnTime);\n\t\tthis._timeoutTimer = setTimeout(() => {\n\t\t\tthis._log.info(\"Session Timeout\");\n\t\t\tthis.emit(\"message\", \"destroy\", \"Session Timeout\");\n\t\t}, timeoutTime);\n\t}\n\n\t// PAYLOAD METHODS: For interrupting the Octave kernel after a large amount of stdout/stderr data to prevent infinite loops from using too much bandwidth.\n\t_resetPayload() {\n\t\tthis._payloadSize = 0;\n\t\tthis._payloadInterrupted = false;\n\t}\n\t_appendToPayload(content) {\n\t\tthis._payloadSize += content.length;\n\t\tif (this._payloadSize > this._payloadLimit && !this._payloadInterrupted) {\n\t\t\tthis._payloadInterrupted = true;\n\n\t\t\t// End the countdown, and instead give the user a specified amount of time to allow the process to continue from where it left off.\n\t\t\tthis._signal(\"SIGSTOP\");\n\t\t\tthis._endCountdown();\n\t\t\tlet payloadDelay = config.session.payloadAcknowledgeDelay;\n\t\t\tthis._payloadInterruptTimer = setTimeout(() => {\n\t\t\t\tthis._signal(\"SIGCONT\");\n\t\t\t\tthis._signal(\"SIGINT\");\n\n\t\t\t\t// Send the error message after a small delay in order to let the output buffers flush first\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.emit(\"message\", \"err\", \"!!! PAYLOAD TOO LARGE !!!\\n\");\n\n\t\t\t\t\t// Octave sometimes gets confused with the interrupt signal, so send an empty command to reset things.\n\t\t\t\t\tthis._sendMessageToHost(\"cmd\", \"\");\n\t\t\t\t}, config.session.payloadMessageDelay);\n\t\t\t}, payloadDelay);\n\n\t\t\t// Tell the user how much time they have.\n\t\t\tthis.emit(\"message\", \"payload-paused\", {\n\t\t\t\tdelay: payloadDelay\n\t\t\t});\n\t\t}\n\t}\n\t_acknowledgePayload() {\n\t\tif (!this._payloadInterrupted) return this._log.warn(\"Attempting to acknowledge payload, but process is not currently paused\");\n\t\tthis._log.trace(\"User manually acknowledged payload\");\n\t\tthis._continueIfPaused();\n\t\tthis._resetPayload();\n\t}\n\t_continueIfPaused() {\n\t\tif (!this._payloadInterrupted) return;\n\t\tthis._log.trace(\"Continuing execution\");\n\t\tclearTimeout(this._payloadInterruptTimer);\n\t\tthis._signal(\"SIGCONT\");\n\t\tthis._startCountdown();\n\t}\n\n\t// AUTO-COMMIT METHODS: For auto-committing the user's files on a fixed interval.\n\t_startAutoCommitLoop() {\n\t\tthis._autoCommitTimer = setInterval(() => {\n\t\t\tthis._log.debug(\"Requesting auto-commit...\");\n\t\t\tthis._commit(\"Scripted auto-commit\", this._handleError.bind(this));\n\t\t}, config.git.autoCommitInterval);\n\t}\n\n\t_commit(comment, next) {\n\t\t// Set a 60-second time limit\n\t\tlet _next = timeLimit(config.git.commitTimeLimit, [new Error(\"Out of time\")], next);\n\n\t\t// Call the callback when a \"committed\" message is received\n\t\tthis._onceMessageFromFiles(\"committed\", () => { _next(null); });\n\n\t\t// Request the commit\n\t\tthis._sendMessageToFiles(\"commit\", { comment });\n\t}\n\n\t// SESSION LOG: Log all commands, input, and output to a log file\n\t_appendToSessionLog(type, content) {\n\t\tif (!this._sessionLogStream) return this._log.warn(\"Cannot log before created\", { type, content });\n\t\tif (this._sessionLogStream.closed) return this._log.warn(\"Cannot log to a closed stream:\", { type, content });\n\t\tthis._sessionLogStream.write(type + \": \" + content.replace(/\\n/g, \"\\n\" + type + \": \") + \"\\n\");\n\t}\n\n\t// PLOTTED PNG IMAGE METHODS: Convert image links to base-64 data URIs\n\t// TODO: A better way to do this would be to modify GNUPlot to directly save PNG images as base-64 URIs.  I did it this way because I wanted to avoid having to maintain a fork from another major project.\n\t_convertPlotImages(content) {\n\t\t// Search the plot SVG for local PNG files that we need to load\n\t\tlet imageNames = [];\n\t\tlet regex = /xlink:href='(\\w+).png'/g;\n\t\tlet match;\n\t\twhile ((match = regex.exec(content.content))) {\n\t\t\timageNames.push(match[1]);\n\t\t}\n\t\tif (imageNames.length === 0) return false;\n\n\t\t// Enqueue the images for loading\n\t\tlet id = uuid.v4();\n\t\tlet svgObj = {\n\t\t\tcontent: content.content,\n\t\t\tcommand_number: (content.command_number || -1),\n\t\t\twaitCount: 0\n\t\t};\n\t\timageNames.forEach((name) => {\n\t\t\tlet filename = name + \".png\";\n\t\t\tif (filename in this._plotPngStore) {\n\t\t\t\treturn this._log.error(\"Plot image is already in the queue:\", filename);\n\t\t\t}\n\t\t\tthis._plotPngStore[filename] = id;\n\t\t\tsvgObj.waitCount++;\n\t\t\t// Actually send the read-image job upstream:\n\t\t\tthis._sendMessageToFiles(\"read-delete-binary\", { filename });\n\t\t});\n\t\tthis._plotSvgStore[id] = svgObj;\n\t\tthis._log.debug(`Loading ${svgObj.waitCount} images for plot`, id);\n\t\treturn true;\n\t}\n\t_onDeletedBinary(content) {\n\t\tif (content.filename in this._plotPngStore) {\n\t\t\tthis._resolvePng(content);\n\t\t\treturn true;\n\t\t} else if (!content.success) {\n\t\t\t// TODO: Implement a better way to resolve load errors.\n\t\t\tthis._log.warn(\"Failed loading a plot image; discarding all pending plots\");\n\t\t\tthis._plotPngStore = {};\n\t\t\tthis._plotSvgStore = {};\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\t_resolvePng(content) {\n\t\tlet filename = content.filename;\n\t\tlet base64data = content.base64data;\n\t\tlet id = this._plotPngStore[filename];\n\t\tdelete this._plotPngStore[filename];\n\t\tlet svgObj = this._plotSvgStore[id];\n\t\tthis._log.trace(`Loaded image '${filename}' for plot`, id);\n\n\t\t// Perform the substitution\n\t\tsvgObj.content = svgObj.content.replace(`xlink:href='${filename}'`, `xlink:href='data:image/png;base64,${base64data}'`);\n\n\t\t// Have we loaded all of the images we need to replace?\n\t\tsvgObj.waitCount--;\n\t\tif (svgObj.waitCount === 0) {\n\t\t\tthis._log.debug(\"Loaded all images for plot\", id);\n\t\t\tthis.emit(\"message\", \"show-static-plot\", {\n\t\t\t\tcontent: svgObj.content,\n\t\t\t\tcommand_number: svgObj.command_number\n\t\t\t});\n\t\t\tdelete this._plotSvgStore[id];\n\t\t}\n\t}\n\n\t// URL METHODS: Perform URL requests on behalf of the user.  Uses a hard-coded whitelist in the config file for filtering out legal URLs.\n\t_handleRequestUrl(content) {\n\t\ttry {\n\t\t\tlet urlObj = url.parse(content.url);\n\n\t\t\t// Check if the hostname is legal\n\t\t\tif (urlObj.hostname === null) {\n\t\t\t\tthis._sendMessageToHost(\"request-url-answer\", [false, \"You must specify a URL of the form http://example.com/\"]);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet isLegal = false;\n\t\t\tfor (let pattern of config.session.urlreadPatterns) {\n\t\t\t\tif (new RegExp(pattern).test(urlObj.hostname)) {\n\t\t\t\t\tisLegal = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!isLegal) {\n\t\t\t\tthis._sendMessageToHost(\"request-url-answer\", [false, `The hostname ${urlObj.hostname} is not whitelisted\\nfor access by Octave Online. If you think this hostname\\nshould be whitelisted, please open a support ticket.`]);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Convert from the query param list to a query string\n\t\t\tlet paramObj = {};\n\t\t\tfor (let i=0; i<content.param.length; i+=2) {\n\t\t\t\tparamObj[content.param[i]] = content.param[i+1];\n\t\t\t}\n\t\t\tlet encodedParams = querystring.stringify(paramObj);\n\n\t\t\t// Set up and perform the request\n\t\t\tlet payload = \"\";\n\t\t\tif (content.action.toLowerCase() === \"post\") {\n\t\t\t\turlObj.method = \"POST\";\n\t\t\t\turlObj.headers = {\n\t\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\t\t\"Content-Length\": Buffer.byteLength(encodedParams)\n\t\t\t\t};\n\t\t\t\tpayload = encodedParams;\n\t\t\t} else {\n\t\t\t\turlObj.method = \"GET\";\n\t\t\t\tif (urlObj.query) {\n\t\t\t\t\turlObj.query += \"&\" + encodedParams;\n\t\t\t\t} else {\n\t\t\t\t\turlObj.query = encodedParams;\n\t\t\t\t}\n\t\t\t\t// shouldn't updating the path and href be automatic inside the url module? :/\n\t\t\t\turlObj.path = urlObj.pathname + (urlObj.query ? \"?\" + urlObj.query : \"\") + (urlObj.hash ? urlObj.hash : \"\");\n\t\t\t\turlObj.href = urlObj.protocol  + \"//\" + (urlObj.auth ? urlObj.auth + \"@\" : \"\") + urlObj.hostname + (urlObj.port ? \":\" + urlObj.port : \"\") + urlObj.path;\n\t\t\t}\n\n\t\t\tthis._log.info(\"Successfully matched URL\", content.url, urlObj);\n\t\t\tthis._log.trace(\"Sending URL request:\", urlObj.href);\n\t\t\tlet httpLib = (urlObj.protocol === \"https:\") ? https : http;\n\t\t\tlet req = httpLib.request(urlObj, (res) => {\n\t\t\t\tthis._log.trace(\"Received URL response:\", res.statusCode, urlObj.href);\n\t\t\t\tres.setEncoding(\"base64\");\n\t\t\t\tlet fullResult = \"\";\n\t\t\t\tlet errmsg = \"\";\n\t\t\t\tres.on(\"data\", (chunk) => {\n\t\t\t\t\tif (chunk.length + fullResult.length > config.session.urlreadMaxBytes) {\n\t\t\t\t\t\terrmsg = `Requested URL exceeds maximum of ${config.session.urlreadMaxBytes} bytes`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfullResult += chunk;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tres.on(\"end\", () => {\n\t\t\t\t\tthis._log.trace(\"URL response after processing:\", errmsg, fullResult.length);\n\t\t\t\t\tif (errmsg) {\n\t\t\t\t\t\tthis._sendMessageToHost(\"request-url-answer\", [false, errmsg]);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis._sendMessageToHost(\"request-url-answer\", [true, fullResult]);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\t\t\treq.on(\"error\", (err) => {\n\t\t\t\tthis._log.trace(\"Problem with URL request:\", err.message);\n\t\t\t\tthis._sendMessageToHost(\"request-url-answer\", [false, err.message]);\n\t\t\t});\n\t\t\treq.write(payload);\n\t\t\treq.end();\n\t\t} catch(err) {\n\t\t\tthis._sendMessageToHost(\"request-url-answer\", [false, err.message]);\n\t\t}\n\t}\n\n\t// BUCKET METHODS: Create (and destroy) buckets with snapshots of static files that can be published.\n\t_createBucket(bucketInfo) {\n\t\tlet filenames = bucketInfo.filenames;\n\t\tif (!Array.isArray(filenames)) return;\n\n\t\t// Create the bucket ID.\n\t\t// Note: Starting 2021-08-07, the bucket ID is generated on the front server instead. The if statement here is for backwards compatibility.\n\t\tlet bucketId = bucketInfo.bucket_id;\n\t\tif (!bucketId) {\n\t\t\tconst bucketIdBuffer = new Buffer(16);\n\t\t\tuuid.v4({}, bucketIdBuffer, 0);\n\t\t\tbucketId = base58.encode(bucketIdBuffer);\n\t\t\tbucketInfo.bucket_id = bucketId;\n\t\t}\n\n\t\tthis._log.debug(\"Creating new bucket:\", bucketId, filenames);\n\t\tasync.auto({\n\t\t\t\"read_files\": (_next) => {\n\t\t\t\t// Load the bucket files into memory.\n\t\t\t\tthis._mlog.trace(\"Reading files for bucket\");\n\t\t\t\tlet jobId = uuid.v4();\n\t\t\t\tthis._onceMessageFromFiles(\"multi-binary:\" + jobId, (err, data) => {\n\t\t\t\t\tif (!data.success) return _next(new Error(\"Unsuccessful call to multi-binary\"));\n\t\t\t\t\t_next(null, data.results);\n\t\t\t\t}, _next);\n\t\t\t\tthis._sendMessageToFiles(\"multi-binary\", {\n\t\t\t\t\tid: jobId,\n\t\t\t\t\tfilenames: filenames\n\t\t\t\t});\n\t\t\t},\n\t\t\t\"tmpdir\": (_next) => {\n\t\t\t\t// We need to create a working directory for the bucket git\n\t\t\t\tthis._mlog.trace(\"Creating tmpdir for bucket\");\n\t\t\t\ttemp.mkdir(\"oo-\", _next);\n\t\t\t},\n\t\t\t\"session\": (_next) => {\n\t\t\t\t// Create a Git session for the new bucket.\n\t\t\t\tconst session = this._makeNewFileSession(\"create-bucket:\" + bucketId);\n\t\t\t\tsession.on(\"message\", (name /*, content */) => {\n\t\t\t\t\tthis._mlog.trace(\"Bucket file session message:\", name);\n\t\t\t\t});\n\t\t\t\tsession.on(\"error\", (err) => {\n\t\t\t\t\tthis._log.error(\"Bucket file session error:\", err);\n\t\t\t\t});\n\t\t\t\t_next(null, session);\n\t\t\t},\n\t\t\t// NOTE: In Async 1.5.x, the version used here, the argument order is (_next, results), but in Async 2.x, the argument order changed to (results, _next).\n\t\t\t\"session_create\": [\"tmpdir\", \"session\", (_next, results) => {\n\t\t\t\tthis._mlog.trace(\"Creating session for bucket\");\n\t\t\t\tresults.session.create(_next, results.tmpdir);\n\t\t\t}],\n\t\t\t\"session_init\": [\"session_create\", (_next, results) => {\n\t\t\t\tthis._mlog.trace(\"Initializing session for bucket\");\n\t\t\t\tonceMessage(results.session, \"filelist\", _next);\n\t\t\t\tresults.session.sendMessage(\"bucket-info\", {\n\t\t\t\t\tid: bucketId,\n\t\t\t\t\treadonly: false\n\t\t\t\t});\n\t\t\t}],\n\t\t\t\"write_files\": [\"read_files\", \"session_init\", (_next, results) => {\n\t\t\t\tthis._mlog.trace(\"Writing files for bucket\");\n\t\t\t\tlet jobId = uuid.v4();\n\t\t\t\tonceMessage(results.session, \"multi-binary-saved:\" + jobId, (err, data) => {\n\t\t\t\t\tif (!data.success) return _next(new Error(\"Unsuccessful call to save-multi-binary\"));\n\t\t\t\t\t_next(null);\n\t\t\t\t});\n\t\t\t\tresults.session.sendMessage(\"save-multi-binary\", {\n\t\t\t\t\tid: jobId,\n\t\t\t\t\tfilenames: filenames,\n\t\t\t\t\tbase64datas: results.read_files\n\t\t\t\t});\n\t\t\t}],\n\t\t\t\"commit\": [\"write_files\", (_next, results) => {\n\t\t\t\tthis._mlog.trace(\"Committing files for bucket\");\n\t\t\t\tonceMessage(results.session, \"committed\", _next);\n\t\t\t\tresults.session.sendMessage(\"commit\", {\n\t\t\t\t\tcomment: \"Scripted bucket creation: \" + bucketId\n\t\t\t\t});\n\t\t\t}],\n\t\t\t\"destroy_session\": [\"commit\", (_next, results) => {\n\t\t\t\tthis._mlog.trace(\"Destroying session for bucket\");\n\t\t\t\tresults.session.destroy(_next);\n\t\t\t}],\n\t\t\t\"destroy_tmpdir\": [\"destroy_session\", (_next, results) => {\n\t\t\t\tthis._mlog.trace(\"Destroying working dir bucket\");\n\t\t\t\tchild_process.exec(`rm -rf ${results.tmpdir}`, _next);\n\t\t\t}]\n\t\t}, (err) => {\n\t\t\tif (err) {\n\t\t\t\tthis._log.error(\"Error creating bucket:\", err);\n\t\t\t\tthis.emit(\"message\", \"err\", \"Encountered an error creating the bucket.\\n\");\n\t\t\t} else {\n\t\t\t\tthis._log.info(\"Finished creating new bucket:\", bucketId);\n\t\t\t\tthis.emit(\"message\", \"bucket-repo-created\", bucketInfo);\n\t\t\t}\n\t\t});\n\t}\n\n\t// Prevent spammy messages from clogging up the server.\n\t// TODO: It would be better if this were done deeper in the stack, such as host.c, so that message spamming doesn't reach all the way into the main event loop.\n\t_checkThrottle() {\n\t\t// FIXME: Make these config values.\n\t\t// Nominal values: 100 messages per 100 milliseconds (1000 messages/second) = spam.\n\t\t// INTERVAL_DURATION should be less than 1e9.\n\t\tlet MSGS_PER_INTERVAL = 100;\n\t\tlet INTERVAL_DURATION = 1e8;\n\n\t\tif (++this._throttleCounter < MSGS_PER_INTERVAL) return;\n\n\t\tthis._throttleCounter = 0;\n\t\tlet diff = process.hrtime(this._throttleTime);\n\t\tthis._throttleTime = process.hrtime();\n\n\t\tif (diff[0] === 0 && diff[1] < INTERVAL_DURATION) {\n\t\t\tthis._log.warn(\"Messages too rapid!  Killing process!\", diff);\n\t\t\tthis.emit(\"message\", \"destroy\", \"Too Many Packets\");\n\t\t}\n\t}\n\n\tsendMessage(name, content) {\n\t\tswitch (name) {\n\t\t\t// Messages requiring special handling\n\t\t\tcase \"interrupt\":\n\t\t\t\tthis._continueIfPaused();\n\t\t\t\tthis.interrupt();\n\t\t\t\tbreak;\n\n\t\t\tcase \"oo.add_time\":\n\t\t\t\tthis._addTime();\n\t\t\t\tthis.resetTimeout();\n\t\t\t\tbreak;\n\n\t\t\tcase \"oo.acknowledge_payload\":\n\t\t\t\tthis._acknowledgePayload();\n\t\t\t\tthis.resetTimeout();\n\t\t\t\tbreak;\n\n\t\t\tcase \"cmd\":\n\t\t\t\t// FIXME: The following translation (from content to content.data) should be performed in message-translator.js, but we're unable to do so because the data isn't downloaded from Redis until after message-translator is run.  Is there a more elegant place to put this?  Maybe all message translation should happen here in octave-session.js instead?\n\t\t\t\tcontent = content.data || \"\";\n\t\t\t\tif (typeof content !== \"string\") {\n\t\t\t\t\tthis._log.error(\"content is not a string:\", content);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tthis._startCountdown();\n\t\t\t\tthis.resetTimeout();\n\t\t\t\tthis._appendToSessionLog(name, content);\n\t\t\t\t// Split the command into individual lines and send them to Octave one-by-one.\n\t\t\t\tcontent.split(\"\\n\").forEach((line) => {\n\t\t\t\t\tthis._sendMessageToHost(name, line);\n\t\t\t\t});\n\t\t\t\tbreak;\n\n\t\t\tcase \"user-info\":\n\t\t\t\tif (content && content.user) {\n\t\t\t\t\tthis._startAutoCommitLoop();\n\t\t\t\t\tthis._legalTime = content.user.legalTime;\n\t\t\t\t\tthis._payloadLimit = content.user.payloadLimit;\n\t\t\t\t\tthis._countdownExtraTime = content.user.countdownExtraTime;\n\t\t\t\t\tthis._countdownRequestTime = content.user.countdownRequestTime;\n\t\t\t\t}\n\t\t\t\tif (content.bucketId && !content.bucket) {\n\t\t\t\t\t// For backwards compatibility\n\t\t\t\t\tcontent.bucket = {\n\t\t\t\t\t\tbucket_id: content.bucketId,\n\t\t\t\t\t\tbutype: \"readonly\",\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (content.bucket) {\n\t\t\t\t\tif (!content.bucket.butype) {\n\t\t\t\t\t\tthis._log.error(\"butype not present in bucket\", content);\n\t\t\t\t\t\tcontent.butype = \"readonly\";\n\t\t\t\t\t}\n\t\t\t\t\tthis._sendMessageToFiles(\"bucket-info\", {\n\t\t\t\t\t\tid: content.bucket.bucket_id,\n\t\t\t\t\t\tlegalTime: this._legalTime, // For backwards compatibility\n\t\t\t\t\t\treadonly: content.bucket.butype === \"readonly\",\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tthis._sendMessageToFiles(name, content);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"oo.create_bucket\":\n\t\t\t\tthis._createBucket(content);\n\t\t\t\tbreak;\n\n\t\t\t// Messages to forward to the file manager\n\t\t\tcase \"list\":\n\t\t\tcase \"refresh\":\n\t\t\tcase \"commit\":\n\t\t\tcase \"save\":\n\t\t\tcase \"rename\":\n\t\t\tcase \"delete\":\n\t\t\tcase \"binary\":\n\t\t\tcase \"read-delete-binary\":\n\t\t\tcase \"siofu_start\":\n\t\t\tcase \"siofu_progress\":\n\t\t\tcase \"siofu_done\":\n\t\t\t\tthis._sendMessageToFiles(name, content);\n\t\t\t\tbreak;\n\n\t\t\t// Messages to forward to the Octave host\n\t\t\tcase \"request-input-answer\":\n\t\t\tcase \"request-url-answer\":\n\t\t\tcase \"confirm-shutdown-answer\":\n\t\t\tcase \"prompt-new-edit-file-answer\":\n\t\t\tcase \"message-dialog-answer\":\n\t\t\tcase \"question-dialog-answer\":\n\t\t\tcase \"list-dialog-answer\":\n\t\t\tcase \"input-dialog-answer\":\n\t\t\tcase \"file-dialog-answer\":\n\t\t\tcase \"debug-cd-or-addpath-error-answer\":\n\t\t\t\tthis._sendMessageToHost(name, content);\n\t\t\t\tbreak;\n\n\t\t\t// Unknown messages\n\t\t\tdefault:\n\t\t\t\tthis._log.debug(\"Message ignored:\", name);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t_handleMessage(name, content) {\n\t\t// Check for message throttling, except for \"out\" and \"err\" messages, which are throttled via payload.\n\t\tif (name !== \"out\" && name !== \"err\") this._checkThrottle();\n\n\t\t// Special pre-processing of a few events here\n\t\tswitch (name) {\n\t\t\tcase \"show-static-plot\":\n\t\t\t\t// Convert PNG file links to embedded base 64 data\n\t\t\t\tif (this._convertPlotImages(content)) return;\n\t\t\t\tbreak;\n\n\t\t\tcase \"deleted-binary\":\n\t\t\t\t// If we're waiting for any binary files, capture them here rather than sending them downstream\n\t\t\t\tif (this._onDeletedBinary(content)) return;\n\t\t\t\tbreak;\n\n\t\t\tcase \"display-warning\":\n\t\t\t\tlet opWarningMatch = /(the '.*' operator)/.exec(content.message);\n\t\t\t\tif (opWarningMatch) {\n\t\t\t\t\tif (this._suppressedWarningsStore[opWarningMatch[0]]) {\n\t\t\t\t\t\tthis._mlog.trace(\"Suppressed duplicate warning:\", content.id);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis._suppressedWarningsStore[opWarningMatch[0]] = true;\n\t\t\t\t}\n\t\t\t\tname = \"err\";\n\t\t\t\tcontent = content.formatted;\n\t\t\t\tbreak;\n\n\t\t\tcase \"display-exception\":\n\t\t\t\tname = \"err\";\n\t\t\t\tcontent = content.ee_str;\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t\tif (name === \"err\") {\n\t\t\t// Filter out some error messages\n\t\t\tif (/warning: readline is not linked/.test(content)) return;\n\t\t\tif (/warning: docstring file/.test(content)) return;\n\t\t\tif (/error: unable to open .+macros\\.texi/.test(content)) return;\n\t\t\tif (/^\\/tmp\\/octave-help-/.test(content)) return;\n\t\t\tif (/built-in-docstrings' not found/.test(content)) return;\n\t\t}\n\t\tif (/^multi-binary:[\\w-]+$/.test(name)) return;\n\n\t\t// Forward remaining events downstream\n\t\tthis.emit(\"message\", name, content);\n\t\tthis.emit(`msg:${name}`, content);\n\n\t\t// Special post-processing of a few more events here\n\t\tswitch (name) {\n\t\t\tcase \"request-input\":\n\t\t\t\tthis._endCountdown();\n\t\t\t\tthis.resetTimeout();\n\t\t\t\tthis._resetPayload();\n\t\t\t\tbreak;\n\n\t\t\tcase \"err\":\n\t\t\tcase \"out\":\n\t\t\t\tthis._appendToPayload(content);\n\t\t\t\tthis._appendToSessionLog(name, content);\n\t\t\t\tbreak;\n\n\t\t\tcase \"request-url\":\n\t\t\t\tthis._handleRequestUrl(content);\n\t\t\t\tbreak;\n\n\t\t\t// UNIMPLEMENTED FEATURES REQUIRING RESPONSE:\n\t\t\tcase \"confirm-shutdown\":\n\t\t\t\tthis._sendMessageToHost(\"confirm-shutdown-answer\", true);\n\t\t\t\tbreak;\n\t\t\tcase \"prompt-new-edit-file\":\n\t\t\t\tthis._sendMessageToHost(\"prompt-new-edit-file-answer\", true);\n\t\t\t\tbreak;\n\t\t\tcase \"message-dialog\":\n\t\t\t\tthis._sendMessageToHost(\"message-dialog-answer\", 0);\n\t\t\t\tbreak;\n\t\t\tcase \"question-dialog\":\n\t\t\t\tthis._sendMessageToHost(\"question-dialog-answer\", \"\");\n\t\t\t\tbreak;\n\t\t\tcase \"list-dialog\":\n\t\t\t\tthis._sendMessageToHost(\"list-dialog-answer\", [[],0]);\n\t\t\t\tbreak;\n\t\t\tcase \"input-dialog\":\n\t\t\t\tthis._sendMessageToHost(\"input-dialog-answer\", []);\n\t\t\t\tbreak;\n\t\t\tcase \"file-dialog\":\n\t\t\t\tthis._sendMessageToHost(\"file-dialog-answer\", []);\n\t\t\t\tbreak;\n\t\t\tcase \"debug-cd-or-addpath-error\":\n\t\t\t\tthis._sendMessageToHost(\"debug-cd-or-addpath-error-answer\", 0);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t_handleError(err) {\n\t\tif (err) this._log.error(err);\n\t}\n}\n\nmodule.exports = OctaveSession;\n"
  },
  {
    "path": "back-master/src/process-handler.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst async = require(\"async\");\nconst logger = require(\"@oo/shared\").logger;\nconst StdioMessenger = require(\"@oo/shared\").StdioMessenger;\n\nclass ProcessHandler extends StdioMessenger {\n\tconstructor(sessCode) {\n\t\tsuper();\n\t\tthis._log = logger(`process-handler:${sessCode}`);\n\t\tthis._mlog = logger(`process-handler:${sessCode}:minor`);\n\t\tthis.sessCode = sessCode;\n\t}\n\n\t_doCreate(next, fn) {\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\t// Spawn the process\n\t\t\t\tlet args = Array.prototype.slice.call(arguments, 2);\n\t\t\t\tthis._mlog.trace(\"Spawning process:\", args[0], args[1].join(\" \"), args[2]);\n\t\t\t\tthis._spwn = fn.apply(this, args);\n\n\t\t\t\t// Create all unexpected error listeners\n\t\t\t\tthis._spwn.on(\"error\", (err) => { this._log.error(\"spwn:\", err); });\n\t\t\t\tthis._spwn.stdin.on(\"error\", (err) => { this._log.error(\"stdin:\", err); });\n\t\t\t\tthis._spwn.stdout.on(\"error\", (err) => { this._log.error(\"stdout:\", err); });\n\t\t\t\tthis._spwn.stderr.on(\"error\", (err) => { this._log.error(\"stderr:\", err); });\n\n\t\t\t\t// Create stderr listener\n\t\t\t\tthis._spwn.stderr.on(\"data\", this._handleLog.bind(this));\n\n\t\t\t\t// Create exit listener\n\t\t\t\tthis._spwn.on(\"exit\", this._handleExit.bind(this));\n\n\t\t\t\t// Listen to main read stream\n\t\t\t\tthis.setReadStream(this._spwn.stdout);\n\n\t\t\t\t// Wait until we get an acknowledgement before continuing.  Two conditions: receipt of the acknowledgement message, and premature exit.\n\t\t\t\tvar ack = false;\n\t\t\t\tthis.once(\"message\", (name /*, content */) => {\n\t\t\t\t\tif (ack) return;\n\t\t\t\t\tack = true;\n\n\t\t\t\t\t// Error if the message is process-exit\n\t\t\t\t\tif (name === \"process-exit\") return _next(new Error(\"Process exited prematurely\"));\n\n\t\t\t\t\t// Don't enable the write stream until down here because we don't want to write messages to the child's STDIN until we've acknowledged that it is online\n\t\t\t\t\tthis.setWriteStream(this._spwn.stdin);\n\t\t\t\t\t_next(null);\n\t\t\t\t});\n\t\t\t}\n\t\t], (err) => {\n\t\t\tif (err) return next(err);\n\t\t\tthis._mlog.debug(\"Finished creating\");\n\t\t\treturn next(null);\n\t\t});\n\t}\n\n\t_doDestroy(next) {\n\t\t// This method wont't be called unless the process state is ONLINE, so we don't need to check.\n\t\tif (this._spwn) {\n\t\t\t// We can ignore the \"next\" callback because it will be implicitly called by _handleExit()\n\t\t\tthis._mlog.trace(\"this._spwn exists\");\n\t\t\tthis._doDestroyProcess();\n\t\t} else {\n\t\t\tthis._mlog.trace(\"this._spwn does not exist\");\n\t\t\tnext(null);\n\t\t}\n\t}\n\n\tsignal(name) {\n\t\tif (!this._spwn) return this._log.warn(\"Tried to signal child process, but it does not exist\");\n\t\tif (this._spwn.exitCode !== null || this._spwn.signalCode !== null) return this._log.warn(\"Tried to signal child process, but it is exited\");\n\t\tthis._signal(name);\n\t\tthis._log.debug(\"Sent \" + name + \" to child process\");\n\t}\n\n\t_signal(name) {\n\t\tthis._spwn.kill(name);\n\t}\n\n\t_handleLog(data) {\n\t\t// Log message to console\n\t\tdata.toString().trim().split(\"\\n\").forEach((line) => {\n\t\t\tthis._mlog.log(line);\n\t\t});\n\t}\n\n\t_handleExit(code, signal) {\n\t\tthis._log.debug(\"Process Exit:\", code, signal);\n\t\tthis._spwn = null;\n\t\tthis.emit(\"message\", \"process-exit\", { code, signal });\n\t\tthis._internalDestroyed(null);\n\n\t\t// TODO: when to emit this.emit(\"message\", \"octave-killed\") ?\n\t}\n}\n\nmodule.exports = ProcessHandler;\n"
  },
  {
    "path": "back-master/src/session-impl.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst OctaveSession = require(\"./octave-session\");\nconst { CappedFileSystem, TmpWorkDirectory } = require(\"./capped-file-system\");\nconst DockerHandler = require(\"./docker-handler\");\nconst ProcessHandler = require(\"./process-handler\");\nconst config = require(\"@oo/shared\").config;\nconst config2 = require(\"@oo/shared\").config2;\nconst async = require(\"async\");\nconst silent = require(\"@oo/shared\").silent;\nconst child_process = require(\"child_process\");\nconst logger = require(\"@oo/shared\").logger;\nconst pstree = require(\"ps-tree\");\nconst temp = require(\"temp\");\nconst OnlineOffline = require(\"@oo/shared\").OnlineOffline;\nconst Queue = require(\"@oo/shared\").Queue;\nconst onceMessage = require(\"@oo/shared\").onceMessage;\nconst FilesController = require(\"../../back-filesystem/src/controller\");\n\nclass SessionImpl extends OctaveSession {\n\tconstructor(sessCode, options) {\n\t\tsuper(sessCode, options);\n\t\tthis.options = options;\n\n\t\tthis._makeSessions();\n\n\t\tif (config.session.implementation === \"unsafe\") {\n\t\t\tthis._cfs = new TmpWorkDirectory(this.sessCode);\n\t\t} else {\n\t\t\tthis._cfs = new CappedFileSystem(this.sessCode, config.docker.diskQuotaKiB);\n\t\t}\n\n\t\tthis._filesSession.on(\"message\", this._handleMessage.bind(this));\n\t\tthis._hostSession.on(\"message\", this._handleMessage.bind(this));\n\n\t\tthis._cfs.on(\"error\", this._handleError.bind(this));\n\t\tthis._filesSession.on(\"error\", this._handleError.bind(this));\n\t\tthis._hostSession.on(\"error\", this._handleError.bind(this));\n\t}\n\n\t_doCreateImpl(next) {\n\t\tasync.auto({\n\t\t\t\"cfs\": (_next) => {\n\t\t\t\tthis._mlog.trace(\"Requesting creation of capped file system\");\n\t\t\t\tthis._cfs.create((err, dataDir) => {\n\t\t\t\t\tif (!err) this._dataDir = dataDir;\n\t\t\t\t\t_next(err);\n\t\t\t\t});\n\t\t\t},\n\t\t\t\"files\": [\"cfs\", (_next) => {\n\t\t\t\tthis._mlog.trace(\"Requesting creation of file manager process\");\n\t\t\t\tthis._filesSession.create(_next, this._dataDir);\n\t\t\t}],\n\t\t\t\"host\": [\"cfs\", (_next) => {\n\t\t\t\tthis._mlog.trace(\"Requesting creation of Octave host process\");\n\t\t\t\tthis._hostSession.create(_next, this._dataDir);\n\t\t\t}]\n\t\t}, (err) => {\n\t\t\tif (err) return next(err);\n\t\t\tthis._log.info(\"Session successfully created\");\n\t\t\tthis.resetTimeout();\n\t\t\treturn next(null);\n\t\t});\n\t}\n\n\t_doDestroyImpl(next, reason) {\n\t\t// TODO: Add an alternative destroy implementation that is synchronous, so that it can be run in an exit handler.\n\t\tasync.auto({\n\t\t\t\"commit\": (_next) => {\n\t\t\t\tthis._mlog.trace(\"Requesting to commit changes to Git\");\n\t\t\t\tthis._commit(\"Scripted user file commit\", silent(/Out of time/, _next));\n\t\t\t},\n\t\t\t\"host\": [\"commit\", (_next) => {\n\t\t\t\tthis._mlog.trace(\"Requesting termination of Octave host process\");\n\t\t\t\tthis._hostSession.destroy(_next);\n\t\t\t}],\n\t\t\t\"files\": [\"commit\", (_next) => {\n\t\t\t\tthis._mlog.trace(\"Requesting termination of file manager process\");\n\t\t\t\tthis._filesSession.destroy(_next);\n\t\t\t}],\n\t\t\t\"cfs\": [\"host\", \"files\", (_next) => {\n\t\t\t\tthis._mlog.trace(\"Requesting deletion of capped file system\");\n\t\t\t\tthis._cfs.destroy(_next);\n\t\t\t}]\n\t\t}, (err) => {\n\t\t\tif (err) return next(err);\n\t\t\tthis._log.info(\"Session successfully destroyed:\", reason);\n\t\t\treturn next(null);\n\t\t});\n\t}\n\n\t_signal(name) {\n\t\tthis._hostSession.signal(name);\n\t}\n\n\t_sendMessageToFiles(name, content) {\n\t\tthis._filesSession.sendMessage(name, content);\n\t}\n\n\t_sendMessageToHost(name, content) {\n\t\tthis._mlog.trace(\"Sending message to Octave host:\", name, content);\n\t\tthis._hostSession.sendMessage(name, content);\n\t}\n\n\t_onceMessageFromFiles(name, next) {\n\t\tonceMessage(this._filesSession, name, next);\n\t}\n}\n\nclass HostProcessHandler extends ProcessHandler {\n\tconstructor(sessCode, options) {\n\t\tsuper(sessCode);\n\t\tthis.options = options;\n\n\t\t// Override default logger with something that says \"host\"\n\t\tthis._log = logger(`host-handler:${sessCode}`);\n\t\tthis._mlog = logger(`host-handler:${sessCode}:minor`);\n\t}\n\n\t_doCreate(next, dataDir) {\n\t\tconst tier = this.options.tier;\n\t\tlet cgroupName = config2.tier(tier)[\"selinux.cgroup.name\"];\n\t\tlet addressSpace = config2.tier(tier)[\"selinux.prlimit.addressSpace\"];\n\n\t\tconst envVars = [\n\t\t\t\"env\", \"GNUTERM=svg\",\n\t\t\t\"env\", \"LD_LIBRARY_PATH=/usr/local/lib\",\n\t\t\t\"env\", \"OO_SESSCODE=\"+this.sessCode,\n\t\t\t\"env\", \"OO_TIER=\"+this.options.tier\n\t\t];\n\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\ttemp.mkdir(\"oo-\", (err, tmpdir) => {\n\t\t\t\t\tthis._mlog.debug(\"Created tmpdir:\", tmpdir);\n\t\t\t\t\tthis.tmpdir = tmpdir;\n\t\t\t\t\t_next(err);\n\t\t\t\t});\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\tif (config.session.implementation === \"unsafe\") {\n\t\t\t\t\t// Spawn un-sandboxed process\n\t\t\t\t\tsuper._doCreate(_next, child_process.spawn, \"env\", [].concat(envVars.slice(1)).concat([\"/usr/local/bin/octave-host\", config.session.jsonMaxMessageLength]), {\n\t\t\t\t\t\tcwd: dataDir\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t// Spawn sandboxed process\n\t\t\t\t\t// The CWD is set to /tmp in order to make the child process not hold a reference to the mount that the application happens to be running under.\n\t\t\t\t\tsuper._doCreate(_next, child_process.spawn, \"/usr/bin/prlimit\", [\n\t\t\t\t\t\t\"--as=\"+addressSpace,\n\t\t\t\t\t\t\"/usr/bin/cgexec\",\n\t\t\t\t\t\t\"-g\", \"cpu:\"+cgroupName,\n\t\t\t\t\t\t\"/usr/bin/sandbox\",\n\t\t\t\t\t\t\"-M\",\n\t\t\t\t\t\t\"-H\", dataDir,\n\t\t\t\t\t\t\"-T\", this.tmpdir,\n\t\t\t\t\t\t\"--level\", \"s0\"]\n\t\t\t\t\t\t.concat(envVars)\n\t\t\t\t\t\t.concat([\n\t\t\t\t\t\t\t\"/usr/local/bin/octave-host\", config.session.jsonMaxMessageLength\n\t\t\t\t\t\t]),\n\t\t\t\t\t{\n\t\t\t\t\t\tcwd: \"/tmp\"\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\t// We need to get the octave-cli PID for signalling, because sandbox handles signals strangely.\n\t\t\t\tthis.octavePID = null;\n\t\t\t\tasync.whilst(\n\t\t\t\t\t() => { return !this.octavePID && this._state !== \"DESTROYED\"; },\n\t\t\t\t\t(__next) => {\n\t\t\t\t\t\tasync.waterfall([\n\t\t\t\t\t\t\t(___next) => {\n\t\t\t\t\t\t\t\tsetTimeout(___next, 250);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t(___next) => {\n\t\t\t\t\t\t\t\tthis._mlog.trace(\"Attempting to get Octave PID...\");\n\t\t\t\t\t\t\t\tpstree(this._spwn.pid, ___next);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t(children, ___next) => {\n\t\t\t\t\t\t\t\tlet child = children.find((_child) => { return /octave-cli/.test(_child.COMMAND); });\n\t\t\t\t\t\t\t\tif (child) {\n\t\t\t\t\t\t\t\t\tthis.octavePID = child.PID;\n\t\t\t\t\t\t\t\t\tthis._mlog.debug(\"Got Octave PID:\", this.octavePID);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t___next(null);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t], __next);\n\t\t\t\t\t},\n\t\t\t\t\t_next\n\t\t\t\t);\n\t\t\t}\n\t\t], next);\n\t}\n\n\t_doDestroy(next) {\n\t\tasync.series([\n\t\t\tsuper._doDestroy.bind(this),\n\t\t\t(_next) => {\n\t\t\t\tif (this.tmpdir) {\n\t\t\t\t\tthis._mlog.trace(\"Destroying tmpdir\");\n\t\t\t\t\tchild_process.exec(`rm -rf ${this.tmpdir}`, _next);\n\t\t\t\t} else {\n\t\t\t\t\tprocess.nextTick(_next);\n\t\t\t\t}\n\t\t\t}\n\t\t], next);\n\t}\n\n\t_doDestroyProcess() {\n\t\t// Starting with Octave 4.4, sending SIGTERM is insufficient to make Octave exit.\n\t\tthis._log.trace(\"Executing 'exit' in Octave process\");\n\t\tthis.sendMessage(\"cmd\", \"exit\");\n\t\tsetTimeout(() => {\n\t\t\tif (!this._spwn) {\n\t\t\t\tthis._mlog.trace(\"Not sending SIGKILL: Process is already exited\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis._log.trace(\"Sending SIGKILL\");\n\t\t\tthis._signal(\"SIGKILL\");\n\t\t}, 10000);\n\t}\n\n\t_signal(name) {\n\t\tif (!this.octavePID) return this._log.error(\"Cannot signal Octave process yet\");\n\t\tchild_process.exec(`kill -s ${name.slice(3)} ${this.octavePID}`, (err) => {\n\t\t\tif (err) this._log.error(\"signalling octave:\", err);\n\t\t});\n\t}\n}\n\nclass FilesControllerHandler extends OnlineOffline {\n\tconstructor(sessCode) {\n\t\tsuper();\n\t\tthis._log = logger(`files-handler:${sessCode}`);\n\t\tthis._mlog = logger(`files-handler:${sessCode}:minor`);\n\t\tthis.sessCode = sessCode;\n\t\tthis._messageQueue = new Queue();\n\t}\n\n\t_doCreate(next, dataDir) {\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\t// Make the gitdir\n\t\t\t\ttemp.mkdir(\"oo-\", (err, tmpdir) => {\n\t\t\t\t\tthis._mlog.debug(\"Created gitdir:\", tmpdir);\n\t\t\t\t\tthis.gitdir = tmpdir;\n\t\t\t\t\t_next(err);\n\t\t\t\t});\n\t\t\t},\n\t\t\t(_next) => {\n\t\t\t\t// Make the controller\n\t\t\t\tthis.controller = new FilesController(this.gitdir, dataDir, this.sessCode);\n\n\t\t\t\t// Flush messages to the controller\n\t\t\t\twhile (!this._messageQueue.isEmpty()) this._flush();\n\t\t\t\tthis._messageQueue.on(\"enqueue\", this._flush.bind(this));\n\n\t\t\t\t// Emit messages from the controller\n\t\t\t\tthis.controller.on(\"message\", (name, content) => {\n\t\t\t\t\tthis.emit(\"message\", name, content);\n\t\t\t\t});\n\n\t\t\t\t_next(null);\n\t\t\t}\n\t\t], next);\n\t}\n\n\t_doDestroy(next) {\n\t\tasync.series([\n\t\t\t(_next) => {\n\t\t\t\tif (this.controller) {\n\t\t\t\t\tthis.controller.destroy();\n\t\t\t\t}\n\t\t\t\tif (this.gitdir) {\n\t\t\t\t\tthis._mlog.trace(\"Destroying gitdir\");\n\t\t\t\t\tchild_process.exec(`rm -rf ${this.gitdir}`, _next);\n\t\t\t\t} else {\n\t\t\t\t\tprocess.nextTick(_next);\n\t\t\t\t}\n\t\t\t}\n\t\t], next);\n\t}\n\n\tsendMessage(name, content) {\n\t\tthis._messageQueue.enqueue([name, content]);\n\t}\n\n\t_flush() {\n\t\tthis.controller.receiveMessage.apply(this.controller, this._messageQueue.dequeue());\n\t}\n}\n\nclass SessionSELinux extends SessionImpl {\n\t_makeSessions() {\n\t\tthis._filesSession = new FilesControllerHandler(this.sessCode);\n\t\tthis._hostSession = new HostProcessHandler(this.sessCode, this.options);\n\t}\n\n\t_makeNewFileSession(sessCode) {\n\t\treturn new FilesControllerHandler(sessCode);\n\t}\n}\n\nclass HostDockerHandler extends DockerHandler {\n\tconstructor(sessCode) {\n\t\tsuper(sessCode);\n\t\tthis._dockerImage = config.docker.images.octaveSuffix;\n\t\tthis._dockerName = `oo-host-${sessCode}`;\n\n\t\t// Override default logger with something that says \"host\"\n\t\tthis._log = logger(`host-handler:${sessCode}`);\n\t\tthis._mlog = logger(`host-handler:${sessCode}:minor`);\n\t}\n\n\t_doCreate(next, dataDir) {\n\t\t// More about resource management: https://goldmann.pl/blog/2014/09/11/resource-management-in-docker/\n\t\tconst dockerArgs = [\n\t\t\t\"run\", \"-i\",\n\t\t\t\"-v\", `${dataDir}:${config.docker.cwd}`,\n\t\t\t\"--cpu-shares\", config.docker.cpuShares,\n\t\t\t\"-m\", config.docker.memoryShares,\n\t\t\t\"--name\", this._dockerName,\n\t\t\t`oo/${this._dockerImage}`\n\t\t];\n\t\tsuper._doCreate(next, dockerArgs);\n\t}\n}\n\nclass FilesDockerHandler extends DockerHandler {\n\tconstructor(sessCode) {\n\t\tsuper(sessCode);\n\t\tthis._dockerImage = config.docker.images.filesystemSuffix;\n\t\tthis._dockerName = `oo-files-${sessCode}`;\n\n\t\t// Override default logger with something that says \"files\"\n\t\tthis._log = logger(`files-handler:${sessCode}`);\n\t\tthis._mlog = logger(`files-handler:${sessCode}:minor`);\n\t}\n\n\t_doCreate(next, dataDir) {\n\t\tconst dockerArgs = [\n\t\t\t\"run\", \"-i\",\n\t\t\t\"-v\", `${dataDir}:${config.docker.cwd}`,\n\t\t\t\"--name\", this._dockerName,\n\t\t\t`oo/${this._dockerImage}`\n\t\t];\n\t\tsuper._doCreate(next, dockerArgs);\n\t}\n}\n\nclass SessionDocker extends SessionImpl {\n\t_makeSessions() {\n\t\tthis._filesSession = new FilesDockerHandler(this.sessCode);\n\t\tthis._hostSession = new HostDockerHandler(this.sessCode);\n\t}\n\n\t_makeNewFileSession(sessCode) {\n\t\treturn new FilesDockerHandler(sessCode);\n\t}\n}\n\nmodule.exports = {\n\tdocker: SessionDocker,\n\tselinux: SessionSELinux,\n\tdocker_handler: HostDockerHandler,\n\tselinux_handler: HostProcessHandler\n};\n"
  },
  {
    "path": "back-master/src/session-manager.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst log = require(\"@oo/shared\").logger(\"session-manager\");\nconst mlog = require(\"@oo/shared\").logger(\"session-manager:minor\");\nconst EventEmitter = require(\"events\");\nconst impls = require(\"./session-impl\");\nconst uuid = require(\"uuid\");\nconst Queue = require(\"@oo/shared\").Queue;\nconst config = require(\"@oo/shared\").config;\nconst config2 = require(\"@oo/shared\").config2;\nconst metrics = require(\"@oo/shared\").metrics;\nconst timeLimit = require(\"@oo/shared\").timeLimit;\n\nclass SessionManager extends EventEmitter {\n\tconstructor(maxOnly) {\n\t\tsuper();\n\t\tthis._pool = {};\n\t\tthis._poolSizes = {};\n\n\t\tlet tiersEnabled;\n\t\tif (maxOnly) {\n\t\t\ttiersEnabled = [\"_maxima\"];\n\t\t} else {\n\t\t\ttiersEnabled = Object.keys(config.tiers);\n\t\t\ttiersEnabled.splice(tiersEnabled.indexOf(\"_maxima\"), 1);\n\t\t}\n\t\ttiersEnabled.forEach((tier) => {\n\t\t\tif (config2.tier(tier)[\"sessionManager.poolTier\"]) {\n\t\t\t\tmlog.info(\"Skipping tier with poolTier:\", tier);\n\t\t\t\t// continue\n\t\t\t} else {\n\t\t\t\tmlog.info(\"Enabling tier:\", tier);\n\t\t\t\tthis._pool[tier] = {};\n\t\t\t\tthis._poolSizes[tier] = config2.tier(tier)[\"sessionManager.poolSize\"];\n\t\t\t}\n\t\t});\n\t\tlog.info(\"Enabled tiers:\", Object.keys(this._pool));\n\n\t\tthis._online = {};\n\t\tthis._monitor_session = null;\n\t\tthis._setup();\n\t\tthis.startPool();\n\t\tthis.recordMetrics();\n\t}\n\n\t_setup() {\n\t\t// Log the session index on a fixed interval\n\t\tthis._logInterval = setInterval(() => {\n\t\t\tObject.keys(this._pool).forEach((tier) => {\n\t\t\t\tlog.debug(\"Current Number of Pooled Sessions, tier \" + tier + \":\", Object.keys(this._pool[tier]).length);\n\t\t\t\tlog.trace(Object.keys(this._pool[tier]).join(\"; \"));\n\t\t\t});\n\t\t\tlog.debug(\"Current Number of Online Sessions:\", Object.keys(this._online).length);\n\t\t\tlog.trace(Object.keys(this._online).join(\"; \"));\n\n\t\t\t// Time an arbitrary command to test server health\n\t\t\tif (this._monitor_session) {\n\t\t\t\tlet t1 = new Date().valueOf();\n\t\t\t\tthis._monitor_session.sendMessage(\"cmd\", { data: \"pinv(magic(500));\" });\n\t\t\t\tthis._monitor_session.once(\"msg:request-input\", () => {\n\t\t\t\t\tconst elapsed = new Date().valueOf() - t1;\n\t\t\t\t\tlog.debug(\"Monitor Time (ms):\", elapsed);\n\t\t\t\t\tmetrics.gauge(\"oo.monitor_time_ms\", elapsed);\n\t\t\t\t});\n\t\t\t}\n\t\t}, config.sessionManager.logInterval);\n\n\t\t// Keep pool sessions alive\n\t\tthis._keepAliveInterval = setInterval(() => {\n\t\t\tObject.keys(this._pool).forEach((tier) => {\n\t\t\t\tObject.keys(this._pool[tier]).forEach((localCode) => {\n\t\t\t\t\tthis._pool[tier][localCode].session.resetTimeout();\n\t\t\t\t});\n\t\t\t});\n\t\t}, config.session.timewarnTime/2);\n\t}\n\n\tnumActiveSessions() {\n\t\treturn Object.keys(this._online).length;\n\t}\n\n\tcanAcceptNewSessions() {\n\t\tif (!this._poolVar.enabled) {\n\t\t\treturn false;\n\t\t}\n\t\tif (this.numActiveSessions() >= config.worker.maxSessions) {\n\t\t\treturn false;\n\t\t}\n\t\tfor (let tier of Object.keys(this._pool)) {\n\t\t\t// Require every tier to have at least 1 session in the pool\n\t\t\tif (Object.keys(this._pool[tier]).length === 0) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\tusagePercent() {\n\t\treturn this.numActiveSessions() / config.worker.maxSessions;\n\t}\n\n\tisHealthy() {\n\t\treturn this._monitor_session && this._monitor_session.isOnline();\n\t}\n\n\t_create(next, options) {\n\t\t// Get the correct implementation\n\t\tconst SessionImpl = config.session.implementation === \"docker\" ? impls.docker : impls.selinux;\n\n\t\t// Create the session object\n\t\tconst localCode = uuid.v4(null, new Buffer(16)).toString(\"hex\");\n\t\tconst session = new SessionImpl(localCode, options);\n\n\t\t// Add messages to a cache when they are created\n\t\tconst cache = new Queue();\n\t\tsession.on(\"message\", (name, content) => {\n\t\t\tcache.enqueue([name, content]);\n\t\t});\n\n\t\tsession.create(timeLimit(config.sessionManager.startupTimeLimit, [new Error(\"Time limit reached\")], (err) => {\n\t\t\t// Get rid of the session if it failed to create\n\t\t\tif (err) {\n\t\t\t\tlog.warn(\"Session failed to create:\", localCode, err);\n\t\t\t\tsession.destroy(null, \"Failed To Create\");\n\t\t\t\tnext();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Call the callback\n\t\t\tnext(localCode, session, cache, options);\n\t\t}));\n\t}\n\n\tattach(remoteCode, content) {\t\t// Move pool session to online session\n\t\tif (!this.canAcceptNewSessions()) return log.warn(\"Cannot accept any new sessions right now\");\n\n\t\t// TODO: Backwards compatibility with old front server: the message content can be the user itself; if null, it is a guest user.\n\t\tif (!content) {\n\t\t\tcontent = { user: null };\n\t\t} else if (content.parametrized) {\n\t\t\tcontent = { user: content };\n\t\t}\n\n\t\t// Determine which tier to use\n\t\t// Important: the user in content.user is not necesarilly the currently authenticated user; it could be the owner of a shared workspace.\n\t\tconst user = content.user;\n\t\tconst tier = content.tier ? content.tier : user ? user.tier : Object.keys(this._pool)[0];\n\t\tconst poolTier = config2.tier(tier)[\"sessionManager.poolTier\"] || tier;\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.assert(Object.keys(this._pool).includes(poolTier), poolTier);\n\n\t\t// Pull from the pool\n\t\tconst localCode = Object.keys(this._pool[poolTier])[0];\n\t\tthis._online[remoteCode] = this._pool[poolTier][localCode];\n\t\tdelete this._pool[poolTier][localCode];\n\t\tlog.info(\"Upgraded pool session\", poolTier, localCode, remoteCode);\n\t\tthis.recordMetrics();\n\n\t\t// Convenience references\n\t\tconst session = this._online[remoteCode].session;\n\t\tconst cache = this._online[remoteCode].cache;\n\n\t\t// Reset the session timeout to leave the user with a full allotment of time\n\t\tsession.resetTimeout();\n\n\t\t// Send payload upstream\n\t\tsession.sendMessage(\"user-info\", content);\n\n\t\t// Forward future messages\n\t\tcache.on(\"enqueue\", () => {\n\t\t\tconst message = cache.dequeue();\n\t\t\tthis.emit(\"message\", remoteCode, message[0], message[1]);\n\t\t});\n\n\t\t// Save the start time to keep a record of the time spent on flavor servers\n\t\tvar startTime = new Date().valueOf();\n\n\t\t// Create touch interval for Redis and save reference\n\t\tconst touchInterval = setInterval(() => {\n\t\t\tthis.emit(\"touch\", remoteCode, startTime);\n\t\t}, config.redis.expire.interval);\n\n\t\t// Emit an event to set to live in Redis (required for OT)\n\t\tthis.emit(\"live\", remoteCode);\n\n\t\t// Add user and touchInterval to store\n\t\tthis._online[remoteCode].user = user;\n\t\tthis._online[remoteCode].touchInterval = touchInterval;\n\n\t\t// Flush cached messages\n\t\t// Do this at the end in case any of the messages are \"exit\" messages\n\t\twhile (!cache.isEmpty()) {\n\t\t\tconst message = cache.dequeue();\n\t\t\tmlog.trace(\"Flushing message:\", remoteCode, message[0]);\n\t\t\tif (/exit/.test(message[0])) log.warn(\"Exit message:\", message[1]);\n\t\t\tthis.emit(\"message\", remoteCode, message[0], message[1]);\n\t\t}\n\t}\n\n\tget(sessCode) {\n\t\t// Look up session\n\t\tconst meta = this._online[sessCode];\n\t\tif (!meta) return null;\n\n\t\t// Return it\n\t\treturn meta.session;\n\t}\n\n\tdestroy(sessCode, reason) {\n\t\t// Look up session\n\t\tconst meta = this._online[sessCode];\n\t\tif (!meta) {\n\t\t\tif (/Shell Exited/.test(reason)) return;\n\t\t\telse return log.warn(\"Cannot find session to destroy:\", sessCode, reason);\n\t\t}\n\n\t\t// Destroy the session\n\t\tconst session = meta.session;\n\t\tsession.destroy((err) => {\n\t\t\tif (err) {\n\t\t\t\tlog.error(\"Error destroying session:\", sessCode, err);\n\t\t\t}\n\t\t\tthis.emit(\"destroy-done\", sessCode);\n\t\t}, reason);\n\n\t\t// Send destroy-u message\n\t\tthis.emit(\"destroy-u\", sessCode, reason);\n\n\t\t// Dereference pointers\n\t\tmeta.session = null;\n\t\tmeta.cache = null;\n\t\tmeta.user = null;\n\t\tclearInterval(meta.touchInterval);\n\n\t\t// Remove it from the index\n\t\tdelete this._online[sessCode];\n\t\tlog.debug(\"Removed session from index\", sessCode);\n\t\tthis.recordMetrics();\n\t}\n\n\tstartPool() {\n\t\tif (this._poolVar && this._poolVar.enabled) {\n\t\t\tthrow new Error(\"Another pool is already running\");\n\t\t}\n\n\t\t// I made poolVar a local variable so that there will be one instance for each startPool closure.  Each pool creation loop should be independent from any other pool creation loops that might start or stop.  (Potential problem that this approach prevents: if a session is in the middle of creating, and the pool is disabled and then immediately enabled again, the first pool might not be destroyed if the \"pool enabled\" variable were singleton.)\n\t\tlet poolVar = { enabled: true };\n\t\tthis._poolVar = poolVar;\n\n\t\tlet _poolCb = (localCode, session, cache, options) => {\n\t\t\t// If we need to disable the pool...\n\t\t\tif (!poolVar.enabled) {\n\t\t\t\tif (session) session.destroy(null, \"Pool Disabled\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If the session was created successfully...\n\t\t\tif (session) {\n\t\t\t\tif (!this._monitor_session) {\n\t\t\t\t\tlog.info(\"Created monitor session:\", localCode);\n\t\t\t\t\tthis._monitor_session = session;\n\t\t\t\t\tcache.enabled = false;\n\t\t\t\t\tcache.removeAll();\n\t\t\t\t} else {\n\t\t\t\t\tthis._pool[options.tier][localCode] = { session, cache };\n\t\t\t\t\tthis.recordMetrics();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we need to put another session in our pool...\n\t\t\tfor (let tier of Object.keys(this._pool)) {\n\t\t\t\tif (Object.keys(this._pool[tier]).length < this._poolSizes[tier]) {\n\t\t\t\t\tlog.trace(\"Creating new session in tier\", tier);\n\t\t\t\t\tthis._create(_poolCb, { tier });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// No more sessions were required.\n\t\t\tsetTimeout(_poolCb, config.sessionManager.poolInterval);\n\t\t};\n\t\tprocess.nextTick(_poolCb);\n\t}\n\n\tdisablePool() {\n\t\tif (!this._poolVar || !this._poolVar.enabled) return;\n\t\tthis._poolVar.enabled = false;\n\t\tObject.keys(this._pool).forEach((tier) => {\n\t\t\tObject.keys(this._pool[tier]).forEach((localCode) => {\n\t\t\t\tthis._pool[tier][localCode].session.destroy(null, \"Pool Disabled\");\n\t\t\t\tthis._pool[tier][localCode].session = null;\n\t\t\t\tthis._pool[tier][localCode].cache = null;\n\t\t\t\tdelete this._pool[tier][localCode];\n\t\t\t});\n\t\t});\n\t\tthis.recordMetrics();\n\t}\n\n\tterminate(reason) {\n\t\tthis.disablePool();\n\t\tclearInterval(this._logInterval);\n\t\tclearInterval(this._keepAliveInterval);\n\n\t\tif (this._monitor_session) {\n\t\t\tthis._monitor_session.destroy(null, reason);\n\t\t\tthis._monitor_session = null;\n\t\t}\n\n\t\tObject.keys(this._online).forEach((remoteCode) => {\n\t\t\tthis.destroy(remoteCode, reason);\n\t\t});\n\t}\n\n\trestart() {\n\t\tthis.startPool();\n\t\tthis._setup();\n\t}\n\n\trecordMetrics() {\n\t\tmetrics.gauge(\"oo.online_sessions\", Object.keys(this._online).length);\n\t\tObject.keys(this._pool).forEach((tier) => {\n\t\t\tmetrics.gauge(`oo.pool_sessions.${tier}`, Object.keys(this._pool[tier]).length);\n\t\t});\n\t}\n}\n\nmodule.exports = SessionManager;\n"
  },
  {
    "path": "back-octave/Makefile",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\nCC = gcc\nCFLAGS = -Wall -pthread -I/usr/local/include -L/usr/local/lib\nDEPS = \nOBJ = host.o\nLIBS = -luv -ljson-c\nEXE = octave-host\n\n%.o: %.c $(DEPS)\n\t$(CC) -c -o $@ $< $(CFLAGS)\n\n$(EXE): $(OBJ)\n\t$(CC) -o $@ $^ $(CFLAGS) $(LIBS)\n\ninstall: $(EXE)\n\tcp $(EXE) /usr/local/bin\n\nclean:\n\trm $(OBJ) $(EXE)\n\nrun: $(EXE)\n\t./$(EXE)\n"
  },
  {
    "path": "back-octave/README.md",
    "content": "Octave Online Server: GNU Octave Utilities\n==========================================\n\nThis directory contains machinery for talking to the GNU Octave process.\n\nSee [containers/README.md](../containers/README.md) for instructions on how to build the custom GNU Octave for Octave Online Server.\n\nThe *oo-changesets* directory contains patches against GNU Octave to add features required for Octave Online.  The primary feature added is a new flag `--json-sock`, which uses a UNIX Socket for passing messages back and forth between the Octave process and the outside world.\n\nThe file *host.c* is a thin GNU Octave wrapper process that creates the UNIX Socket for talking to GNU Octave.  It reads messages from STDIN and marshals them into the UNIX Socket, and when it receives a message from the socket, it prints the message to STDOUT.\n\nThe *Makefile* is used for building *host.c* into an executable.\n\nThe file *octaverc.m* is the default site-wide octaverc file for Octave Online Server.  It is installed either into the Docker instance or into the current local server via the *install-site-m* make target in the top-level directory.\n"
  },
  {
    "path": "back-octave/host.c",
    "content": "/*\r\n * Copyright © 2018, Octave Online LLC\r\n *\r\n * This file is part of Octave Online Server.\r\n *\r\n * Octave Online Server is free software: you can redistribute it and/or\r\n * modify it under the terms of the GNU Affero General Public License as\r\n * published by the Free Software Foundation, either version 3 of the License,\r\n * or (at your option) any later version.\r\n *\r\n * Octave Online Server is distributed in the hope that it will be useful, but\r\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\r\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\r\n * License for more details.\r\n *\r\n * You should have received a copy of the GNU Affero General Public License\r\n * along with Octave Online Server.  If not, see\r\n * <https://www.gnu.org/licenses/>.\r\n */\r\n\r\n#include <stdio.h>\r\n#include <stdlib.h>\r\n#include <inttypes.h>\r\n#include <string.h>\r\n#include <uv.h>\r\n#include <json-c/json.h>\r\n#include <unistd.h>\r\n\r\n// MACROS, CONSTANTS, GLOBALS, AND TYPEDEFS\r\n\r\nint r;\r\n#define CATCH(expr){ \\\r\n\tr = expr; \\\r\n\tif(r){ \\\r\n\t\tfprintf(stderr, \"@ %s (%d): \", __FILE__, __LINE__); \\\r\n\t\tfprintf(stderr, \"%d: %s\\n\", r, uv_strerror(r)); \\\r\n\t\tfflush(stderr); \\\r\n\t\treturn r; \\\r\n\t} \\\r\n}\r\n\r\n#define LOG(...){ \\\r\n\tfprintf(stderr, __VA_ARGS__); \\\r\n\tfprintf(stderr, \"\\n\"); \\\r\n\tfflush(stderr); \\\r\n}\r\n\r\n#define TMP_DIR_STRLEN 14\r\n#define TMP_DIR_FORMAT \"/tmp/octXXXXXX\"\r\n#define TMP_COM_STRLEN 19\r\n#define TMP_COM_FORMAT \"/tmp/octXXXXXX/sock\"\r\n\r\nuv_loop_t* loop = NULL;\r\nuv_tcp_t* sock_client = NULL;\r\n\r\nuv_process_t child_req;\r\nuv_process_options_t options;\r\nchar* tmp_path;\r\nchar* com_path;\r\nuv_pipe_t worker_com_p;\r\nuv_pipe_t worker_out_p;\r\nuv_pipe_t worker_err_p;\r\nuv_pipe_t host_in_p;\r\nuv_pipe_t host_out_p;\r\n\r\nuv_signal_t sigint_s;\r\nuv_signal_t sigterm_s;\r\nuv_signal_t sighup_s;\r\n\r\ntypedef struct {\r\n\tuv_write_t req;\r\n\tuv_buf_t buf;\r\n} write_req_t;\r\n\r\nenum STD_STREAM {\r\n\tSTD_STREAM_SOCKET_OUT,\r\n\tSTD_STREAM_SOCKET_ERR,\r\n\tSTD_STREAM_HOST_IN,\r\n\tSTD_STREAM_SOCKET\r\n};\r\n\r\n// UTILITY CALLBACKS\r\n\r\nvoid cb_walk(uv_handle_t* handle, void* arg) {\r\n\tuv_close(handle, NULL);\r\n}\r\n\r\nvoid cleanup() {\r\n\t// Close all remaining file descriptors\r\n\tuv_walk(loop, cb_walk, NULL);\r\n\r\n\t// End the loop\r\n\tuv_loop_close(loop);\r\n}\r\n\r\nvoid cb_cleanup_write_req(uv_write_t *req, int status) {\r\n\twrite_req_t *wr = (write_req_t*) req;\r\n\tfree(wr->buf.base);\r\n\tfree(wr);\r\n}\r\n\r\nvoid cb_alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {\r\n\t*buf = uv_buf_init((char*) malloc(suggested_size), suggested_size);\r\n}\r\n\r\nvoid cb_sigfwd(uv_signal_t *handle, int signum) {\r\n\tLOG(\"Forwarding signal %d\", signum);\r\n\tkill(child_req.pid, signum);\r\n}\r\n\r\nvoid cb_exit(uv_process_t* req, int64_t exit_status, int term_signal) {\r\n\tLOG(\"Process exited with status %\" PRId64 \", signal %d\", exit_status, term_signal);\r\n\tcleanup();\r\n}\r\n\r\n// UTILITY FUNCTIONS\r\n\r\n// write_to_socket and write_to_stdout copy the memory so that other parts of the program cannot asynchronously mess with the writing process.\r\n\r\nvoid write_to_socket(const char* str, size_t len) {\r\n\twrite_req_t* req = malloc(sizeof(write_req_t));\r\n\treq->buf = uv_buf_init((char*) malloc(len), len);\r\n\tmemcpy(req->buf.base, str, len);\r\n\tuv_write((uv_write_t*) req, (uv_stream_t*) sock_client, &req->buf, 1, cb_cleanup_write_req);\r\n}\r\n\r\nvoid write_to_stdout(const char* str, size_t len) {\r\n\twrite_req_t* req = malloc(sizeof(write_req_t));\r\n\treq->buf = uv_buf_init((char*) malloc(len), len);\r\n\tmemcpy(req->buf.base, str, len);\r\n\tuv_write((uv_write_t*) req, (uv_stream_t*) &host_out_p, &req->buf, 1, cb_cleanup_write_req);\r\n}\r\n\r\nvoid print_json_msg_str(const char* name, const char* str, size_t len) {\r\n\t// Make the object\r\n\tjson_object* nameobj = json_object_new_string(name);\r\n\tjson_object* strobj = json_object_new_string_len(str, len);\r\n\tjson_object* obj = json_object_new_array();\r\n\tjson_object_array_add(obj, nameobj);\r\n\tjson_object_array_add(obj, strobj);\r\n\r\n\t// Make the string and print it\r\n\tconst char* jstr = json_object_to_json_string(obj);\r\n\twrite_to_stdout(jstr, strlen(jstr));\r\n\r\n\t// Relese memory\r\n\t// jstr is automatically released along with obj: https://github.com/json-c/json-c/issues/83\r\n\tjson_object_put(obj);\r\n}\r\n\r\n// Process all messages received from the streams: child out, child err, stdin, and socket.\r\n//  - \"nread\" is the number of bytes in the output.\r\n//  - \"buf\" is the buffer, which was originally created by \"alloc_buffer\".\r\n//    It may be longer than \"nread\".\r\nvoid process_std_stream(enum STD_STREAM type, uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {\r\n\r\n\t// Is the stream closed?\r\n\tif (nread < 0) {\r\n\t\tif (nread == UV_EOF) {\r\n\t\t\tuv_close((uv_handle_t*) stream, NULL);\r\n\t\t}\r\n\t}\r\n\r\n\t// What to do with the data?\r\n\telse if (nread > 0) {\r\n\t\tswitch (type) {\r\n\r\n\t\t\tcase STD_STREAM_SOCKET_OUT:\r\n\t\t\t\tprint_json_msg_str(\"out\", buf->base, nread);\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase STD_STREAM_SOCKET_ERR:\r\n\t\t\t\tprint_json_msg_str(\"err\", buf->base, nread);\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase STD_STREAM_HOST_IN:\r\n\t\t\t\twrite_to_socket(buf->base, nread);\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase STD_STREAM_SOCKET:\r\n\t\t\t\twrite_to_stdout(buf->base, nread);\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tdefault:\r\n\t\t\t\tbreak;\r\n\r\n\t\t}\r\n\t}\r\n\r\n\t// Free memory (corresponding malloc: cb_alloc_buffer)\r\n\tif (buf->base) free(buf->base);\r\n}\r\n\r\n// MAIN CALLBACKS\r\n\r\nvoid cb_stdmsg(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {\r\n\tprocess_std_stream(STD_STREAM_SOCKET, stream, nread, buf);\r\n}\r\n\r\nvoid cb_stdout(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {\r\n\tprocess_std_stream(STD_STREAM_SOCKET_OUT, stream, nread, buf);\r\n}\r\n\r\nvoid cb_stderr(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {\r\n\tprocess_std_stream(STD_STREAM_SOCKET_ERR, stream, nread, buf);\r\n}\r\n\r\nvoid cb_stdin(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {\r\n\tprocess_std_stream(STD_STREAM_HOST_IN, stream, nread, buf);\r\n}\r\n\r\nvoid cb_connect(uv_stream_t* comm, int status) {\r\n\tif (status == -1) return;\r\n\tLOG(\"Connection received\");\r\n\r\n\t// Ignore connection if we already have one\r\n\tif (sock_client != NULL) return;\r\n\tsock_client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));\r\n\r\n\tuv_tcp_init(loop, sock_client);\r\n\tif (uv_accept(comm, (uv_stream_t*) sock_client) == 0) {\r\n\t\tuv_read_start((uv_stream_t*) sock_client, cb_alloc_buffer, cb_stdmsg);\r\n\t}\r\n\telse {\r\n\t\tuv_close((uv_handle_t*) sock_client, NULL);\r\n\t\tsock_client = NULL;\r\n\t}\r\n}\r\n\r\n// MAIN FUNCTION\r\n\r\nint main(int argc, char* argv[]) {\r\n\tsignal(SIGPIPE, SIG_IGN);\r\n\tloop = uv_default_loop();\r\n\r\n\ttmp_path = malloc(TMP_DIR_STRLEN); strcpy(tmp_path, TMP_DIR_FORMAT);\r\n\tcom_path = malloc(TMP_COM_STRLEN); strcpy(com_path, TMP_COM_FORMAT);\r\n\tmkdtemp(tmp_path);\r\n\tmemcpy(com_path, tmp_path, TMP_DIR_STRLEN);\r\n\tLOG(\"tmpdir: %s\", com_path);\r\n\t// FIXME: Delete the temp dir before the process exits.\r\n\r\n\tCATCH(uv_pipe_init(loop, &worker_com_p, 0));\r\n\tCATCH(uv_pipe_init(loop, &worker_out_p, 0));\r\n\tCATCH(uv_pipe_init(loop, &worker_err_p, 0));\r\n\tCATCH(uv_pipe_init(loop, &host_in_p, 0));\r\n\tCATCH(uv_pipe_init(loop, &host_out_p, 0));\r\n\tCATCH(uv_pipe_open(&host_in_p, 0));\r\n\tCATCH(uv_pipe_open(&host_out_p, 1));\r\n\r\n\tCATCH(uv_signal_init(loop, &sigint_s));\r\n\tCATCH(uv_signal_init(loop, &sigterm_s));\r\n\tCATCH(uv_signal_init(loop, &sighup_s));\r\n\r\n\t// Let the command line argument translate into \"--json-max-len\"\r\n\tchar* args[9];\r\n\targs[0] = \"octave\";\r\n\targs[1] = \"--json-sock\";\r\n\targs[2] = com_path;\r\n\targs[3] = \"--interactive\";\r\n\targs[4] = \"--quiet\";\r\n\targs[5] = \"--no-window-system\";\r\n\targs[6] = \"--json-max-len\";\r\n\tif (argc > 1) {\r\n\t\targs[7] = argv[1];\r\n\t} else {\r\n\t\targs[7] = \"0\";\r\n\t}\r\n\targs[8] = NULL;\r\n\r\n\toptions.exit_cb = cb_exit;\r\n\toptions.file = \"/usr/local/bin/octave\";\r\n\t// options.file = \"/vagrant/octave/octave/build-no-docs/run-octave\";\r\n\toptions.args = args;\r\n\r\n\toptions.stdio_count = 3;\r\n\tuv_stdio_container_t child_stdio[3];\r\n\tchild_stdio[0].flags = UV_IGNORE;\r\n\tchild_stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;\r\n\tchild_stdio[1].data.stream = (uv_stream_t*) &worker_out_p;\r\n\tchild_stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;\r\n\tchild_stdio[2].data.stream = (uv_stream_t*) &worker_err_p;\r\n\toptions.stdio = child_stdio;\r\n\r\n\tCATCH(uv_spawn(loop, &child_req, &options));\r\n\tCATCH(uv_pipe_bind(&worker_com_p, com_path));\r\n\tCATCH(uv_listen((uv_stream_t*) &worker_com_p, 128, cb_connect));\r\n\tCATCH(uv_read_start((uv_stream_t*) &worker_out_p, cb_alloc_buffer, cb_stdout));\r\n\tCATCH(uv_read_start((uv_stream_t*) &worker_err_p, cb_alloc_buffer, cb_stderr));\r\n\tCATCH(uv_read_start((uv_stream_t*) &host_in_p, cb_alloc_buffer, cb_stdin));\r\n\r\n\tCATCH(uv_signal_start(&sigint_s, cb_sigfwd, SIGINT));\r\n\tCATCH(uv_signal_start(&sigterm_s, cb_sigfwd, SIGTERM));\r\n\tCATCH(uv_signal_start(&sighup_s, cb_sigfwd, SIGHUP));\r\n\r\n\tLOG(\"Launched process with ID %d\", child_req.pid);\r\n\r\n\tfree(tmp_path);\r\n\tfree(com_path);\r\n\r\n\treturn uv_run(loop, UV_RUN_DEFAULT);\r\n}\r\n"
  },
  {
    "path": "back-octave/oo-changesets/000-README.md",
    "content": "Patch files from `001` until `100` are based on commit `323e92c4589f` in the GNU Octave mercurial repository, which is in the stable branch of version *4.1rc*.\n"
  },
  {
    "path": "back-octave/oo-changesets/001-d38b7c534496.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1453104462 21600\n#      Mon Jan 18 02:07:42 2016 -0600\n# Branch oo\n# Node ID d38b7c534496ca887f197901cf4fea75788ea579\n# Parent  323e92c4589f7a47d67033e689e61dfa510cf911\nAdding octave link binding \"request_input\".  When enabled, the binding will be called whenever the user is prompted for input, and it should return a string corresponding to what the user entered in response.  It should be enabled on an instance of octave_link by setting the private field _request_input_enabled=true.\n\ndiff -r 323e92c4589f -r d38b7c534496 libinterp/corefcn/input.cc\n--- a/libinterp/corefcn/input.cc\tSat Jan 16 18:14:35 2016 -0800\n+++ b/libinterp/corefcn/input.cc\tMon Jan 18 02:07:42 2016 -0600\n@@ -184,7 +184,11 @@\n \n   eof = false;\n \n-  std::string retval = command_editor::readline (s, eof);\n+  std::string retval;\n+  if (octave_link::request_input_enabled ())\n+    retval = octave_link::request_input (s);\n+  else\n+    retval = command_editor::readline (s, eof);\n \n   if (! eof && retval.empty ())\n     retval = \"\\n\";\ndiff -r 323e92c4589f -r d38b7c534496 libinterp/corefcn/octave-link.h\n--- a/libinterp/corefcn/octave-link.h\tSat Jan 16 18:14:35 2016 -0800\n+++ b/libinterp/corefcn/octave-link.h\tMon Jan 18 02:07:42 2016 -0600\n@@ -276,6 +276,13 @@\n       instance->do_post_input_event ();\n   }\n \n+  static std::string request_input (const std::string& prompt)\n+  {\n+    return request_input_enabled ()\n+      ? instance->do_request_input (prompt)\n+      : std::string ();\n+  }\n+\n   static void enter_debugger_event (const std::string& file, int line)\n   {\n     if (enabled ())\n@@ -323,6 +330,11 @@\n     return instance_ok () ? instance->link_enabled : false;\n   }\n \n+  static bool request_input_enabled (void)\n+  {\n+    return enabled () ? instance->_request_input_enabled : false;\n+  }\n+\n   static bool\n   show_preferences ()\n   {\n@@ -396,6 +408,9 @@\n   void do_entered_readline_hook (void) { }\n   void do_finished_readline_hook (void) { }\n \n+  bool _request_input_enabled;\n+  virtual std::string do_request_input (const std::string&) = 0;\n+\n   virtual bool do_confirm_shutdown (void) = 0;\n   virtual bool do_exit (int status) = 0;\n \n"
  },
  {
    "path": "back-octave/oo-changesets/002-d3de6023e846.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1453119478 21600\n#      Mon Jan 18 06:17:58 2016 -0600\n# Branch oo\n# Node ID d3de6023e84666d9db08e00e3417d2601d05989e\n# Parent  d38b7c534496ca887f197901cf4fea75788ea579\nAdding \"show_static_plot\" endpoint in octave link and synchronizing with GNUPlot when creating output files upon redraw plot commands.\n\ndiff -r d38b7c534496 -r d3de6023e846 libinterp/corefcn/octave-link.cc\n--- a/libinterp/corefcn/octave-link.cc\tMon Jan 18 02:07:42 2016 -0600\n+++ b/libinterp/corefcn/octave-link.cc\tMon Jan 18 06:17:58 2016 -0600\n@@ -448,5 +448,30 @@\n   return retval;\n }\n \n+DEFUN (__octave_link_plot_destination__, , ,\n+       \"-*- texinfo -*-\\n\\\n+@deftypefn {} {} __octave_link_plot_destination__ ()\\n\\\n+Undocumented internal function.\\n\\\n+@end deftypefn\")\n+{\n+  return octave_value (octave_link::plot_destination ());\n+}\n \n+DEFUN (__octave_link_show_static_plot__, args, ,\n+       \"-*- texinfo -*-\\n\\\n+@deftypefn {} {} __octave_link_show_static_plot__ (@var{term}, @var{content})\\n\\\n+Undocumented internal function.\\n\\\n+@end deftypefn\")\n+{\n+  if (args.length () != 2) {\n+    octave_value retval; return retval;  // v4.0\n+    // return ovl ();  // v4.1.0+\n+  }\n \n+  std::string term = args(0).string_value();\n+  std::string content = args(1).string_value();\n+  return octave_value (octave_link::show_static_plot (term, content));\n+}\n+\n+\n+\ndiff -r d38b7c534496 -r d3de6023e846 libinterp/corefcn/octave-link.h\n--- a/libinterp/corefcn/octave-link.h\tMon Jan 18 02:07:42 2016 -0600\n+++ b/libinterp/corefcn/octave-link.h\tMon Jan 18 06:17:58 2016 -0600\n@@ -335,6 +335,29 @@\n     return enabled () ? instance->_request_input_enabled : false;\n   }\n \n+  enum plot_destination_t {\n+    TERMINAL_ONLY = 0,\n+    STATIC_ONLY = 1,\n+    TERMINAL_AND_STATIC = 2\n+  };\n+\n+  static plot_destination_t plot_destination (void)\n+  {\n+    return enabled () ? instance->_plot_destination : TERMINAL_ONLY;\n+  }\n+\n+  static bool\n+  show_static_plot (const std::string& term, const std::string& content)\n+  {\n+    if (enabled ())\n+      {\n+        instance->do_show_static_plot (term, content);\n+        return true;\n+      }\n+    else\n+      return false;\n+  }\n+\n   static bool\n   show_preferences ()\n   {\n@@ -488,7 +511,11 @@\n \n   virtual void do_show_preferences (void) = 0;\n \n-  virtual void do_show_doc (const std::string &file) = 0;\n+  virtual void do_show_doc (const std::string& file) = 0;\n+\n+  plot_destination_t _plot_destination;\n+  virtual void do_show_static_plot (const std::string& term,\n+                                    const std::string& content) = 0;\n };\n \n #endif // OCTAVELINK_H\ndiff -r d38b7c534496 -r d3de6023e846 scripts/plot/util/__gnuplot_drawnow__.m\n--- a/scripts/plot/util/__gnuplot_drawnow__.m\tMon Jan 18 02:07:42 2016 -0600\n+++ b/scripts/plot/util/__gnuplot_drawnow__.m\tMon Jan 18 06:17:58 2016 -0600\n@@ -27,9 +27,75 @@\n \n   if (nargin < 1 || nargin > 5 || nargin == 2)\n     print_usage ();\n+\n+  elseif (nargin >= 3 && nargin <= 5)\n+    ## Write the plot to the given file (e.g., via the \"print\" command)\n+    __gnuplot_draw_to_file__ (h, term, file, mono, debug_file);\n+\n+  elseif (nargin == 1)\n+    ## Plot to terminal and/or static (e.g., via the \"plot\" command)\n+    plot_stream = get (h, \"__plot_stream__\");\n+    if (isempty (plot_stream))\n+      plot_stream = __gnuplot_open_stream__ (2, h);\n+      new_stream = true;\n+    else\n+      new_stream = false;\n+    endif\n+    term = gnuplot_default_term (plot_stream);\n+\n+    ## There are a few options for how we can proceed.\n+    ## In most cases, we will tell GNUPLOT to put the plot in its terminal.\n+    ## If we have no display, we want to use the \"dumb\" terminal.\n+    ## Octave Link may request that we send the plot as an event.\n+    ## The latter two cases require plotting to a temp file.\n+\n+    should_plot_to_terminal = (\n+      !strcmp (term, \"dumb\") && (\n+        __octave_link_plot_destination__ () == 0 ||\n+        __octave_link_plot_destination__ () == 2\n+      )\n+    );\n+\n+    if (should_plot_to_terminal)\n+      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n+      __go_draw_figure__ (h, plot_stream(1), enhanced, mono);\n+      fflush (plot_stream(1));\n+    endif\n+\n+    should_plot_to_temp_file = (\n+      strcmp (term, \"dumb\") ||\n+      __octave_link_plot_destination__ () == 1 ||\n+      __octave_link_plot_destination__ () == 2\n+    );\n+\n+    if (should_plot_to_temp_file)\n+      tmp_file = tempname ();\n+      __gnuplot_draw_to_file__ (h, term, tmp_file);\n+      pause (1);\n+\n+      ## Read the temp file into memory and then delete it\n+      fid = fopen (tmp_file, 'r');\n+      [a, count] = fscanf (fid, '%c', Inf);\n+      fclose (fid);\n+      unlink (tmp_file);\n+\n+      ## What to do with the plot data?\n+      if (count > 0)\n+        if (a(1) == 12)\n+          a = a(2:end);  # avoid ^L at the beginning\n+        endif\n+        if strcmp (term, \"dumb\")\n+          puts (a);\n+        else\n+          __octave_link_show_static_plot__ (term, a);\n+        endif\n+      endif\n+    endif\n+\n   endif\n+endfunction\n \n-  if (nargin >= 3 && nargin <= 5)\n+function __gnuplot_draw_to_file__ (h, term, file, mono = false, debug_file)\n     ## Produce various output formats, or redirect gnuplot stream to a\n     ## debug file.\n     plot_stream = [];\n@@ -65,45 +131,6 @@\n         fclose (fid);\n       endif\n     end_unwind_protect\n-  else  # nargin == 1\n-    ##  Graphics terminal for display.\n-    plot_stream = get (h, \"__plot_stream__\");\n-    if (isempty (plot_stream))\n-      plot_stream = __gnuplot_open_stream__ (2, h);\n-      new_stream = true;\n-    else\n-      new_stream = false;\n-    endif\n-    term = gnuplot_default_term (plot_stream);\n-    if (strcmp (term, \"dumb\"))\n-      ## popen2 eats stdout of gnuplot, use temporary file instead\n-      dumb_tmp_file = tempname ();\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h,\n-                                   term, dumb_tmp_file);\n-    else\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n-    endif\n-    __go_draw_figure__ (h, plot_stream(1), enhanced, mono);\n-    fflush (plot_stream(1));\n-    if (strcmp (term, \"dumb\"))\n-      fid = -1;\n-      while (fid < 0)\n-        pause (0.1);\n-        fid = fopen (dumb_tmp_file, 'r');\n-      endwhile\n-      ## reprint the plot on screen\n-      [a, count] = fscanf (fid, '%c', Inf);\n-      fclose (fid);\n-      if (count > 0)\n-        if (a(1) == 12)\n-          a = a(2:end);  # avoid ^L at the beginning\n-        endif\n-        puts (a);\n-      endif\n-      unlink (dumb_tmp_file);\n-    endif\n-  endif\n-\n endfunction\n \n function enhanced = gnuplot_set_term (plot_stream, new_stream, h, term, file)\n"
  },
  {
    "path": "back-octave/oo-changesets/003-4d28376c34a8.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1453120386 21600\n#      Mon Jan 18 06:33:06 2016 -0600\n# Branch oo\n# Node ID 4d28376c34a877ba9eb5aefb84282c5bdf72a277\n# Parent  d3de6023e84666d9db08e00e3417d2601d05989e\nAdding octave link endpoint clear_screen (for the \"clc\" command).\n\ndiff -r d3de6023e846 -r 4d28376c34a8 libgui/src/octave-qt-link.cc\n--- a/libgui/src/octave-qt-link.cc\tMon Jan 18 06:17:58 2016 -0600\n+++ b/libgui/src/octave-qt-link.cc\tMon Jan 18 06:33:06 2016 -0600\n@@ -509,6 +509,11 @@\n }\n \n void\n+octave_qt_link::do_clear_screen (void)\n+{\n+}\n+\n+void\n octave_qt_link::do_pre_input_event (void)\n {\n }\ndiff -r d3de6023e846 -r 4d28376c34a8 libgui/src/octave-qt-link.h\n--- a/libgui/src/octave-qt-link.h\tMon Jan 18 06:17:58 2016 -0600\n+++ b/libgui/src/octave-qt-link.h\tMon Jan 18 06:33:06 2016 -0600\n@@ -117,6 +117,8 @@\n   void do_append_history (const std::string& hist_entry);\n   void do_clear_history (void);\n \n+  void do_clear_screen (void);\n+\n   void do_pre_input_event (void);\n   void do_post_input_event (void);\n \ndiff -r d3de6023e846 -r 4d28376c34a8 libinterp/corefcn/octave-link.h\n--- a/libinterp/corefcn/octave-link.h\tMon Jan 18 06:17:58 2016 -0600\n+++ b/libinterp/corefcn/octave-link.h\tMon Jan 18 06:33:06 2016 -0600\n@@ -264,6 +264,12 @@\n       instance->do_clear_history ();\n   }\n \n+  static void clear_screen (void)\n+  {\n+    if (enabled ())\n+      instance->do_clear_screen ();\n+  }\n+\n   static void pre_input_event (void)\n   {\n     if (enabled ())\n@@ -492,6 +498,8 @@\n   virtual void do_append_history (const std::string& hist_entry) = 0;\n   virtual void do_clear_history (void) = 0;\n \n+  virtual void do_clear_screen (void) = 0;\n+\n   virtual void do_pre_input_event (void) = 0;\n   virtual void do_post_input_event (void) = 0;\n \ndiff -r d3de6023e846 -r 4d28376c34a8 libinterp/corefcn/sysdep.cc\n--- a/libinterp/corefcn/sysdep.cc\tMon Jan 18 06:17:58 2016 -0600\n+++ b/libinterp/corefcn/sysdep.cc\tMon Jan 18 06:33:06 2016 -0600\n@@ -75,6 +75,7 @@\n #include \"error.h\"\n #include \"input.h\"\n #include \"oct-obj.h\"\n+#include \"octave-link.h\"\n #include \"ov.h\"\n #include \"pager.h\"\n #include \"parse.h\"\n@@ -596,6 +597,8 @@\n {\n   bool skip_redisplay = true;\n \n+  octave_link::clear_screen ();\n+\n   command_editor::clear_screen (skip_redisplay);\n \n   return octave_value_list ();\n"
  },
  {
    "path": "back-octave/oo-changesets/004-6ff3e34eea77.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1453120461 21600\n#      Mon Jan 18 06:34:21 2016 -0600\n# Branch oo\n# Node ID 6ff3e34eea77bae35e07007cbd7a1a17e7fedacc\n# Parent  4d28376c34a877ba9eb5aefb84282c5bdf72a277\nAdding support for condition variables to octave_mutex.\n\ndiff -r 4d28376c34a8 -r 6ff3e34eea77 liboctave/util/oct-mutex.cc\n--- a/liboctave/util/oct-mutex.cc\tMon Jan 18 06:33:06 2016 -0600\n+++ b/liboctave/util/oct-mutex.cc\tMon Jan 18 06:34:21 2016 -0600\n@@ -53,6 +53,18 @@\n   return false;\n }\n \n+void\n+octave_base_mutex::cond_wait (void)\n+{\n+  (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+}\n+\n+void\n+octave_base_mutex::cond_signal (void)\n+{\n+  (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+}\n+\n #if defined (__WIN32__) && ! defined (__CYGWIN__)\n \n class\n@@ -63,11 +75,13 @@\n     : octave_base_mutex ()\n   {\n     InitializeCriticalSection (&cs);\n+    InitializeConditionVariable (&cv);\n   }\n \n   ~octave_w32_mutex (void)\n   {\n     DeleteCriticalSection (&cs);\n+    // no need to delete cv: http://stackoverflow.com/a/28981408/1407170\n   }\n \n   void lock (void)\n@@ -85,8 +99,21 @@\n     return (TryEnterCriticalSection (&cs) != 0);\n   }\n \n+  void cond_wait (void)\n+  {\n+    SleepConditionVariableCS (&cv, &cs, INFINITE);\n+  }\n+\n+  void cond_signal (void)\n+  {\n+    WakeConditionVariable (&cv);\n+  }\n+\n+  void \n+\n private:\n   CRITICAL_SECTION cs;\n+  CONDITION_VARIABLE cv;\n };\n \n static DWORD octave_thread_id = 0;\n@@ -118,11 +145,18 @@\n     pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);\n     pthread_mutex_init (&pm, &attr);\n     pthread_mutexattr_destroy (&attr);\n+\n+    pthread_condattr_t condattr;\n+\n+    pthread_condattr_init (&condattr);\n+    pthread_cond_init (&condv, &condattr);\n+    pthread_condattr_destroy (&condattr);\n   }\n \n   ~octave_pthread_mutex (void)\n   {\n     pthread_mutex_destroy (&pm);\n+    pthread_cond_destroy (&condv);\n   }\n \n   void lock (void)\n@@ -140,8 +174,20 @@\n     return (pthread_mutex_trylock (&pm) == 0);\n   }\n \n+  void cond_wait (void)\n+  {\n+    pthread_cond_wait (&condv, &pm);\n+  }\n+\n+  void cond_signal (void)\n+  {\n+    pthread_cond_signal (&condv);\n+  }\n+\n private:\n   pthread_mutex_t pm;\n+  pthread_cond_t condv;\n+\n };\n \n static pthread_t octave_thread_id = 0;\ndiff -r 4d28376c34a8 -r 6ff3e34eea77 liboctave/util/oct-mutex.h\n--- a/liboctave/util/oct-mutex.h\tMon Jan 18 06:33:06 2016 -0600\n+++ b/liboctave/util/oct-mutex.h\tMon Jan 18 06:34:21 2016 -0600\n@@ -43,6 +43,10 @@\n \n   virtual bool try_lock (void);\n \n+  virtual void cond_wait (void);\n+\n+  virtual void cond_signal (void);\n+\n private:\n   octave_refcount<int> count;\n };\n@@ -95,6 +99,16 @@\n     return rep->try_lock ();\n   }\n \n+  void cond_wait (void)\n+  {\n+    rep->cond_wait ();\n+  }\n+\n+  void cond_signal (void)\n+  {\n+    rep->cond_signal ();\n+  }\n+\n protected:\n   octave_base_mutex *rep;\n };\n"
  },
  {
    "path": "back-octave/oo-changesets/005-9e73fe0d92d5.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1453121851 21600\n#      Mon Jan 18 06:57:31 2016 -0600\n# Branch oo\n# Node ID 9e73fe0d92d5817002f67034983af5b557f874be\n# Parent  6ff3e34eea77bae35e07007cbd7a1a17e7fedacc\nChanging definition of \"isguirunning()\" to \"octave_link::enabled()\".  This enables triggering octave link endpoints by implementations of octave link other than the GUI.\n\ndiff -r 6ff3e34eea77 -r 9e73fe0d92d5 libinterp/octave.cc\n--- a/libinterp/octave.cc\tMon Jan 18 06:34:21 2016 -0600\n+++ b/libinterp/octave.cc\tMon Jan 18 06:57:31 2016 -0600\n@@ -65,6 +65,7 @@\n #include \"oct-map.h\"\n #include \"oct-mutex.h\"\n #include \"oct-obj.h\"\n+#include \"octave-link.h\"\n #include \"ops.h\"\n #include \"options-usage.h\"\n #include \"ov.h\"\n@@ -989,7 +990,7 @@\n   octave_value retval;\n \n   if (args.length () == 0)\n-    retval = start_gui;\n+    retval = octave_link::enabled ();\n   else\n     print_usage ();\n \n"
  },
  {
    "path": "back-octave/oo-changesets/006-15d21ceec728.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1453127653 21600\n#      Mon Jan 18 08:34:13 2016 -0600\n# Branch oo\n# Node ID 15d21ceec7282b94db2d86503127d03730d5c316\n# Parent  9e73fe0d92d5817002f67034983af5b557f874be\nAdding a \"--json-sock\" command line option.  With this option, Octave will publish all octave link messages as JSON objects to the specified UNIX domain socket.\n\ndiff -r 9e73fe0d92d5 -r 15d21ceec728 libinterp/corefcn/json-main.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.cc\tMon Jan 18 08:34:13 2016 -0600\n@@ -0,0 +1,79 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include \"json-main.h\"\r\n+\r\n+#include <iostream>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <unistd.h>\r\n+\r\n+\r\n+// Analog of main-window.cc\r\n+// TODO: Think more about concurrency and null pointer exceptions\r\n+\r\n+void* run_loop_pthread(void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->run_loop();\r\n+  return NULL;\r\n+}\r\n+\r\n+void json_object_cb(std::string name, JSON_OBJECT_T jobj, void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->process_json_object(name, jobj);\r\n+}\r\n+\r\n+json_main::json_main(const std::string& json_sock_path)\r\n+  : _json_sock_path (json_sock_path),\r\n+    _loop_thread_active (false),\r\n+    _octave_json_link (this)\r\n+{\r\n+  // Enable octave_json_link instance\r\n+\toctave_link::connect_link(&_octave_json_link);\r\n+\r\n+  // Open UNIX socket file descriptor\r\n+  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\r\n+  struct sockaddr_un addr;\r\n+  memset(&addr, 0, sizeof(addr));\r\n+  addr.sun_family = AF_UNIX;\r\n+  memcpy(&addr.sun_path, _json_sock_path.c_str(), sizeof(addr.sun_path)-1);\r\n+  connect(\r\n+    sockfd,\r\n+    reinterpret_cast<struct sockaddr*>(&addr),\r\n+    sizeof(addr));\r\n+}\r\n+\r\n+json_main::~json_main(void) {\r\n+  close(sockfd);\r\n+\r\n+  // TODO: Stop the _loop_thread\r\n+}\r\n+\r\n+void json_main::publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  std::string jstr = json_util::to_message(name, jobj);\r\n+  send(sockfd, jstr.c_str(), jstr.length(), 0);\r\n+}\r\n+\r\n+void json_main::run_loop_on_new_thread(void) {\r\n+  if (_loop_thread_active)\r\n+    perror(\"won't run JSON socket loop multiple times\");\r\n+  _loop_thread_active = true;\r\n+\r\n+  pthread_create(\r\n+    &_loop_thread,\r\n+    NULL,\r\n+    run_loop_pthread,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::run_loop(void) {\r\n+  json_util::read_stream(\r\n+    sockfd,\r\n+    json_object_cb,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::process_json_object(std::string name, JSON_OBJECT_T jobj) {\r\n+  _octave_json_link.receive_message(name, jobj);\r\n+}\r\ndiff -r 9e73fe0d92d5 -r 15d21ceec728 libinterp/corefcn/json-main.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.h\tMon Jan 18 08:34:13 2016 -0600\n@@ -0,0 +1,29 @@\n+#ifndef json_main_h\r\n+#define json_main_h\r\n+\r\n+#include <queue>\r\n+#include <pthread.h>\r\n+#include <stdio.h>\r\n+\r\n+#include \"octave-json-link.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+class json_main {\r\n+public:\r\n+\tjson_main(const std::string& json_sock_path);\r\n+\t~json_main(void);\r\n+\r\n+\tvoid publish_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\tvoid run_loop_on_new_thread(void);\r\n+\tvoid run_loop(void);\r\n+\tvoid process_json_object(std::string name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+\tstd::string _json_sock_path;\r\n+\tint sockfd;\r\n+\tbool _loop_thread_active;\r\n+\tpthread_t _loop_thread;\r\n+\toctave_json_link _octave_json_link;\r\n+};\r\n+\r\n+#endif\r\ndiff -r 9e73fe0d92d5 -r 15d21ceec728 libinterp/corefcn/json-util.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.cc\tMon Jan 18 08:34:13 2016 -0600\n@@ -0,0 +1,226 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <cstdlib>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <stdio.h>\r\n+#include <json-c/arraylist.h>\r\n+\r\n+#include \"str-vec.h\"\r\n+\r\n+#include \"json-util.h\"\r\n+\r\n+JSON_OBJECT_T json_util::from_string(const std::string& str) {\r\n+\treturn json_object_new_string(str.c_str());\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int(int i) {\r\n+\treturn json_object_new_int(i);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float(float flt) {\r\n+\treturn json_object_new_double(flt);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_boolean(bool b) {\r\n+\treturn json_object_new_boolean(b);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::empty() {\r\n+\treturn json_object_new_object();\r\n+}\r\n+\r\n+template<typename T>\r\n+JSON_OBJECT_T json_object_from_list(const std::list<T>& list, JSON_OBJECT_T (*convert)(T)) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\ttypename std::list<T>::const_iterator it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, convert(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_list(const std::list<std::string>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_vector(const string_vector& vect) {\r\n+\t// TODO: Make sure this function does what it's supposed to do\r\n+\tstd::list<std::string> list;\r\n+\tfor (int i = 0; i < vect.numel(); ++i) {\r\n+\t\tlist.push_back(vect[i]);\r\n+\t}\r\n+\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int_list(const std::list<int>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_int);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float_list(const std::list<float>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_float);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_workspace_list(const std::list<workspace_element>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_workspace_element);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_filter_list(const octave_link::filter_list& list) {\r\n+\treturn json_object_from_list(list, json_util::from_pair);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_value_string(const std::string str) {\r\n+\treturn json_util::from_string(str);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_workspace_element(workspace_element element) {\r\n+\tJSON_MAP_T m;\r\n+\tm[\"scope\"] = json_util::from_int(element.scope());\r\n+\tm[\"symbol\"] = json_util::from_string(element.symbol());\r\n+\tm[\"class_name\"] = json_util::from_string(element.class_name());\r\n+\tm[\"dimension\"] = json_util::from_string(element.dimension());\r\n+\tm[\"value\"] = json_util::from_string(element.value());\r\n+\tm[\"complex_flag\"] = json_util::from_boolean(element.complex_flag());\r\n+\treturn json_util::from_map(m);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_pair(std::pair<std::string, std::string> pair) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tjson_object_array_add(jobj, json_object_new_string(pair.first.c_str()));\r\n+\tjson_object_array_add(jobj, json_object_new_string(pair.second.c_str()));\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_map(JSON_MAP_T m) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_object();\r\n+\tfor(\r\n+\t\tstd::map<std::string, JSON_OBJECT_T>::iterator it = m.begin();\r\n+\t\tit != m.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_object_add(jobj, it->first.c_str(), it->second);\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+std::string json_util::to_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  JSON_OBJECT_T jmsg = json_object_new_array();\r\n+  json_object_array_add(jmsg, json_util::from_string(name));\r\n+  json_object_array_add(jmsg, jobj);\r\n+  std::string str (json_object_to_json_string(jmsg));\r\n+  return str;\r\n+}\r\n+\r\n+std::string json_util::to_string(JSON_OBJECT_T jobj) {\r\n+  return std::string(json_object_get_string(jobj));\r\n+}\r\n+\r\n+template<typename T>\r\n+std::list<T> json_object_to_list(JSON_OBJECT_T jobj, T (*convert)(JSON_OBJECT_T)) {\r\n+\tstd::list<T> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tfor (int i = 0; i < array_list_length(arr); ++i) {\r\n+\t\tJSON_OBJECT_T jsub = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, i));\r\n+\t\tret.push_back(convert(jsub));\r\n+\t}\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> json_util::to_int_list_int_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<std::list<int>, int> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_to_list<int>(first, json_util::to_int);\r\n+\tret.second = json_object_get_int(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::list<std::string> json_util::to_string_list(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_to_list<std::string>(jobj, json_util::to_string);\r\n+}\r\n+\r\n+int json_util::to_int(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_int(jobj);\r\n+}\r\n+\r\n+bool json_util::to_boolean(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_boolean(jobj);\r\n+}\r\n+\r\n+void json_util::read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+\r\n+\t// Make some local variables\r\n+\tint BUF_LEN = 24;\r\n+\tchar* buf = new char[BUF_LEN];  // buffer for socket read\r\n+\tint buf_len;  // length of new bytes in the buffer\r\n+\tint buf_offset;  // offset of the JSON parser in the buffer\r\n+\tJSON_OBJECT_T jobj;  // pointer to parsed JSON object\r\n+\tjson_tokener* tok = json_tokener_new();  // JSON tokenizer instance\r\n+\tenum json_tokener_error jerr;  // status of JSON tokenizer\r\n+\r\n+\t// Start the blocking I/O loop\r\n+\twhile( (buf_len=recv(sockfd, buf, BUF_LEN, 0)) > 0) {\r\n+\t\tbuf_offset = 0;\r\n+\t\twhile(buf_offset < buf_len){\r\n+\t\t\tjobj = json_tokener_parse_ex(tok, buf + buf_offset, buf_len - buf_offset);\r\n+\t\t\tjerr = json_tokener_get_error(tok);\r\n+\t\t\tbuf_offset += tok->char_offset;\r\n+\r\n+\t\t\t// Do we need more material in order to make JSON?\r\n+\t\t\tif (jerr == json_tokener_continue) {\r\n+\t\t\t\tcontinue;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Make a new tokenizer\r\n+\t\t\tjson_tokener_free(tok);\r\n+\t\t\ttok = json_tokener_new();\r\n+\r\n+\t\t\t// Did we encounter a malformed JSON object?\r\n+\t\t\tif (jerr != json_tokener_success) {\r\n+\t\t\t\tfprintf(stderr,\r\n+\t\t\t\t\t\"JSON parse error: %s: '%.*s'\\n\",\r\n+\t\t\t\t\tjson_tokener_error_desc(jerr),\r\n+\t\t\t\t\t1,\r\n+\t\t\t\t\tbuf + buf_offset);\r\n+\t\t\t\tfflush(stderr);\r\n+\t\t\t\tbreak;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Our object is ready\r\n+\t\t\tprocess_message(jobj, cb, arg);\r\n+\t\t}\r\n+\t}\r\n+\r\n+\tjson_tokener_free(tok);\r\n+\tdelete buf;\r\n+}\r\n+\r\n+void json_util::process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+  if (!json_object_is_type(jobj, json_type_array))\r\n+    return;\r\n+  if (json_object_array_length(jobj) != 2)\r\n+    return;\r\n+\r\n+  cb(\r\n+  \tjson_util::to_string(json_object_array_get_idx(jobj, 0)),\r\n+  \tjson_object_array_get_idx(jobj, 1),\r\n+  \targ\r\n+  );\r\n+}\r\ndiff -r 9e73fe0d92d5 -r 15d21ceec728 libinterp/corefcn/json-util.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.h\tMon Jan 18 08:34:13 2016 -0600\n@@ -0,0 +1,55 @@\n+#ifndef json_util_h\r\n+#define json_util_h\r\n+\r\n+#include <json-c/json.h>\r\n+#include <map>\r\n+#include <list>\r\n+\r\n+#include \"workspace-element.h\"\r\n+#include \"octave-link.h\"\r\n+\r\n+class string_vector;\r\n+\r\n+// All of the code interacting with the external JSON library should be in\r\n+// the json-util.h and json-util.cc files.  This way, if we want to change\r\n+// the external JSON library, we can do it all in one place.\r\n+\r\n+#define JSON_OBJECT_T json_object*\r\n+#define JSON_MAP_T std::map<std::string, JSON_OBJECT_T>\r\n+\r\n+class json_util {\r\n+public:\r\n+\tstatic JSON_OBJECT_T from_string(const std::string& str);\r\n+\tstatic JSON_OBJECT_T from_int(int i);\r\n+\tstatic JSON_OBJECT_T from_float(float flt);\r\n+\tstatic JSON_OBJECT_T from_boolean(bool b);\r\n+\tstatic JSON_OBJECT_T empty();\r\n+\r\n+\tstatic JSON_OBJECT_T from_string_list(const std::list<std::string>& list);\r\n+\tstatic JSON_OBJECT_T from_string_vector(const string_vector& list);\r\n+\tstatic JSON_OBJECT_T from_int_list(const std::list<int>& list);\r\n+\tstatic JSON_OBJECT_T from_float_list(const std::list<float>& list);\r\n+\tstatic JSON_OBJECT_T from_workspace_list(const std::list<workspace_element>& list);\r\n+\tstatic JSON_OBJECT_T from_filter_list(const octave_link::filter_list& list);\r\n+\r\n+\tstatic JSON_OBJECT_T from_value_string(const std::string str);\r\n+\tstatic JSON_OBJECT_T from_workspace_element(workspace_element element);\r\n+\tstatic JSON_OBJECT_T from_pair(std::pair<std::string, std::string> pair);\r\n+\r\n+\tstatic JSON_OBJECT_T from_map(JSON_MAP_T m);\r\n+\r\n+\tstatic std::string to_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic std::string to_string(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<std::list<int>, int> to_int_list_int_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::list<std::string> to_string_list(JSON_OBJECT_T jobj);\r\n+\tstatic int to_int(JSON_OBJECT_T jobj);\r\n+\tstatic bool to_boolean(JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic void read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+\r\n+private:\r\n+\tstatic void process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+};\r\n+\r\n+#endif\r\ndiff -r 9e73fe0d92d5 -r 15d21ceec728 libinterp/corefcn/module.mk\n--- a/libinterp/corefcn/module.mk\tMon Jan 18 06:57:31 2016 -0600\n+++ b/libinterp/corefcn/module.mk\tMon Jan 18 08:34:13 2016 -0600\n@@ -66,6 +66,8 @@\n   corefcn/help.h \\\n   corefcn/hook-fcn.h \\\n   corefcn/input.h \\\n+  corefcn/json-main.h \\\n+  corefcn/json-util.h \\\n   corefcn/load-path.h \\\n   corefcn/load-save.h \\\n   corefcn/ls-ascii-helper.h \\\n@@ -95,6 +97,7 @@\n   corefcn/oct-strstrm.h \\\n   corefcn/oct.h \\\n   corefcn/octave-default-image.h \\\n+  corefcn/octave-json-link.h \\\n   corefcn/octave-link.h \\\n   corefcn/pager.h \\\n   corefcn/pr-output.h \\\n@@ -192,6 +195,8 @@\n   corefcn/hex2num.cc \\\n   corefcn/hook-fcn.cc \\\n   corefcn/input.cc \\\n+  corefcn/json-main.cc \\\n+  corefcn/json-util.cc \\\n   corefcn/inv.cc \\\n   corefcn/kron.cc \\\n   corefcn/load-path.cc \\\n@@ -226,6 +231,7 @@\n   corefcn/oct-procbuf.cc \\\n   corefcn/oct-stream.cc \\\n   corefcn/oct-strstrm.cc \\\n+  corefcn/octave-json-link.cc \\\n   corefcn/octave-link.cc \\\n   corefcn/ordschur.cc \\\n   corefcn/pager.cc \\\ndiff -r 9e73fe0d92d5 -r 15d21ceec728 libinterp/corefcn/octave-json-link.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.cc\tMon Jan 18 08:34:13 2016 -0600\n@@ -0,0 +1,338 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <iostream>\r\n+#include \"octave-json-link.h\"\r\n+#include \"workspace-element.h\"\r\n+#include \"json-main.h\"\r\n+\r\n+// MAP_SET(m, foo, string)\r\n+//   => m[\"foo\"] = json_util::from_string(foo);\r\n+#define MAP_SET(M, FIELD, TYPE){ \\\r\n+\tm[#FIELD] = json_util::from_##TYPE (FIELD); \\\r\n+}\r\n+\r\n+octave_json_link::octave_json_link(json_main* __json_main)\r\n+\t: octave_link (),\r\n+\t\t_json_main (__json_main)\r\n+{\r\n+\t_request_input_enabled = true;\r\n+\t_plot_destination = STATIC_ONLY;\r\n+}\r\n+\r\n+octave_json_link::~octave_json_link(void) { }\r\n+\r\n+std::string octave_json_link::do_request_input(const std::string& prompt) {\r\n+\t// Triggered whenever the console prompts for user input\r\n+\t_publish_message(\"request-input\", json_util::from_string(prompt));\r\n+\r\n+\treturn request_input_queue.dequeue();\r\n+}\r\n+\r\n+bool octave_json_link::do_confirm_shutdown(void) {\r\n+\t// Triggered when the kernel tries to exit\r\n+\t_publish_message(\"confirm-shutdown\", json_util::empty());\r\n+\r\n+\treturn confirm_shutdown_queue.dequeue();\r\n+}\r\n+\r\n+bool octave_json_link::do_exit(int status) {\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, status, int);\r\n+\t_publish_message(\"exit\", json_util::from_map(m));\r\n+\r\n+\t// It is our responsibility in octave_link to call exit. If we don't, then\r\n+\t// the kernel waits for 24 hours expecting us to do something.\r\n+\t::exit(status);\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::do_copy_image_to_clipboard(const std::string& file) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, file, string);\r\n+\t_publish_message(\"copy-image-to-clipboard\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::do_edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for existing files\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, file, string);\r\n+\t_publish_message(\"edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::do_prompt_new_edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for new files\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, file, string);\r\n+\t_publish_message(\"prompt-new-edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn prompt_new_edit_file_queue.dequeue();\r\n+}\r\n+\r\n+int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {\r\n+\t// Triggered in \"msgbox\", \"helpdlg\", and \"errordlg\", among others\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\r\n+\tMAP_SET(m, msg, string);\r\n+\tMAP_SET(m, title, string);\r\n+\t_publish_message(\"message-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn message_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::string octave_json_link::do_question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n+\t// Triggered in \"questdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, msg, string);\r\n+\tMAP_SET(m, title, string);\r\n+\tMAP_SET(m, btn1, string);\r\n+\tMAP_SET(m, btn2, string);\r\n+\tMAP_SET(m, btn3, string);\r\n+\tMAP_SET(m, btndef, string);\r\n+\t_publish_message(\"question-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn question_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> octave_json_link::do_list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {\r\n+\t// Triggered in \"listdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, list, string_list);\r\n+\tMAP_SET(m, mode, string);\r\n+\tMAP_SET(m, width, int);\r\n+\tMAP_SET(m, height, int);\r\n+\tMAP_SET(m, initial_value, int_list);\r\n+\tMAP_SET(m, name, string);\r\n+\tMAP_SET(m, prompt, string_list);\r\n+\tMAP_SET(m, ok_string, string);\r\n+\tMAP_SET(m, cancel_string, string);\r\n+\t_publish_message(\"list-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn list_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::do_input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {\r\n+\t// Triggered in \"inputdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, prompt, string_list);\r\n+\tMAP_SET(m, title, string);\r\n+\tMAP_SET(m, nr, float_list);\r\n+\tMAP_SET(m, nc, float_list);\r\n+\tMAP_SET(m, defaults, string_list);\r\n+\t_publish_message(\"input-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn input_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::do_file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {\r\n+\t// Triggered in \"uiputfile\", \"uigetfile\", and \"uigetdir\"\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, filter, filter_list);\r\n+\tMAP_SET(m, title, string);\r\n+\tMAP_SET(m, filename, string);\r\n+\tMAP_SET(m, pathname, string);\r\n+\tMAP_SET(m, multimode, string);\r\n+\t_publish_message(\"file-dialog\", json_util::from_map(m));\r\n+\t\r\n+\treturn file_dialog_queue.dequeue();\r\n+}\r\n+\r\n+int octave_json_link::do_debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\r\n+\t// This endpoint might be unused?  (No references)\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, file, string);\r\n+\tMAP_SET(m, dir, string);\r\n+\tMAP_SET(m, addpath_option, boolean);\r\n+\t_publish_message(\"debug-cd-or-addpath-error\", json_util::from_map(m));\r\n+\r\n+\treturn debug_cd_or_addpath_error_queue.dequeue();\r\n+}\r\n+\r\n+void octave_json_link::do_change_directory(const std::string& dir) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, dir, string);\r\n+\t_publish_message(\"change-directory\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_execute_command_in_terminal(const std::string& command) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, command, string);\r\n+\t_publish_message(\"execute-command-in-terminal\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_set_workspace(bool top_level, bool debug, const std::list<workspace_element>& ws) {\r\n+\t// Triggered on every new line entry\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, top_level, boolean);\r\n+\tMAP_SET(m, debug, boolean);\r\n+\tMAP_SET(m, ws, workspace_list);\r\n+\t_publish_message(\"set-workspace\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_clear_workspace(void) {\r\n+\t// Triggered on \"clear\" command (but not \"clear all\" or \"clear foo\")\r\n+\t_publish_message(\"clear-workspace\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_set_history(const string_vector& hist) {\r\n+\t// Called at startup, possibly more?\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, hist, string_vector);\r\n+\t_publish_message(\"set-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_append_history(const std::string& hist_entry) {\r\n+\t// Appears to be tied to readline, if available\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, hist_entry, string);\r\n+\t_publish_message(\"append-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_clear_history(void) {\r\n+\t// Appears to be tied to readline, if available\r\n+\t_publish_message(\"clear-history\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_clear_screen(void) {\r\n+\t// Triggered by clc\r\n+\t_publish_message(\"clear-screen\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_pre_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::do_post_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::do_enter_debugger_event(const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, file, string);\r\n+\tMAP_SET(m, line, int);\r\n+\t_publish_message(\"enter-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_execute_in_debugger_event(const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, file, string);\r\n+\tMAP_SET(m, line, int);\r\n+\t_publish_message(\"execute-in-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_exit_debugger_event(void) {\r\n+\t_publish_message(\"exit-debugger-event\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_update_breakpoint(bool insert, const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, insert, boolean);\r\n+\tMAP_SET(m, file, string);\r\n+\tMAP_SET(m, line, int);\r\n+\t_publish_message(\"update-breakpoint\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {\r\n+\t// Triggered upon interpreter startup\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, ps1, string);\r\n+\tMAP_SET(m, ps2, string);\r\n+\tMAP_SET(m, ps4, string);\r\n+\t_publish_message(\"set-default-prompts\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_show_preferences(void) {\r\n+\t// Triggered on \"preferences\" command\r\n+\t_publish_message(\"show-preferences\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_show_doc(const std::string& file) {\r\n+\t// Triggered on \"doc\" command\r\n+\t_publish_message(\"show-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::do_show_static_plot(const std::string& term, const std::string& content) {\r\n+\t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\r\n+\tJSON_MAP_T m;\r\n+\tMAP_SET(m, term, string);\r\n+\tMAP_SET(m, content, string);\r\n+\t_publish_message(\"show-static-plot\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::receive_message (const std::string& name, JSON_OBJECT_T jobj) {\r\n+\tif (name == \"cmd\" || name == \"request-input-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\trequest_input_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"confirm-shutdown-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tconfirm_shutdown_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"prompt-new-edit-file-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tprompt_new_edit_file_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"message-dialog-answer\"){\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tmessage_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"question-dialog-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\tquestion_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"list-dialog-answer\") {\r\n+\t\tstd::pair<std::list<int>, int> answer = json_util::to_int_list_int_pair(jobj);\r\n+\t\tlist_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"input-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tinput_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"file-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tfile_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"debug-cd-or-addpath-error-answer\") {\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tdebug_cd_or_addpath_error_queue.enqueue(answer);\r\n+\t}\r\n+\telse {\r\n+\t\tstd::cerr << \"warning: received unknown message: \" << name << std::endl;\r\n+\t}\r\n+}\r\n+\r\n+void octave_json_link::_publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+\t_json_main->publish_message(name, jobj);\r\n+}\r\n+\r\ndiff -r 9e73fe0d92d5 -r 15d21ceec728 libinterp/corefcn/octave-json-link.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.h\tMon Jan 18 08:34:13 2016 -0600\n@@ -0,0 +1,185 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifndef octave_json_link_h\r\n+#define octave_json_link_h\r\n+\r\n+#include <list>\r\n+#include <string>\r\n+\r\n+#include \"octave-link.h\"\r\n+#include \"json-util.h\"\r\n+#include \"oct-mutex.h\"\r\n+\r\n+// Circular reference\r\n+class json_main;\r\n+\r\n+// Thread-safe queue\r\n+template<typename T> class json_queue {\r\n+public:\r\n+  json_queue();\r\n+  ~json_queue();\r\n+\r\n+  void enqueue(const T& value);\r\n+  T dequeue();\r\n+\r\n+private:\r\n+  std::queue<T> _queue;\r\n+  octave_mutex _mutex;\r\n+};\r\n+\r\n+class octave_json_link : public octave_link\r\n+{\r\n+\r\n+public:\r\n+\r\n+  octave_json_link (json_main* __json_main);\r\n+\r\n+  ~octave_json_link (void);\r\n+\r\n+  std::string do_request_input (const std::string& prompt);\r\n+\r\n+  bool do_confirm_shutdown (void);\r\n+  bool do_exit (int status);\r\n+\r\n+  bool do_copy_image_to_clipboard (const std::string& file);\r\n+\r\n+  bool do_edit_file (const std::string& file);\r\n+  bool do_prompt_new_edit_file (const std::string& file);\r\n+\r\n+  int do_message_dialog (const std::string& dlg, const std::string& msg,\r\n+                         const std::string& title);\r\n+\r\n+  std::string\r\n+  do_question_dialog (const std::string& msg, const std::string& title,\r\n+                      const std::string& btn1, const std::string& btn2,\r\n+                      const std::string& btn3, const std::string& btndef);\r\n+\r\n+  std::pair<std::list<int>, int>\r\n+  do_list_dialog (const std::list<std::string>& list,\r\n+                  const std::string& mode,\r\n+                  int width, int height,\r\n+                  const std::list<int>& initial_value,\r\n+                  const std::string& name,\r\n+                  const std::list<std::string>& prompt,\r\n+                  const std::string& ok_string,\r\n+                  const std::string& cancel_string);\r\n+\r\n+  std::list<std::string>\r\n+  do_input_dialog (const std::list<std::string>& prompt,\r\n+                   const std::string& title,\r\n+                   const std::list<float>& nr,\r\n+                   const std::list<float>& nc,\r\n+                   const std::list<std::string>& defaults);\r\n+\r\n+  std::list<std::string>\r\n+  do_file_dialog (const filter_list& filter, const std::string& title,\r\n+                  const std::string &filename, const std::string &pathname,\r\n+                  const std::string& multimode);\r\n+\r\n+  int\r\n+  do_debug_cd_or_addpath_error (const std::string& file,\r\n+                                const std::string& dir,\r\n+                                bool addpath_option);\r\n+\r\n+  void do_change_directory (const std::string& dir);\r\n+\r\n+  void do_execute_command_in_terminal (const std::string& command);\r\n+\r\n+  void do_set_workspace (bool top_level, bool debug,\r\n+                         const std::list<workspace_element>& ws);\r\n+\r\n+  void do_clear_workspace (void);\r\n+\r\n+  void do_set_history (const string_vector& hist);\r\n+  void do_append_history (const std::string& hist_entry);\r\n+  void do_clear_history (void);\r\n+\r\n+  void do_clear_screen (void);\r\n+\r\n+  void do_pre_input_event (void);\r\n+  void do_post_input_event (void);\r\n+\r\n+  void do_enter_debugger_event (const std::string& file, int line);\r\n+  void do_execute_in_debugger_event (const std::string& file, int line);\r\n+  void do_exit_debugger_event (void);\r\n+\r\n+  void do_update_breakpoint (bool insert, const std::string& file, int line);\r\n+\r\n+  void do_set_default_prompts (std::string& ps1, std::string& ps2,\r\n+                               std::string& ps4);\r\n+\r\n+  void do_show_preferences (void);\r\n+\r\n+  void do_show_doc (const std::string& file);\r\n+\r\n+  void do_show_static_plot (const std::string& term,\r\n+                            const std::string& content);\r\n+\r\n+  // Custom methods\r\n+  void receive_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+  json_main* _json_main;\r\n+  void _publish_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+  // Queues\r\n+  json_queue<std::string> request_input_queue;\r\n+  json_queue<bool> confirm_shutdown_queue;\r\n+  json_queue<bool> prompt_new_edit_file_queue;\r\n+  json_queue<int> message_dialog_queue;\r\n+  json_queue<std::string> question_dialog_queue;\r\n+  json_queue<std::pair<std::list<int>, int> > list_dialog_queue;\r\n+  json_queue<std::list<std::string> > input_dialog_queue;\r\n+  json_queue<std::list<std::string> > file_dialog_queue;\r\n+  json_queue<int> debug_cd_or_addpath_error_queue;\r\n+};\r\n+\r\n+// Template classes require definitions in the header file...\r\n+\r\n+template<typename T>\r\n+json_queue<T>::json_queue() { }\r\n+\r\n+template<typename T>\r\n+json_queue<T>::~json_queue() { }\r\n+\r\n+template<typename T>\r\n+void json_queue<T>::enqueue(const T& value) {\r\n+  _mutex.lock();\r\n+  _queue.push(value);\r\n+  _mutex.cond_signal();\r\n+  _mutex.unlock();\r\n+}\r\n+\r\n+template<typename T>\r\n+T json_queue<T>::dequeue() {\r\n+  _mutex.lock();\r\n+  while (_queue.empty()) {\r\n+    _mutex.cond_wait();\r\n+  }\r\n+  T value = _queue.front();\r\n+  _queue.pop();\r\n+  _mutex.unlock();\r\n+  return value;\r\n+}\r\n+\r\n+#endif\r\ndiff -r 9e73fe0d92d5 -r 15d21ceec728 libinterp/link-deps.mk\n--- a/libinterp/link-deps.mk\tMon Jan 18 06:57:31 2016 -0600\n+++ b/libinterp/link-deps.mk\tMon Jan 18 08:34:13 2016 -0600\n@@ -17,7 +17,8 @@\n   $(GL2PS_LIBS) \\\n   $(LLVM_LIBS) \\\n   $(JAVA_LIBS) \\\n-  $(LAPACK_LIBS)\n+  $(LAPACK_LIBS) \\\n+  -ljson-c\n \n LIBOCTINTERP_LINK_OPTS = \\\n   $(FT2_LDFLAGS) \\\ndiff -r 9e73fe0d92d5 -r 15d21ceec728 libinterp/octave.cc\n--- a/libinterp/octave.cc\tMon Jan 18 06:57:31 2016 -0600\n+++ b/libinterp/octave.cc\tMon Jan 18 08:34:13 2016 -0600\n@@ -56,6 +56,7 @@\n #include \"file-io.h\"\n #include \"help.h\"\n #include \"input.h\"\n+#include \"json-main.h\"\n #include \"lex.h\"\n #include \"load-path.h\"\n #include \"load-save.h\"\n@@ -154,6 +155,10 @@\n // (--image-path)\n static std::string image_path;\n \n+// The value for \"JSON_SOCK\" specified on the command line.\n+// (--json-sock)\n+static std::string json_sock_path;\n+\n // If TRUE, ignore the window system even if it is available.\n // (--no-window-system, -W)\n static bool no_window_system = false;\n@@ -657,6 +662,11 @@\n           forced_line_editing = true;\n           break;\n \n+        case JSON_SOCK_OPTION:\n+          if (optarg)\n+            json_sock_path = optarg;\n+          break;\n+\n         case NO_GUI_OPTION:\n           no_gui_option = true;\n           break;\n@@ -832,6 +842,11 @@\n \n   initialize_version_info ();\n \n+  if (!json_sock_path.empty ()) {\n+    static json_main _json_main (json_sock_path);\n+    _json_main.run_loop_on_new_thread();\n+  }\n+\n   // Make all command-line arguments available to startup files,\n   // including PKG_ADD files.\n \ndiff -r 9e73fe0d92d5 -r 15d21ceec728 libinterp/options-usage.h\n--- a/libinterp/options-usage.h\tMon Jan 18 06:57:31 2016 -0600\n+++ b/libinterp/options-usage.h\tMon Jan 18 08:34:13 2016 -0600\n@@ -33,8 +33,8 @@\n        [--echo-commands] [--eval CODE] [--exec-path path]\\n\\\n        [--force-gui] [--help] [--image-path path]\\n\\\n        [--info-file file] [--info-program prog] [--interactive]\\n\\\n-       [--jit-compiler] [--line-editing] [--no-gui] [--no-history]\\n\\\n-       [--no-init-file] [--no-init-path] [--no-line-editing]\\n\\\n+       [--jit-compiler] [--json-sock] [--line-editing] [--no-gui]\\n\\\n+       [--no-history][--no-init-file] [--no-init-path] [--no-line-editing]\\n\\\n        [--no-site-file] [--no-window-system] [--norc] [-p path]\\n\\\n        [--path path] [--persist] [--silent] [--traditional]\\n\\\n        [--verbose] [--version] [file]\";\n@@ -56,15 +56,16 @@\n #define INFO_PROG_OPTION 8\n #define DEBUG_JIT_OPTION 9\n #define JIT_COMPILER_OPTION 10\n-#define LINE_EDITING_OPTION 11\n-#define NO_GUI_OPTION 12\n-#define NO_INIT_FILE_OPTION 13\n-#define NO_INIT_PATH_OPTION 14\n-#define NO_LINE_EDITING_OPTION 15\n-#define NO_SITE_FILE_OPTION 16\n-#define PERSIST_OPTION 17\n-#define TEXI_MACROS_FILE_OPTION 18\n-#define TRADITIONAL_OPTION 19\n+#define JSON_SOCK_OPTION 11\n+#define LINE_EDITING_OPTION 12\n+#define NO_GUI_OPTION 13\n+#define NO_INIT_FILE_OPTION 14\n+#define NO_INIT_PATH_OPTION 15\n+#define NO_LINE_EDITING_OPTION 16\n+#define NO_SITE_FILE_OPTION 17\n+#define PERSIST_OPTION 18\n+#define TEXI_MACROS_FILE_OPTION 19\n+#define TRADITIONAL_OPTION 20\n struct option long_opts[] =\n {\n   { \"braindead\",                no_argument,       0, TRADITIONAL_OPTION },\n@@ -82,6 +83,7 @@\n   { \"info-program\",             required_argument, 0, INFO_PROG_OPTION },\n   { \"interactive\",              no_argument,       0, 'i' },\n   { \"jit-compiler\",             no_argument,       0, JIT_COMPILER_OPTION },\n+  { \"json-sock\",                required_argument, 0, JSON_SOCK_OPTION },\n   { \"line-editing\",             no_argument,       0, LINE_EDITING_OPTION },\n   { \"no-gui\",                   no_argument,       0, NO_GUI_OPTION },\n   { \"no-history\",               no_argument,       0, 'H' },\n@@ -128,6 +130,7 @@\n   --info-program PROGRAM  Use PROGRAM for reading info files.\\n\\\n   --interactive, -i       Force interactive behavior.\\n\\\n   --jit-compiler          Enable the JIT compiler.\\n\\\n+  --json-sock PATH        Listen to and publish events on this UNIX socket.\\n\\\n   --line-editing          Force readline use for command-line editing.\\n\\\n   --no-gui                Disable the graphical user interface.\\n\\\n   --no-history, -H        Don't save commands to the history list\\n\\\n"
  },
  {
    "path": "back-octave/oo-changesets/007-4d778d6ebbd0.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1453275208 21600\n#      Wed Jan 20 01:33:28 2016 -0600\n# Branch oo\n# Node ID 4d778d6ebbd04afad8886efb7770690fbba675a9\n# Parent  15d21ceec7282b94db2d86503127d03730d5c316\nFixing undefined reference error when DISPLAY is enabled.\n\ndiff -r 15d21ceec728 -r 4d778d6ebbd0 scripts/plot/util/__gnuplot_drawnow__.m\n--- a/scripts/plot/util/__gnuplot_drawnow__.m\tMon Jan 18 08:34:13 2016 -0600\n+++ b/scripts/plot/util/__gnuplot_drawnow__.m\tWed Jan 20 01:33:28 2016 -0600\n@@ -30,7 +30,11 @@\n \n   elseif (nargin >= 3 && nargin <= 5)\n     ## Write the plot to the given file (e.g., via the \"print\" command)\n-    __gnuplot_draw_to_file__ (h, term, file, mono, debug_file);\n+    if (nargin == 5)\n+      __gnuplot_draw_to_file__ (h, term, file, mono, debug_file);\n+    else\n+      __gnuplot_draw_to_file__ (h, term, file, mono);\n+    endif\n \n   elseif (nargin == 1)\n     ## Plot to terminal and/or static (e.g., via the \"plot\" command)\n"
  },
  {
    "path": "back-octave/oo-changesets/008-e8ef7f3333bf.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1458303601 18000\n#      Fri Mar 18 07:20:01 2016 -0500\n# Branch oo\n# Node ID e8ef7f3333bfd96d811bde8cbc68738f37b191a6\n# Parent  4d778d6ebbd04afad8886efb7770690fbba675a9\nMoving macro \"MAP_SET\" into json-util.h and renaming it to \"JSON_MAP_SET\"\n\ndiff -r 4d778d6ebbd0 -r e8ef7f3333bf libinterp/corefcn/json-util.h\n--- a/libinterp/corefcn/json-util.h\tWed Jan 20 01:33:28 2016 -0600\n+++ b/libinterp/corefcn/json-util.h\tFri Mar 18 07:20:01 2016 -0500\n@@ -17,6 +17,10 @@\n #define JSON_OBJECT_T json_object*\r\n #define JSON_MAP_T std::map<std::string, JSON_OBJECT_T>\r\n \r\n+#define JSON_MAP_SET(M, FIELD, TYPE){ \\\r\n+\tm[#FIELD] = json_util::from_##TYPE (FIELD); \\\r\n+}\r\n+\r\n class json_util {\r\n public:\r\n \tstatic JSON_OBJECT_T from_string(const std::string& str);\r\ndiff -r 4d778d6ebbd0 -r e8ef7f3333bf libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tWed Jan 20 01:33:28 2016 -0600\n+++ b/libinterp/corefcn/octave-json-link.cc\tFri Mar 18 07:20:01 2016 -0500\n@@ -28,12 +28,7 @@\n #include \"octave-json-link.h\"\r\n #include \"workspace-element.h\"\r\n #include \"json-main.h\"\r\n-\r\n-// MAP_SET(m, foo, string)\r\n-//   => m[\"foo\"] = json_util::from_string(foo);\r\n-#define MAP_SET(M, FIELD, TYPE){ \\\r\n-\tm[#FIELD] = json_util::from_##TYPE (FIELD); \\\r\n-}\r\n+#include \"json-util.h\"\r\n \r\n octave_json_link::octave_json_link(json_main* __json_main)\r\n \t: octave_link (),\r\n@@ -61,7 +56,7 @@\n \r\n bool octave_json_link::do_exit(int status) {\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, status, int);\r\n+\tJSON_MAP_SET(m, status, int);\r\n \t_publish_message(\"exit\", json_util::from_map(m));\r\n \r\n \t// It is our responsibility in octave_link to call exit. If we don't, then\r\n@@ -74,7 +69,7 @@\n bool octave_json_link::do_copy_image_to_clipboard(const std::string& file) {\r\n \t// This endpoint might be unused?  (References appear only in libgui)\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, file, string);\r\n \t_publish_message(\"copy-image-to-clipboard\", json_util::from_map(m));\r\n \r\n \treturn true;\r\n@@ -83,7 +78,7 @@\n bool octave_json_link::do_edit_file(const std::string& file) {\r\n \t// Triggered in \"edit\" for existing files\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, file, string);\r\n \t_publish_message(\"edit-file\", json_util::from_map(m));\r\n \r\n \treturn true;\r\n@@ -92,7 +87,7 @@\n bool octave_json_link::do_prompt_new_edit_file(const std::string& file) {\r\n \t// Triggered in \"edit\" for new files\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, file, string);\r\n \t_publish_message(\"prompt-new-edit-file\", json_util::from_map(m));\r\n \r\n \treturn prompt_new_edit_file_queue.dequeue();\r\n@@ -101,9 +96,9 @@\n int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {\r\n \t// Triggered in \"msgbox\", \"helpdlg\", and \"errordlg\", among others\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\r\n-\tMAP_SET(m, msg, string);\r\n-\tMAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\r\n+\tJSON_MAP_SET(m, msg, string);\r\n+\tJSON_MAP_SET(m, title, string);\r\n \t_publish_message(\"message-dialog\", json_util::from_map(m));\r\n \r\n \treturn message_dialog_queue.dequeue();\r\n@@ -112,12 +107,12 @@\n std::string octave_json_link::do_question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n \t// Triggered in \"questdlg\"\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, msg, string);\r\n-\tMAP_SET(m, title, string);\r\n-\tMAP_SET(m, btn1, string);\r\n-\tMAP_SET(m, btn2, string);\r\n-\tMAP_SET(m, btn3, string);\r\n-\tMAP_SET(m, btndef, string);\r\n+\tJSON_MAP_SET(m, msg, string);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, btn1, string);\r\n+\tJSON_MAP_SET(m, btn2, string);\r\n+\tJSON_MAP_SET(m, btn3, string);\r\n+\tJSON_MAP_SET(m, btndef, string);\r\n \t_publish_message(\"question-dialog\", json_util::from_map(m));\r\n \r\n \treturn question_dialog_queue.dequeue();\r\n@@ -126,15 +121,15 @@\n std::pair<std::list<int>, int> octave_json_link::do_list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {\r\n \t// Triggered in \"listdlg\"\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, list, string_list);\r\n-\tMAP_SET(m, mode, string);\r\n-\tMAP_SET(m, width, int);\r\n-\tMAP_SET(m, height, int);\r\n-\tMAP_SET(m, initial_value, int_list);\r\n-\tMAP_SET(m, name, string);\r\n-\tMAP_SET(m, prompt, string_list);\r\n-\tMAP_SET(m, ok_string, string);\r\n-\tMAP_SET(m, cancel_string, string);\r\n+\tJSON_MAP_SET(m, list, string_list);\r\n+\tJSON_MAP_SET(m, mode, string);\r\n+\tJSON_MAP_SET(m, width, int);\r\n+\tJSON_MAP_SET(m, height, int);\r\n+\tJSON_MAP_SET(m, initial_value, int_list);\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, ok_string, string);\r\n+\tJSON_MAP_SET(m, cancel_string, string);\r\n \t_publish_message(\"list-dialog\", json_util::from_map(m));\r\n \r\n \treturn list_dialog_queue.dequeue();\r\n@@ -143,11 +138,11 @@\n std::list<std::string> octave_json_link::do_input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {\r\n \t// Triggered in \"inputdlg\"\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, prompt, string_list);\r\n-\tMAP_SET(m, title, string);\r\n-\tMAP_SET(m, nr, float_list);\r\n-\tMAP_SET(m, nc, float_list);\r\n-\tMAP_SET(m, defaults, string_list);\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, nr, float_list);\r\n+\tJSON_MAP_SET(m, nc, float_list);\r\n+\tJSON_MAP_SET(m, defaults, string_list);\r\n \t_publish_message(\"input-dialog\", json_util::from_map(m));\r\n \r\n \treturn input_dialog_queue.dequeue();\r\n@@ -156,11 +151,11 @@\n std::list<std::string> octave_json_link::do_file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {\r\n \t// Triggered in \"uiputfile\", \"uigetfile\", and \"uigetdir\"\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, filter, filter_list);\r\n-\tMAP_SET(m, title, string);\r\n-\tMAP_SET(m, filename, string);\r\n-\tMAP_SET(m, pathname, string);\r\n-\tMAP_SET(m, multimode, string);\r\n+\tJSON_MAP_SET(m, filter, filter_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, filename, string);\r\n+\tJSON_MAP_SET(m, pathname, string);\r\n+\tJSON_MAP_SET(m, multimode, string);\r\n \t_publish_message(\"file-dialog\", json_util::from_map(m));\r\n \t\r\n \treturn file_dialog_queue.dequeue();\r\n@@ -169,9 +164,9 @@\n int octave_json_link::do_debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\r\n \t// This endpoint might be unused?  (No references)\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, file, string);\r\n-\tMAP_SET(m, dir, string);\r\n-\tMAP_SET(m, addpath_option, boolean);\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\tJSON_MAP_SET(m, addpath_option, boolean);\r\n \t_publish_message(\"debug-cd-or-addpath-error\", json_util::from_map(m));\r\n \r\n \treturn debug_cd_or_addpath_error_queue.dequeue();\r\n@@ -180,23 +175,23 @@\n void octave_json_link::do_change_directory(const std::string& dir) {\r\n \t// This endpoint might be unused?  (References appear only in libgui)\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, dir, string);\r\n+\tJSON_MAP_SET(m, dir, string);\r\n \t_publish_message(\"change-directory\", json_util::from_map(m));\r\n }\r\n \r\n void octave_json_link::do_execute_command_in_terminal(const std::string& command) {\r\n \t// This endpoint might be unused?  (References appear only in libgui)\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, command, string);\r\n+\tJSON_MAP_SET(m, command, string);\r\n \t_publish_message(\"execute-command-in-terminal\", json_util::from_map(m));\r\n }\r\n \r\n void octave_json_link::do_set_workspace(bool top_level, bool debug, const std::list<workspace_element>& ws) {\r\n \t// Triggered on every new line entry\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, top_level, boolean);\r\n-\tMAP_SET(m, debug, boolean);\r\n-\tMAP_SET(m, ws, workspace_list);\r\n+\tJSON_MAP_SET(m, top_level, boolean);\r\n+\tJSON_MAP_SET(m, debug, boolean);\r\n+\tJSON_MAP_SET(m, ws, workspace_list);\r\n \t_publish_message(\"set-workspace\", json_util::from_map(m));\r\n }\r\n \r\n@@ -208,14 +203,14 @@\n void octave_json_link::do_set_history(const string_vector& hist) {\r\n \t// Called at startup, possibly more?\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, hist, string_vector);\r\n+\tJSON_MAP_SET(m, hist, string_vector);\r\n \t_publish_message(\"set-history\", json_util::from_map(m));\r\n }\r\n \r\n void octave_json_link::do_append_history(const std::string& hist_entry) {\r\n \t// Appears to be tied to readline, if available\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, hist_entry, string);\r\n+\tJSON_MAP_SET(m, hist_entry, string);\r\n \t_publish_message(\"append-history\", json_util::from_map(m));\r\n }\r\n \r\n@@ -239,15 +234,15 @@\n \r\n void octave_json_link::do_enter_debugger_event(const std::string& file, int line) {\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, file, string);\r\n-\tMAP_SET(m, line, int);\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n \t_publish_message(\"enter-debugger-event\", json_util::from_map(m));\r\n }\r\n \r\n void octave_json_link::do_execute_in_debugger_event(const std::string& file, int line) {\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, file, string);\r\n-\tMAP_SET(m, line, int);\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n \t_publish_message(\"execute-in-debugger-event\", json_util::from_map(m));\r\n }\r\n \r\n@@ -257,18 +252,18 @@\n \r\n void octave_json_link::do_update_breakpoint(bool insert, const std::string& file, int line) {\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, insert, boolean);\r\n-\tMAP_SET(m, file, string);\r\n-\tMAP_SET(m, line, int);\r\n+\tJSON_MAP_SET(m, insert, boolean);\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n \t_publish_message(\"update-breakpoint\", json_util::from_map(m));\r\n }\r\n \r\n void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {\r\n \t// Triggered upon interpreter startup\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, ps1, string);\r\n-\tMAP_SET(m, ps2, string);\r\n-\tMAP_SET(m, ps4, string);\r\n+\tJSON_MAP_SET(m, ps1, string);\r\n+\tJSON_MAP_SET(m, ps2, string);\r\n+\tJSON_MAP_SET(m, ps4, string);\r\n \t_publish_message(\"set-default-prompts\", json_util::from_map(m));\r\n }\r\n \r\n@@ -285,8 +280,8 @@\n void octave_json_link::do_show_static_plot(const std::string& term, const std::string& content) {\r\n \t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\r\n \tJSON_MAP_T m;\r\n-\tMAP_SET(m, term, string);\r\n-\tMAP_SET(m, content, string);\r\n+\tJSON_MAP_SET(m, term, string);\r\n+\tJSON_MAP_SET(m, content, string);\r\n \t_publish_message(\"show-static-plot\", json_util::from_map(m));\r\n }\r\n \r\n"
  },
  {
    "path": "back-octave/oo-changesets/009-05f7272c001e.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1458307337 18000\n#      Fri Mar 18 08:22:17 2016 -0500\n# Branch oo\n# Node ID 05f7272c001e9be692baa7ad0d0f5b4d4b29c642\n# Parent  e8ef7f3333bfd96d811bde8cbc68738f37b191a6\nAdding a new \"--json-max-len\" option that puts a limit on the size of messages pushed through the JSON socket.\n\ndiff -r e8ef7f3333bf -r 05f7272c001e libinterp/corefcn/json-main.cc\n--- a/libinterp/corefcn/json-main.cc\tFri Mar 18 07:20:01 2016 -0500\n+++ b/libinterp/corefcn/json-main.cc\tFri Mar 18 08:22:17 2016 -0500\n@@ -24,8 +24,9 @@\n   _json_main->process_json_object(name, jobj);\r\n }\r\n \r\n-json_main::json_main(const std::string& json_sock_path)\r\n+json_main::json_main(const std::string& json_sock_path, int max_message_length)\r\n   : _json_sock_path (json_sock_path),\r\n+    _max_message_length (max_message_length),\r\n     _loop_thread_active (false),\r\n     _octave_json_link (this)\r\n {\r\n@@ -52,6 +53,19 @@\n \r\n void json_main::publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n   std::string jstr = json_util::to_message(name, jobj);\r\n+\r\n+  // Do not send any messages over the socket that exceed the user-specified max length.  Instead, send an error message.  If max_length is 0 (default), do not suppress any messages.\r\n+  // In stress testing, Node may be able to handle as much as 10-20 MB, but I'd prefer to stay on the safe side.  Before this safeguard was implemented, fewer than 5% of plots exceeded 1 MB.\r\n+  int length = jstr.length();\r\n+  int max_length = _max_message_length;\r\n+  if (max_length > 0 && length > max_length) {\r\n+    JSON_MAP_T m;\r\n+    JSON_MAP_SET(m, name, string);\r\n+    JSON_MAP_SET(m, length, int);\r\n+    JSON_MAP_SET(m, max_length, int);\r\n+    jstr = json_util::to_message(\"message-too-long\", json_util::from_map(m));\r\n+  }\r\n+\r\n   send(sockfd, jstr.c_str(), jstr.length(), 0);\r\n }\r\n \r\ndiff -r e8ef7f3333bf -r 05f7272c001e libinterp/corefcn/json-main.h\n--- a/libinterp/corefcn/json-main.h\tFri Mar 18 07:20:01 2016 -0500\n+++ b/libinterp/corefcn/json-main.h\tFri Mar 18 08:22:17 2016 -0500\n@@ -10,7 +10,7 @@\n \r\n class json_main {\r\n public:\r\n-\tjson_main(const std::string& json_sock_path);\r\n+\tjson_main(const std::string& json_sock_path, int max_message_length);\r\n \t~json_main(void);\r\n \r\n \tvoid publish_message(const std::string& name, JSON_OBJECT_T jobj);\r\n@@ -20,6 +20,7 @@\n \r\n private:\r\n \tstd::string _json_sock_path;\r\n+\tint _max_message_length;\r\n \tint sockfd;\r\n \tbool _loop_thread_active;\r\n \tpthread_t _loop_thread;\r\ndiff -r e8ef7f3333bf -r 05f7272c001e libinterp/octave.cc\n--- a/libinterp/octave.cc\tFri Mar 18 07:20:01 2016 -0500\n+++ b/libinterp/octave.cc\tFri Mar 18 08:22:17 2016 -0500\n@@ -159,6 +159,10 @@\n // (--json-sock)\n static std::string json_sock_path;\n \n+// The maximum message length; valid only if \"JSON_SOCK\" is specified.\n+// (--json-max-len)\n+static int json_max_message_length = 0;\n+\n // If TRUE, ignore the window system even if it is available.\n // (--no-window-system, -W)\n static bool no_window_system = false;\n@@ -667,6 +671,11 @@\n             json_sock_path = optarg;\n           break;\n \n+        case JSON_MAX_LEN_OPTION:\n+          if (optarg)\n+            json_max_message_length = strtol(optarg, NULL, 10);\n+          break;\n+\n         case NO_GUI_OPTION:\n           no_gui_option = true;\n           break;\n@@ -843,7 +852,7 @@\n   initialize_version_info ();\n \n   if (!json_sock_path.empty ()) {\n-    static json_main _json_main (json_sock_path);\n+    static json_main _json_main (json_sock_path, json_max_message_length);\n     _json_main.run_loop_on_new_thread();\n   }\n \ndiff -r e8ef7f3333bf -r 05f7272c001e libinterp/options-usage.h\n--- a/libinterp/options-usage.h\tFri Mar 18 07:20:01 2016 -0500\n+++ b/libinterp/options-usage.h\tFri Mar 18 08:22:17 2016 -0500\n@@ -33,10 +33,10 @@\n        [--echo-commands] [--eval CODE] [--exec-path path]\\n\\\n        [--force-gui] [--help] [--image-path path]\\n\\\n        [--info-file file] [--info-program prog] [--interactive]\\n\\\n-       [--jit-compiler] [--json-sock] [--line-editing] [--no-gui]\\n\\\n-       [--no-history][--no-init-file] [--no-init-path] [--no-line-editing]\\n\\\n-       [--no-site-file] [--no-window-system] [--norc] [-p path]\\n\\\n-       [--path path] [--persist] [--silent] [--traditional]\\n\\\n+       [--jit-compiler] [--json-sock] [--json-max-len] [--line-editing]\\n\\\n+       [--no-gui] [--no-history][--no-init-file] [--no-init-path]\\n\\\n+       [--no-line-editing] [--no-site-file] [--no-window-system] [--norc]\\n\\\n+       [-p path] [--path path] [--persist] [--silent] [--traditional]\\n\\\n        [--verbose] [--version] [file]\";\n \n // This is here so that it's more likely that the usage message and\n@@ -57,15 +57,16 @@\n #define DEBUG_JIT_OPTION 9\n #define JIT_COMPILER_OPTION 10\n #define JSON_SOCK_OPTION 11\n-#define LINE_EDITING_OPTION 12\n-#define NO_GUI_OPTION 13\n-#define NO_INIT_FILE_OPTION 14\n-#define NO_INIT_PATH_OPTION 15\n-#define NO_LINE_EDITING_OPTION 16\n-#define NO_SITE_FILE_OPTION 17\n-#define PERSIST_OPTION 18\n-#define TEXI_MACROS_FILE_OPTION 19\n-#define TRADITIONAL_OPTION 20\n+#define JSON_MAX_LEN_OPTION 12\n+#define LINE_EDITING_OPTION 13\n+#define NO_GUI_OPTION 14\n+#define NO_INIT_FILE_OPTION 15\n+#define NO_INIT_PATH_OPTION 16\n+#define NO_LINE_EDITING_OPTION 17\n+#define NO_SITE_FILE_OPTION 18\n+#define PERSIST_OPTION 19\n+#define TEXI_MACROS_FILE_OPTION 20\n+#define TRADITIONAL_OPTION 21\n struct option long_opts[] =\n {\n   { \"braindead\",                no_argument,       0, TRADITIONAL_OPTION },\n@@ -84,6 +85,7 @@\n   { \"interactive\",              no_argument,       0, 'i' },\n   { \"jit-compiler\",             no_argument,       0, JIT_COMPILER_OPTION },\n   { \"json-sock\",                required_argument, 0, JSON_SOCK_OPTION },\n+  { \"json-max-len\",             required_argument, 0, JSON_MAX_LEN_OPTION },\n   { \"line-editing\",             no_argument,       0, LINE_EDITING_OPTION },\n   { \"no-gui\",                   no_argument,       0, NO_GUI_OPTION },\n   { \"no-history\",               no_argument,       0, 'H' },\n@@ -131,6 +133,7 @@\n   --interactive, -i       Force interactive behavior.\\n\\\n   --jit-compiler          Enable the JIT compiler.\\n\\\n   --json-sock PATH        Listen to and publish events on this UNIX socket.\\n\\\n+  --json-max-len LEN      Suppress JSON messages greater than LEN bytes.\\n\\\n   --line-editing          Force readline use for command-line editing.\\n\\\n   --no-gui                Disable the graphical user interface.\\n\\\n   --no-history, -H        Don't save commands to the history list\\n\\\n"
  },
  {
    "path": "back-octave/oo-changesets/010-4a1afb661c55.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1458312433 18000\n#      Fri Mar 18 09:47:13 2016 -0500\n# Branch oo\n# Node ID 4a1afb661c55f34994b53920f97c33ff3d03a27d\n# Parent  05f7272c001e9be692baa7ad0d0f5b4d4b29c642\nModifying code to not emit \"request-input\" events when there is a command waiting in the queue.\n\ndiff -r 05f7272c001e -r 4a1afb661c55 libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tFri Mar 18 08:22:17 2016 -0500\n+++ b/libinterp/corefcn/octave-json-link.cc\tFri Mar 18 09:47:13 2016 -0500\n@@ -42,9 +42,13 @@\n \r\n std::string octave_json_link::do_request_input(const std::string& prompt) {\r\n \t// Triggered whenever the console prompts for user input\r\n-\t_publish_message(\"request-input\", json_util::from_string(prompt));\r\n \r\n-\treturn request_input_queue.dequeue();\r\n+\tstd::string value;\r\n+\tif (!request_input_queue.dequeue_to(&value)) {\r\n+\t\t_publish_message(\"request-input\", json_util::from_string(prompt));\r\n+\t\tvalue = request_input_queue.dequeue();\r\n+\t}\r\n+\treturn value;\r\n }\r\n \r\n bool octave_json_link::do_confirm_shutdown(void) {\r\ndiff -r 05f7272c001e -r 4a1afb661c55 libinterp/corefcn/octave-json-link.h\n--- a/libinterp/corefcn/octave-json-link.h\tFri Mar 18 08:22:17 2016 -0500\n+++ b/libinterp/corefcn/octave-json-link.h\tFri Mar 18 09:47:13 2016 -0500\n@@ -41,6 +41,7 @@\n \r\n   void enqueue(const T& value);\r\n   T dequeue();\r\n+  bool dequeue_to(T* destination);\r\n \r\n private:\r\n   std::queue<T> _queue;\r\n@@ -182,4 +183,17 @@\n   return value;\r\n }\r\n \r\n+template<typename T>\r\n+bool json_queue<T>::dequeue_to(T* destination) {\r\n+  _mutex.lock();\r\n+  bool retval = false;\r\n+  if (!_queue.empty()) {\r\n+    retval = true;\r\n+    *destination = _queue.front();\r\n+    _queue.pop();\r\n+  }\r\n+  _mutex.unlock();\r\n+  return retval;\r\n+}\r\n+\r\n #endif\r\n"
  },
  {
    "path": "back-octave/oo-changesets/011-7327936fa23e.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1481411965 0\n#      Sat Dec 10 23:19:25 2016 +0000\n# Branch oo\n# Node ID 7327936fa23e3070c748c8db6ef4d1a0fc473da1\n# Parent  4a1afb661c55f34994b53920f97c33ff3d03a27d\nAdding support for a \"request-url\" JSON event.\n\ndiff -r 4a1afb661c55 -r 7327936fa23e libinterp/corefcn/json-util.cc\n--- a/libinterp/corefcn/json-util.cc\tFri Mar 18 09:47:13 2016 -0500\n+++ b/libinterp/corefcn/json-util.cc\tSat Dec 10 23:19:25 2016 +0000\n@@ -152,6 +152,22 @@\n \treturn ret;\r\n }\r\n \r\n+std::pair<bool, std::string> json_util::to_bool_string_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<bool, std::string> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_get_boolean(first);\r\n+\tret.second = json_object_get_string(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n std::list<std::string> json_util::to_string_list(JSON_OBJECT_T jobj) {\r\n \treturn json_object_to_list<std::string>(jobj, json_util::to_string);\r\n }\r\ndiff -r 4a1afb661c55 -r 7327936fa23e libinterp/corefcn/json-util.h\n--- a/libinterp/corefcn/json-util.h\tFri Mar 18 09:47:13 2016 -0500\n+++ b/libinterp/corefcn/json-util.h\tSat Dec 10 23:19:25 2016 +0000\n@@ -46,6 +46,7 @@\n \r\n \tstatic std::string to_string(JSON_OBJECT_T jobj);\r\n \tstatic std::pair<std::list<int>, int> to_int_list_int_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<bool, std::string> to_bool_string_pair(JSON_OBJECT_T jobj);\r\n \tstatic std::list<std::string> to_string_list(JSON_OBJECT_T jobj);\r\n \tstatic int to_int(JSON_OBJECT_T jobj);\r\n \tstatic bool to_boolean(JSON_OBJECT_T jobj);\r\ndiff -r 4a1afb661c55 -r 7327936fa23e libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tFri Mar 18 09:47:13 2016 -0500\n+++ b/libinterp/corefcn/octave-json-link.cc\tSat Dec 10 23:19:25 2016 +0000\n@@ -51,6 +51,20 @@\n \treturn value;\r\n }\r\n \r\n+std::string octave_json_link::do_request_url(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\r\n+\t// Triggered on urlread/urlwrite\r\n+\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, url, string);\r\n+\tJSON_MAP_SET(m, param, string_list);\r\n+\tJSON_MAP_SET(m, action, string);\r\n+\r\n+\t_publish_message(\"request-url\", json_util::from_map(m));\r\n+\tstd::pair<bool, std::string> result = request_url_queue.dequeue();\r\n+\tsuccess = result.first;\r\n+\treturn result.second;\r\n+}\r\n+\r\n bool octave_json_link::do_confirm_shutdown(void) {\r\n \t// Triggered when the kernel tries to exit\r\n \t_publish_message(\"confirm-shutdown\", json_util::empty());\r\n@@ -294,6 +308,10 @@\n \t\tstd::string answer = json_util::to_string(jobj);\r\n \t\trequest_input_queue.enqueue(answer);\r\n \t}\r\n+\telse if (name == \"request-url-answer\") {\r\n+\t\tstd::pair<bool, std::string> answer = json_util::to_bool_string_pair(jobj);\r\n+\t\trequest_url_queue.enqueue(answer);\r\n+\t}\r\n \telse if (name == \"confirm-shutdown-answer\"){\r\n \t\tbool answer = json_util::to_boolean(jobj);\r\n \t\tconfirm_shutdown_queue.enqueue(answer);\r\ndiff -r 4a1afb661c55 -r 7327936fa23e libinterp/corefcn/octave-json-link.h\n--- a/libinterp/corefcn/octave-json-link.h\tFri Mar 18 09:47:13 2016 -0500\n+++ b/libinterp/corefcn/octave-json-link.h\tSat Dec 10 23:19:25 2016 +0000\n@@ -58,6 +58,7 @@\n   ~octave_json_link (void);\r\n \r\n   std::string do_request_input (const std::string& prompt);\r\n+  std::string do_request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\r\n \r\n   bool do_confirm_shutdown (void);\r\n   bool do_exit (int status);\r\n@@ -145,6 +146,7 @@\n \r\n   // Queues\r\n   json_queue<std::string> request_input_queue;\r\n+  json_queue<std::pair<bool, std::string> > request_url_queue;\r\n   json_queue<bool> confirm_shutdown_queue;\r\n   json_queue<bool> prompt_new_edit_file_queue;\r\n   json_queue<int> message_dialog_queue;\r\ndiff -r 4a1afb661c55 -r 7327936fa23e libinterp/corefcn/octave-link.h\n--- a/libinterp/corefcn/octave-link.h\tFri Mar 18 09:47:13 2016 -0500\n+++ b/libinterp/corefcn/octave-link.h\tSat Dec 10 23:19:25 2016 +0000\n@@ -289,6 +289,13 @@\n       : std::string ();\n   }\n \n+  static std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success)\n+  {\n+    return request_input_enabled ()\n+      ? instance->do_request_url (url, param, action, success)\n+      : std::string ();\n+  }\n+\n   static void enter_debugger_event (const std::string& file, int line)\n   {\n     if (enabled ())\n@@ -439,6 +446,7 @@\n \n   bool _request_input_enabled;\n   virtual std::string do_request_input (const std::string&) = 0;\n+  virtual std::string do_request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) = 0;\n \n   virtual bool do_confirm_shutdown (void) = 0;\n   virtual bool do_exit (int status) = 0;\ndiff -r 4a1afb661c55 -r 7327936fa23e liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tFri Mar 18 09:47:13 2016 -0500\n+++ b/liboctave/util/url-transfer.cc\tSat Dec 10 23:19:25 2016 +0000\n@@ -36,6 +36,7 @@\n #include \"dir-ops.h\"\n #include \"file-ops.h\"\n #include \"file-stat.h\"\n+#include \"../../libinterp/corefcn/octave-link.h\"\n #include \"unwind-prot.h\"\n #include \"url-transfer.h\"\n \n@@ -214,6 +215,75 @@\n   return file_list;\n }\n \n+\n+class oo_url_transfer : public base_url_transfer\n+{\n+public:\n+\n+  oo_url_transfer (void)\n+      : base_url_transfer () {\n+    valid = true;\n+    std::cout << \"init method 1\" << std::endl;\n+  }\n+\n+  oo_url_transfer (const std::string& host, const std::string& user_arg,\n+                 const std::string& passwd, std::ostream& os)\n+      : base_url_transfer (host, user_arg, passwd, os) {\n+    valid = true;\n+    // url = \"ftp://\" + host;\n+    std::cout << \"init method 2\" << std::endl;\n+    // Set up the link, with no transfer.\n+    // perform ();\n+  }\n+\n+  oo_url_transfer (const std::string& url_str, std::ostream& os)\n+      : base_url_transfer (url_str, os) {\n+    valid = true;\n+    std::cout << \"init method 3\" << std::endl;\n+  }\n+\n+  ~oo_url_transfer (void) {}\n+\n+  void http_get (const Array<std::string>& param) {\n+    perform (param, \"get\");\n+  }\n+\n+  void http_post (const Array<std::string>& param) {\n+    perform (param, \"post\");\n+  }\n+\n+  void http_action (const Array<std::string>& param, const std::string& action) {\n+    perform (param, action);\n+  }\n+\n+private:\n+  void perform(const Array<std::string>& param, const std::string& action) {\n+    std::string url = host_or_url;\n+\n+    // Convert from Array to std::list\n+    std::list<std::string> paramList;\n+    for (int i = 0; i < param.numel(); i ++) {\n+      std::string value = param(i);\n+      paramList.push_back(value);\n+    }\n+\n+    if (octave_link::request_input_enabled ()) {\n+      bool success;\n+      std::string result = octave_link::request_url (url, paramList, action, success);\n+      if (success) {\n+        (*curr_ostream) << result;\n+      } else {\n+        ok = false;\n+        errmsg = result;\n+      }\n+    } else {\n+      ok = false;\n+      errmsg = \"octave_link not connected for oo_url_transfer\";\n+    }\n+  }\n+};\n+\n+\n #if defined (HAVE_CURL)\n \n static int\n@@ -769,11 +839,12 @@\n \n #endif\n \n-#if defined (HAVE_CURL)\n-# define REP_CLASS curl_transfer\n-#else\n-# define REP_CLASS base_url_transfer\n-#endif\n+// #if defined (HAVE_CURL)\n+// # define REP_CLASS curl_transfer\n+// #else\n+// # define REP_CLASS base_url_transfer\n+// #endif\n+#define REP_CLASS oo_url_transfer\n \n url_transfer::url_transfer (void) : rep (new REP_CLASS ())\n { }\n"
  },
  {
    "path": "back-octave/oo-changesets/012-84390db50239.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1481412676 28800\n#      Sat Dec 10 15:31:16 2016 -0800\n# Branch oo\n# Node ID 84390db5023918d68c1c133642f9fa6cb6aa189d\n# Parent  7327936fa23e3070c748c8db6ef4d1a0fc473da1\nRemoving the \"fork\" command from the Octave interpreter.\n\ndiff -r 7327936fa23e -r 84390db50239 libinterp/corefcn/syscalls.cc\n--- a/libinterp/corefcn/syscalls.cc\tSat Dec 10 23:19:25 2016 +0000\n+++ b/libinterp/corefcn/syscalls.cc\tSat Dec 10 15:31:16 2016 -0800\n@@ -167,9 +167,8 @@\n @deftypefn {Built-in Function} {[@var{err}, @var{msg}] =} exec (@var{file}, @var{args})\\n\\\n Replace current process with a new process.\\n\\\n \\n\\\n-Calling @code{exec} without first calling @code{fork} will terminate your\\n\\\n-current Octave process and replace it with the program named by @var{file}.\\n\\\n-For example,\\n\\\n+Calling @code{exec} will terminate your current Octave process and replace\\n\\\n+it with the program named by @var{file}. For example,\\n\\\n \\n\\\n @example\\n\\\n exec (\\\"ls\\\" \\\"-l\\\")\\n\\\n@@ -552,51 +551,6 @@\n   return retval;\n }\n \n-DEFUNX (\"fork\", Ffork, args, ,\n-        \"-*- texinfo -*-\\n\\\n-@deftypefn {Built-in Function} {[@var{pid}, @var{msg}] =} fork ()\\n\\\n-Create a copy of the current process.\\n\\\n-\\n\\\n-Fork can return one of the following values:\\n\\\n-\\n\\\n-@table @asis\\n\\\n-@item > 0\\n\\\n-You are in the parent process.  The value returned from @code{fork} is the\\n\\\n-process id of the child process.  You should probably arrange to wait for\\n\\\n-any child processes to exit.\\n\\\n-\\n\\\n-@item 0\\n\\\n-You are in the child process.  You can call @code{exec} to start another\\n\\\n-process.  If that fails, you should probably call @code{exit}.\\n\\\n-\\n\\\n-@item < 0\\n\\\n-The call to @code{fork} failed for some reason.  You must take evasive\\n\\\n-action.  A system dependent error message will be waiting in @var{msg}.\\n\\\n-@end table\\n\\\n-@end deftypefn\")\n-{\n-  octave_value_list retval;\n-\n-  retval(1) = std::string ();\n-  retval(0) = -1;\n-\n-  int nargin = args.length ();\n-\n-  if (nargin == 0)\n-    {\n-      std::string msg;\n-\n-      pid_t pid = octave_syscalls::fork (msg);\n-\n-      retval(1) = msg;\n-      retval(0) = pid;\n-    }\n-  else\n-    print_usage ();\n-\n-  return retval;\n-}\n-\n DEFUNX (\"getpgrp\", Fgetpgrp, args, ,\n         \"-*- texinfo -*-\\n\\\n @deftypefn {Built-in Function} {pgid =} getpgrp ()\\n\\\n"
  },
  {
    "path": "back-octave/oo-changesets/013-f4110d638cdb.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1481417641 28800\n#      Sat Dec 10 16:54:01 2016 -0800\n# Branch oo\n# Node ID f4110d638cdb9b0a06d504cfe2420aad4e954a17\n# Parent  84390db5023918d68c1c133642f9fa6cb6aa189d\nRemoving debugging print statements from url-transfer.cc.\n\ndiff -r 84390db50239 -r f4110d638cdb liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tSat Dec 10 15:31:16 2016 -0800\n+++ b/liboctave/util/url-transfer.cc\tSat Dec 10 16:54:01 2016 -0800\n@@ -223,7 +223,6 @@\n   oo_url_transfer (void)\n       : base_url_transfer () {\n     valid = true;\n-    std::cout << \"init method 1\" << std::endl;\n   }\n \n   oo_url_transfer (const std::string& host, const std::string& user_arg,\n@@ -231,15 +230,11 @@\n       : base_url_transfer (host, user_arg, passwd, os) {\n     valid = true;\n     // url = \"ftp://\" + host;\n-    std::cout << \"init method 2\" << std::endl;\n-    // Set up the link, with no transfer.\n-    // perform ();\n   }\n \n   oo_url_transfer (const std::string& url_str, std::ostream& os)\n       : base_url_transfer (url_str, os) {\n     valid = true;\n-    std::cout << \"init method 3\" << std::endl;\n   }\n \n   ~oo_url_transfer (void) {}\n"
  },
  {
    "path": "back-octave/oo-changesets/014-21fd506b7530.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1481421015 28800\n#      Sat Dec 10 17:50:15 2016 -0800\n# Branch oo\n# Node ID 21fd506b753001b4d568e34b06a9f8dd94c1d8df\n# Parent  f4110d638cdb9b0a06d504cfe2420aad4e954a17\nAdding support for binary text via base64 to oo_url_transfer.\n\ndiff -r f4110d638cdb -r 21fd506b7530 liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tSat Dec 10 16:54:01 2016 -0800\n+++ b/liboctave/util/url-transfer.cc\tSat Dec 10 17:50:15 2016 -0800\n@@ -37,6 +37,7 @@\n #include \"file-ops.h\"\n #include \"file-stat.h\"\n #include \"../../libinterp/corefcn/octave-link.h\"\n+#include \"base64.h\"\n #include \"unwind-prot.h\"\n #include \"url-transfer.h\"\n \n@@ -266,7 +267,7 @@\n       bool success;\n       std::string result = octave_link::request_url (url, paramList, action, success);\n       if (success) {\n-        (*curr_ostream) << result;\n+        process_success(result);\n       } else {\n         ok = false;\n         errmsg = result;\n@@ -276,6 +277,22 @@\n       errmsg = \"octave_link not connected for oo_url_transfer\";\n     }\n   }\n+\n+  void process_success(const std::string& result) {\n+    // If success, the result is returned as a base64 string, and we need to decode it.\n+    // Use the base64 implementation from gnulib, which is already an Octave dependency.\n+    const char *inc = &(result[0]);\n+    char *out;\n+    size_t outlen;\n+    bool b64_ok = base64_decode_alloc(inc, result.length(), &out, &outlen);\n+    if (!b64_ok) {\n+      ok = false;\n+      errmsg = \"failed decoding base64 from octave_link\";\n+    } else {\n+      curr_ostream->write(out, outlen);\n+      ::free(out);\n+    }\n+  }\n };\n \n \n"
  },
  {
    "path": "back-octave/oo-changesets/100-2d1fd5fdd1d5.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1514199455 21600\n#      Mon Dec 25 04:57:35 2017 -0600\n# Branch oo-4.2.1\n# Node ID 2d1fd5fdd1d591cb643542703e3679da48056fee\n# Parent  b9d482dd90f32c78d544125ec063dbd88c6da476\n# Parent  21fd506b753001b4d568e34b06a9f8dd94c1d8df\nMerging Octave Online patch into version 4.2.1.\n\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 configure.ac\n--- a/configure.ac\tFri Dec 22 15:29:45 2017 -0800\n+++ b/configure.ac\tMon Dec 25 04:57:35 2017 -0600\n@@ -2969,7 +2969,7 @@\n   LIBOCTINTERP_LINK_DEPS=\"$DLDFCN_LIBS\"\n fi\n \n-LIBOCTINTERP_LINK_DEPS=\"$LIBOCTINTERP_LINK_DEPS $FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $LLVM_LIBS $JAVA_LIBS $LAPACK_LIBS\"\n+LIBOCTINTERP_LINK_DEPS=\"$LIBOCTINTERP_LINK_DEPS $FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $LLVM_LIBS $JAVA_LIBS $LAPACK_LIBS -ljson-c\"\n \n LIBOCTINTERP_LINK_OPTS=\"$FT2_LDFLAGS $HDF5_LDFLAGS $MAGICK_LDFLAGS $Z_LDFLAGS $FFTW_XLDFLAGS $LLVM_LDFLAGS\"\n \ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libgui/src/octave-qt-link.cc\n--- a/libgui/src/octave-qt-link.cc\tFri Dec 22 15:29:45 2017 -0800\n+++ b/libgui/src/octave-qt-link.cc\tMon Dec 25 04:57:35 2017 -0600\n@@ -86,6 +86,16 @@\n   emit execute_interpreter_signal ();\n }\n \n+std::string octave_qt_link::do_request_input (const std::string&)\n+{\n+  return {};\n+}\n+\n+std::string octave_qt_link::do_request_url (const std::string&, const std::list<std::string>&, const std::string&, bool&)\n+{\n+  return {};\n+}\n+\n bool\n octave_qt_link::do_confirm_shutdown (void)\n {\n@@ -530,6 +540,11 @@\n }\n \n void\n+octave_qt_link::do_clear_screen (void)\n+{\n+}\n+\n+void\n octave_qt_link::do_pre_input_event (void)\n { }\n \n@@ -673,6 +688,12 @@\n }\n \n void\n+octave_qt_link::do_show_static_plot (const std::string&, const std::string&)\n+{\n+  return;\n+}\n+\n+void\n octave_qt_link::terminal_interrupt (void)\n {\n   command_interpreter->interrupt ();\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libgui/src/octave-qt-link.h\n--- a/libgui/src/octave-qt-link.h\tFri Dec 22 15:29:45 2017 -0800\n+++ b/libgui/src/octave-qt-link.h\tMon Dec 25 04:57:35 2017 -0600\n@@ -62,6 +62,9 @@\n \n   void execute_interpreter (void);\n \n+  std::string do_request_input (const std::string&);\n+  std::string do_request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n   bool do_confirm_shutdown (void);\n   bool do_exit (int status);\n \n@@ -118,6 +121,8 @@\n   void do_append_history (const std::string& hist_entry);\n   void do_clear_history (void);\n \n+  void do_clear_screen (void);\n+\n   void do_pre_input_event (void);\n   void do_post_input_event (void);\n \n@@ -137,6 +142,9 @@\n \n   void do_show_doc (const std::string& file);\n \n+  void do_show_static_plot (const std::string& term,\n+                            const std::string& content);\n+\n   QMutex mutex;\n   QWaitCondition waitcondition;\n   void shutdown_confirmation (bool sd) {_shutdown_confirm_result = sd;}\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/input.cc\n--- a/libinterp/corefcn/input.cc\tFri Dec 22 15:29:45 2017 -0800\n+++ b/libinterp/corefcn/input.cc\tMon Dec 25 04:57:35 2017 -0600\n@@ -183,7 +183,11 @@\n \n   eof = false;\n \n-  std::string retval = octave::command_editor::readline (s, eof);\n+  std::string retval;\n+  if (octave_link::request_input_enabled ())\n+    retval = octave_link::request_input (s);\n+  else\n+    retval = octave::command_editor::readline (s, eof);\n \n   if (! eof && retval.empty ())\n     retval = \"\\n\";\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/interpreter.cc\n--- a/libinterp/corefcn/interpreter.cc\tFri Dec 22 15:29:45 2017 -0800\n+++ b/libinterp/corefcn/interpreter.cc\tMon Dec 25 04:57:35 2017 -0600\n@@ -47,6 +47,7 @@\n #include \"file-io.h\"\n #include \"graphics.h\"\n #include \"interpreter.h\"\n+#include \"json-main.h\"\n #include \"load-path.h\"\n #include \"load-save.h\"\n #include \"octave-link.h\"\n@@ -635,6 +636,11 @@\n \n     initialize_version_info ();\n \n+    if (!options.json_sock_path().empty ()) {\n+      static json_main _json_main (options.json_sock_path(), options.json_max_message_length());\n+      _json_main.run_loop_on_new_thread();\n+    }\n+\n     // Make all command-line arguments available to startup files,\n     // including PKG_ADD files.\n \ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/json-main.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.cc\tMon Dec 25 04:57:35 2017 -0600\n@@ -0,0 +1,93 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include \"json-main.h\"\r\n+\r\n+#include <iostream>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <unistd.h>\r\n+\r\n+\r\n+// Analog of main-window.cc\r\n+// TODO: Think more about concurrency and null pointer exceptions\r\n+\r\n+void* run_loop_pthread(void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->run_loop();\r\n+  return NULL;\r\n+}\r\n+\r\n+void json_object_cb(std::string name, JSON_OBJECT_T jobj, void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->process_json_object(name, jobj);\r\n+}\r\n+\r\n+json_main::json_main(const std::string& json_sock_path, int max_message_length)\r\n+  : _json_sock_path (json_sock_path),\r\n+    _max_message_length (max_message_length),\r\n+    _loop_thread_active (false),\r\n+    _octave_json_link (this)\r\n+{\r\n+  // Enable octave_json_link instance\r\n+\toctave_link::connect_link(&_octave_json_link);\r\n+\r\n+  // Open UNIX socket file descriptor\r\n+  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\r\n+  struct sockaddr_un addr;\r\n+  memset(&addr, 0, sizeof(addr));\r\n+  addr.sun_family = AF_UNIX;\r\n+  memcpy(&addr.sun_path, _json_sock_path.c_str(), sizeof(addr.sun_path)-1);\r\n+  connect(\r\n+    sockfd,\r\n+    reinterpret_cast<struct sockaddr*>(&addr),\r\n+    sizeof(addr));\r\n+}\r\n+\r\n+json_main::~json_main(void) {\r\n+  close(sockfd);\r\n+\r\n+  // TODO: Stop the _loop_thread\r\n+}\r\n+\r\n+void json_main::publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  std::string jstr = json_util::to_message(name, jobj);\r\n+\r\n+  // Do not send any messages over the socket that exceed the user-specified max length.  Instead, send an error message.  If max_length is 0 (default), do not suppress any messages.\r\n+  // In stress testing, Node may be able to handle as much as 10-20 MB, but I'd prefer to stay on the safe side.  Before this safeguard was implemented, fewer than 5% of plots exceeded 1 MB.\r\n+  int length = jstr.length();\r\n+  int max_length = _max_message_length;\r\n+  if (max_length > 0 && length > max_length) {\r\n+    JSON_MAP_T m;\r\n+    JSON_MAP_SET(m, name, string);\r\n+    JSON_MAP_SET(m, length, int);\r\n+    JSON_MAP_SET(m, max_length, int);\r\n+    jstr = json_util::to_message(\"message-too-long\", json_util::from_map(m));\r\n+  }\r\n+\r\n+  send(sockfd, jstr.c_str(), jstr.length(), 0);\r\n+}\r\n+\r\n+void json_main::run_loop_on_new_thread(void) {\r\n+  if (_loop_thread_active)\r\n+    perror(\"won't run JSON socket loop multiple times\");\r\n+  _loop_thread_active = true;\r\n+\r\n+  pthread_create(\r\n+    &_loop_thread,\r\n+    NULL,\r\n+    run_loop_pthread,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::run_loop(void) {\r\n+  json_util::read_stream(\r\n+    sockfd,\r\n+    json_object_cb,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::process_json_object(std::string name, JSON_OBJECT_T jobj) {\r\n+  _octave_json_link.receive_message(name, jobj);\r\n+}\r\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/json-main.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.h\tMon Dec 25 04:57:35 2017 -0600\n@@ -0,0 +1,30 @@\n+#ifndef json_main_h\r\n+#define json_main_h\r\n+\r\n+#include <queue>\r\n+#include <pthread.h>\r\n+#include <stdio.h>\r\n+\r\n+#include \"octave-json-link.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+class json_main {\r\n+public:\r\n+\tjson_main(const std::string& json_sock_path, int max_message_length);\r\n+\t~json_main(void);\r\n+\r\n+\tvoid publish_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\tvoid run_loop_on_new_thread(void);\r\n+\tvoid run_loop(void);\r\n+\tvoid process_json_object(std::string name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+\tstd::string _json_sock_path;\r\n+\tint _max_message_length;\r\n+\tint sockfd;\r\n+\tbool _loop_thread_active;\r\n+\tpthread_t _loop_thread;\r\n+\toctave_json_link _octave_json_link;\r\n+};\r\n+\r\n+#endif\r\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/json-util.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.cc\tMon Dec 25 04:57:35 2017 -0600\n@@ -0,0 +1,242 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <cstdlib>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <stdio.h>\r\n+#include <json-c/arraylist.h>\r\n+\r\n+#include \"str-vec.h\"\r\n+\r\n+#include \"json-util.h\"\r\n+\r\n+JSON_OBJECT_T json_util::from_string(const std::string& str) {\r\n+\treturn json_object_new_string(str.c_str());\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int(int i) {\r\n+\treturn json_object_new_int(i);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float(float flt) {\r\n+\treturn json_object_new_double(flt);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_boolean(bool b) {\r\n+\treturn json_object_new_boolean(b);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::empty() {\r\n+\treturn json_object_new_object();\r\n+}\r\n+\r\n+template<typename T>\r\n+JSON_OBJECT_T json_object_from_list(const std::list<T>& list, JSON_OBJECT_T (*convert)(T)) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\ttypename std::list<T>::const_iterator it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, convert(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_list(const std::list<std::string>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_vector(const string_vector& vect) {\r\n+\t// TODO: Make sure this function does what it's supposed to do\r\n+\tstd::list<std::string> list;\r\n+\tfor (int i = 0; i < vect.numel(); ++i) {\r\n+\t\tlist.push_back(vect[i]);\r\n+\t}\r\n+\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int_list(const std::list<int>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_int);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float_list(const std::list<float>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_float);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_workspace_list(const std::list<workspace_element>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_workspace_element);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_filter_list(const octave_link::filter_list& list) {\r\n+\treturn json_object_from_list(list, json_util::from_pair);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_value_string(const std::string str) {\r\n+\treturn json_util::from_string(str);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_workspace_element(workspace_element element) {\r\n+\tJSON_MAP_T m;\r\n+\tm[\"scope\"] = json_util::from_int(element.scope());\r\n+\tm[\"symbol\"] = json_util::from_string(element.symbol());\r\n+\tm[\"class_name\"] = json_util::from_string(element.class_name());\r\n+\tm[\"dimension\"] = json_util::from_string(element.dimension());\r\n+\tm[\"value\"] = json_util::from_string(element.value());\r\n+\tm[\"complex_flag\"] = json_util::from_boolean(element.complex_flag());\r\n+\treturn json_util::from_map(m);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_pair(std::pair<std::string, std::string> pair) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tjson_object_array_add(jobj, json_object_new_string(pair.first.c_str()));\r\n+\tjson_object_array_add(jobj, json_object_new_string(pair.second.c_str()));\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_map(JSON_MAP_T m) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_object();\r\n+\tfor(\r\n+\t\tstd::map<std::string, JSON_OBJECT_T>::iterator it = m.begin();\r\n+\t\tit != m.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_object_add(jobj, it->first.c_str(), it->second);\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+std::string json_util::to_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  JSON_OBJECT_T jmsg = json_object_new_array();\r\n+  json_object_array_add(jmsg, json_util::from_string(name));\r\n+  json_object_array_add(jmsg, jobj);\r\n+  std::string str (json_object_to_json_string(jmsg));\r\n+  return str;\r\n+}\r\n+\r\n+std::string json_util::to_string(JSON_OBJECT_T jobj) {\r\n+  return std::string(json_object_get_string(jobj));\r\n+}\r\n+\r\n+template<typename T>\r\n+std::list<T> json_object_to_list(JSON_OBJECT_T jobj, T (*convert)(JSON_OBJECT_T)) {\r\n+\tstd::list<T> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tfor (int i = 0; i < array_list_length(arr); ++i) {\r\n+\t\tJSON_OBJECT_T jsub = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, i));\r\n+\t\tret.push_back(convert(jsub));\r\n+\t}\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> json_util::to_int_list_int_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<std::list<int>, int> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_to_list<int>(first, json_util::to_int);\r\n+\tret.second = json_object_get_int(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<bool, std::string> json_util::to_bool_string_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<bool, std::string> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_get_boolean(first);\r\n+\tret.second = json_object_get_string(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::list<std::string> json_util::to_string_list(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_to_list<std::string>(jobj, json_util::to_string);\r\n+}\r\n+\r\n+int json_util::to_int(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_int(jobj);\r\n+}\r\n+\r\n+bool json_util::to_boolean(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_boolean(jobj);\r\n+}\r\n+\r\n+void json_util::read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+\r\n+\t// Make some local variables\r\n+\tint BUF_LEN = 24;\r\n+\tchar* buf = new char[BUF_LEN];  // buffer for socket read\r\n+\tint buf_len;  // length of new bytes in the buffer\r\n+\tint buf_offset;  // offset of the JSON parser in the buffer\r\n+\tJSON_OBJECT_T jobj;  // pointer to parsed JSON object\r\n+\tjson_tokener* tok = json_tokener_new();  // JSON tokenizer instance\r\n+\tenum json_tokener_error jerr;  // status of JSON tokenizer\r\n+\r\n+\t// Start the blocking I/O loop\r\n+\twhile( (buf_len=recv(sockfd, buf, BUF_LEN, 0)) > 0) {\r\n+\t\tbuf_offset = 0;\r\n+\t\twhile(buf_offset < buf_len){\r\n+\t\t\tjobj = json_tokener_parse_ex(tok, buf + buf_offset, buf_len - buf_offset);\r\n+\t\t\tjerr = json_tokener_get_error(tok);\r\n+\t\t\tbuf_offset += tok->char_offset;\r\n+\r\n+\t\t\t// Do we need more material in order to make JSON?\r\n+\t\t\tif (jerr == json_tokener_continue) {\r\n+\t\t\t\tcontinue;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Make a new tokenizer\r\n+\t\t\tjson_tokener_free(tok);\r\n+\t\t\ttok = json_tokener_new();\r\n+\r\n+\t\t\t// Did we encounter a malformed JSON object?\r\n+\t\t\tif (jerr != json_tokener_success) {\r\n+\t\t\t\tfprintf(stderr,\r\n+\t\t\t\t\t\"JSON parse error: %s: '%.*s'\\n\",\r\n+\t\t\t\t\tjson_tokener_error_desc(jerr),\r\n+\t\t\t\t\t1,\r\n+\t\t\t\t\tbuf + buf_offset);\r\n+\t\t\t\tfflush(stderr);\r\n+\t\t\t\tbreak;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Our object is ready\r\n+\t\t\tprocess_message(jobj, cb, arg);\r\n+\t\t}\r\n+\t}\r\n+\r\n+\tjson_tokener_free(tok);\r\n+\tdelete buf;\r\n+}\r\n+\r\n+void json_util::process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+  if (!json_object_is_type(jobj, json_type_array))\r\n+    return;\r\n+  if (json_object_array_length(jobj) != 2)\r\n+    return;\r\n+\r\n+  cb(\r\n+  \tjson_util::to_string(json_object_array_get_idx(jobj, 0)),\r\n+  \tjson_object_array_get_idx(jobj, 1),\r\n+  \targ\r\n+  );\r\n+}\r\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/json-util.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.h\tMon Dec 25 04:57:35 2017 -0600\n@@ -0,0 +1,60 @@\n+#ifndef json_util_h\r\n+#define json_util_h\r\n+\r\n+#include <json-c/json.h>\r\n+#include <map>\r\n+#include <list>\r\n+\r\n+#include \"workspace-element.h\"\r\n+#include \"octave-link.h\"\r\n+\r\n+class string_vector;\r\n+\r\n+// All of the code interacting with the external JSON library should be in\r\n+// the json-util.h and json-util.cc files.  This way, if we want to change\r\n+// the external JSON library, we can do it all in one place.\r\n+\r\n+#define JSON_OBJECT_T json_object*\r\n+#define JSON_MAP_T std::map<std::string, JSON_OBJECT_T>\r\n+\r\n+#define JSON_MAP_SET(M, FIELD, TYPE){ \\\r\n+\tm[#FIELD] = json_util::from_##TYPE (FIELD); \\\r\n+}\r\n+\r\n+class json_util {\r\n+public:\r\n+\tstatic JSON_OBJECT_T from_string(const std::string& str);\r\n+\tstatic JSON_OBJECT_T from_int(int i);\r\n+\tstatic JSON_OBJECT_T from_float(float flt);\r\n+\tstatic JSON_OBJECT_T from_boolean(bool b);\r\n+\tstatic JSON_OBJECT_T empty();\r\n+\r\n+\tstatic JSON_OBJECT_T from_string_list(const std::list<std::string>& list);\r\n+\tstatic JSON_OBJECT_T from_string_vector(const string_vector& list);\r\n+\tstatic JSON_OBJECT_T from_int_list(const std::list<int>& list);\r\n+\tstatic JSON_OBJECT_T from_float_list(const std::list<float>& list);\r\n+\tstatic JSON_OBJECT_T from_workspace_list(const std::list<workspace_element>& list);\r\n+\tstatic JSON_OBJECT_T from_filter_list(const octave_link::filter_list& list);\r\n+\r\n+\tstatic JSON_OBJECT_T from_value_string(const std::string str);\r\n+\tstatic JSON_OBJECT_T from_workspace_element(workspace_element element);\r\n+\tstatic JSON_OBJECT_T from_pair(std::pair<std::string, std::string> pair);\r\n+\r\n+\tstatic JSON_OBJECT_T from_map(JSON_MAP_T m);\r\n+\r\n+\tstatic std::string to_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic std::string to_string(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<std::list<int>, int> to_int_list_int_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<bool, std::string> to_bool_string_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::list<std::string> to_string_list(JSON_OBJECT_T jobj);\r\n+\tstatic int to_int(JSON_OBJECT_T jobj);\r\n+\tstatic bool to_boolean(JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic void read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+\r\n+private:\r\n+\tstatic void process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+};\r\n+\r\n+#endif\r\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/module.mk\n--- a/libinterp/corefcn/module.mk\tFri Dec 22 15:29:45 2017 -0800\n+++ b/libinterp/corefcn/module.mk\tMon Dec 25 04:57:35 2017 -0600\n@@ -48,6 +48,8 @@\n   libinterp/corefcn/help.h \\\n   libinterp/corefcn/hook-fcn.h \\\n   libinterp/corefcn/input.h \\\n+  libinterp/corefcn/json-main.h \\\n+  libinterp/corefcn/json-util.h \\\n   libinterp/corefcn/interpreter.h \\\n   libinterp/corefcn/load-path.h \\\n   libinterp/corefcn/load-save.h \\\n@@ -77,6 +79,7 @@\n   libinterp/corefcn/oct-strstrm.h \\\n   libinterp/corefcn/oct.h \\\n   libinterp/corefcn/octave-default-image.h \\\n+  libinterp/corefcn/octave-json-link.h \\\n   libinterp/corefcn/octave-link.h \\\n   libinterp/corefcn/octave-preserve-stream-state.h \\\n   libinterp/corefcn/pager.h \\\n@@ -179,6 +182,8 @@\n   libinterp/corefcn/hex2num.cc \\\n   libinterp/corefcn/hook-fcn.cc \\\n   libinterp/corefcn/input.cc \\\n+  libinterp/corefcn/json-main.cc \\\n+  libinterp/corefcn/json-util.cc \\\n   libinterp/corefcn/inv.cc \\\n   libinterp/corefcn/interpreter.cc \\\n   libinterp/corefcn/kron.cc \\\n@@ -214,6 +219,7 @@\n   libinterp/corefcn/oct-tex-lexer.ll \\\n   libinterp/corefcn/oct-tex-parser.h \\\n   libinterp/corefcn/oct-tex-parser.yy \\\n+  libinterp/corefcn/octave-json-link.cc \\\n   libinterp/corefcn/octave-link.cc \\\n   libinterp/corefcn/ordschur.cc \\\n   libinterp/corefcn/pager.cc \\\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/octave-json-link.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.cc\tMon Dec 25 04:57:35 2017 -0600\n@@ -0,0 +1,362 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <iostream>\r\n+#include \"octave-json-link.h\"\r\n+#include \"workspace-element.h\"\r\n+#include \"json-main.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+octave_json_link::octave_json_link(json_main* __json_main)\r\n+\t: octave_link (),\r\n+\t\t_json_main (__json_main)\r\n+{\r\n+\t_request_input_enabled = true;\r\n+\t_plot_destination = STATIC_ONLY;\r\n+}\r\n+\r\n+octave_json_link::~octave_json_link(void) { }\r\n+\r\n+std::string octave_json_link::do_request_input(const std::string& prompt) {\r\n+\t// Triggered whenever the console prompts for user input\r\n+\r\n+\tstd::string value;\r\n+\tif (!request_input_queue.dequeue_to(&value)) {\r\n+\t\t_publish_message(\"request-input\", json_util::from_string(prompt));\r\n+\t\tvalue = request_input_queue.dequeue();\r\n+\t}\r\n+\treturn value;\r\n+}\r\n+\r\n+std::string octave_json_link::do_request_url(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\r\n+\t// Triggered on urlread/urlwrite\r\n+\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, url, string);\r\n+\tJSON_MAP_SET(m, param, string_list);\r\n+\tJSON_MAP_SET(m, action, string);\r\n+\r\n+\t_publish_message(\"request-url\", json_util::from_map(m));\r\n+\tstd::pair<bool, std::string> result = request_url_queue.dequeue();\r\n+\tsuccess = result.first;\r\n+\treturn result.second;\r\n+}\r\n+\r\n+bool octave_json_link::do_confirm_shutdown(void) {\r\n+\t// Triggered when the kernel tries to exit\r\n+\t_publish_message(\"confirm-shutdown\", json_util::empty());\r\n+\r\n+\treturn confirm_shutdown_queue.dequeue();\r\n+}\r\n+\r\n+bool octave_json_link::do_exit(int status) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, status, int);\r\n+\t_publish_message(\"exit\", json_util::from_map(m));\r\n+\r\n+\t// It is our responsibility in octave_link to call exit. If we don't, then\r\n+\t// the kernel waits for 24 hours expecting us to do something.\r\n+\t::exit(status);\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::do_copy_image_to_clipboard(const std::string& file) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"copy-image-to-clipboard\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::do_edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for existing files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::do_prompt_new_edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for new files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"prompt-new-edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn prompt_new_edit_file_queue.dequeue();\r\n+}\r\n+\r\n+int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {\r\n+\t// Triggered in \"msgbox\", \"helpdlg\", and \"errordlg\", among others\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\r\n+\tJSON_MAP_SET(m, msg, string);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\t_publish_message(\"message-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn message_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::string octave_json_link::do_question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n+\t// Triggered in \"questdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, msg, string);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, btn1, string);\r\n+\tJSON_MAP_SET(m, btn2, string);\r\n+\tJSON_MAP_SET(m, btn3, string);\r\n+\tJSON_MAP_SET(m, btndef, string);\r\n+\t_publish_message(\"question-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn question_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> octave_json_link::do_list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {\r\n+\t// Triggered in \"listdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, list, string_list);\r\n+\tJSON_MAP_SET(m, mode, string);\r\n+\tJSON_MAP_SET(m, width, int);\r\n+\tJSON_MAP_SET(m, height, int);\r\n+\tJSON_MAP_SET(m, initial_value, int_list);\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, ok_string, string);\r\n+\tJSON_MAP_SET(m, cancel_string, string);\r\n+\t_publish_message(\"list-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn list_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::do_input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {\r\n+\t// Triggered in \"inputdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, nr, float_list);\r\n+\tJSON_MAP_SET(m, nc, float_list);\r\n+\tJSON_MAP_SET(m, defaults, string_list);\r\n+\t_publish_message(\"input-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn input_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::do_file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {\r\n+\t// Triggered in \"uiputfile\", \"uigetfile\", and \"uigetdir\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, filter, filter_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, filename, string);\r\n+\tJSON_MAP_SET(m, pathname, string);\r\n+\tJSON_MAP_SET(m, multimode, string);\r\n+\t_publish_message(\"file-dialog\", json_util::from_map(m));\r\n+\t\r\n+\treturn file_dialog_queue.dequeue();\r\n+}\r\n+\r\n+int octave_json_link::do_debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\r\n+\t// This endpoint might be unused?  (No references)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\tJSON_MAP_SET(m, addpath_option, boolean);\r\n+\t_publish_message(\"debug-cd-or-addpath-error\", json_util::from_map(m));\r\n+\r\n+\treturn debug_cd_or_addpath_error_queue.dequeue();\r\n+}\r\n+\r\n+void octave_json_link::do_change_directory(const std::string& dir) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\t_publish_message(\"change-directory\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_execute_command_in_terminal(const std::string& command) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, command, string);\r\n+\t_publish_message(\"execute-command-in-terminal\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_set_workspace(bool top_level, bool debug, const std::list<workspace_element>& ws /*, const bool& variable_editor_too */) {\r\n+\t// Triggered on every new line entry\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, top_level, boolean);\r\n+\tJSON_MAP_SET(m, debug, boolean);\r\n+\tJSON_MAP_SET(m, ws, workspace_list);\r\n+\t// variable_editor_too?\r\n+\t_publish_message(\"set-workspace\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_clear_workspace(void) {\r\n+\t// Triggered on \"clear\" command (but not \"clear all\" or \"clear foo\")\r\n+\t_publish_message(\"clear-workspace\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_set_history(const string_vector& hist) {\r\n+\t// Called at startup, possibly more?\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist, string_vector);\r\n+\t_publish_message(\"set-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_append_history(const std::string& hist_entry) {\r\n+\t// Appears to be tied to readline, if available\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist_entry, string);\r\n+\t_publish_message(\"append-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_clear_history(void) {\r\n+\t// Appears to be tied to readline, if available\r\n+\t_publish_message(\"clear-history\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_clear_screen(void) {\r\n+\t// Triggered by clc\r\n+\t_publish_message(\"clear-screen\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_pre_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::do_post_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::do_enter_debugger_event(const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"enter-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_execute_in_debugger_event(const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"execute-in-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_exit_debugger_event(void) {\r\n+\t_publish_message(\"exit-debugger-event\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_update_breakpoint(bool insert, const std::string& file, int line, const std::string& cond) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, insert, boolean);\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\tJSON_MAP_SET(m, cond, string);\r\n+\t_publish_message(\"update-breakpoint\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {\r\n+\t// Triggered upon interpreter startup\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, ps1, string);\r\n+\tJSON_MAP_SET(m, ps2, string);\r\n+\tJSON_MAP_SET(m, ps4, string);\r\n+\t_publish_message(\"set-default-prompts\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_show_preferences(void) {\r\n+\t// Triggered on \"preferences\" command\r\n+\t_publish_message(\"show-preferences\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_show_doc(const std::string& file) {\r\n+\t// Triggered on \"doc\" command\r\n+\t_publish_message(\"show-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+// void octave_json_link::do_openvar(const std::string& name) {\r\n+// \t// Triggered on \"openvar\" command\r\n+// \t_publish_message(\"openvar\", json_util::from_string(name));\r\n+// }\r\n+\r\n+void octave_json_link::do_show_static_plot(const std::string& term, const std::string& content) {\r\n+\t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, term, string);\r\n+\tJSON_MAP_SET(m, content, string);\r\n+\t_publish_message(\"show-static-plot\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::receive_message (const std::string& name, JSON_OBJECT_T jobj) {\r\n+\tif (name == \"cmd\" || name == \"request-input-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\trequest_input_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"request-url-answer\") {\r\n+\t\tstd::pair<bool, std::string> answer = json_util::to_bool_string_pair(jobj);\r\n+\t\trequest_url_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"confirm-shutdown-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tconfirm_shutdown_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"prompt-new-edit-file-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tprompt_new_edit_file_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"message-dialog-answer\"){\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tmessage_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"question-dialog-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\tquestion_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"list-dialog-answer\") {\r\n+\t\tstd::pair<std::list<int>, int> answer = json_util::to_int_list_int_pair(jobj);\r\n+\t\tlist_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"input-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tinput_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"file-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tfile_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"debug-cd-or-addpath-error-answer\") {\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tdebug_cd_or_addpath_error_queue.enqueue(answer);\r\n+\t}\r\n+\telse {\r\n+\t\tstd::cerr << \"warning: received unknown message: \" << name << std::endl;\r\n+\t}\r\n+}\r\n+\r\n+void octave_json_link::_publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+\t_json_main->publish_message(name, jobj);\r\n+}\r\n+\r\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/octave-json-link.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.h\tMon Dec 25 04:57:35 2017 -0600\n@@ -0,0 +1,209 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifndef octave_json_link_h\r\n+#define octave_json_link_h\r\n+\r\n+#include <list>\r\n+#include <string>\r\n+\r\n+#include \"octave-link.h\"\r\n+#include \"json-util.h\"\r\n+#include \"oct-mutex.h\"\r\n+\r\n+// Circular reference\r\n+class json_main;\r\n+\r\n+// Thread-safe queue\r\n+template<typename T> class json_queue {\r\n+public:\r\n+  json_queue();\r\n+  ~json_queue();\r\n+\r\n+  void enqueue(const T& value);\r\n+  T dequeue();\r\n+  bool dequeue_to(T* destination);\r\n+\r\n+private:\r\n+  std::queue<T> _queue;\r\n+  octave_mutex _mutex;\r\n+};\r\n+\r\n+class octave_json_link : public octave_link\r\n+{\r\n+\r\n+public:\r\n+\r\n+  octave_json_link (json_main* __json_main);\r\n+\r\n+  ~octave_json_link (void);\r\n+\r\n+  std::string do_request_input (const std::string& prompt) override;\r\n+  std::string do_request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\r\n+\r\n+  bool do_confirm_shutdown (void) override;\r\n+  bool do_exit (int status) override;\r\n+\r\n+  bool do_copy_image_to_clipboard (const std::string& file) override;\r\n+\r\n+  bool do_edit_file (const std::string& file) override;\r\n+  bool do_prompt_new_edit_file (const std::string& file) override;\r\n+\r\n+  int do_message_dialog (const std::string& dlg, const std::string& msg,\r\n+                         const std::string& title) override;\r\n+\r\n+  std::string\r\n+  do_question_dialog (const std::string& msg, const std::string& title,\r\n+                      const std::string& btn1, const std::string& btn2,\r\n+                      const std::string& btn3, const std::string& btndef) override;\r\n+\r\n+  std::pair<std::list<int>, int>\r\n+  do_list_dialog (const std::list<std::string>& list,\r\n+                  const std::string& mode,\r\n+                  int width, int height,\r\n+                  const std::list<int>& initial_value,\r\n+                  const std::string& name,\r\n+                  const std::list<std::string>& prompt,\r\n+                  const std::string& ok_string,\r\n+                  const std::string& cancel_string) override;\r\n+\r\n+  std::list<std::string>\r\n+  do_input_dialog (const std::list<std::string>& prompt,\r\n+                   const std::string& title,\r\n+                   const std::list<float>& nr,\r\n+                   const std::list<float>& nc,\r\n+                   const std::list<std::string>& defaults) override;\r\n+\r\n+  std::list<std::string>\r\n+  do_file_dialog (const filter_list& filter, const std::string& title,\r\n+                  const std::string &filename, const std::string &pathname,\r\n+                  const std::string& multimode) override;\r\n+\r\n+  int\r\n+  do_debug_cd_or_addpath_error (const std::string& file,\r\n+                                const std::string& dir,\r\n+                                bool addpath_option) override;\r\n+\r\n+  void do_change_directory (const std::string& dir) override;\r\n+\r\n+  void do_execute_command_in_terminal (const std::string& command) override;\r\n+\r\n+  void do_set_workspace (bool top_level, bool debug,\r\n+                         const std::list<workspace_element>& ws\r\n+                         // Added on head but not yet in stable:\r\n+                         // , const bool& variable_editor_too = true\r\n+                         ) override;\r\n+\r\n+  void do_clear_workspace (void) override;\r\n+\r\n+  void do_set_history (const string_vector& hist) override;\r\n+  void do_append_history (const std::string& hist_entry) override;\r\n+  void do_clear_history (void) override;\r\n+\r\n+  void do_clear_screen (void) override;\r\n+\r\n+  void do_pre_input_event (void) override;\r\n+  void do_post_input_event (void) override;\r\n+\r\n+  void do_enter_debugger_event (const std::string& file, int line) override;\r\n+  void do_execute_in_debugger_event (const std::string& file, int line) override;\r\n+  void do_exit_debugger_event (void) override;\r\n+\r\n+  void do_update_breakpoint (bool insert,\r\n+                             const std::string& file, int line,\r\n+                             const std::string& cond) override;\r\n+\r\n+  void do_set_default_prompts (std::string& ps1, std::string& ps2,\r\n+                               std::string& ps4) override;\r\n+\r\n+  void do_show_preferences (void) override;\r\n+\r\n+  void do_show_doc (const std::string& file) override;\r\n+\r\n+  // Added on head but not yet in stable:\r\n+  // void do_openvar (const std::string& name) override;\r\n+\r\n+  void do_show_static_plot (const std::string& term,\r\n+                            const std::string& content) override;\r\n+\r\n+  // Custom methods\r\n+  void receive_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+  json_main* _json_main;\r\n+  void _publish_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+  // Queues\r\n+  json_queue<std::string> request_input_queue;\r\n+  json_queue<std::pair<bool, std::string> > request_url_queue;\r\n+  json_queue<bool> confirm_shutdown_queue;\r\n+  json_queue<bool> prompt_new_edit_file_queue;\r\n+  json_queue<int> message_dialog_queue;\r\n+  json_queue<std::string> question_dialog_queue;\r\n+  json_queue<std::pair<std::list<int>, int> > list_dialog_queue;\r\n+  json_queue<std::list<std::string> > input_dialog_queue;\r\n+  json_queue<std::list<std::string> > file_dialog_queue;\r\n+  json_queue<int> debug_cd_or_addpath_error_queue;\r\n+};\r\n+\r\n+// Template classes require definitions in the header file...\r\n+\r\n+template<typename T>\r\n+json_queue<T>::json_queue() { }\r\n+\r\n+template<typename T>\r\n+json_queue<T>::~json_queue() { }\r\n+\r\n+template<typename T>\r\n+void json_queue<T>::enqueue(const T& value) {\r\n+  _mutex.lock();\r\n+  _queue.push(value);\r\n+  _mutex.cond_signal();\r\n+  _mutex.unlock();\r\n+}\r\n+\r\n+template<typename T>\r\n+T json_queue<T>::dequeue() {\r\n+  _mutex.lock();\r\n+  while (_queue.empty()) {\r\n+    _mutex.cond_wait();\r\n+  }\r\n+  T value = _queue.front();\r\n+  _queue.pop();\r\n+  _mutex.unlock();\r\n+  return value;\r\n+}\r\n+\r\n+template<typename T>\r\n+bool json_queue<T>::dequeue_to(T* destination) {\r\n+  _mutex.lock();\r\n+  bool retval = false;\r\n+  if (!_queue.empty()) {\r\n+    retval = true;\r\n+    *destination = _queue.front();\r\n+    _queue.pop();\r\n+  }\r\n+  _mutex.unlock();\r\n+  return retval;\r\n+}\r\n+\r\n+#endif\r\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/octave-link.cc\n--- a/libinterp/corefcn/octave-link.cc\tFri Dec 22 15:29:45 2017 -0800\n+++ b/libinterp/corefcn/octave-link.cc\tMon Dec 25 04:57:35 2017 -0600\n@@ -395,3 +395,30 @@\n \n   return ovl (octave_link::show_doc (file));\n }\n+\n+DEFUN (__octave_link_plot_destination__, , ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn {} {} __octave_link_plot_destination__ ()\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  return ovl (octave_link::plot_destination ());\n+}\n+\n+DEFUN (__octave_link_show_static_plot__, args, ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn {} {} __octave_link_show_static_plot__ (@var{term}, @var{content})\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  if (args.length () != 2) {\n+    return ovl ();\n+  }\n+\n+  std::string term = args(0).string_value();\n+  std::string content = args(1).string_value();\n+  return ovl (octave_link::show_static_plot (term, content));\n+}\n+\n+\n+\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/octave-link.h\n--- a/libinterp/corefcn/octave-link.h\tFri Dec 22 15:29:45 2017 -0800\n+++ b/libinterp/corefcn/octave-link.h\tMon Dec 25 04:57:35 2017 -0600\n@@ -265,6 +265,12 @@\n       instance->do_clear_history ();\n   }\n \n+  static void clear_screen (void)\n+  {\n+    if (enabled ())\n+      instance->do_clear_screen ();\n+  }\n+\n   static void pre_input_event (void)\n   {\n     if (enabled ())\n@@ -277,6 +283,20 @@\n       instance->do_post_input_event ();\n   }\n \n+  static std::string request_input (const std::string& prompt)\n+  {\n+    return request_input_enabled ()\n+      ? instance->do_request_input (prompt)\n+      : std::string ();\n+  }\n+\n+  static std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success)\n+  {\n+    return request_input_enabled ()\n+      ? instance->do_request_url (url, param, action, success)\n+      : std::string ();\n+  }\n+\n   static void enter_debugger_event (const std::string& file, int line)\n   {\n     if (enabled ())\n@@ -325,6 +345,34 @@\n     return instance_ok () ? instance->link_enabled : false;\n   }\n \n+  static bool request_input_enabled (void)\n+  {\n+    return enabled () ? instance->_request_input_enabled : false;\n+  }\n+\n+  enum plot_destination_t {\n+    TERMINAL_ONLY = 0,\n+    STATIC_ONLY = 1,\n+    TERMINAL_AND_STATIC = 2\n+  };\n+\n+  static plot_destination_t plot_destination (void)\n+  {\n+    return enabled () ? instance->_plot_destination : TERMINAL_ONLY;\n+  }\n+\n+  static bool\n+  show_static_plot (const std::string& term, const std::string& content)\n+  {\n+    if (enabled ())\n+      {\n+        instance->do_show_static_plot (term, content);\n+        return true;\n+      }\n+    else\n+      return false;\n+  }\n+\n   static bool\n   show_preferences ()\n   {\n@@ -398,6 +446,10 @@\n   void do_entered_readline_hook (void) { }\n   void do_finished_readline_hook (void) { }\n \n+  bool _request_input_enabled;\n+  virtual std::string do_request_input (const std::string&) = 0;\n+  virtual std::string do_request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) = 0;\n+\n   virtual bool do_confirm_shutdown (void) = 0;\n   virtual bool do_exit (int status) = 0;\n \n@@ -456,6 +508,8 @@\n   virtual void do_append_history (const std::string& hist_entry) = 0;\n   virtual void do_clear_history (void) = 0;\n \n+  virtual void do_clear_screen (void) = 0;\n+\n   virtual void do_pre_input_event (void) = 0;\n   virtual void do_post_input_event (void) = 0;\n \n@@ -476,7 +530,11 @@\n \n   virtual void do_show_preferences (void) = 0;\n \n-  virtual void do_show_doc (const std::string &file) = 0;\n+  virtual void do_show_doc (const std::string& file) = 0;\n+\n+  plot_destination_t _plot_destination;\n+  virtual void do_show_static_plot (const std::string& term,\n+                                    const std::string& content) = 0;\n };\n \n #endif\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/syscalls.cc\n--- a/libinterp/corefcn/syscalls.cc\tFri Dec 22 15:29:45 2017 -0800\n+++ b/libinterp/corefcn/syscalls.cc\tMon Dec 25 04:57:35 2017 -0600\n@@ -143,9 +143,8 @@\n @deftypefn {} {[@var{err}, @var{msg}] =} exec (@var{file}, @var{args})\n Replace current process with a new process.\n \n-Calling @code{exec} without first calling @code{fork} will terminate your\n-current Octave process and replace it with the program named by @var{file}.\n-For example,\n+Calling @code{exec} will terminate your current Octave process and replace\n+it with the program named by @var{file}. For example,\n \n @example\n exec (\"ls\", \"-l\")\n@@ -453,41 +452,6 @@\n   return ovl (status, msg);\n }\n \n-DEFUNX (\"fork\", Ffork, args, ,\n-        doc: /* -*- texinfo -*-\n-@deftypefn {} {[@var{pid}, @var{msg}] =} fork ()\n-Create a copy of the current process.\n-\n-Fork can return one of the following values:\n-\n-@table @asis\n-@item > 0\n-You are in the parent process.  The value returned from @code{fork} is the\n-process id of the child process.  You should probably arrange to wait for\n-any child processes to exit.\n-\n-@item 0\n-You are in the child process.  You can call @code{exec} to start another\n-process.  If that fails, you should probably call @code{exit}.\n-\n-@item < 0\n-The call to @code{fork} failed for some reason.  You must take evasive\n-action.  A system dependent error message will be waiting in @var{msg}.\n-@end table\n-@end deftypefn */)\n-{\n-  if (args.length () != 0)\n-    print_usage ();\n-\n-  if (symbol_table::at_top_level ())\n-    error (\"fork: cannot be called from command line\");\n-\n-  std::string msg;\n-\n-  pid_t pid = octave::sys::fork (msg);\n-\n-  return ovl (pid, msg);\n-}\n \n DEFUNX (\"getpgrp\", Fgetpgrp, args, ,\n         doc: /* -*- texinfo -*-\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/corefcn/sysdep.cc\n--- a/libinterp/corefcn/sysdep.cc\tFri Dec 22 15:29:45 2017 -0800\n+++ b/libinterp/corefcn/sysdep.cc\tMon Dec 25 04:57:35 2017 -0600\n@@ -79,6 +79,7 @@\n #include \"errwarn.h\"\n #include \"input.h\"\n #include \"octave.h\"\n+#include \"octave-link.h\"\n #include \"ov.h\"\n #include \"ovl.h\"\n #include \"pager.h\"\n@@ -624,6 +625,8 @@\n {\n   bool skip_redisplay = true;\n \n+  octave_link::clear_screen ();\n+\n   octave::command_editor::clear_screen (skip_redisplay);\n \n   return ovl ();\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/octave.cc\n--- a/libinterp/octave.cc\tFri Dec 22 15:29:45 2017 -0800\n+++ b/libinterp/octave.cc\tMon Dec 25 04:57:35 2017 -0600\n@@ -45,6 +45,7 @@\n #include \"octave.h\"\n #include \"oct-hist.h\"\n #include \"oct-map.h\"\n+#include \"octave-link.h\"\n #include \"ovl.h\"\n #include \"options-usage.h\"\n #include \"ov.h\"\n@@ -188,6 +189,16 @@\n           case LINE_EDITING_OPTION:\n             m_forced_line_editing = m_line_editing = true;\n             break;\n+ \n+          case JSON_SOCK_OPTION:\n+            if (octave_optarg_wrapper ())\n+              m_json_sock_path = octave_optarg_wrapper ();\n+            break;\n+\n+          case JSON_MAX_LEN_OPTION:\n+            if (octave_optarg_wrapper ())\n+              m_json_max_message_length = strtol(octave_optarg_wrapper (), NULL, 10);\n+            break;\n \n           case NO_GUI_OPTION:\n             m_no_gui = true;\n@@ -264,6 +275,8 @@\n       m_command_line_path (opts.m_command_line_path),\n       m_exec_path (opts.m_exec_path),\n       m_image_path (opts.m_image_path),\n+      m_json_sock_path (opts.m_json_sock_path),\n+      m_json_max_message_length (opts.m_json_max_message_length),\n       m_all_args (opts.m_all_args),\n       m_remaining_args (opts.m_remaining_args)\n   { }\n@@ -291,6 +304,8 @@\n         m_command_line_path = opts.m_command_line_path;\n         m_exec_path = opts.m_exec_path;\n         m_image_path = opts.m_image_path;\n+        m_json_sock_path = opts.m_json_sock_path;\n+        m_json_max_message_length = opts.m_json_max_message_length;\n         m_all_args = opts.m_all_args;\n         m_remaining_args = opts.m_remaining_args;\n       }\n@@ -472,7 +487,7 @@\n   // FIXME: This isn't quite right, it just says that we intended to\n   // start the GUI, not that it is actually running.\n \n-  return ovl (octave::application::is_gui_running ());\n+  return ovl (octave_link::enabled ());\n }\n \n /*\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/octave.h\n--- a/libinterp/octave.h\tFri Dec 22 15:29:45 2017 -0800\n+++ b/libinterp/octave.h\tMon Dec 25 04:57:35 2017 -0600\n@@ -66,6 +66,8 @@\n     std::list<std::string> command_line_path (void) const { return m_command_line_path; }\n     std::string exec_path (void) const { return m_exec_path; }\n     std::string image_path (void) const { return m_image_path; }\n+    std::string json_sock_path (void) const { return m_json_sock_path; }\n+    int json_max_message_length (void) const { return m_json_max_message_length; }\n     string_vector all_args (void) const { return m_all_args; }\n     string_vector remaining_args (void) const { return m_remaining_args; }\n \n@@ -87,6 +89,8 @@\n     void command_line_path (const std::list<std::string>& arg) { m_command_line_path = arg; }\n     void exec_path (const std::string& arg) { m_exec_path = arg; }\n     void image_path (const std::string& arg) { m_image_path = arg; }\n+    void json_sock_path (const std::string& arg) { m_json_sock_path = arg; }\n+    void json_max_message_length (int arg) { m_json_max_message_length = arg; }\n     void all_args (const string_vector& arg) { m_all_args = arg; }\n     void remaining_args (const string_vector& arg) { m_remaining_args = arg; }\n \n@@ -164,6 +168,14 @@\n     // (--image-path)\n     std::string m_image_path;\n \n+    // The value for \"JSON_SOCK\" specified on the command line.\n+    // (--json-sock)\n+    std::string m_json_sock_path;\n+\n+    // The maximum message length; valid only if \"JSON_SOCK\" is specified.\n+    // (--json-max-len)\n+    int m_json_max_message_length = 0;\n+\n     // All arguments passed to the argc, argv constructor.\n     string_vector m_all_args;\n \ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 libinterp/options-usage.h\n--- a/libinterp/options-usage.h\tFri Dec 22 15:29:45 2017 -0800\n+++ b/libinterp/options-usage.h\tMon Dec 25 04:57:35 2017 -0600\n@@ -35,10 +35,10 @@\n        [--echo-commands] [--eval CODE] [--exec-path path]\\n\\\n        [--force-gui] [--help] [--image-path path]\\n\\\n        [--info-file file] [--info-program prog] [--interactive]\\n\\\n-       [--jit-compiler] [--line-editing] [--no-gui] [--no-history]\\n\\\n-       [--no-init-file] [--no-init-path] [--no-line-editing]\\n\\\n-       [--no-site-file] [--no-window-system] [--norc] [-p path]\\n\\\n-       [--path path] [--persist] [--silent] [--traditional]\\n\\\n+       [--jit-compiler] [--json-sock] [--json-max-len] [--line-editing]\\n\\\n+       [--no-gui] [--no-history][--no-init-file] [--no-init-path]\\n\\\n+       [--no-line-editing] [--no-site-file] [--no-window-system] [--norc]\\n\\\n+       [-p path] [--path path] [--persist] [--silent] [--traditional]\\n\\\n        [--verbose] [--version] [file]\";\n \n // This is here so that it's more likely that the usage message and\n@@ -58,15 +58,17 @@\n #define INFO_PROG_OPTION 8\n #define DEBUG_JIT_OPTION 9\n #define JIT_COMPILER_OPTION 10\n-#define LINE_EDITING_OPTION 11\n-#define NO_GUI_OPTION 12\n-#define NO_INIT_FILE_OPTION 13\n-#define NO_INIT_PATH_OPTION 14\n-#define NO_LINE_EDITING_OPTION 15\n-#define NO_SITE_FILE_OPTION 16\n-#define PERSIST_OPTION 17\n-#define TEXI_MACROS_FILE_OPTION 18\n-#define TRADITIONAL_OPTION 19\n+#define JSON_SOCK_OPTION 11\n+#define JSON_MAX_LEN_OPTION 12\n+#define LINE_EDITING_OPTION 13\n+#define NO_GUI_OPTION 14\n+#define NO_INIT_FILE_OPTION 15\n+#define NO_INIT_PATH_OPTION 16\n+#define NO_LINE_EDITING_OPTION 17\n+#define NO_SITE_FILE_OPTION 18\n+#define PERSIST_OPTION 19\n+#define TEXI_MACROS_FILE_OPTION 20\n+#define TRADITIONAL_OPTION 21\n struct octave_getopt_options long_opts[] =\n {\n   { \"braindead\",                octave_no_arg,       0, TRADITIONAL_OPTION },\n@@ -84,6 +86,8 @@\n   { \"info-program\",             octave_required_arg, 0, INFO_PROG_OPTION },\n   { \"interactive\",              octave_no_arg,       0, 'i' },\n   { \"jit-compiler\",             octave_no_arg,       0, JIT_COMPILER_OPTION },\n+  { \"json-sock\",                octave_required_arg, 0, JSON_SOCK_OPTION },\n+  { \"json-max-len\",             octave_required_arg, 0, JSON_MAX_LEN_OPTION },\n   { \"line-editing\",             octave_no_arg,       0, LINE_EDITING_OPTION },\n   { \"no-gui\",                   octave_no_arg,       0, NO_GUI_OPTION },\n   { \"no-history\",               octave_no_arg,       0, 'H' },\n@@ -130,6 +134,8 @@\n   --info-program PROGRAM  Use PROGRAM for reading info files.\\n\\\n   --interactive, -i       Force interactive behavior.\\n\\\n   --jit-compiler          Enable the JIT compiler.\\n\\\n+  --json-sock PATH        Listen to and publish events on this UNIX socket.\\n\\\n+  --json-max-len LEN      Suppress JSON messages greater than LEN bytes.\\n\\\n   --line-editing          Force readline use for command-line editing.\\n\\\n   --no-gui                Disable the graphical user interface.\\n\\\n   --no-history, -H        Don't save commands to the history list\\n\\\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 liboctave/util/oct-mutex.cc\n--- a/liboctave/util/oct-mutex.cc\tFri Dec 22 15:29:45 2017 -0800\n+++ b/liboctave/util/oct-mutex.cc\tMon Dec 25 04:57:35 2017 -0600\n@@ -53,6 +53,18 @@\n   return false;\n }\n \n+void\n+octave_base_mutex::cond_wait (void)\n+{\n+  (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+}\n+\n+void\n+octave_base_mutex::cond_signal (void)\n+{\n+  (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+}\n+\n #if defined (OCTAVE_USE_WINDOWS_API)\n \n class\n@@ -63,11 +75,13 @@\n     : octave_base_mutex ()\n   {\n     InitializeCriticalSection (&cs);\n+    InitializeConditionVariable (&cv);\n   }\n \n   ~octave_w32_mutex (void)\n   {\n     DeleteCriticalSection (&cs);\n+    // no need to delete cv: http://stackoverflow.com/a/28981408/1407170\n   }\n \n   void lock (void)\n@@ -85,8 +99,21 @@\n     return (TryEnterCriticalSection (&cs) != 0);\n   }\n \n+  void cond_wait (void)\n+  {\n+    SleepConditionVariableCS (&cv, &cs, INFINITE);\n+  }\n+\n+  void cond_signal (void)\n+  {\n+    WakeConditionVariable (&cv);\n+  }\n+\n+  void \n+\n private:\n   CRITICAL_SECTION cs;\n+  CONDITION_VARIABLE cv;\n };\n \n static DWORD octave_thread_id = 0;\n@@ -118,11 +145,18 @@\n     pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);\n     pthread_mutex_init (&pm, &attr);\n     pthread_mutexattr_destroy (&attr);\n+\n+    pthread_condattr_t condattr;\n+\n+    pthread_condattr_init (&condattr);\n+    pthread_cond_init (&condv, &condattr);\n+    pthread_condattr_destroy (&condattr);\n   }\n \n   ~octave_pthread_mutex (void)\n   {\n     pthread_mutex_destroy (&pm);\n+    pthread_cond_destroy (&condv);\n   }\n \n   void lock (void)\n@@ -140,8 +174,20 @@\n     return (pthread_mutex_trylock (&pm) == 0);\n   }\n \n+  void cond_wait (void)\n+  {\n+    pthread_cond_wait (&condv, &pm);\n+  }\n+\n+  void cond_signal (void)\n+  {\n+    pthread_cond_signal (&condv);\n+  }\n+\n private:\n   pthread_mutex_t pm;\n+  pthread_cond_t condv;\n+\n };\n \n static pthread_t octave_thread_id = 0;\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 liboctave/util/oct-mutex.h\n--- a/liboctave/util/oct-mutex.h\tFri Dec 22 15:29:45 2017 -0800\n+++ b/liboctave/util/oct-mutex.h\tMon Dec 25 04:57:35 2017 -0600\n@@ -45,6 +45,10 @@\n \n   virtual bool try_lock (void);\n \n+  virtual void cond_wait (void);\n+\n+  virtual void cond_signal (void);\n+\n private:\n   octave_refcount<int> count;\n };\n@@ -97,6 +101,16 @@\n     return rep->try_lock ();\n   }\n \n+  void cond_wait (void)\n+  {\n+    rep->cond_wait ();\n+  }\n+\n+  void cond_signal (void)\n+  {\n+    rep->cond_signal ();\n+  }\n+\n protected:\n   octave_base_mutex *rep;\n };\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tFri Dec 22 15:29:45 2017 -0800\n+++ b/liboctave/util/url-transfer.cc\tMon Dec 25 04:57:35 2017 -0600\n@@ -36,6 +36,8 @@\n #include \"dir-ops.h\"\n #include \"file-ops.h\"\n #include \"file-stat.h\"\n+#include \"libinterp/corefcn/octave-link.h\"\n+#include \"gnulib/lib/base64.h\"\n #include \"unwind-prot.h\"\n #include \"url-transfer.h\"\n \n@@ -216,6 +218,86 @@\n     return file_list;\n   }\n \n+\n+class oo_url_transfer : public base_url_transfer\n+{\n+public:\n+\n+  oo_url_transfer (void)\n+      : base_url_transfer () {\n+    valid = true;\n+  }\n+\n+  oo_url_transfer (const std::string& host, const std::string& user_arg,\n+                 const std::string& passwd, std::ostream& os)\n+      : base_url_transfer (host, user_arg, passwd, os) {\n+    valid = true;\n+    // url = \"ftp://\" + host;\n+  }\n+\n+  oo_url_transfer (const std::string& url_str, std::ostream& os)\n+      : base_url_transfer (url_str, os) {\n+    valid = true;\n+  }\n+\n+  ~oo_url_transfer (void) {}\n+\n+  void http_get (const Array<std::string>& param) {\n+    perform (param, \"get\");\n+  }\n+\n+  void http_post (const Array<std::string>& param) {\n+    perform (param, \"post\");\n+  }\n+\n+  void http_action (const Array<std::string>& param, const std::string& action) {\n+    perform (param, action);\n+  }\n+\n+private:\n+  void perform(const Array<std::string>& param, const std::string& action) {\n+    std::string url = host_or_url;\n+\n+    // Convert from Array to std::list\n+    std::list<std::string> paramList;\n+    for (int i = 0; i < param.numel(); i ++) {\n+      std::string value = param(i);\n+      paramList.push_back(value);\n+    }\n+\n+    if (octave_link::request_input_enabled ()) {\n+      bool success;\n+      std::string result = octave_link::request_url (url, paramList, action, success);\n+      if (success) {\n+        process_success(result);\n+      } else {\n+        ok = false;\n+        errmsg = result;\n+      }\n+    } else {\n+      ok = false;\n+      errmsg = \"octave_link not connected for oo_url_transfer\";\n+    }\n+  }\n+\n+  void process_success(const std::string& result) {\n+    // If success, the result is returned as a base64 string, and we need to decode it.\n+    // Use the base64 implementation from gnulib, which is already an Octave dependency.\n+    const char *inc = &(result[0]);\n+    char *out;\n+    size_t outlen;\n+    bool b64_ok = base64_decode_alloc(inc, result.length(), &out, &outlen);\n+    if (!b64_ok) {\n+      ok = false;\n+      errmsg = \"failed decoding base64 from octave_link\";\n+    } else {\n+      curr_ostream->write(out, outlen);\n+      ::free(out);\n+    }\n+  }\n+};\n+\n+\n #if defined (HAVE_CURL)\n \n   static int\n@@ -771,11 +853,12 @@\n \n #endif\n \n-#if defined (HAVE_CURL)\n-#  define REP_CLASS curl_transfer\n-#else\n-#  define REP_CLASS base_url_transfer\n-#endif\n+// #if defined (HAVE_CURL)\n+// #  define REP_CLASS curl_transfer\n+// #else\n+// #  define REP_CLASS base_url_transfer\n+// #endif\n+#define REP_CLASS oo_url_transfer\n \n   url_transfer::url_transfer (void) : rep (new REP_CLASS ())\n   { }\ndiff -r b9d482dd90f3 -r 2d1fd5fdd1d5 scripts/plot/util/__gnuplot_drawnow__.m\n--- a/scripts/plot/util/__gnuplot_drawnow__.m\tFri Dec 22 15:29:45 2017 -0800\n+++ b/scripts/plot/util/__gnuplot_drawnow__.m\tMon Dec 25 04:57:35 2017 -0600\n@@ -27,9 +27,79 @@\n \n   if (nargin < 1 || nargin > 4 || nargin == 2)\n     print_usage ();\n+\n+  elseif (nargin >= 3 && nargin <= 4)\n+    ## Write the plot to the given file (e.g., via the \"print\" command)\n+    if (nargin == 5)\n+      __gnuplot_draw_to_file__ (h, term, file, debug_file);\n+    else\n+      __gnuplot_draw_to_file__ (h, term, file);\n+    endif\n+\n+  else  # nargin == 1\n+    ##  Plot to terminal and/or static (e.g., via the \"plot\" command)\n+    plot_stream = get (h, \"__plot_stream__\");\n+    if (isempty (plot_stream))\n+      plot_stream = __gnuplot_open_stream__ (2, h);\n+      new_stream = true;\n+    else\n+      new_stream = false;\n+    endif\n+    term = gnuplot_default_term (plot_stream);\n+\n+    ## There are a few options for how we can proceed.\n+    ## In most cases, we will tell GNUPLOT to put the plot in its terminal.\n+    ## If we have no display, we want to use the \"dumb\" terminal.\n+    ## Octave Link may request that we send the plot as an event.\n+    ## The latter two cases require plotting to a temp file.\n+\n+    should_plot_to_terminal = (\n+      !strcmp (term, \"dumb\") && (\n+        __octave_link_plot_destination__ () == 0 ||\n+        __octave_link_plot_destination__ () == 2\n+      )\n+    );\n+\n+    if (should_plot_to_terminal)\n+      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n+      __gnuplot_draw_figure__ (h, plot_stream(1), enhanced, mono);\n+      fflush (plot_stream(1));\n+    endif\n+\n+    should_plot_to_temp_file = (\n+      strcmp (term, \"dumb\") ||\n+      __octave_link_plot_destination__ () == 1 ||\n+      __octave_link_plot_destination__ () == 2\n+    );\n+\n+    if (should_plot_to_temp_file)\n+      tmp_file = tempname ();\n+      __gnuplot_draw_to_file__ (h, term, tmp_file);\n+      pause (1);\n+\n+      ## Read the temp file into memory and then delete it\n+      fid = fopen (tmp_file, 'r');\n+      [a, count] = fscanf (fid, '%c', Inf);\n+      fclose (fid);\n+      unlink (tmp_file);\n+\n+      ## What to do with the plot data?\n+      if (count > 0)\n+        if (a(1) == 12)\n+          a = a(2:end);  # avoid ^L at the beginning\n+        endif\n+        if strcmp (term, \"dumb\")\n+          puts (a);\n+        else\n+          __octave_link_show_static_plot__ (term, a);\n+        endif\n+      endif\n+    endif\n+\n   endif\n+endfunction\n \n-  if (nargin >= 3 && nargin <= 4)\n+function __gnuplot_draw_to_file__ (h, term, file, debug_file)\n     ## Produce various output formats, or redirect gnuplot stream to a\n     ## debug file.\n     plot_stream = [];\n@@ -65,44 +135,6 @@\n         fclose (fid);\n       endif\n     end_unwind_protect\n-  else  # nargin == 1\n-    ##  Graphics terminal for display.\n-    plot_stream = get (h, \"__plot_stream__\");\n-    if (isempty (plot_stream))\n-      plot_stream = __gnuplot_open_stream__ (2, h);\n-      new_stream = true;\n-    else\n-      new_stream = false;\n-    endif\n-    term = gnuplot_default_term (plot_stream);\n-    if (strcmp (term, \"dumb\"))\n-      ## popen2 eats stdout of gnuplot, use temporary file instead\n-      dumb_tmp_file = tempname ();\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h,\n-                                   term, dumb_tmp_file);\n-    else\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n-    endif\n-    __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n-    fflush (plot_stream(1));\n-    if (strcmp (term, \"dumb\"))\n-      fid = -1;\n-      while (fid < 0)\n-        pause (0.1);\n-        fid = fopen (dumb_tmp_file, 'r');\n-      endwhile\n-      ## reprint the plot on screen\n-      [a, count] = fscanf (fid, '%c', Inf);\n-      fclose (fid);\n-      if (count > 0)\n-        if (a(1) == 12)\n-          a = a(2:end);  # avoid ^L at the beginning\n-        endif\n-        puts (a);\n-      endif\n-      unlink (dumb_tmp_file);\n-    endif\n-  endif\n \n endfunction\n \n"
  },
  {
    "path": "back-octave/oo-changesets/100-README.md",
    "content": "Patch file `100` contains all previous patch files combined and applied against commit `b9d482dd90f3` in the GNU Octave mercurial repository, which is in the stable branch of version *4.2*.\n\nPatch files `101` and up are additional commits.\n"
  },
  {
    "path": "back-octave/oo-changesets/101-bc8cd93feec5.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1514203589 21600\n#      Mon Dec 25 06:06:29 2017 -0600\n# Branch oo-4.2.1\n# Node ID bc8cd93feec50aa5f30d6ad0aa0e85f31693c71f\n# Parent  2d1fd5fdd1d591cb643542703e3679da48056fee\nRemoving pause(1) from the mandatory critical path for plotting.\n\ndiff -r 2d1fd5fdd1d5 -r bc8cd93feec5 scripts/plot/util/__gnuplot_drawnow__.m\n--- a/scripts/plot/util/__gnuplot_drawnow__.m\tMon Dec 25 04:57:35 2017 -0600\n+++ b/scripts/plot/util/__gnuplot_drawnow__.m\tMon Dec 25 06:06:29 2017 -0600\n@@ -62,7 +62,7 @@\n \n     if (should_plot_to_terminal)\n       enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n-      __gnuplot_draw_figure__ (h, plot_stream(1), enhanced, mono);\n+      __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n       fflush (plot_stream(1));\n     endif\n \n@@ -75,10 +75,15 @@\n     if (should_plot_to_temp_file)\n       tmp_file = tempname ();\n       __gnuplot_draw_to_file__ (h, term, tmp_file);\n-      pause (1);\n+      fflush (plot_stream(1));\n \n       ## Read the temp file into memory and then delete it\n       fid = fopen (tmp_file, 'r');\n+      while (fid < 0)\n+        fprintf (stderr, \"🛈 Waiting for plot to finish… ⏳\\n\");\n+        pause (0.5);\n+        fid = fopen (tmp_file, 'r');\n+      endwhile\n       [a, count] = fscanf (fid, '%c', Inf);\n       fclose (fid);\n       unlink (tmp_file);\n"
  },
  {
    "path": "back-octave/oo-changesets/102-30d8ba0fbc32.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1514234238 21600\n#      Mon Dec 25 14:37:18 2017 -0600\n# Branch oo-4.2.1\n# Node ID 30d8ba0fbc32805a870089c2159fc74cec21d22a\n# Parent  bc8cd93feec50aa5f30d6ad0aa0e85f31693c71f\nIn urlread, enable octave_link::request_url only when octave_link is enabled.\n\ndiff -r bc8cd93feec5 -r 30d8ba0fbc32 liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tMon Dec 25 06:06:29 2017 -0600\n+++ b/liboctave/util/url-transfer.cc\tMon Dec 25 14:37:18 2017 -0600\n@@ -219,28 +219,28 @@\n   }\n \n \n-class oo_url_transfer : public base_url_transfer\n+class link_transfer : public base_url_transfer\n {\n public:\n \n-  oo_url_transfer (void)\n+  link_transfer (void)\n       : base_url_transfer () {\n     valid = true;\n   }\n \n-  oo_url_transfer (const std::string& host, const std::string& user_arg,\n+  link_transfer (const std::string& host, const std::string& user_arg,\n                  const std::string& passwd, std::ostream& os)\n       : base_url_transfer (host, user_arg, passwd, os) {\n     valid = true;\n     // url = \"ftp://\" + host;\n   }\n \n-  oo_url_transfer (const std::string& url_str, std::ostream& os)\n+  link_transfer (const std::string& url_str, std::ostream& os)\n       : base_url_transfer (url_str, os) {\n     valid = true;\n   }\n \n-  ~oo_url_transfer (void) {}\n+  ~link_transfer (void) {}\n \n   void http_get (const Array<std::string>& param) {\n     perform (param, \"get\");\n@@ -276,7 +276,7 @@\n       }\n     } else {\n       ok = false;\n-      errmsg = \"octave_link not connected for oo_url_transfer\";\n+      errmsg = \"octave_link not connected for link_transfer\";\n     }\n   }\n \n@@ -853,24 +853,36 @@\n \n #endif\n \n-// #if defined (HAVE_CURL)\n-// #  define REP_CLASS curl_transfer\n-// #else\n-// #  define REP_CLASS base_url_transfer\n-// #endif\n-#define REP_CLASS oo_url_transfer\n+#if defined (HAVE_CURL)\n+#  define REP_CLASS curl_transfer\n+#else\n+#  define REP_CLASS base_url_transfer\n+#endif\n \n-  url_transfer::url_transfer (void) : rep (new REP_CLASS ())\n-  { }\n+  url_transfer::url_transfer (void) {\n+    if (octave_link::request_input_enabled()) {\n+      rep = new link_transfer();\n+    } else {\n+      rep = new REP_CLASS();\n+    }\n+  }\n \n   url_transfer::url_transfer (const std::string& host, const std::string& user,\n-                              const std::string& passwd, std::ostream& os)\n-    : rep (new REP_CLASS (host, user, passwd, os))\n-  { }\n+                              const std::string& passwd, std::ostream& os) {\n+    if (octave_link::request_input_enabled()) {\n+      rep = new link_transfer(host, user, passwd, os);\n+    } else {\n+      rep = new REP_CLASS(host, user, passwd, os);\n+    }\n+  }\n \n-  url_transfer::url_transfer (const std::string& url, std::ostream& os)\n-    : rep (new REP_CLASS (url, os))\n-  { }\n+  url_transfer::url_transfer (const std::string& url, std::ostream& os) {\n+    if (octave_link::request_input_enabled()) {\n+      rep = new link_transfer(url, os);\n+    } else {\n+      rep = new REP_CLASS(url, os);\n+    }\n+  }\n \n #undef REP_CLASS\n \n"
  },
  {
    "path": "back-octave/oo-changesets/103-352b599bc533.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1514767734 21600\n#      Sun Dec 31 18:48:54 2017 -0600\n# Branch oo-4.2.1\n# Node ID 352b599bc5337bc9c3465092aa36e320811f781a\n# Parent  30d8ba0fbc32805a870089c2159fc74cec21d22a\nChanging octave_kbhit() to use octave_link::request_input, fixing the functions kbhit and pause.\n\ndiff -r 30d8ba0fbc32 -r 352b599bc533 libinterp/corefcn/sysdep.cc\n--- a/libinterp/corefcn/sysdep.cc\tMon Dec 25 14:37:18 2017 -0600\n+++ b/libinterp/corefcn/sysdep.cc\tSun Dec 31 18:48:54 2017 -0600\n@@ -539,8 +539,8 @@\n \n // Read one character from the terminal.\n \n-int\n-octave_kbhit (bool wait)\n+static int\n+octave_kbhit (const std::string& prompt, bool wait)\n {\n #if defined (HAVE__KBHIT) && defined (HAVE__GETCH)\n   // This essentially means we are on a Windows system.\n@@ -563,10 +563,19 @@\n \n   octave::set_interrupt_handler (saved_interrupt_handler, false);\n \n-  int c = std::cin.get ();\n-\n-  if (std::cin.fail () || std::cin.eof ())\n-    std::cin.clear ();\n+  int c;\n+  if (octave_link::request_input_enabled ()) {\n+    std::string line = octave_link::request_input (prompt);\n+    if (line.length() >= 1) {\n+      c = line.at(0);\n+    } else {\n+      c = '\\n';\n+    }\n+  } else {\n+    c = std::cin.get ();\n+    if (std::cin.fail () || std::cin.eof ())\n+      std::cin.clear ();\n+  }\n \n   // Restore it, enabling system call restarts (if possible).\n   octave::set_interrupt_handler (saved_interrupt_handler, true);\n@@ -577,6 +586,12 @@\n   return c;\n }\n \n+int\n+octave_kbhit (bool wait)\n+{\n+  return octave_kbhit(\"\", wait);\n+}\n+\n std::string\n get_P_tmpdir (void)\n {\n@@ -760,7 +775,7 @@\n     {\n       Fdrawnow ();\n \n-      int c = octave_kbhit (args.length () == 0);\n+      int c = octave_kbhit (\"kbhit>\", args.length () == 0);\n \n       if (c == -1)\n         c = 0;\n@@ -824,7 +839,7 @@\n           if (octave::math::isinf (dval))\n             {\n               flush_octave_stdout ();\n-              octave_kbhit ();\n+              octave_kbhit (\"press enter to continue\", false);\n             }\n           else\n             octave_sleep (dval);\n@@ -834,7 +849,7 @@\n     {\n       Fdrawnow ();\n       flush_octave_stdout ();\n-      octave_kbhit ();\n+      octave_kbhit (\"press enter to continue\", false);\n     }\n \n   return ovl ();\n"
  },
  {
    "path": "back-octave/oo-changesets/104-9475120a3110.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1515058277 21600\n#      Thu Jan 04 03:31:17 2018 -0600\n# Branch oo-4.2.1\n# Node ID 9475120a3110c6932c85fd16ea40dc51137ce78e\n# Parent  352b599bc5337bc9c3465092aa36e320811f781a\nChecking unknown functions against all available functions in installed packages.\n\ndiff -r 352b599bc533 -r 9475120a3110 scripts/help/__unimplemented__.m\n--- a/scripts/help/__unimplemented__.m\tSun Dec 31 18:48:54 2017 -0600\n+++ b/scripts/help/__unimplemented__.m\tThu Jan 04 03:31:17 2018 -0600\n@@ -40,7 +40,30 @@\n \n   is_matlab_function = true;\n \n+  ## First look at the package metadata\n+  # To generate package_metadata.mat, run: packages={}; for p=pkg('list'); packages={packages{:} pkg('describe', '-verbose', p{1}.name){:}}; endfor; save('package_metadata.mat', 'packages');\n+  found_in_package_metadata = false;\n+  try\n+    vars = load(\"package_metadata.mat\");\n+    for lvl1 = vars.packages\n+      for lvl2 = lvl1{1}.provides\n+        for lvl3 = lvl2{1}.functions\n+          if strcmp(fcn, lvl3{1})\n+            txt = check_package(fcn, lvl1{1}.name);\n+            found_in_package_metadata = true;\n+            break;\n+          endif\n+        endfor\n+        if found_in_package_metadata, break; endif\n+      endfor\n+      if found_in_package_metadata, break; endif\n+    endfor\n+  catch err\n+    warning(err)\n+  end_try_catch\n+\n   ## Some smarter cases, add more as needed.\n+  if !found_in_package_metadata\n   switch (fcn)\n     case {\"avifile\", \"aviinfo\", \"aviread\"}\n       txt = [\"Basic video file support is provided in the video package.  \", ...\n@@ -511,6 +534,7 @@\n         txt = \"\";\n       endif\n   endswitch\n+  endif\n \n   if (is_matlab_function)\n     txt = [txt, \"\\n\\n@noindent\\nPlease read \", ...\n@@ -553,13 +577,13 @@\n       endfor\n       txt = sprintf (\"%s but has not yet been implemented.\", txt);\n     case \"not loaded\",\n-      txt = sprintf ([\"%s which you have installed but not loaded.  To \", ...\n-                      \"load the package, run `pkg load %s' from the \", ...\n-                      \"Octave prompt.\"], txt, name);\n+      txt = sprintf ([\"%s, which you have installed but not loaded.\\n\\n\", ...\n+                      \"Run `pkg load %s' to use `%s'.\"], ...\n+                     txt, name, fcn);\n     otherwise\n       ## this includes \"not installed\" and anything else if pkg changes\n       ## the output of describe\n-      txt = sprintf (\"%s which seems to not be installed in your system.\", txt);\n+      txt = sprintf (\"%s, which seems to not be installed in your system.\", txt);\n   endswitch\n \n endfunction\n"
  },
  {
    "path": "back-octave/oo-changesets/105-ccbef5c9b050.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1515058304 21600\n#      Thu Jan 04 03:31:44 2018 -0600\n# Branch oo-4.2.1\n# Node ID ccbef5c9b050b55218de6bdfb7d241fc90e0d032\n# Parent  9475120a3110c6932c85fd16ea40dc51137ce78e\nAdding Octave binding for current_command_number variable.\n\ndiff -r 9475120a3110 -r ccbef5c9b050 libinterp/corefcn/input.cc\n--- a/libinterp/corefcn/input.cc\tThu Jan 04 03:31:17 2018 -0600\n+++ b/libinterp/corefcn/input.cc\tThu Jan 04 03:31:44 2018 -0600\n@@ -1591,3 +1591,32 @@\n \n   return retval;\n }\n+\n+DEFUN (current_command_number, args, ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn  {} {@var{val} =} current_command_number ()\n+@deftypefnx {} {@var{old_val} =} current_command_number (@var{new_val})\n+Sets the current command number, which appears in the prompt string.\n+For example, if the prompt says \"octave:1>\", then the current command\n+number is 1.\n+\n+This is a custom function in Octave Online.\n+\n+@example\n+current_command_number(1)\n+@end example\n+@end deftypefn */)\n+{\n+  int nargin = args.length ();\n+  if (nargin == 0) {\n+    int n = octave::command_editor::current_command_number();\n+    return ovl(n);\n+  } else if (nargin > 1) {\n+    print_usage ();\n+    return ovl();\n+  } else {\n+    int n = args(0).int_value ();\n+    octave::command_editor::reset_current_command_number(n);\n+    return ovl(n);\n+  }\n+}\n"
  },
  {
    "path": "back-octave/oo-changesets/106-91cb270ffac0.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1515059962 21600\n#      Thu Jan 04 03:59:22 2018 -0600\n# Branch oo-4.2.1\n# Node ID 91cb270ffac07018236b9e394378051df7b612c5\n# Parent  ccbef5c9b050b55218de6bdfb7d241fc90e0d032\nChanging package_metadata.mat load to use absolute path.\n\ndiff -r ccbef5c9b050 -r 91cb270ffac0 scripts/help/__unimplemented__.m\n--- a/scripts/help/__unimplemented__.m\tThu Jan 04 03:31:44 2018 -0600\n+++ b/scripts/help/__unimplemented__.m\tThu Jan 04 03:59:22 2018 -0600\n@@ -41,10 +41,10 @@\n   is_matlab_function = true;\n \n   ## First look at the package metadata\n-  # To generate package_metadata.mat, run: packages={}; for p=pkg('list'); packages={packages{:} pkg('describe', '-verbose', p{1}.name){:}}; endfor; save('package_metadata.mat', 'packages');\n+  # To generate package_metadata.mat, run: packages={}; for p=pkg('list'); packages={packages{:} pkg('describe', '-verbose', p{1}.name){:}}; endfor; save('/usr/local/share/octave/site/m/package_metadata.mat', 'packages');\n   found_in_package_metadata = false;\n   try\n-    vars = load(\"package_metadata.mat\");\n+    vars = load(\"/usr/local/share/octave/site/m/package_metadata.mat\");\n     for lvl1 = vars.packages\n       for lvl2 = lvl1{1}.provides\n         for lvl3 = lvl2{1}.functions\n"
  },
  {
    "path": "back-octave/oo-changesets/107-80081f9d8ff7.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1515065941 21600\n#      Thu Jan 04 05:39:01 2018 -0600\n# Branch oo-4.2.1\n# Node ID 80081f9d8ff7179f1d5d82293f4c83030b8e0357\n# Parent  91cb270ffac07018236b9e394378051df7b612c5\nSending current command number along with show-static-plot octave_link message.\n\ndiff -r 91cb270ffac0 -r 80081f9d8ff7 libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tThu Jan 04 03:59:22 2018 -0600\n+++ b/libinterp/corefcn/octave-json-link.cc\tThu Jan 04 05:39:01 2018 -0600\n@@ -27,6 +27,7 @@\n #include <iostream>\r\n #include \"octave-json-link.h\"\r\n #include \"workspace-element.h\"\r\n+#include \"cmd-edit.h\"\r\n #include \"json-main.h\"\r\n #include \"json-util.h\"\r\n \r\n@@ -304,9 +305,11 @@\n \r\n void octave_json_link::do_show_static_plot(const std::string& term, const std::string& content) {\r\n \t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\r\n+\tint command_number = octave::command_editor::current_command_number();\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, term, string);\r\n \tJSON_MAP_SET(m, content, string);\r\n+\tJSON_MAP_SET(m, command_number, int);\r\n \t_publish_message(\"show-static-plot\", json_util::from_map(m));\r\n }\r\n \r\n"
  },
  {
    "path": "back-octave/oo-changesets/108-9b39ca8bcbfd.hg.txt",
    "content": "# HG changeset patch\n# User Octave Online <webmaster@octave-online.net>\n# Date 1555656254 0\n#      Fri Apr 19 06:44:14 2019 +0000\n# Branch stable\n# Node ID 9b39ca8bcbfd5398e342a6d8f9f81ac06069307b\n# Parent  abdfdd6f14cc4063475c5968c4623b3ebd0cddde\nMinor gnuplot plotting fixes.\n\ndiff -r abdfdd6f14cc -r 9b39ca8bcbfd scripts/plot/appearance/legend.m\n--- a/scripts/plot/appearance/legend.m\tThu Jan 04 05:39:01 2018 -0600\n+++ b/scripts/plot/appearance/legend.m\tFri Apr 19 06:44:14 2019 +0000\n@@ -563,8 +563,8 @@\n           old_hplots = [ get(hlegend, \"deletefcn\"){6:end} ];\n         endif\n         if (addprops)\n-          addproperty (\"edgecolor\", hlegend, \"color\", [0, 0, 0]);\n-          addproperty (\"textcolor\", hlegend, \"color\", [0, 0, 0]);\n+          addproperty (\"edgecolor\", hlegend, \"color\", get(0, \"defaultaxesxcolor\"));\n+          addproperty (\"textcolor\", hlegend, \"color\", get(0, \"defaulttextcolor\"));\n           locations = {\"north\", \"south\", \"east\", \"west\", ...\n                        \"{northeast}\", \"southeast\", \"northwest\", \"southwest\", ...\n                        \"northoutside\", \"southoutside\", ...\ndiff -r abdfdd6f14cc -r 9b39ca8bcbfd scripts/plot/util/private/__gnuplot_draw_axes__.m\n--- a/scripts/plot/util/private/__gnuplot_draw_axes__.m\tThu Jan 04 05:39:01 2018 -0600\n+++ b/scripts/plot/util/private/__gnuplot_draw_axes__.m\tFri Apr 19 06:44:14 2019 +0000\n@@ -2880,6 +2880,8 @@\n     else\n       cdata(:) = fix (255 / 2);\n     endif\n+    ## OO PATCH\n+    cdata = 1 + cdata * (cmap_sz-1)/255;\n   else\n     if (islogical (cdata))\n       cdata += 1;\n"
  },
  {
    "path": "back-octave/oo-changesets/200-84cbf166497f.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1577968674 21600\n#      Thu Jan 02 06:37:54 2020 -0600\n# Branch oo-5.2\n# Node ID 84cbf166497fa3d942be6a0b026ec24521301d16\n# Parent  56dd7419d7aa197340040f914d8ea69aa948710e\n# Parent  80081f9d8ff7179f1d5d82293f4c83030b8e0357\nMerge oo-4.2.1 onto stable (5.2-rc)\n\ndiff -r 56dd7419d7aa -r 84cbf166497f configure.ac\n--- a/configure.ac\tTue Dec 24 04:01:33 2019 +0100\n+++ b/configure.ac\tThu Jan 02 06:37:54 2020 -0600\n@@ -2752,7 +2752,7 @@\n AC_SUBST(LIBOCTAVE_LINK_DEPS)\n AC_SUBST(LIBOCTAVE_LINK_OPTS)\n \n-LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $LLVM_LIBS $JAVA_LIBS $LAPACK_LIBS\"\n+LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $LLVM_LIBS $JAVA_LIBS $LAPACK_LIBS -ljson-c\"\n \n LIBOCTINTERP_LINK_OPTS=\"$FT2_LDFLAGS $HDF5_LDFLAGS $MAGICK_LDFLAGS $Z_LDFLAGS $FFTW_XLDFLAGS $LLVM_LDFLAGS\"\n \ndiff -r 56dd7419d7aa -r 84cbf166497f libgui/src/octave-qt-link.cc\n--- a/libgui/src/octave-qt-link.cc\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libgui/src/octave-qt-link.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -62,6 +62,16 @@\n     qRegisterMetaType<symbol_info_list> (\"symbol_info_list\");\n   }\n \n+  std::string octave_qt_link::do_request_input (const std::string&)\n+  {\n+    return {};\n+  }\n+\n+  std::string octave_qt_link::do_request_url (const std::string&, const std::list<std::string>&, const std::string&, bool&)\n+  {\n+    return {};\n+  }\n+\n   bool octave_qt_link::do_confirm_shutdown (void)\n   {\n     // Lock the mutex before emitting signal.\n@@ -470,6 +480,9 @@\n     emit clear_history_signal ();\n   }\n \n+  void octave_qt_link::do_clear_screen (void)\n+  { }\n+\n   void octave_qt_link::do_pre_input_event (void)\n   { }\n \n@@ -645,4 +658,10 @@\n   {\n     emit delete_debugger_pointer_signal (QString::fromStdString (file), line);\n   }\n+\n+  void\n+  octave_qt_link::do_show_static_plot (const std::string&, const std::string&)\n+  {\n+    return;\n+  }\n }\ndiff -r 56dd7419d7aa -r 84cbf166497f libgui/src/octave-qt-link.h\n--- a/libgui/src/octave-qt-link.h\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libgui/src/octave-qt-link.h\tThu Jan 02 06:37:54 2020 -0600\n@@ -67,6 +67,9 @@\n \n     ~octave_qt_link (void) = default;\n \n+    std::string do_request_input (const std::string&);\n+    std::string do_request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n     bool do_confirm_shutdown (void);\n \n     bool do_copy_image_to_clipboard (const std::string& file);\n@@ -126,6 +129,8 @@\n     void do_append_history (const std::string& hist_entry);\n     void do_clear_history (void);\n \n+    void do_clear_screen (void);\n+\n     void do_pre_input_event (void);\n     void do_post_input_event (void);\n \n@@ -148,6 +153,8 @@\n \n     void do_edit_variable (const std::string& name, const octave_value& val);\n \n+    void do_show_static_plot (const std::string& term, const std::string& content);\n+\n     void shutdown_confirmation (bool sd) { m_shutdown_confirm_result = sd; }\n \n     void lock (void) { m_mutex.lock (); }\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/input.cc\n--- a/libinterp/corefcn/input.cc\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libinterp/corefcn/input.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -740,7 +740,11 @@\n \n     eof = false;\n \n-    std::string retval = octave::command_editor::readline (s, eof);\n+    std::string retval;\n+    if (octave_link::request_input_enabled ())\n+      retval = octave_link::request_input (s);\n+    else\n+      retval = octave::command_editor::readline (s, eof);\n \n     if (! eof && retval.empty ())\n       retval = \"\\n\";\n@@ -1674,3 +1678,32 @@\n }\n \n // #endif\n+\n+DEFUN (current_command_number, args, ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn  {} {@var{val} =} current_command_number ()\n+@deftypefnx {} {@var{old_val} =} current_command_number (@var{new_val})\n+Sets the current command number, which appears in the prompt string.\n+For example, if the prompt says \"octave:1>\", then the current command\n+number is 1.\n+\n+This is a custom function in Octave Online.\n+\n+@example\n+current_command_number(1)\n+@end example\n+@end deftypefn */)\n+{\n+  int nargin = args.length ();\n+  if (nargin == 0) {\n+    int n = octave::command_editor::current_command_number();\n+    return ovl(n);\n+  } else if (nargin > 1) {\n+    print_usage ();\n+    return ovl();\n+  } else {\n+    int n = args(0).int_value ();\n+    octave::command_editor::reset_current_command_number(n);\n+    return ovl(n);\n+  }\n+}\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/interpreter.cc\n--- a/libinterp/corefcn/interpreter.cc\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libinterp/corefcn/interpreter.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -51,6 +51,7 @@\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"interpreter.h\"\n+#include \"json-main.h\"\n #include \"load-path.h\"\n #include \"load-save.h\"\n #include \"octave-link.h\"\n@@ -518,6 +519,11 @@\n \n     initialize_version_info ();\n \n+    if (!options.json_sock_path().empty ()) {\n+      static json_main _json_main (options.json_sock_path(), options.json_max_message_length());\n+      _json_main.run_loop_on_new_thread();\n+    }\n+\n     // This should be done before initializing the load path because\n     // some PKG_ADD files might need --traditional behavior.\n \ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/json-main.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -0,0 +1,93 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include \"json-main.h\"\r\n+\r\n+#include <iostream>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <unistd.h>\r\n+\r\n+\r\n+// Analog of main-window.cc\r\n+// TODO: Think more about concurrency and null pointer exceptions\r\n+\r\n+void* run_loop_pthread(void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->run_loop();\r\n+  return NULL;\r\n+}\r\n+\r\n+void json_object_cb(std::string name, JSON_OBJECT_T jobj, void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->process_json_object(name, jobj);\r\n+}\r\n+\r\n+json_main::json_main(const std::string& json_sock_path, int max_message_length)\r\n+  : _json_sock_path (json_sock_path),\r\n+    _max_message_length (max_message_length),\r\n+    _loop_thread_active (false),\r\n+    _octave_json_link (this)\r\n+{\r\n+  // Enable octave_json_link instance\r\n+\toctave_link::connect_link(&_octave_json_link);\r\n+\r\n+  // Open UNIX socket file descriptor\r\n+  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\r\n+  struct sockaddr_un addr;\r\n+  memset(&addr, 0, sizeof(addr));\r\n+  addr.sun_family = AF_UNIX;\r\n+  memcpy(&addr.sun_path, _json_sock_path.c_str(), sizeof(addr.sun_path)-1);\r\n+  connect(\r\n+    sockfd,\r\n+    reinterpret_cast<struct sockaddr*>(&addr),\r\n+    sizeof(addr));\r\n+}\r\n+\r\n+json_main::~json_main(void) {\r\n+  close(sockfd);\r\n+\r\n+  // TODO: Stop the _loop_thread\r\n+}\r\n+\r\n+void json_main::publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  std::string jstr = json_util::to_message(name, jobj);\r\n+\r\n+  // Do not send any messages over the socket that exceed the user-specified max length.  Instead, send an error message.  If max_length is 0 (default), do not suppress any messages.\r\n+  // In stress testing, Node may be able to handle as much as 10-20 MB, but I'd prefer to stay on the safe side.  Before this safeguard was implemented, fewer than 5% of plots exceeded 1 MB.\r\n+  int length = jstr.length();\r\n+  int max_length = _max_message_length;\r\n+  if (max_length > 0 && length > max_length) {\r\n+    JSON_MAP_T m;\r\n+    JSON_MAP_SET(m, name, string);\r\n+    JSON_MAP_SET(m, length, int);\r\n+    JSON_MAP_SET(m, max_length, int);\r\n+    jstr = json_util::to_message(\"message-too-long\", json_util::from_map(m));\r\n+  }\r\n+\r\n+  send(sockfd, jstr.c_str(), jstr.length(), 0);\r\n+}\r\n+\r\n+void json_main::run_loop_on_new_thread(void) {\r\n+  if (_loop_thread_active)\r\n+    perror(\"won't run JSON socket loop multiple times\");\r\n+  _loop_thread_active = true;\r\n+\r\n+  pthread_create(\r\n+    &_loop_thread,\r\n+    NULL,\r\n+    run_loop_pthread,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::run_loop(void) {\r\n+  json_util::read_stream(\r\n+    sockfd,\r\n+    json_object_cb,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::process_json_object(std::string name, JSON_OBJECT_T jobj) {\r\n+  _octave_json_link.receive_message(name, jobj);\r\n+}\r\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/json-main.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.h\tThu Jan 02 06:37:54 2020 -0600\n@@ -0,0 +1,30 @@\n+#ifndef json_main_h\r\n+#define json_main_h\r\n+\r\n+#include <queue>\r\n+#include <pthread.h>\r\n+#include <stdio.h>\r\n+\r\n+#include \"octave-json-link.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+class json_main {\r\n+public:\r\n+\tjson_main(const std::string& json_sock_path, int max_message_length);\r\n+\t~json_main(void);\r\n+\r\n+\tvoid publish_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\tvoid run_loop_on_new_thread(void);\r\n+\tvoid run_loop(void);\r\n+\tvoid process_json_object(std::string name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+\tstd::string _json_sock_path;\r\n+\tint _max_message_length;\r\n+\tint sockfd;\r\n+\tbool _loop_thread_active;\r\n+\tpthread_t _loop_thread;\r\n+\toctave_json_link _octave_json_link;\r\n+};\r\n+\r\n+#endif\r\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/json-util.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -0,0 +1,242 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <cstdlib>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <stdio.h>\r\n+#include <json-c/arraylist.h>\r\n+\r\n+#include \"str-vec.h\"\r\n+\r\n+#include \"json-util.h\"\r\n+\r\n+JSON_OBJECT_T json_util::from_string(const std::string& str) {\r\n+\treturn json_object_new_string(str.c_str());\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int(int i) {\r\n+\treturn json_object_new_int(i);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float(float flt) {\r\n+\treturn json_object_new_double(flt);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_boolean(bool b) {\r\n+\treturn json_object_new_boolean(b);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::empty() {\r\n+\treturn json_object_new_object();\r\n+}\r\n+\r\n+template<typename T>\r\n+JSON_OBJECT_T json_object_from_list(const std::list<T>& list, JSON_OBJECT_T (*convert)(T)) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\ttypename std::list<T>::const_iterator it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, convert(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_list(const std::list<std::string>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_vector(const string_vector& vect) {\r\n+\t// TODO: Make sure this function does what it's supposed to do\r\n+\tstd::list<std::string> list;\r\n+\tfor (int i = 0; i < vect.numel(); ++i) {\r\n+\t\tlist.push_back(vect[i]);\r\n+\t}\r\n+\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int_list(const std::list<int>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_int);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float_list(const std::list<float>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_float);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_workspace_list(const std::list<workspace_element>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_workspace_element);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_filter_list(const octave_link::filter_list& list) {\r\n+\treturn json_object_from_list(list, json_util::from_pair);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_value_string(const std::string str) {\r\n+\treturn json_util::from_string(str);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_workspace_element(workspace_element element) {\r\n+\tJSON_MAP_T m;\r\n+\tm[\"scope\"] = json_util::from_int(element.scope());\r\n+\tm[\"symbol\"] = json_util::from_string(element.symbol());\r\n+\tm[\"class_name\"] = json_util::from_string(element.class_name());\r\n+\tm[\"dimension\"] = json_util::from_string(element.dimension());\r\n+\tm[\"value\"] = json_util::from_string(element.value());\r\n+\tm[\"complex_flag\"] = json_util::from_boolean(element.complex_flag());\r\n+\treturn json_util::from_map(m);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_pair(std::pair<std::string, std::string> pair) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tjson_object_array_add(jobj, json_object_new_string(pair.first.c_str()));\r\n+\tjson_object_array_add(jobj, json_object_new_string(pair.second.c_str()));\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_map(JSON_MAP_T m) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_object();\r\n+\tfor(\r\n+\t\tstd::map<std::string, JSON_OBJECT_T>::iterator it = m.begin();\r\n+\t\tit != m.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_object_add(jobj, it->first.c_str(), it->second);\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+std::string json_util::to_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  JSON_OBJECT_T jmsg = json_object_new_array();\r\n+  json_object_array_add(jmsg, json_util::from_string(name));\r\n+  json_object_array_add(jmsg, jobj);\r\n+  std::string str (json_object_to_json_string(jmsg));\r\n+  return str;\r\n+}\r\n+\r\n+std::string json_util::to_string(JSON_OBJECT_T jobj) {\r\n+  return std::string(json_object_get_string(jobj));\r\n+}\r\n+\r\n+template<typename T>\r\n+std::list<T> json_object_to_list(JSON_OBJECT_T jobj, T (*convert)(JSON_OBJECT_T)) {\r\n+\tstd::list<T> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tfor (int i = 0; i < array_list_length(arr); ++i) {\r\n+\t\tJSON_OBJECT_T jsub = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, i));\r\n+\t\tret.push_back(convert(jsub));\r\n+\t}\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> json_util::to_int_list_int_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<std::list<int>, int> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_to_list<int>(first, json_util::to_int);\r\n+\tret.second = json_object_get_int(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<bool, std::string> json_util::to_bool_string_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<bool, std::string> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_get_boolean(first);\r\n+\tret.second = json_object_get_string(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::list<std::string> json_util::to_string_list(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_to_list<std::string>(jobj, json_util::to_string);\r\n+}\r\n+\r\n+int json_util::to_int(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_int(jobj);\r\n+}\r\n+\r\n+bool json_util::to_boolean(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_boolean(jobj);\r\n+}\r\n+\r\n+void json_util::read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+\r\n+\t// Make some local variables\r\n+\tint BUF_LEN = 24;\r\n+\tchar* buf = new char[BUF_LEN];  // buffer for socket read\r\n+\tint buf_len;  // length of new bytes in the buffer\r\n+\tint buf_offset;  // offset of the JSON parser in the buffer\r\n+\tJSON_OBJECT_T jobj;  // pointer to parsed JSON object\r\n+\tjson_tokener* tok = json_tokener_new();  // JSON tokenizer instance\r\n+\tenum json_tokener_error jerr;  // status of JSON tokenizer\r\n+\r\n+\t// Start the blocking I/O loop\r\n+\twhile( (buf_len=recv(sockfd, buf, BUF_LEN, 0)) > 0) {\r\n+\t\tbuf_offset = 0;\r\n+\t\twhile(buf_offset < buf_len){\r\n+\t\t\tjobj = json_tokener_parse_ex(tok, buf + buf_offset, buf_len - buf_offset);\r\n+\t\t\tjerr = json_tokener_get_error(tok);\r\n+\t\t\tbuf_offset += tok->char_offset;\r\n+\r\n+\t\t\t// Do we need more material in order to make JSON?\r\n+\t\t\tif (jerr == json_tokener_continue) {\r\n+\t\t\t\tcontinue;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Make a new tokenizer\r\n+\t\t\tjson_tokener_free(tok);\r\n+\t\t\ttok = json_tokener_new();\r\n+\r\n+\t\t\t// Did we encounter a malformed JSON object?\r\n+\t\t\tif (jerr != json_tokener_success) {\r\n+\t\t\t\tfprintf(stderr,\r\n+\t\t\t\t\t\"JSON parse error: %s: '%.*s'\\n\",\r\n+\t\t\t\t\tjson_tokener_error_desc(jerr),\r\n+\t\t\t\t\t1,\r\n+\t\t\t\t\tbuf + buf_offset);\r\n+\t\t\t\tfflush(stderr);\r\n+\t\t\t\tbreak;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Our object is ready\r\n+\t\t\tprocess_message(jobj, cb, arg);\r\n+\t\t}\r\n+\t}\r\n+\r\n+\tjson_tokener_free(tok);\r\n+\tdelete buf;\r\n+}\r\n+\r\n+void json_util::process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+  if (!json_object_is_type(jobj, json_type_array))\r\n+    return;\r\n+  if (json_object_array_length(jobj) != 2)\r\n+    return;\r\n+\r\n+  cb(\r\n+  \tjson_util::to_string(json_object_array_get_idx(jobj, 0)),\r\n+  \tjson_object_array_get_idx(jobj, 1),\r\n+  \targ\r\n+  );\r\n+}\r\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/json-util.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.h\tThu Jan 02 06:37:54 2020 -0600\n@@ -0,0 +1,60 @@\n+#ifndef json_util_h\r\n+#define json_util_h\r\n+\r\n+#include <json-c/json.h>\r\n+#include <map>\r\n+#include <list>\r\n+\r\n+#include \"workspace-element.h\"\r\n+#include \"octave-link.h\"\r\n+\r\n+class string_vector;\r\n+\r\n+// All of the code interacting with the external JSON library should be in\r\n+// the json-util.h and json-util.cc files.  This way, if we want to change\r\n+// the external JSON library, we can do it all in one place.\r\n+\r\n+#define JSON_OBJECT_T json_object*\r\n+#define JSON_MAP_T std::map<std::string, JSON_OBJECT_T>\r\n+\r\n+#define JSON_MAP_SET(M, FIELD, TYPE){ \\\r\n+\tm[#FIELD] = json_util::from_##TYPE (FIELD); \\\r\n+}\r\n+\r\n+class json_util {\r\n+public:\r\n+\tstatic JSON_OBJECT_T from_string(const std::string& str);\r\n+\tstatic JSON_OBJECT_T from_int(int i);\r\n+\tstatic JSON_OBJECT_T from_float(float flt);\r\n+\tstatic JSON_OBJECT_T from_boolean(bool b);\r\n+\tstatic JSON_OBJECT_T empty();\r\n+\r\n+\tstatic JSON_OBJECT_T from_string_list(const std::list<std::string>& list);\r\n+\tstatic JSON_OBJECT_T from_string_vector(const string_vector& list);\r\n+\tstatic JSON_OBJECT_T from_int_list(const std::list<int>& list);\r\n+\tstatic JSON_OBJECT_T from_float_list(const std::list<float>& list);\r\n+\tstatic JSON_OBJECT_T from_workspace_list(const std::list<workspace_element>& list);\r\n+\tstatic JSON_OBJECT_T from_filter_list(const octave_link::filter_list& list);\r\n+\r\n+\tstatic JSON_OBJECT_T from_value_string(const std::string str);\r\n+\tstatic JSON_OBJECT_T from_workspace_element(workspace_element element);\r\n+\tstatic JSON_OBJECT_T from_pair(std::pair<std::string, std::string> pair);\r\n+\r\n+\tstatic JSON_OBJECT_T from_map(JSON_MAP_T m);\r\n+\r\n+\tstatic std::string to_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic std::string to_string(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<std::list<int>, int> to_int_list_int_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<bool, std::string> to_bool_string_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::list<std::string> to_string_list(JSON_OBJECT_T jobj);\r\n+\tstatic int to_int(JSON_OBJECT_T jobj);\r\n+\tstatic bool to_boolean(JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic void read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+\r\n+private:\r\n+\tstatic void process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+};\r\n+\r\n+#endif\r\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/module.mk\n--- a/libinterp/corefcn/module.mk\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libinterp/corefcn/module.mk\tThu Jan 02 06:37:54 2020 -0600\n@@ -45,6 +45,8 @@\n   %reldir%/help.h \\\n   %reldir%/hook-fcn.h \\\n   %reldir%/input.h \\\n+  %reldir%/json-main.h \\\n+  %reldir%/json-util.h \\\n   %reldir%/interpreter.h \\\n   %reldir%/load-path.h \\\n   %reldir%/load-save.h \\\n@@ -73,6 +75,7 @@\n   %reldir%/oct-strstrm.h \\\n   %reldir%/oct.h \\\n   %reldir%/octave-default-image.h \\\n+  %reldir%/octave-json-link.h \\\n   %reldir%/octave-link.h \\\n   %reldir%/pager.h \\\n   %reldir%/pr-flt-fmt.h \\\n@@ -175,6 +178,8 @@\n   %reldir%/hex2num.cc \\\n   %reldir%/hook-fcn.cc \\\n   %reldir%/input.cc \\\n+  %reldir%/json-main.cc \\\n+  %reldir%/json-util.cc \\\n   %reldir%/inv.cc \\\n   %reldir%/interpreter-private.cc \\\n   %reldir%/interpreter.cc \\\n@@ -210,6 +215,7 @@\n   %reldir%/oct-tex-lexer.ll \\\n   %reldir%/oct-tex-parser.h \\\n   %reldir%/oct-tex-parser.yy \\\n+  %reldir%/octave-json-link.cc \\\n   %reldir%/octave-link.cc \\\n   %reldir%/ordschur.cc \\\n   %reldir%/pager.cc \\\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/octave-json-link.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -0,0 +1,365 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <iostream>\r\n+#include \"octave-json-link.h\"\r\n+#include \"workspace-element.h\"\r\n+#include \"cmd-edit.h\"\r\n+#include \"json-main.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+octave_json_link::octave_json_link(json_main* __json_main)\r\n+\t: octave_link (),\r\n+\t\t_json_main (__json_main)\r\n+{\r\n+\t_request_input_enabled = true;\r\n+\t_plot_destination = STATIC_ONLY;\r\n+}\r\n+\r\n+octave_json_link::~octave_json_link(void) { }\r\n+\r\n+std::string octave_json_link::do_request_input(const std::string& prompt) {\r\n+\t// Triggered whenever the console prompts for user input\r\n+\r\n+\tstd::string value;\r\n+\tif (!request_input_queue.dequeue_to(&value)) {\r\n+\t\t_publish_message(\"request-input\", json_util::from_string(prompt));\r\n+\t\tvalue = request_input_queue.dequeue();\r\n+\t}\r\n+\treturn value;\r\n+}\r\n+\r\n+std::string octave_json_link::do_request_url(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\r\n+\t// Triggered on urlread/urlwrite\r\n+\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, url, string);\r\n+\tJSON_MAP_SET(m, param, string_list);\r\n+\tJSON_MAP_SET(m, action, string);\r\n+\r\n+\t_publish_message(\"request-url\", json_util::from_map(m));\r\n+\tstd::pair<bool, std::string> result = request_url_queue.dequeue();\r\n+\tsuccess = result.first;\r\n+\treturn result.second;\r\n+}\r\n+\r\n+bool octave_json_link::do_confirm_shutdown(void) {\r\n+\t// Triggered when the kernel tries to exit\r\n+\t_publish_message(\"confirm-shutdown\", json_util::empty());\r\n+\r\n+\treturn confirm_shutdown_queue.dequeue();\r\n+}\r\n+\r\n+bool octave_json_link::do_exit(int status) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, status, int);\r\n+\t_publish_message(\"exit\", json_util::from_map(m));\r\n+\r\n+\t// It is our responsibility in octave_link to call exit. If we don't, then\r\n+\t// the kernel waits for 24 hours expecting us to do something.\r\n+\t::exit(status);\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::do_copy_image_to_clipboard(const std::string& file) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"copy-image-to-clipboard\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::do_edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for existing files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::do_prompt_new_edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for new files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"prompt-new-edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn prompt_new_edit_file_queue.dequeue();\r\n+}\r\n+\r\n+int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {\r\n+\t// Triggered in \"msgbox\", \"helpdlg\", and \"errordlg\", among others\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\r\n+\tJSON_MAP_SET(m, msg, string);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\t_publish_message(\"message-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn message_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::string octave_json_link::do_question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n+\t// Triggered in \"questdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, msg, string);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, btn1, string);\r\n+\tJSON_MAP_SET(m, btn2, string);\r\n+\tJSON_MAP_SET(m, btn3, string);\r\n+\tJSON_MAP_SET(m, btndef, string);\r\n+\t_publish_message(\"question-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn question_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> octave_json_link::do_list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {\r\n+\t// Triggered in \"listdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, list, string_list);\r\n+\tJSON_MAP_SET(m, mode, string);\r\n+\tJSON_MAP_SET(m, width, int);\r\n+\tJSON_MAP_SET(m, height, int);\r\n+\tJSON_MAP_SET(m, initial_value, int_list);\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, ok_string, string);\r\n+\tJSON_MAP_SET(m, cancel_string, string);\r\n+\t_publish_message(\"list-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn list_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::do_input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {\r\n+\t// Triggered in \"inputdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, nr, float_list);\r\n+\tJSON_MAP_SET(m, nc, float_list);\r\n+\tJSON_MAP_SET(m, defaults, string_list);\r\n+\t_publish_message(\"input-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn input_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::do_file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {\r\n+\t// Triggered in \"uiputfile\", \"uigetfile\", and \"uigetdir\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, filter, filter_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, filename, string);\r\n+\tJSON_MAP_SET(m, pathname, string);\r\n+\tJSON_MAP_SET(m, multimode, string);\r\n+\t_publish_message(\"file-dialog\", json_util::from_map(m));\r\n+\t\r\n+\treturn file_dialog_queue.dequeue();\r\n+}\r\n+\r\n+int octave_json_link::do_debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\r\n+\t// This endpoint might be unused?  (No references)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\tJSON_MAP_SET(m, addpath_option, boolean);\r\n+\t_publish_message(\"debug-cd-or-addpath-error\", json_util::from_map(m));\r\n+\r\n+\treturn debug_cd_or_addpath_error_queue.dequeue();\r\n+}\r\n+\r\n+void octave_json_link::do_change_directory(const std::string& dir) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\t_publish_message(\"change-directory\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_execute_command_in_terminal(const std::string& command) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, command, string);\r\n+\t_publish_message(\"execute-command-in-terminal\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_set_workspace(bool top_level, bool debug, const std::list<workspace_element>& ws /*, const bool& variable_editor_too */) {\r\n+\t// Triggered on every new line entry\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, top_level, boolean);\r\n+\tJSON_MAP_SET(m, debug, boolean);\r\n+\tJSON_MAP_SET(m, ws, workspace_list);\r\n+\t// variable_editor_too?\r\n+\t_publish_message(\"set-workspace\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_clear_workspace(void) {\r\n+\t// Triggered on \"clear\" command (but not \"clear all\" or \"clear foo\")\r\n+\t_publish_message(\"clear-workspace\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_set_history(const string_vector& hist) {\r\n+\t// Called at startup, possibly more?\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist, string_vector);\r\n+\t_publish_message(\"set-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_append_history(const std::string& hist_entry) {\r\n+\t// Appears to be tied to readline, if available\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist_entry, string);\r\n+\t_publish_message(\"append-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_clear_history(void) {\r\n+\t// Appears to be tied to readline, if available\r\n+\t_publish_message(\"clear-history\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_clear_screen(void) {\r\n+\t// Triggered by clc\r\n+\t_publish_message(\"clear-screen\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_pre_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::do_post_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::do_enter_debugger_event(const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"enter-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_execute_in_debugger_event(const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"execute-in-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_exit_debugger_event(void) {\r\n+\t_publish_message(\"exit-debugger-event\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_update_breakpoint(bool insert, const std::string& file, int line, const std::string& cond) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, insert, boolean);\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\tJSON_MAP_SET(m, cond, string);\r\n+\t_publish_message(\"update-breakpoint\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {\r\n+\t// Triggered upon interpreter startup\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, ps1, string);\r\n+\tJSON_MAP_SET(m, ps2, string);\r\n+\tJSON_MAP_SET(m, ps4, string);\r\n+\t_publish_message(\"set-default-prompts\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_show_preferences(void) {\r\n+\t// Triggered on \"preferences\" command\r\n+\t_publish_message(\"show-preferences\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_show_doc(const std::string& file) {\r\n+\t// Triggered on \"doc\" command\r\n+\t_publish_message(\"show-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+// void octave_json_link::do_openvar(const std::string& name) {\r\n+// \t// Triggered on \"openvar\" command\r\n+// \t_publish_message(\"openvar\", json_util::from_string(name));\r\n+// }\r\n+\r\n+void octave_json_link::do_show_static_plot(const std::string& term, const std::string& content) {\r\n+\t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\r\n+\tint command_number = octave::command_editor::current_command_number();\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, term, string);\r\n+\tJSON_MAP_SET(m, content, string);\r\n+\tJSON_MAP_SET(m, command_number, int);\r\n+\t_publish_message(\"show-static-plot\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::receive_message (const std::string& name, JSON_OBJECT_T jobj) {\r\n+\tif (name == \"cmd\" || name == \"request-input-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\trequest_input_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"request-url-answer\") {\r\n+\t\tstd::pair<bool, std::string> answer = json_util::to_bool_string_pair(jobj);\r\n+\t\trequest_url_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"confirm-shutdown-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tconfirm_shutdown_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"prompt-new-edit-file-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tprompt_new_edit_file_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"message-dialog-answer\"){\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tmessage_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"question-dialog-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\tquestion_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"list-dialog-answer\") {\r\n+\t\tstd::pair<std::list<int>, int> answer = json_util::to_int_list_int_pair(jobj);\r\n+\t\tlist_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"input-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tinput_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"file-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tfile_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"debug-cd-or-addpath-error-answer\") {\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tdebug_cd_or_addpath_error_queue.enqueue(answer);\r\n+\t}\r\n+\telse {\r\n+\t\tstd::cerr << \"warning: received unknown message: \" << name << std::endl;\r\n+\t}\r\n+}\r\n+\r\n+void octave_json_link::_publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+\t_json_main->publish_message(name, jobj);\r\n+}\r\n+\r\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/octave-json-link.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.h\tThu Jan 02 06:37:54 2020 -0600\n@@ -0,0 +1,209 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifndef octave_json_link_h\r\n+#define octave_json_link_h\r\n+\r\n+#include <list>\r\n+#include <string>\r\n+\r\n+#include \"octave-link.h\"\r\n+#include \"json-util.h\"\r\n+#include \"oct-mutex.h\"\r\n+\r\n+// Circular reference\r\n+class json_main;\r\n+\r\n+// Thread-safe queue\r\n+template<typename T> class json_queue {\r\n+public:\r\n+  json_queue();\r\n+  ~json_queue();\r\n+\r\n+  void enqueue(const T& value);\r\n+  T dequeue();\r\n+  bool dequeue_to(T* destination);\r\n+\r\n+private:\r\n+  std::queue<T> _queue;\r\n+  octave_mutex _mutex;\r\n+};\r\n+\r\n+class octave_json_link : public octave_link\r\n+{\r\n+\r\n+public:\r\n+\r\n+  octave_json_link (json_main* __json_main);\r\n+\r\n+  ~octave_json_link (void);\r\n+\r\n+  std::string do_request_input (const std::string& prompt) override;\r\n+  std::string do_request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\r\n+\r\n+  bool do_confirm_shutdown (void) override;\r\n+  bool do_exit (int status) override;\r\n+\r\n+  bool do_copy_image_to_clipboard (const std::string& file) override;\r\n+\r\n+  bool do_edit_file (const std::string& file) override;\r\n+  bool do_prompt_new_edit_file (const std::string& file) override;\r\n+\r\n+  int do_message_dialog (const std::string& dlg, const std::string& msg,\r\n+                         const std::string& title) override;\r\n+\r\n+  std::string\r\n+  do_question_dialog (const std::string& msg, const std::string& title,\r\n+                      const std::string& btn1, const std::string& btn2,\r\n+                      const std::string& btn3, const std::string& btndef) override;\r\n+\r\n+  std::pair<std::list<int>, int>\r\n+  do_list_dialog (const std::list<std::string>& list,\r\n+                  const std::string& mode,\r\n+                  int width, int height,\r\n+                  const std::list<int>& initial_value,\r\n+                  const std::string& name,\r\n+                  const std::list<std::string>& prompt,\r\n+                  const std::string& ok_string,\r\n+                  const std::string& cancel_string) override;\r\n+\r\n+  std::list<std::string>\r\n+  do_input_dialog (const std::list<std::string>& prompt,\r\n+                   const std::string& title,\r\n+                   const std::list<float>& nr,\r\n+                   const std::list<float>& nc,\r\n+                   const std::list<std::string>& defaults) override;\r\n+\r\n+  std::list<std::string>\r\n+  do_file_dialog (const filter_list& filter, const std::string& title,\r\n+                  const std::string &filename, const std::string &pathname,\r\n+                  const std::string& multimode) override;\r\n+\r\n+  int\r\n+  do_debug_cd_or_addpath_error (const std::string& file,\r\n+                                const std::string& dir,\r\n+                                bool addpath_option) override;\r\n+\r\n+  void do_change_directory (const std::string& dir) override;\r\n+\r\n+  void do_execute_command_in_terminal (const std::string& command) override;\r\n+\r\n+  void do_set_workspace (bool top_level, bool debug,\r\n+                         const std::list<workspace_element>& ws\r\n+                         // Added on head but not yet in stable:\r\n+                         // , const bool& variable_editor_too = true\r\n+                         ) override;\r\n+\r\n+  void do_clear_workspace (void) override;\r\n+\r\n+  void do_set_history (const string_vector& hist) override;\r\n+  void do_append_history (const std::string& hist_entry) override;\r\n+  void do_clear_history (void) override;\r\n+\r\n+  void do_clear_screen (void) override;\r\n+\r\n+  void do_pre_input_event (void) override;\r\n+  void do_post_input_event (void) override;\r\n+\r\n+  void do_enter_debugger_event (const std::string& file, int line) override;\r\n+  void do_execute_in_debugger_event (const std::string& file, int line) override;\r\n+  void do_exit_debugger_event (void) override;\r\n+\r\n+  void do_update_breakpoint (bool insert,\r\n+                             const std::string& file, int line,\r\n+                             const std::string& cond) override;\r\n+\r\n+  void do_set_default_prompts (std::string& ps1, std::string& ps2,\r\n+                               std::string& ps4) override;\r\n+\r\n+  void do_show_preferences (void) override;\r\n+\r\n+  void do_show_doc (const std::string& file) override;\r\n+\r\n+  // Added on head but not yet in stable:\r\n+  // void do_openvar (const std::string& name) override;\r\n+\r\n+  void do_show_static_plot (const std::string& term,\r\n+                            const std::string& content) override;\r\n+\r\n+  // Custom methods\r\n+  void receive_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+  json_main* _json_main;\r\n+  void _publish_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+  // Queues\r\n+  json_queue<std::string> request_input_queue;\r\n+  json_queue<std::pair<bool, std::string> > request_url_queue;\r\n+  json_queue<bool> confirm_shutdown_queue;\r\n+  json_queue<bool> prompt_new_edit_file_queue;\r\n+  json_queue<int> message_dialog_queue;\r\n+  json_queue<std::string> question_dialog_queue;\r\n+  json_queue<std::pair<std::list<int>, int> > list_dialog_queue;\r\n+  json_queue<std::list<std::string> > input_dialog_queue;\r\n+  json_queue<std::list<std::string> > file_dialog_queue;\r\n+  json_queue<int> debug_cd_or_addpath_error_queue;\r\n+};\r\n+\r\n+// Template classes require definitions in the header file...\r\n+\r\n+template<typename T>\r\n+json_queue<T>::json_queue() { }\r\n+\r\n+template<typename T>\r\n+json_queue<T>::~json_queue() { }\r\n+\r\n+template<typename T>\r\n+void json_queue<T>::enqueue(const T& value) {\r\n+  _mutex.lock();\r\n+  _queue.push(value);\r\n+  _mutex.cond_signal();\r\n+  _mutex.unlock();\r\n+}\r\n+\r\n+template<typename T>\r\n+T json_queue<T>::dequeue() {\r\n+  _mutex.lock();\r\n+  while (_queue.empty()) {\r\n+    _mutex.cond_wait();\r\n+  }\r\n+  T value = _queue.front();\r\n+  _queue.pop();\r\n+  _mutex.unlock();\r\n+  return value;\r\n+}\r\n+\r\n+template<typename T>\r\n+bool json_queue<T>::dequeue_to(T* destination) {\r\n+  _mutex.lock();\r\n+  bool retval = false;\r\n+  if (!_queue.empty()) {\r\n+    retval = true;\r\n+    *destination = _queue.front();\r\n+    _queue.pop();\r\n+  }\r\n+  _mutex.unlock();\r\n+  return retval;\r\n+}\r\n+\r\n+#endif\r\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/octave-link.cc\n--- a/libinterp/corefcn/octave-link.cc\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libinterp/corefcn/octave-link.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -525,3 +525,28 @@\n \n   return ovl (octave_link::unregister_doc (file));\n }\n+\n+DEFUN (__octave_link_plot_destination__, , ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn {} {} __octave_link_plot_destination__ ()\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  return ovl (octave_link::plot_destination ());\n+}\n+\n+DEFUN (__octave_link_show_static_plot__, args, ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn {} {} __octave_link_show_static_plot__ (@var{term}, @var{content})\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  if (args.length () != 2) {\n+    return ovl ();\n+  }\n+\n+  std::string term = args(0).string_value();\n+  std::string content = args(1).string_value();\n+  return ovl (octave_link::show_static_plot (term, content));\n+}\n+\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/octave-link.h\n--- a/libinterp/corefcn/octave-link.h\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libinterp/corefcn/octave-link.h\tThu Jan 02 06:37:54 2020 -0600\n@@ -278,6 +278,12 @@\n       instance->do_clear_history ();\n   }\n \n+  static void clear_screen (void)\n+  {\n+    if (enabled ())\n+      instance->do_clear_screen ();\n+  }\n+\n   static void pre_input_event (void)\n   {\n     if (enabled ())\n@@ -290,6 +296,20 @@\n       instance->do_post_input_event ();\n   }\n \n+  static std::string request_input (const std::string& prompt)\n+  {\n+    return request_input_enabled ()\n+      ? instance->do_request_input (prompt)\n+      : std::string ();\n+  }\n+\n+  static std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success)\n+  {\n+    return request_input_enabled ()\n+      ? instance->do_request_url (url, param, action, success)\n+      : std::string ();\n+  }\n+\n   static void enter_debugger_event (const std::string& file, int line)\n   {\n     if (enabled ())\n@@ -371,6 +391,34 @@\n     return instance_ok () ? instance->link_enabled : false;\n   }\n \n+  static bool request_input_enabled (void)\n+  {\n+    return enabled () ? instance->_request_input_enabled : false;\n+  }\n+\n+  enum plot_destination_t {\n+    TERMINAL_ONLY = 0,\n+    STATIC_ONLY = 1,\n+    TERMINAL_AND_STATIC = 2\n+  };\n+\n+  static plot_destination_t plot_destination (void)\n+  {\n+    return enabled () ? instance->_plot_destination : TERMINAL_ONLY;\n+  }\n+\n+  static bool\n+  show_static_plot (const std::string& term, const std::string& content)\n+  {\n+    if (enabled ())\n+      {\n+        instance->do_show_static_plot (term, content);\n+        return true;\n+      }\n+    else\n+      return false;\n+  }\n+\n   static bool\n   show_preferences ()\n   {\n@@ -486,6 +534,10 @@\n   void do_entered_readline_hook (void) { }\n   void do_finished_readline_hook (void) { }\n \n+  bool _request_input_enabled;\n+  virtual std::string do_request_input (const std::string&) = 0;\n+  virtual std::string do_request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) = 0;\n+\n   virtual bool do_confirm_shutdown (void) = 0;\n \n   virtual bool do_copy_image_to_clipboard (const std::string& file) = 0;\n@@ -546,6 +598,8 @@\n   virtual void do_append_history (const std::string& hist_entry) = 0;\n   virtual void do_clear_history (void) = 0;\n \n+  virtual void do_clear_screen (void) = 0;\n+\n   virtual void do_pre_input_event (void) = 0;\n   virtual void do_post_input_event (void) = 0;\n \n@@ -574,6 +628,10 @@\n \n   virtual void\n   do_edit_variable (const std::string& name, const octave_value& val) = 0;\n+\n+  plot_destination_t _plot_destination;\n+  virtual void do_show_static_plot (const std::string& term,\n+                                    const std::string& content) = 0;\n };\n \n #endif\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/syscalls.cc\n--- a/libinterp/corefcn/syscalls.cc\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libinterp/corefcn/syscalls.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -146,9 +146,8 @@\n @deftypefn {} {[@var{err}, @var{msg}] =} exec (@var{file}, @var{args})\n Replace current process with a new process.\n \n-Calling @code{exec} without first calling @code{fork} will terminate your\n-current Octave process and replace it with the program named by @var{file}.\n-For example,\n+Calling @code{exec} will terminate your current Octave process and replace\n+it with the program named by @var{file}. For example,\n \n @example\n exec (\"ls\", \"-l\")\n@@ -458,44 +457,6 @@\n   return ovl (status, msg);\n }\n \n-DEFMETHODX (\"fork\", Ffork, interp, args, ,\n-            doc: /* -*- texinfo -*-\n-@deftypefn {} {[@var{pid}, @var{msg}] =} fork ()\n-Create a copy of the current process.\n-\n-Fork can return one of the following values:\n-\n-@table @asis\n-@item > 0\n-You are in the parent process.  The value returned from @code{fork} is the\n-process id of the child process.  You should probably arrange to wait for\n-any child processes to exit.\n-\n-@item 0\n-You are in the child process.  You can call @code{exec} to start another\n-process.  If that fails, you should probably call @code{exit}.\n-\n-@item < 0\n-The call to @code{fork} failed for some reason.  You must take evasive\n-action.  A system dependent error message will be waiting in @var{msg}.\n-@end table\n-@end deftypefn */)\n-{\n-  if (args.length () != 0)\n-    print_usage ();\n-\n-  octave::symbol_table& symtab = interp.get_symbol_table ();\n-\n-  if (symtab.at_top_level ())\n-    error (\"fork: cannot be called from command line\");\n-\n-  std::string msg;\n-\n-  pid_t pid = octave::sys::fork (msg);\n-\n-  return ovl (pid, msg);\n-}\n-\n DEFUNX (\"getpgrp\", Fgetpgrp, args, ,\n         doc: /* -*- texinfo -*-\n @deftypefn {} {pgid =} getpgrp ()\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/sysdep.cc\n--- a/libinterp/corefcn/sysdep.cc\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libinterp/corefcn/sysdep.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -74,6 +74,7 @@\n #include \"errwarn.h\"\n #include \"input.h\"\n #include \"octave.h\"\n+#include \"octave-link.h\"\n #include \"ov.h\"\n #include \"ovl.h\"\n #include \"pager.h\"\n@@ -549,7 +550,7 @@\n \n   // Read one character from the terminal.\n \n-  int kbhit (bool wait)\n+  int kbhit (const std::string& prompt, bool wait)\n   {\n #if defined (HAVE__KBHIT) && defined (HAVE__GETCH)\n     // This essentially means we are on a Windows system.\n@@ -576,13 +577,23 @@\n \n     octave::set_interrupt_handler (saved_interrupt_handler, false);\n \n-    int c = std::cin.get ();\n+    int c;\n+    if (octave_link::request_input_enabled ()) {\n+      std::string line = octave_link::request_input (prompt);\n+      if (line.length() >= 1) {\n+        c = line.at(0);\n+      } else {\n+        c = '\\n';\n+      }\n+    } else {\n+      c = std::cin.get ();\n \n-    if (std::cin.fail () || std::cin.eof ())\n-      {\n-        std::cin.clear ();\n-        clearerr (stdin);\n-      }\n+      if (std::cin.fail () || std::cin.eof ())\n+        {\n+          std::cin.clear ();\n+          clearerr (stdin);\n+        }\n+    }\n \n     // Restore it, enabling system call restarts (if possible).\n     octave::set_interrupt_handler (saved_interrupt_handler, true);\n@@ -641,6 +652,8 @@\n {\n   bool skip_redisplay = true;\n \n+  octave_link::clear_screen ();\n+\n   octave::command_editor::clear_screen (skip_redisplay);\n \n   return ovl ();\n@@ -1060,7 +1073,7 @@\n \n   Fdrawnow ();\n \n-  int c = octave::kbhit (args.length () == 0);\n+  int c = octave::kbhit (\"kbhit>\", args.length () == 0);\n \n   if (c == -1)\n     c = 0;\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/sysdep.h\n--- a/libinterp/corefcn/sysdep.h\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libinterp/corefcn/sysdep.h\tThu Jan 02 06:37:54 2020 -0600\n@@ -46,7 +46,7 @@\n \n   extern OCTINTERP_API int pclose (FILE *f);\n \n-  extern OCTINTERP_API int kbhit (bool wait = true);\n+  extern OCTINTERP_API int kbhit (const std::string& prompt, bool wait);\n \n   extern OCTINTERP_API std::string get_P_tmpdir (void);\n \n@@ -102,7 +102,7 @@\n inline int\n octave_kbhit (bool wait = true)\n {\n-  return octave::kbhit (wait);\n+  return octave::kbhit (\"\", wait);\n }\n \n OCTAVE_DEPRECATED (5, \"use 'octave::get_P_tmpdir' instead\")\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/corefcn/utils.cc\n--- a/libinterp/corefcn/utils.cc\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libinterp/corefcn/utils.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -1301,7 +1301,7 @@\n             if (do_graphics_events)\n               gh_manager::process_events ();\n \n-            c = octave::kbhit (false);\n+            c = octave::kbhit (\"press enter to continue\", false);\n           }\n       }\n     else\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/octave.cc\n--- a/libinterp/octave.cc\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libinterp/octave.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -44,6 +44,7 @@\n #include \"octave.h\"\n #include \"oct-hist.h\"\n #include \"oct-map.h\"\n+#include \"octave-link.h\"\n #include \"ovl.h\"\n #include \"options-usage.h\"\n #include \"ov.h\"\n@@ -184,6 +185,16 @@\n           case LINE_EDITING_OPTION:\n             m_forced_line_editing = m_line_editing = true;\n             break;\n+ \n+          case JSON_SOCK_OPTION:\n+            if (octave_optarg_wrapper ())\n+              m_json_sock_path = octave_optarg_wrapper ();\n+            break;\n+\n+          case JSON_MAX_LEN_OPTION:\n+            if (octave_optarg_wrapper ())\n+              m_json_max_message_length = strtol(octave_optarg_wrapper (), NULL, 10);\n+            break;\n \n           case NO_GUI_OPTION:\n             m_gui = false;\n@@ -431,7 +442,7 @@\n   // FIXME: This isn't quite right, it just says that we intended to\n   // start the GUI, not that it is actually running.\n \n-  return ovl (octave::application::is_gui_running ());\n+  return ovl (octave_link::enabled ());\n }\n \n /*\ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/octave.h\n--- a/libinterp/octave.h\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libinterp/octave.h\tThu Jan 02 06:37:54 2020 -0600\n@@ -84,6 +84,8 @@\n     std::string info_file (void) const { return m_info_file; }\n     std::string info_program (void) const { return m_info_program; }\n     std::string texi_macros_file (void) const {return m_texi_macros_file; }\n+    std::string json_sock_path (void) const { return m_json_sock_path; }\n+    int json_max_message_length (void) const { return m_json_max_message_length; }\n     string_vector all_args (void) const { return m_all_args; }\n     string_vector remaining_args (void) const { return m_remaining_args; }\n \n@@ -120,6 +122,8 @@\n     void info_file (const std::string& arg) { m_info_file = arg; }\n     void info_program (const std::string& arg) { m_info_program = arg; }\n     void texi_macros_file (const std::string& arg) { m_texi_macros_file = arg; }\n+    void json_sock_path (const std::string& arg) { m_json_sock_path = arg; }\n+    void json_max_message_length (int arg) { m_json_max_message_length = arg; }\n     void all_args (const string_vector& arg) { m_all_args = arg; }\n     void remaining_args (const string_vector& arg) { m_remaining_args = arg; }\n \n@@ -226,6 +230,14 @@\n     // (--texi-macros-file)\n     std::string m_texi_macros_file;\n \n+    // The value for \"JSON_SOCK\" specified on the command line.\n+    // (--json-sock)\n+    std::string m_json_sock_path;\n+\n+    // The maximum message length; valid only if \"JSON_SOCK\" is specified.\n+    // (--json-max-len)\n+    int m_json_max_message_length = 0;\n+\n     // All arguments passed to the argc, argv constructor.\n     string_vector m_all_args;\n \ndiff -r 56dd7419d7aa -r 84cbf166497f libinterp/options-usage.h\n--- a/libinterp/options-usage.h\tTue Dec 24 04:01:33 2019 +0100\n+++ b/libinterp/options-usage.h\tThu Jan 02 06:37:54 2020 -0600\n@@ -35,10 +35,10 @@\n        [--echo-commands] [--eval CODE] [--exec-path path]\\n\\\n        [--gui] [--help] [--image-path path]\\n\\\n        [--info-file file] [--info-program prog] [--interactive]\\n\\\n-       [--jit-compiler] [--line-editing] [--no-gui] [--no-history]\\n\\\n-       [--no-init-file] [--no-init-path] [--no-line-editing]\\n\\\n-       [--no-site-file] [--no-window-system] [--norc] [-p path]\\n\\\n-       [--path path] [--persist] [--silent] [--traditional]\\n\\\n+       [--jit-compiler] [--json-sock] [--json-max-len] [--line-editing]\\n\\\n+       [--no-gui] [--no-history][--no-init-file] [--no-init-path]\\n\\\n+       [--no-line-editing] [--no-site-file] [--no-window-system] [--norc]\\n\\\n+       [-p path] [--path path] [--persist] [--silent] [--traditional]\\n\\\n        [--verbose] [--version] [file]\";\n \n // This is here so that it's more likely that the usage message and\n@@ -67,15 +67,17 @@\n #define INFO_PROG_OPTION 8\n #define DEBUG_JIT_OPTION 9\n #define JIT_COMPILER_OPTION 10\n-#define LINE_EDITING_OPTION 11\n-#define NO_GUI_OPTION 12\n-#define NO_INIT_FILE_OPTION 13\n-#define NO_INIT_PATH_OPTION 14\n-#define NO_LINE_EDITING_OPTION 15\n-#define NO_SITE_FILE_OPTION 16\n-#define PERSIST_OPTION 17\n-#define TEXI_MACROS_FILE_OPTION 18\n-#define TRADITIONAL_OPTION 19\n+#define JSON_SOCK_OPTION 11\n+#define JSON_MAX_LEN_OPTION 12\n+#define LINE_EDITING_OPTION 13\n+#define NO_GUI_OPTION 14\n+#define NO_INIT_FILE_OPTION 15\n+#define NO_INIT_PATH_OPTION 16\n+#define NO_LINE_EDITING_OPTION 17\n+#define NO_SITE_FILE_OPTION 18\n+#define PERSIST_OPTION 19\n+#define TEXI_MACROS_FILE_OPTION 20\n+#define TRADITIONAL_OPTION 21\n struct octave_getopt_options long_opts[] =\n {\n   { \"braindead\",                octave_no_arg,       0, TRADITIONAL_OPTION },\n@@ -94,6 +96,8 @@\n   { \"info-program\",             octave_required_arg, 0, INFO_PROG_OPTION },\n   { \"interactive\",              octave_no_arg,       0, 'i' },\n   { \"jit-compiler\",             octave_no_arg,       0, JIT_COMPILER_OPTION },\n+  { \"json-sock\",                octave_required_arg, 0, JSON_SOCK_OPTION },\n+  { \"json-max-len\",             octave_required_arg, 0, JSON_MAX_LEN_OPTION },\n   { \"line-editing\",             octave_no_arg,       0, LINE_EDITING_OPTION },\n   { \"no-gui\",                   octave_no_arg,       0, NO_GUI_OPTION },\n   { \"no-history\",               octave_no_arg,       0, 'H' },\n@@ -145,6 +149,8 @@\n   --info-program PROGRAM  Use PROGRAM for reading info files.\\n\\\n   --interactive, -i       Force interactive behavior.\\n\\\n   --jit-compiler          Enable the JIT compiler.\\n\\\n+  --json-sock PATH        Listen to and publish events on this UNIX socket.\\n\\\n+  --json-max-len LEN      Suppress JSON messages greater than LEN bytes.\\n\\\n   --line-editing          Force readline use for command-line editing.\\n\\\n   --no-gui                Disable the graphical user interface.\\n\\\n   --no-history, -H        Don't save commands to the history list\\n\\\ndiff -r 56dd7419d7aa -r 84cbf166497f liboctave/util/oct-mutex.cc\n--- a/liboctave/util/oct-mutex.cc\tTue Dec 24 04:01:33 2019 +0100\n+++ b/liboctave/util/oct-mutex.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -55,6 +55,18 @@\n     return false;\n   }\n \n+  void\n+  base_mutex::cond_wait (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n+  void\n+  base_mutex::cond_signal (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n #if defined (OCTAVE_USE_WINDOWS_API)\n \n   class\n@@ -65,11 +77,13 @@\n       : base_mutex ()\n     {\n       InitializeCriticalSection (&cs);\n+      InitializeConditionVariable (&cv);\n     }\n \n     ~w32_mutex (void)\n     {\n       DeleteCriticalSection (&cs);\n+      // no need to delete cv: http://stackoverflow.com/a/28981408/1407170\n     }\n \n     void lock (void)\n@@ -87,8 +101,19 @@\n       return (TryEnterCriticalSection (&cs) != 0);\n     }\n \n+    void cond_wait (void)\n+    {\n+      SleepConditionVariableCS (&cv, &cs, INFINITE);\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      WakeConditionVariable (&cv);\n+    }\n+\n   private:\n     CRITICAL_SECTION cs;\n+    CONDITION_VARIABLE cv;\n   };\n \n   static DWORD thread_id = 0;\n@@ -120,11 +145,18 @@\n       pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);\n       pthread_mutex_init (&pm, &attr);\n       pthread_mutexattr_destroy (&attr);\n+\n+      pthread_condattr_t condattr;\n+\n+      pthread_condattr_init (&condattr);\n+      pthread_cond_init (&condv, &condattr);\n+      pthread_condattr_destroy (&condattr);\n     }\n \n     ~pthread_mutex (void)\n     {\n       pthread_mutex_destroy (&pm);\n+      pthread_cond_destroy (&condv);\n     }\n \n     void lock (void)\n@@ -142,8 +174,19 @@\n       return (pthread_mutex_trylock (&pm) == 0);\n     }\n \n+    void cond_wait (void)\n+    {\n+      pthread_cond_wait (&condv, &pm);\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      pthread_cond_signal (&condv);\n+    }\n+\n   private:\n     pthread_mutex_t pm;\n+    pthread_cond_t condv;\n   };\n \n   static pthread_t thread_id = 0;\ndiff -r 56dd7419d7aa -r 84cbf166497f liboctave/util/oct-mutex.h\n--- a/liboctave/util/oct-mutex.h\tTue Dec 24 04:01:33 2019 +0100\n+++ b/liboctave/util/oct-mutex.h\tThu Jan 02 06:37:54 2020 -0600\n@@ -47,6 +47,10 @@\n \n     virtual bool try_lock (void);\n \n+    virtual void cond_wait (void);\n+\n+    virtual void cond_signal (void);\n+\n   private:\n     refcount<int> count;\n   };\n@@ -99,6 +103,16 @@\n       return rep->try_lock ();\n     }\n \n+    void cond_wait (void)\n+    {\n+      rep->cond_wait ();\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      rep->cond_signal ();\n+    }\n+\n   protected:\n     base_mutex *rep;\n   };\ndiff -r 56dd7419d7aa -r 84cbf166497f liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tTue Dec 24 04:01:33 2019 +0100\n+++ b/liboctave/util/url-transfer.cc\tThu Jan 02 06:37:54 2020 -0600\n@@ -38,6 +38,8 @@\n #include \"file-stat.h\"\n #include \"lo-sysdep.h\"\n #include \"oct-env.h\"\n+#include \"libinterp/corefcn/octave-link.h\"\n+#include \"gnulib/lib/base64.h\"\n #include \"unwind-prot.h\"\n #include \"url-transfer.h\"\n \n@@ -237,6 +239,86 @@\n     return file_list;\n   }\n \n+\n+class link_transfer : public base_url_transfer\n+{\n+public:\n+\n+  link_transfer (void)\n+      : base_url_transfer () {\n+    valid = true;\n+  }\n+\n+  link_transfer (const std::string& host, const std::string& user_arg,\n+                 const std::string& passwd, std::ostream& os)\n+      : base_url_transfer (host, user_arg, passwd, os) {\n+    valid = true;\n+    // url = \"ftp://\" + host;\n+  }\n+\n+  link_transfer (const std::string& url_str, std::ostream& os)\n+      : base_url_transfer (url_str, os) {\n+    valid = true;\n+  }\n+\n+  ~link_transfer (void) {}\n+\n+  void http_get (const Array<std::string>& param) {\n+    perform (param, \"get\");\n+  }\n+\n+  void http_post (const Array<std::string>& param) {\n+    perform (param, \"post\");\n+  }\n+\n+  void http_action (const Array<std::string>& param, const std::string& action) {\n+    perform (param, action);\n+  }\n+\n+private:\n+  void perform(const Array<std::string>& param, const std::string& action) {\n+    std::string url = host_or_url;\n+\n+    // Convert from Array to std::list\n+    std::list<std::string> paramList;\n+    for (int i = 0; i < param.numel(); i ++) {\n+      std::string value = param(i);\n+      paramList.push_back(value);\n+    }\n+\n+    if (octave_link::request_input_enabled ()) {\n+      bool success;\n+      std::string result = octave_link::request_url (url, paramList, action, success);\n+      if (success) {\n+        process_success(result);\n+      } else {\n+        ok = false;\n+        errmsg = result;\n+      }\n+    } else {\n+      ok = false;\n+      errmsg = \"octave_link not connected for link_transfer\";\n+    }\n+  }\n+\n+  void process_success(const std::string& result) {\n+    // If success, the result is returned as a base64 string, and we need to decode it.\n+    // Use the base64 implementation from gnulib, which is already an Octave dependency.\n+    const char *inc = &(result[0]);\n+    char *out;\n+    size_t outlen;\n+    bool b64_ok = base64_decode_alloc(inc, result.length(), &out, &outlen);\n+    if (!b64_ok) {\n+      ok = false;\n+      errmsg = \"failed decoding base64 from octave_link\";\n+    } else {\n+      curr_ostream->write(out, outlen);\n+      ::free(out);\n+    }\n+  }\n+};\n+\n+\n #if defined (HAVE_CURL)\n \n   static int\n@@ -806,17 +888,30 @@\n #  define REP_CLASS base_url_transfer\n #endif\n \n-  url_transfer::url_transfer (void) : rep (new REP_CLASS ())\n-  { }\n+  url_transfer::url_transfer (void) {\n+    if (octave_link::request_input_enabled()) {\n+      rep = new link_transfer();\n+    } else {\n+      rep = new REP_CLASS();\n+    }\n+  }\n \n   url_transfer::url_transfer (const std::string& host, const std::string& user,\n-                              const std::string& passwd, std::ostream& os)\n-    : rep (new REP_CLASS (host, user, passwd, os))\n-  { }\n+                              const std::string& passwd, std::ostream& os) {\n+    if (octave_link::request_input_enabled()) {\n+      rep = new link_transfer(host, user, passwd, os);\n+    } else {\n+      rep = new REP_CLASS(host, user, passwd, os);\n+    }\n+  }\n \n-  url_transfer::url_transfer (const std::string& url, std::ostream& os)\n-    : rep (new REP_CLASS (url, os))\n-  { }\n+  url_transfer::url_transfer (const std::string& url, std::ostream& os) {\n+    if (octave_link::request_input_enabled()) {\n+      rep = new link_transfer(url, os);\n+    } else {\n+      rep = new REP_CLASS(url, os);\n+    }\n+  }\n \n #undef REP_CLASS\n \ndiff -r 56dd7419d7aa -r 84cbf166497f scripts/help/__unimplemented__.m\n--- a/scripts/help/__unimplemented__.m\tTue Dec 24 04:01:33 2019 +0100\n+++ b/scripts/help/__unimplemented__.m\tThu Jan 02 06:37:54 2020 -0600\n@@ -40,7 +40,30 @@\n \n   is_matlab_function = true;\n \n+  ## First look at the package metadata\n+  # To generate package_metadata.mat, run: packages={}; for p=pkg('list'); packages={packages{:} pkg('describe', '-verbose', p{1}.name){:}}; endfor; save('/usr/local/share/octave/site/m/package_metadata.mat', 'packages');\n+  found_in_package_metadata = false;\n+  try\n+    vars = load(\"/usr/local/share/octave/site/m/package_metadata.mat\");\n+    for lvl1 = vars.packages\n+      for lvl2 = lvl1{1}.provides\n+        for lvl3 = lvl2{1}.functions\n+          if strcmp(fcn, lvl3{1})\n+            txt = check_package(fcn, lvl1{1}.name);\n+            found_in_package_metadata = true;\n+            break;\n+          endif\n+        endfor\n+        if found_in_package_metadata, break; endif\n+      endfor\n+      if found_in_package_metadata, break; endif\n+    endfor\n+  catch err\n+    warning(err)\n+  end_try_catch\n+\n   ## Some smarter cases, add more as needed.\n+  if !found_in_package_metadata\n   switch (fcn)\n     case {\"avifile\", \"aviinfo\", \"aviread\"}\n       txt = [\"Basic video file support is provided in the video package.  \", ...\n@@ -514,6 +537,7 @@\n         txt = \"\";\n       endif\n   endswitch\n+  endif\n \n   if (is_matlab_function)\n     txt = [txt, \"\\n\\n@noindent\\nPlease read \", ...\n@@ -556,13 +580,13 @@\n       endfor\n       txt = sprintf (\"%s but has not yet been implemented.\", txt);\n     case \"not loaded\",\n-      txt = sprintf ([\"%s which you have installed but not loaded.  To \", ...\n-                      \"load the package, run `pkg load %s' from the \", ...\n-                      \"Octave prompt.\"], txt, name);\n+      txt = sprintf ([\"%s, which you have installed but not loaded.\\n\\n\", ...\n+                      \"Run `pkg load %s' to use `%s'.\"], ...\n+                     txt, name, fcn);\n     otherwise\n       ## this includes \"not installed\" and anything else if pkg changes\n       ## the output of describe\n-      txt = sprintf (\"%s which seems to not be installed in your system.\", txt);\n+      txt = sprintf (\"%s, which seems to not be installed in your system.\", txt);\n   endswitch\n \n endfunction\ndiff -r 56dd7419d7aa -r 84cbf166497f scripts/plot/util/__gnuplot_drawnow__.m\n--- a/scripts/plot/util/__gnuplot_drawnow__.m\tTue Dec 24 04:01:33 2019 +0100\n+++ b/scripts/plot/util/__gnuplot_drawnow__.m\tThu Jan 02 06:37:54 2020 -0600\n@@ -27,9 +27,84 @@\n \n   if (nargin < 1 || nargin > 4 || nargin == 2)\n     print_usage ();\n+\n+  elseif (nargin >= 3 && nargin <= 4)\n+    ## Write the plot to the given file (e.g., via the \"print\" command)\n+    if (nargin == 5)\n+      __gnuplot_draw_to_file__ (h, term, file, debug_file);\n+    else\n+      __gnuplot_draw_to_file__ (h, term, file);\n+    endif\n+\n+  else  # nargin == 1\n+    ##  Plot to terminal and/or static (e.g., via the \"plot\" command)\n+    plot_stream = get (h, \"__plot_stream__\");\n+    if (isempty (plot_stream))\n+      plot_stream = __gnuplot_open_stream__ (2, h);\n+      new_stream = true;\n+    else\n+      new_stream = false;\n+    endif\n+    term = gnuplot_default_term (plot_stream);\n+\n+    ## There are a few options for how we can proceed.\n+    ## In most cases, we will tell GNUPLOT to put the plot in its terminal.\n+    ## If we have no display, we want to use the \"dumb\" terminal.\n+    ## Octave Link may request that we send the plot as an event.\n+    ## The latter two cases require plotting to a temp file.\n+\n+    should_plot_to_terminal = (\n+      !strcmp (term, \"dumb\") && (\n+        __octave_link_plot_destination__ () == 0 ||\n+        __octave_link_plot_destination__ () == 2\n+      )\n+    );\n+\n+    if (should_plot_to_terminal)\n+      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n+      __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n+      fflush (plot_stream(1));\n+    endif\n+\n+    should_plot_to_temp_file = (\n+      strcmp (term, \"dumb\") ||\n+      __octave_link_plot_destination__ () == 1 ||\n+      __octave_link_plot_destination__ () == 2\n+    );\n+\n+    if (should_plot_to_temp_file)\n+      tmp_file = tempname ();\n+      __gnuplot_draw_to_file__ (h, term, tmp_file);\n+      fflush (plot_stream(1));\n+\n+      ## Read the temp file into memory and then delete it\n+      fid = fopen (tmp_file, 'r');\n+      while (fid < 0)\n+        fprintf (stderr, \"🛈 Waiting for plot to finish… ⏳\\n\");\n+        pause (0.5);\n+        fid = fopen (tmp_file, 'r');\n+      endwhile\n+      [a, count] = fscanf (fid, '%c', Inf);\n+      fclose (fid);\n+      unlink (tmp_file);\n+\n+      ## What to do with the plot data?\n+      if (count > 0)\n+        if (a(1) == 12)\n+          a = a(2:end);  # avoid ^L at the beginning\n+        endif\n+        if strcmp (term, \"dumb\")\n+          puts (a);\n+        else\n+          __octave_link_show_static_plot__ (term, a);\n+        endif\n+      endif\n+    endif\n+\n   endif\n+endfunction\n \n-  if (nargin >= 3 && nargin <= 4)\n+function __gnuplot_draw_to_file__ (h, term, file, debug_file)\n     ## Produce various output formats, or redirect gnuplot stream to a\n     ## debug file.\n     plot_stream = [];\n@@ -65,44 +140,6 @@\n         fclose (fid);\n       endif\n     end_unwind_protect\n-  else  # nargin == 1\n-    ##  Graphics terminal for display.\n-    plot_stream = get (h, \"__plot_stream__\");\n-    if (isempty (plot_stream))\n-      plot_stream = __gnuplot_open_stream__ (2, h);\n-      new_stream = true;\n-    else\n-      new_stream = false;\n-    endif\n-    term = gnuplot_default_term (plot_stream);\n-    if (strcmp (term, \"dumb\"))\n-      ## popen2 eats stdout of gnuplot, use temporary file instead\n-      dumb_tmp_file = tempname ();\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h,\n-                                   term, dumb_tmp_file);\n-    else\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n-    endif\n-    __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n-    fflush (plot_stream(1));\n-    if (strcmp (term, \"dumb\"))\n-      fid = -1;\n-      while (fid < 0)\n-        pause (0.1);\n-        fid = fopen (dumb_tmp_file, 'r');\n-      endwhile\n-      ## reprint the plot on screen\n-      [a, count] = fscanf (fid, '%c', Inf);\n-      fclose (fid);\n-      if (count > 0)\n-        if (a(1) == 12)\n-          a = a(2:end);  # avoid ^L at the beginning\n-        endif\n-        puts (a);\n-      endif\n-      unlink (dumb_tmp_file);\n-    endif\n-  endif\n \n endfunction\n \n"
  },
  {
    "path": "back-octave/oo-changesets/200-README.md",
    "content": "Patch file `200` is the data on the oo-4.2.1 branch applied against Octave 5.2 RC and saved in a new branch oo-5.2.  It is based on commit `56dd7419d7aa` on the stable branch.\n"
  },
  {
    "path": "back-octave/oo-changesets/201-b993253f19d0.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1578034524 21600\n#      Fri Jan 03 00:55:24 2020 -0600\n# Branch oo-5.2\n# Node ID b993253f19d07cc1e9373cd8e9a35745cde9fef9\n# Parent  84cbf166497fa3d942be6a0b026ec24521301d16\nUpdating OO patch code to be 5.2-compatible\n\ndiff -r 84cbf166497f -r b993253f19d0 libinterp/corefcn/interpreter.cc\n--- a/libinterp/corefcn/interpreter.cc\tThu Jan 02 06:37:54 2020 -0600\n+++ b/libinterp/corefcn/interpreter.cc\tFri Jan 03 00:55:24 2020 -0600\n@@ -510,6 +510,11 @@\n         std::string texi_macros_file = options.texi_macros_file ();\n         if (! texi_macros_file.empty ())\n           Ftexi_macros_file (*this, octave_value (texi_macros_file));\n+\n+        if (!options.json_sock_path().empty ()) {\n+          static json_main _json_main (options.json_sock_path(), options.json_max_message_length());\n+          _json_main.run_loop_on_new_thread();\n+        }\n       }\n \n     m_input_system.initialize (line_editing);\n@@ -519,11 +524,6 @@\n \n     initialize_version_info ();\n \n-    if (!options.json_sock_path().empty ()) {\n-      static json_main _json_main (options.json_sock_path(), options.json_max_message_length());\n-      _json_main.run_loop_on_new_thread();\n-    }\n-\n     // This should be done before initializing the load path because\n     // some PKG_ADD files might need --traditional behavior.\n \ndiff -r 84cbf166497f -r b993253f19d0 libinterp/corefcn/json-util.cc\n--- a/libinterp/corefcn/json-util.cc\tThu Jan 02 06:37:54 2020 -0600\n+++ b/libinterp/corefcn/json-util.cc\tFri Jan 03 00:55:24 2020 -0600\n@@ -3,17 +3,107 @@\n #endif\r\n \r\n #include <cstdlib>\r\n+#include <assert.h>\r\n #include <sys/un.h>\r\n #include <sys/socket.h>\r\n #include <stdio.h>\r\n #include <json-c/arraylist.h>\r\n+#include <json-c/json_object.h>\r\n \r\n #include \"str-vec.h\"\r\n \r\n #include \"json-util.h\"\r\n \r\n JSON_OBJECT_T json_util::from_string(const std::string& str) {\r\n-\treturn json_object_new_string(str.c_str());\r\n+\tconst char* snowflake = \"\\xEF\\xBF\\xBD\";\r\n+\r\n+\t// Ensure that the string is valid UTF-8\r\n+\tstd::string sanitized;\r\n+\tsize_t state = 0;\r\n+\tsize_t cpLength = 0;\r\n+\tfor (size_t i=0; i<str.length(); i++) {\r\n+\t\tunsigned char c = str[i];\r\n+\t\tswitch (state) {\r\n+\t\tcase 0:\r\n+\t\t\tif (c < 0x80) {\r\n+\t\t\t\t// 1-byte character\r\n+\t\t\t\tsanitized.push_back(c);\r\n+\t\t\t} else if (c >= 0xC2 && c <= 0xDF) {\r\n+\t\t\t\t// 2-byte character\r\n+\t\t\t\tstate = 1;\r\n+\t\t\t\tcpLength = 2;\r\n+\t\t\t} else if (c >= 0xE0 && c <= 0xEF) {\r\n+\t\t\t\t// 3-byte character\r\n+\t\t\t\tstate = 1;\r\n+\t\t\t\tcpLength = 3;\r\n+\t\t\t} else if (c >= 0xF0 && c <= 0xF4) {\r\n+\t\t\t\t// 4-byte character\r\n+\t\t\t\tstate = 1;\r\n+\t\t\t\tcpLength = 4;\r\n+\t\t\t} else {\r\n+\t\t\t\t// Invalid byte\r\n+\t\t\t\tsanitized.append(snowflake);\r\n+\t\t\t}\r\n+\t\t\tbreak;\r\n+\r\n+\t\tcase 1:\r\n+\t\t\tif (c < 0x80 || c > 0xBF) {\r\n+\t\t\t\t// Invalid byte\r\n+\t\t\t\tsanitized.append(snowflake);\r\n+\t\t\t\tstate = 0;\r\n+\t\t\t} else if (cpLength == 2) {\r\n+\t\t\t\t// Final byte in 2-byte character\r\n+\t\t\t\tsanitized.push_back(str[i-1]);\r\n+\t\t\t\tsanitized.push_back(c);\r\n+\t\t\t\tstate = 0;\r\n+\t\t\t} else {\r\n+\t\t\t\t// 3-byte or 4-byte character\r\n+\t\t\t\tstate = 2;\r\n+\t\t\t}\r\n+\t\t\tbreak;\r\n+\r\n+\t\tcase 2:\r\n+\t\t\tif (c < 0x80 || c > 0xBF) {\r\n+\t\t\t\t// Invalid byte\r\n+\t\t\t\tsanitized.append(snowflake);\r\n+\t\t\t\tstate = 0;\r\n+\t\t\t} else if (cpLength == 3) {\r\n+\t\t\t\t// Final byte in 3-byte character\r\n+\t\t\t\tsanitized.push_back(str[i-2]);\r\n+\t\t\t\tsanitized.push_back(str[i-1]);\r\n+\t\t\t\tsanitized.push_back(c);\r\n+\t\t\t\tstate = 0;\r\n+\t\t\t} else {\r\n+\t\t\t\t// 4-byte character\r\n+\t\t\t\tstate = 3;\r\n+\t\t\t}\r\n+\t\t\tbreak;\r\n+\r\n+\t\tcase 3:\r\n+\t\t\tif (c < 0x80 || c > 0xBF) {\r\n+\t\t\t\t// Invalid byte\r\n+\t\t\t\tsanitized.append(snowflake);\r\n+\t\t\t\tstate = 0;\r\n+\t\t\t} else {\r\n+\t\t\t\tassert(cpLength == 4);\r\n+\t\t\t\tsanitized.push_back(str[i-3]);\r\n+\t\t\t\tsanitized.push_back(str[i-2]);\r\n+\t\t\t\tsanitized.push_back(str[i-1]);\r\n+\t\t\t\tsanitized.push_back(c);\r\n+\t\t\t\tstate = 0;\r\n+\t\t\t}\r\n+\t\t\tbreak;\r\n+\r\n+\t\t}\r\n+\t}\r\n+\r\n+\tif (state != 0) {\r\n+\t\t// Last character is invalid\r\n+\t\tsanitized.append(snowflake);\r\n+\t\tstate = 0;\r\n+\t}\r\n+\r\n+\treturn json_object_new_string_len(sanitized.c_str(), sanitized.length());\r\n }\r\n \r\n JSON_OBJECT_T json_util::from_int(int i) {\r\n@@ -36,7 +126,7 @@\n JSON_OBJECT_T json_object_from_list(const std::list<T>& list, JSON_OBJECT_T (*convert)(T)) {\r\n \tJSON_OBJECT_T jobj = json_object_new_array();\r\n \tfor (\r\n-\t\ttypename std::list<T>::const_iterator it = list.begin();\r\n+\t\tauto it = list.begin();\r\n \t\tit != list.end();\r\n \t\t++it\r\n \t){\r\n@@ -67,8 +157,16 @@\n \treturn json_object_from_list(list, json_util::from_float);\r\n }\r\n \r\n-JSON_OBJECT_T json_util::from_workspace_list(const std::list<workspace_element>& list) {\r\n-\treturn json_object_from_list(list, json_util::from_workspace_element);\r\n+JSON_OBJECT_T json_util::from_symbol_info_list(const octave::symbol_info_list& list) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\tauto it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, json_util::from_symbol_info(*it));\r\n+\t}\r\n+\treturn jobj;\r\n }\r\n \r\n JSON_OBJECT_T json_util::from_filter_list(const octave_link::filter_list& list) {\r\n@@ -79,21 +177,28 @@\n \treturn json_util::from_string(str);\r\n }\r\n \r\n-JSON_OBJECT_T json_util::from_workspace_element(workspace_element element) {\r\n+JSON_OBJECT_T json_util::from_symbol_info(const octave::symbol_info element) {\r\n+\toctave_value val = element.value();\r\n+\r\n+\tstd::string dims_str = val.get_dims_str();\r\n+\r\n+\tstd::ostringstream display_str;\r\n+\tval.short_disp(display_str);\r\n+\r\n \tJSON_MAP_T m;\r\n-\tm[\"scope\"] = json_util::from_int(element.scope());\r\n-\tm[\"symbol\"] = json_util::from_string(element.symbol());\r\n-\tm[\"class_name\"] = json_util::from_string(element.class_name());\r\n-\tm[\"dimension\"] = json_util::from_string(element.dimension());\r\n-\tm[\"value\"] = json_util::from_string(element.value());\r\n-\tm[\"complex_flag\"] = json_util::from_boolean(element.complex_flag());\r\n+\t// m[\"scope\"] = json_util::from_int(element.scope());\r\n+\tm[\"symbol\"] = json_util::from_string(element.name());\r\n+\tm[\"class_name\"] = json_util::from_string(val.class_name());\r\n+\tm[\"dimension\"] = json_util::from_string(dims_str);\r\n+\tm[\"value\"] = json_util::from_string(display_str.str());\r\n+\tm[\"complex_flag\"] = json_util::from_boolean(element.is_complex());\r\n \treturn json_util::from_map(m);\r\n }\r\n \r\n JSON_OBJECT_T json_util::from_pair(std::pair<std::string, std::string> pair) {\r\n \tJSON_OBJECT_T jobj = json_object_new_array();\r\n-\tjson_object_array_add(jobj, json_object_new_string(pair.first.c_str()));\r\n-\tjson_object_array_add(jobj, json_object_new_string(pair.second.c_str()));\r\n+\tjson_object_array_add(jobj, json_util::from_string(pair.first.c_str()));\r\n+\tjson_object_array_add(jobj, json_util::from_string(pair.second.c_str()));\r\n \treturn jobj;\r\n }\r\n \r\n@@ -129,7 +234,7 @@\n \tif (arr == NULL)\r\n \t\treturn ret;\r\n \r\n-\tfor (int i = 0; i < array_list_length(arr); ++i) {\r\n+\tfor (size_t i = 0; i < array_list_length(arr); ++i) {\r\n \t\tJSON_OBJECT_T jsub = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, i));\r\n \t\tret.push_back(convert(jsub));\r\n \t}\r\ndiff -r 84cbf166497f -r b993253f19d0 libinterp/corefcn/json-util.h\n--- a/libinterp/corefcn/json-util.h\tThu Jan 02 06:37:54 2020 -0600\n+++ b/libinterp/corefcn/json-util.h\tFri Jan 03 00:55:24 2020 -0600\n@@ -5,7 +5,7 @@\n #include <map>\r\n #include <list>\r\n \r\n-#include \"workspace-element.h\"\r\n+#include \"syminfo.h\"\r\n #include \"octave-link.h\"\r\n \r\n class string_vector;\r\n@@ -33,11 +33,11 @@\n \tstatic JSON_OBJECT_T from_string_vector(const string_vector& list);\r\n \tstatic JSON_OBJECT_T from_int_list(const std::list<int>& list);\r\n \tstatic JSON_OBJECT_T from_float_list(const std::list<float>& list);\r\n-\tstatic JSON_OBJECT_T from_workspace_list(const std::list<workspace_element>& list);\r\n+\tstatic JSON_OBJECT_T from_symbol_info_list(const octave::symbol_info_list& list);\r\n \tstatic JSON_OBJECT_T from_filter_list(const octave_link::filter_list& list);\r\n \r\n \tstatic JSON_OBJECT_T from_value_string(const std::string str);\r\n-\tstatic JSON_OBJECT_T from_workspace_element(workspace_element element);\r\n+\tstatic JSON_OBJECT_T from_symbol_info(const octave::symbol_info element);\r\n \tstatic JSON_OBJECT_T from_pair(std::pair<std::string, std::string> pair);\r\n \r\n \tstatic JSON_OBJECT_T from_map(JSON_MAP_T m);\r\ndiff -r 84cbf166497f -r b993253f19d0 libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tThu Jan 02 06:37:54 2020 -0600\n+++ b/libinterp/corefcn/octave-json-link.cc\tFri Jan 03 00:55:24 2020 -0600\n@@ -26,7 +26,6 @@\n \r\n #include <iostream>\r\n #include \"octave-json-link.h\"\r\n-#include \"workspace-element.h\"\r\n #include \"cmd-edit.h\"\r\n #include \"json-main.h\"\r\n #include \"json-util.h\"\r\n@@ -73,17 +72,18 @@\n \treturn confirm_shutdown_queue.dequeue();\r\n }\r\n \r\n-bool octave_json_link::do_exit(int status) {\r\n-\tJSON_MAP_T m;\r\n-\tJSON_MAP_SET(m, status, int);\r\n-\t_publish_message(\"exit\", json_util::from_map(m));\r\n+// do_exit was removed in Octave 5\r\n+// bool octave_json_link::do_exit(int status) {\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, status, int);\r\n+// \t_publish_message(\"exit\", json_util::from_map(m));\r\n \r\n-\t// It is our responsibility in octave_link to call exit. If we don't, then\r\n-\t// the kernel waits for 24 hours expecting us to do something.\r\n-\t::exit(status);\r\n+// \t// It is our responsibility in octave_link to call exit. If we don't, then\r\n+// \t// the kernel waits for 24 hours expecting us to do something.\r\n+// \t::exit(status);\r\n \r\n-\treturn true;\r\n-}\r\n+// \treturn true;\r\n+// }\r\n \r\n bool octave_json_link::do_copy_image_to_clipboard(const std::string& file) {\r\n \t// This endpoint might be unused?  (References appear only in libgui)\r\n@@ -112,16 +112,16 @@\n \treturn prompt_new_edit_file_queue.dequeue();\r\n }\r\n \r\n-int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {\r\n-\t// Triggered in \"msgbox\", \"helpdlg\", and \"errordlg\", among others\r\n-\tJSON_MAP_T m;\r\n-\tJSON_MAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\r\n-\tJSON_MAP_SET(m, msg, string);\r\n-\tJSON_MAP_SET(m, title, string);\r\n-\t_publish_message(\"message-dialog\", json_util::from_map(m));\r\n+// int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {\r\n+// \t// Triggered in \"msgbox\", \"helpdlg\", and \"errordlg\", among others\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\r\n+// \tJSON_MAP_SET(m, msg, string);\r\n+// \tJSON_MAP_SET(m, title, string);\r\n+// \t_publish_message(\"message-dialog\", json_util::from_map(m));\r\n \r\n-\treturn message_dialog_queue.dequeue();\r\n-}\r\n+// \treturn message_dialog_queue.dequeue();\r\n+// }\r\n \r\n std::string octave_json_link::do_question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n \t// Triggered in \"questdlg\"\r\n@@ -198,6 +198,19 @@\n \t_publish_message(\"change-directory\", json_util::from_map(m));\r\n }\r\n \r\n+void octave_json_link::do_file_remove (const std::string& old_name, const std::string& new_name) {\r\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, old_name, string);\r\n+\tJSON_MAP_SET(m, new_name, string);\r\n+\t_publish_message(\"file-remove\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_file_renamed (bool status) {\r\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n+\t_publish_message(\"file-renamed\", json_util::from_boolean(status));\r\n+}\r\n+\r\n void octave_json_link::do_execute_command_in_terminal(const std::string& command) {\r\n \t// This endpoint might be unused?  (References appear only in libgui)\r\n \tJSON_MAP_T m;\r\n@@ -205,13 +218,22 @@\n \t_publish_message(\"execute-command-in-terminal\", json_util::from_map(m));\r\n }\r\n \r\n-void octave_json_link::do_set_workspace(bool top_level, bool debug, const std::list<workspace_element>& ws /*, const bool& variable_editor_too */) {\r\n+uint8NDArray octave_json_link::do_get_named_icon (const std::string& /* icon_name */) {\r\n+\t// Called from msgbox.m\r\n+\t// TODO: Implement request/response for this event\r\n+\tuint8NDArray retval;\r\n+\treturn retval;\r\n+}\r\n+\r\n+void octave_json_link::do_set_workspace(bool top_level, bool debug,\r\n+                         const octave::symbol_info_list& ws,\r\n+                         bool update_variable_editor) {\r\n \t// Triggered on every new line entry\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, top_level, boolean);\r\n \tJSON_MAP_SET(m, debug, boolean);\r\n-\tJSON_MAP_SET(m, ws, workspace_list);\r\n-\t// variable_editor_too?\r\n+\tJSON_MAP_SET(m, ws, symbol_info_list);\r\n+\tJSON_MAP_SET(m, update_variable_editor, boolean);\r\n \t_publish_message(\"set-workspace\", json_util::from_map(m));\r\n }\r\n \r\n@@ -279,29 +301,49 @@\n \t_publish_message(\"update-breakpoint\", json_util::from_map(m));\r\n }\r\n \r\n-void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {\r\n-\t// Triggered upon interpreter startup\r\n-\tJSON_MAP_T m;\r\n-\tJSON_MAP_SET(m, ps1, string);\r\n-\tJSON_MAP_SET(m, ps2, string);\r\n-\tJSON_MAP_SET(m, ps4, string);\r\n-\t_publish_message(\"set-default-prompts\", json_util::from_map(m));\r\n-}\r\n+// void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {\r\n+// \t// Triggered upon interpreter startup\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, ps1, string);\r\n+// \tJSON_MAP_SET(m, ps2, string);\r\n+// \tJSON_MAP_SET(m, ps4, string);\r\n+// \t_publish_message(\"set-default-prompts\", json_util::from_map(m));\r\n+// }\r\n \r\n void octave_json_link::do_show_preferences(void) {\r\n \t// Triggered on \"preferences\" command\r\n \t_publish_message(\"show-preferences\", json_util::empty());\r\n }\r\n \r\n+std::string octave_json_link::do_gui_preference (const std::string& /* key */, const std::string& /* value */) {\r\n+\t// Used by Octave GUI?\r\n+\t// TODO: Implement request/response for this event\r\n+\tstd::string retval;\r\n+\treturn retval;\r\n+}\r\n+\r\n void octave_json_link::do_show_doc(const std::string& file) {\r\n \t// Triggered on \"doc\" command\r\n \t_publish_message(\"show-doc\", json_util::from_string(file));\r\n }\r\n \r\n-// void octave_json_link::do_openvar(const std::string& name) {\r\n-// \t// Triggered on \"openvar\" command\r\n-// \t_publish_message(\"openvar\", json_util::from_string(name));\r\n-// }\r\n+void octave_json_link::do_register_doc (const std::string& file) {\r\n+\t// Triggered by the GUI documentation viewer?\r\n+\t_publish_message(\"register-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::do_unregister_doc (const std::string& file) {\r\n+\t// Triggered by the GUI documentation viewer?\r\n+\t_publish_message(\"unregister-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::do_edit_variable (const std::string& name, const octave_value& /* val */) {\r\n+\t// Triggered on \"openvar\" command\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\t// TODO: val\r\n+\t_publish_message(\"edit-variable\", json_util::from_map(m));\r\n+}\r\n \r\n void octave_json_link::do_show_static_plot(const std::string& term, const std::string& content) {\r\n \t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\r\ndiff -r 84cbf166497f -r b993253f19d0 libinterp/corefcn/octave-json-link.h\n--- a/libinterp/corefcn/octave-json-link.h\tThu Jan 02 06:37:54 2020 -0600\n+++ b/libinterp/corefcn/octave-json-link.h\tFri Jan 03 00:55:24 2020 -0600\n@@ -45,7 +45,7 @@\n \r\n private:\r\n   std::queue<T> _queue;\r\n-  octave_mutex _mutex;\r\n+  octave::mutex _mutex;\r\n };\r\n \r\n class octave_json_link : public octave_link\r\n@@ -61,16 +61,12 @@\n   std::string do_request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\r\n \r\n   bool do_confirm_shutdown (void) override;\r\n-  bool do_exit (int status) override;\r\n \r\n   bool do_copy_image_to_clipboard (const std::string& file) override;\r\n \r\n   bool do_edit_file (const std::string& file) override;\r\n   bool do_prompt_new_edit_file (const std::string& file) override;\r\n \r\n-  int do_message_dialog (const std::string& dlg, const std::string& msg,\r\n-                         const std::string& title) override;\r\n-\r\n   std::string\r\n   do_question_dialog (const std::string& msg, const std::string& title,\r\n                       const std::string& btn1, const std::string& btn2,\r\n@@ -105,13 +101,16 @@\n \r\n   void do_change_directory (const std::string& dir) override;\r\n \r\n+  void do_file_remove (const std::string& old_name, const std::string& new_name) override;\r\n+  void do_file_renamed (bool) override;\r\n+\r\n   void do_execute_command_in_terminal (const std::string& command) override;\r\n \r\n+  uint8NDArray do_get_named_icon (const std::string& icon_name) override;\r\n+\r\n   void do_set_workspace (bool top_level, bool debug,\r\n-                         const std::list<workspace_element>& ws\r\n-                         // Added on head but not yet in stable:\r\n-                         // , const bool& variable_editor_too = true\r\n-                         ) override;\r\n+                         const octave::symbol_info_list& ws,\r\n+                         bool update_variable_editor) override;\r\n \r\n   void do_clear_workspace (void) override;\r\n \r\n@@ -132,15 +131,17 @@\n                              const std::string& file, int line,\r\n                              const std::string& cond) override;\r\n \r\n-  void do_set_default_prompts (std::string& ps1, std::string& ps2,\r\n-                               std::string& ps4) override;\r\n+  void do_show_preferences (void) override;\r\n \r\n-  void do_show_preferences (void) override;\r\n+  std::string do_gui_preference (const std::string& key, const std::string& value) override;\r\n \r\n   void do_show_doc (const std::string& file) override;\r\n \r\n-  // Added on head but not yet in stable:\r\n-  // void do_openvar (const std::string& name) override;\r\n+  void do_register_doc (const std::string& file) override;\r\n+\r\n+  void do_unregister_doc (const std::string& file) override;\r\n+\r\n+  void do_edit_variable (const std::string& name, const octave_value& val) override;\r\n \r\n   void do_show_static_plot (const std::string& term,\r\n                             const std::string& content) override;\r\ndiff -r 84cbf166497f -r b993253f19d0 libinterp/corefcn/octave-link.h\n--- a/libinterp/corefcn/octave-link.h\tThu Jan 02 06:37:54 2020 -0600\n+++ b/libinterp/corefcn/octave-link.h\tFri Jan 03 00:55:24 2020 -0600\n@@ -31,7 +31,7 @@\n #include <string>\n \n #include \"oct-mutex.h\"\n-#include \"octave.h\"\n+#include \"libinterp/octave.h\"\n #include \"event-queue.h\"\n #include \"uint8NDArray.h\"\n \ndiff -r 84cbf166497f -r b993253f19d0 liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tThu Jan 02 06:37:54 2020 -0600\n+++ b/liboctave/util/url-transfer.cc\tFri Jan 03 00:55:24 2020 -0600\n@@ -890,26 +890,26 @@\n \n   url_transfer::url_transfer (void) {\n     if (octave_link::request_input_enabled()) {\n-      rep = new link_transfer();\n+      rep.reset(new link_transfer());\n     } else {\n-      rep = new REP_CLASS();\n+      rep.reset(new REP_CLASS());\n     }\n   }\n \n   url_transfer::url_transfer (const std::string& host, const std::string& user,\n                               const std::string& passwd, std::ostream& os) {\n     if (octave_link::request_input_enabled()) {\n-      rep = new link_transfer(host, user, passwd, os);\n+      rep.reset(new link_transfer(host, user, passwd, os));\n     } else {\n-      rep = new REP_CLASS(host, user, passwd, os);\n+      rep.reset(new REP_CLASS(host, user, passwd, os));\n     }\n   }\n \n   url_transfer::url_transfer (const std::string& url, std::ostream& os) {\n     if (octave_link::request_input_enabled()) {\n-      rep = new link_transfer(url, os);\n+      rep.reset(new link_transfer(url, os));\n     } else {\n-      rep = new REP_CLASS(url, os);\n+      rep.reset(new REP_CLASS(url, os));\n     }\n   }\n \n"
  },
  {
    "path": "back-octave/oo-changesets/202-d9d23f97ba78.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1578045495 21600\n#      Fri Jan 03 03:58:15 2020 -0600\n# Branch oo-5.2\n# Node ID d9d23f97ba78c49221f476e82b550a59e9f146a0\n# Parent  b993253f19d07cc1e9373cd8e9a35745cde9fef9\nUpdating json-main to obey new ownership policy of octave_link\n\ndiff -r b993253f19d0 -r d9d23f97ba78 libinterp/corefcn/json-main.cc\n--- a/libinterp/corefcn/json-main.cc\tFri Jan 03 00:55:24 2020 -0600\n+++ b/libinterp/corefcn/json-main.cc\tFri Jan 03 03:58:15 2020 -0600\n@@ -28,11 +28,12 @@\n   : _json_sock_path (json_sock_path),\r\n     _max_message_length (max_message_length),\r\n     _loop_thread_active (false),\r\n-    _octave_json_link (this)\r\n+    _octave_json_link (new octave_json_link(this))\r\n {\r\n-  // Enable octave_json_link instance\r\n-\toctave_link::connect_link(&_octave_json_link);\r\n-\r\n+  // Enable the octave_json_link instance\r\n+  // Note: this passes ownership to octave_link\r\n+\toctave_link::connect_link(_octave_json_link);\r\n+j\r\n   // Open UNIX socket file descriptor\r\n   sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\r\n   struct sockaddr_un addr;\r\n@@ -89,5 +90,5 @@\n }\r\n \r\n void json_main::process_json_object(std::string name, JSON_OBJECT_T jobj) {\r\n-  _octave_json_link.receive_message(name, jobj);\r\n+  _octave_json_link->receive_message(name, jobj);\r\n }\r\ndiff -r b993253f19d0 -r d9d23f97ba78 libinterp/corefcn/json-main.h\n--- a/libinterp/corefcn/json-main.h\tFri Jan 03 00:55:24 2020 -0600\n+++ b/libinterp/corefcn/json-main.h\tFri Jan 03 03:58:15 2020 -0600\n@@ -24,7 +24,9 @@\n \tint sockfd;\r\n \tbool _loop_thread_active;\r\n \tpthread_t _loop_thread;\r\n-\toctave_json_link _octave_json_link;\r\n+\r\n+\t// Owned by octave_link\r\n+\toctave_json_link* _octave_json_link;\r\n };\r\n \r\n #endif\r\n"
  },
  {
    "path": "back-octave/oo-changesets/203-d6b5ffb8e4cc.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1578047258 21600\n#      Fri Jan 03 04:27:38 2020 -0600\n# Branch oo-5.2\n# Node ID d6b5ffb8e4cc940b7337575d0194d025c7385b24\n# Parent  d9d23f97ba78c49221f476e82b550a59e9f146a0\nRemove extra character\n\ndiff -r d9d23f97ba78 -r d6b5ffb8e4cc libinterp/corefcn/json-main.cc\n--- a/libinterp/corefcn/json-main.cc\tFri Jan 03 03:58:15 2020 -0600\n+++ b/libinterp/corefcn/json-main.cc\tFri Jan 03 04:27:38 2020 -0600\n@@ -33,7 +33,7 @@\n   // Enable the octave_json_link instance\r\n   // Note: this passes ownership to octave_link\r\n \toctave_link::connect_link(_octave_json_link);\r\n-j\r\n+\r\n   // Open UNIX socket file descriptor\r\n   sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\r\n   struct sockaddr_un addr;\r\n"
  },
  {
    "path": "back-octave/oo-changesets/204-e61d7b8918e2.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane@sffc.xyz>\n# Date 1578122988 21600\n#      Sat Jan 04 01:29:48 2020 -0600\n# Branch oo-5.2\n# Node ID e61d7b8918e2632d23f537cd679e1f532ed317d7\n# Parent  d6b5ffb8e4cc940b7337575d0194d025c7385b24\nRemoving UTF-8 logic from json-util.cc\n\ndiff -r d6b5ffb8e4cc -r e61d7b8918e2 libinterp/corefcn/json-util.cc\n--- a/libinterp/corefcn/json-util.cc\tFri Jan 03 04:27:38 2020 -0600\n+++ b/libinterp/corefcn/json-util.cc\tSat Jan 04 01:29:48 2020 -0600\n@@ -15,95 +15,8 @@\n #include \"json-util.h\"\r\n \r\n JSON_OBJECT_T json_util::from_string(const std::string& str) {\r\n-\tconst char* snowflake = \"\\xEF\\xBF\\xBD\";\r\n-\r\n-\t// Ensure that the string is valid UTF-8\r\n-\tstd::string sanitized;\r\n-\tsize_t state = 0;\r\n-\tsize_t cpLength = 0;\r\n-\tfor (size_t i=0; i<str.length(); i++) {\r\n-\t\tunsigned char c = str[i];\r\n-\t\tswitch (state) {\r\n-\t\tcase 0:\r\n-\t\t\tif (c < 0x80) {\r\n-\t\t\t\t// 1-byte character\r\n-\t\t\t\tsanitized.push_back(c);\r\n-\t\t\t} else if (c >= 0xC2 && c <= 0xDF) {\r\n-\t\t\t\t// 2-byte character\r\n-\t\t\t\tstate = 1;\r\n-\t\t\t\tcpLength = 2;\r\n-\t\t\t} else if (c >= 0xE0 && c <= 0xEF) {\r\n-\t\t\t\t// 3-byte character\r\n-\t\t\t\tstate = 1;\r\n-\t\t\t\tcpLength = 3;\r\n-\t\t\t} else if (c >= 0xF0 && c <= 0xF4) {\r\n-\t\t\t\t// 4-byte character\r\n-\t\t\t\tstate = 1;\r\n-\t\t\t\tcpLength = 4;\r\n-\t\t\t} else {\r\n-\t\t\t\t// Invalid byte\r\n-\t\t\t\tsanitized.append(snowflake);\r\n-\t\t\t}\r\n-\t\t\tbreak;\r\n-\r\n-\t\tcase 1:\r\n-\t\t\tif (c < 0x80 || c > 0xBF) {\r\n-\t\t\t\t// Invalid byte\r\n-\t\t\t\tsanitized.append(snowflake);\r\n-\t\t\t\tstate = 0;\r\n-\t\t\t} else if (cpLength == 2) {\r\n-\t\t\t\t// Final byte in 2-byte character\r\n-\t\t\t\tsanitized.push_back(str[i-1]);\r\n-\t\t\t\tsanitized.push_back(c);\r\n-\t\t\t\tstate = 0;\r\n-\t\t\t} else {\r\n-\t\t\t\t// 3-byte or 4-byte character\r\n-\t\t\t\tstate = 2;\r\n-\t\t\t}\r\n-\t\t\tbreak;\r\n-\r\n-\t\tcase 2:\r\n-\t\t\tif (c < 0x80 || c > 0xBF) {\r\n-\t\t\t\t// Invalid byte\r\n-\t\t\t\tsanitized.append(snowflake);\r\n-\t\t\t\tstate = 0;\r\n-\t\t\t} else if (cpLength == 3) {\r\n-\t\t\t\t// Final byte in 3-byte character\r\n-\t\t\t\tsanitized.push_back(str[i-2]);\r\n-\t\t\t\tsanitized.push_back(str[i-1]);\r\n-\t\t\t\tsanitized.push_back(c);\r\n-\t\t\t\tstate = 0;\r\n-\t\t\t} else {\r\n-\t\t\t\t// 4-byte character\r\n-\t\t\t\tstate = 3;\r\n-\t\t\t}\r\n-\t\t\tbreak;\r\n-\r\n-\t\tcase 3:\r\n-\t\t\tif (c < 0x80 || c > 0xBF) {\r\n-\t\t\t\t// Invalid byte\r\n-\t\t\t\tsanitized.append(snowflake);\r\n-\t\t\t\tstate = 0;\r\n-\t\t\t} else {\r\n-\t\t\t\tassert(cpLength == 4);\r\n-\t\t\t\tsanitized.push_back(str[i-3]);\r\n-\t\t\t\tsanitized.push_back(str[i-2]);\r\n-\t\t\t\tsanitized.push_back(str[i-1]);\r\n-\t\t\t\tsanitized.push_back(c);\r\n-\t\t\t\tstate = 0;\r\n-\t\t\t}\r\n-\t\t\tbreak;\r\n-\r\n-\t\t}\r\n-\t}\r\n-\r\n-\tif (state != 0) {\r\n-\t\t// Last character is invalid\r\n-\t\tsanitized.append(snowflake);\r\n-\t\tstate = 0;\r\n-\t}\r\n-\r\n-\treturn json_object_new_string_len(sanitized.c_str(), sanitized.length());\r\n+\t// Note: the string is not necesarilly valid UTF-8. The consumers of this stream must be able to handle that situation and substitute replacement characters, etc., where necessary.\r\n+\treturn json_object_new_string_len(str.c_str(), str.length());\r\n }\r\n \r\n JSON_OBJECT_T json_util::from_int(int i) {\r\n"
  },
  {
    "path": "back-octave/oo-changesets/300-d78448f9c483.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1593731318 18000\n#      Thu Jul 02 18:08:38 2020 -0500\n# Branch oo-5.2a\n# Node ID d78448f9c48344a58f5f83d5d52c29bf1ad92cb7\n# Parent  9e7b2625e5744cfbb01bdd67a3487c75a7ef957a\n# Parent  e61d7b8918e2632d23f537cd679e1f532ed317d7\nMerge oo-5.2 into stable\n\ndiff -r 9e7b2625e574 -r d78448f9c483 configure.ac\n--- a/configure.ac\tFri Jun 26 18:44:35 2020 +0200\n+++ b/configure.ac\tThu Jul 02 18:08:38 2020 -0500\n@@ -2812,7 +2812,7 @@\n AC_SUBST(LIBOCTAVE_LINK_DEPS)\n AC_SUBST(LIBOCTAVE_LINK_OPTS)\n \n-LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $SPARSE_XLIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $LLVM_LIBS $JAVA_LIBS $LAPACK_LIBS\"\n+LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $SPARSE_XLIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $LLVM_LIBS $JAVA_LIBS $LAPACK_LIBS -ljson-c\"\n \n LIBOCTINTERP_LINK_OPTS=\"$FT2_LDFLAGS $HDF5_LDFLAGS $MAGICK_LDFLAGS $Z_LDFLAGS $SPARSE_XLDFLAGS $FFTW_XLDFLAGS $LLVM_LDFLAGS\"\n \ndiff -r 9e7b2625e574 -r d78448f9c483 libgui/src/qt-interpreter-events.cc\n--- a/libgui/src/qt-interpreter-events.cc\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libgui/src/qt-interpreter-events.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -261,6 +261,21 @@\n     emit edit_variable_signal (QString::fromStdString (expr), val);\n   }\n \n+  void qt_interpreter_events::show_static_plot (const std::string&, const std::string&)\n+  {\n+    return;\n+  }\n+\n+  std::string qt_interpreter_events::request_input (const std::string&)\n+  {\n+    return {};\n+  }\n+\n+  std::string qt_interpreter_events::request_url (const std::string&, const std::list<std::string>&, const std::string&, bool&)\n+  {\n+    return {};\n+  }\n+\n   bool qt_interpreter_events::confirm_shutdown (void)\n   {\n     QMutexLocker autolock (&m_mutex);\n@@ -505,6 +520,9 @@\n     emit clear_history_signal ();\n   }\n \n+  void qt_interpreter_events::do_clear_screen (void)\n+  { }\n+\n   void qt_interpreter_events::pre_input_event (void)\n   { }\n \ndiff -r 9e7b2625e574 -r d78448f9c483 libgui/src/qt-interpreter-events.h\n--- a/libgui/src/qt-interpreter-events.h\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libgui/src/qt-interpreter-events.h\tThu Jul 02 18:08:38 2020 -0500\n@@ -118,6 +118,12 @@\n \n     void edit_variable (const std::string& name, const octave_value& val);\n \n+    void show_static_plot (const std::string& term, const std::string& content);\n+\n+    std::string request_input (const std::string&);\n+\n+    std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n     bool confirm_shutdown (void);\n \n     bool prompt_new_edit_file (const std::string& file);\n@@ -160,6 +166,8 @@\n \n     void clear_history (void);\n \n+    void clear_screen (void);\n+\n     void pre_input_event (void);\n \n     void post_input_event (void);\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/event-manager.cc\n--- a/libinterp/corefcn/event-manager.cc\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libinterp/corefcn/event-manager.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -652,3 +652,28 @@\n   evmgr.focus_window (\"workspace\");\n   return ovl ();\n }\n+\n+DEFUN (__octave_link_plot_destination__, , ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn {} {} __octave_link_plot_destination__ ()\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  return ovl (octave_link::plot_destination ());\n+}\n+\n+DEFUN (__octave_link_show_static_plot__, args, ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn {} {} __octave_link_show_static_plot__ (@var{term}, @var{content})\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  if (args.length () != 2) {\n+    return ovl ();\n+  }\n+\n+  std::string term = args(0).string_value();\n+  std::string content = args(1).string_value();\n+  return ovl (octave_link::show_static_plot (term, content));\n+}\n+\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/event-manager.h\n--- a/libinterp/corefcn/event-manager.h\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libinterp/corefcn/event-manager.h\tThu Jul 02 18:08:38 2020 -0500\n@@ -147,6 +147,13 @@\n     // confirmation before another action.  Could these be reformulated\n     // using the question_dialog action?\n \n+    bool _request_input_enabled;\n+    virtual std::string request_input (const std::string&) = 0;\n+    virtual std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) = 0;\n+\n+    plot_destination_t _plot_destination;\n+    virtual void show_static_plot (const std::string& term, const std::string& content) = 0;\n+\n     virtual bool confirm_shutdown (void) { return false; }\n \n     virtual bool prompt_new_edit_file (const std::string& /*file*/)\n@@ -220,6 +227,8 @@\n \n     virtual void clear_history (void) { }\n \n+    virtual void clear_screen (void) { }\n+\n     virtual void pre_input_event (void) { }\n \n     virtual void post_input_event (void) { }\n@@ -373,6 +382,34 @@\n         instance->update_path_dialog ();\n     }\n \n+    bool request_input_enabled (void)\n+    {\n+      return enabled () ? instance->_request_input_enabled : false;\n+    }\n+\n+    enum plot_destination_t {\n+      TERMINAL_ONLY = 0,\n+      STATIC_ONLY = 1,\n+      TERMINAL_AND_STATIC = 2\n+    };\n+\n+    plot_destination_t plot_destination (void)\n+    {\n+      return enabled () ? instance->_plot_destination : TERMINAL_ONLY;\n+    }\n+\n+    bool\n+    show_static_plot (const std::string& term, const std::string& content)\n+    {\n+      if (enabled ())\n+        {\n+          instance->show_static_plot (term, content);\n+          return true;\n+        }\n+      else\n+        return false;\n+    }\n+\n     bool show_preferences (void)\n     {\n       if (enabled ())\n@@ -551,6 +588,12 @@\n         instance->clear_history ();\n     }\n \n+    void clear_screen (void)\n+    {\n+      if (enabled ())\n+        instance->clear_screen ();\n+    }\n+\n     void pre_input_event (void)\n     {\n       if (enabled ())\n@@ -563,6 +606,21 @@\n         instance->post_input_event ();\n     }\n \n+\n+    std::string request_input (const std::string& prompt)\n+    {\n+      return request_input_enabled ()\n+        ? instance->request_input (prompt)\n+        : std::string ();\n+    }\n+\n+    std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success)\n+    {\n+      return request_input_enabled ()\n+        ? instance->request_url (url, param, action, success)\n+        : std::string ();\n+    }\n+\n     void enter_debugger_event (const std::string& fcn_name,\n                                const std::string& fcn_file_name, int line)\n     {\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/input.cc\n--- a/libinterp/corefcn/input.cc\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libinterp/corefcn/input.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -671,7 +671,12 @@\n \n     eof = false;\n \n-    std::string retval = command_editor::readline (s, eof);\n+    std::string retval;\n+    event_manager& evmgr = m_interpreter.get_event_manager ();\n+    if (evmgr.request_input_enabled ())\n+      retval = evmgr.request_input (s);\n+    else\n+      retval = command_editor::readline (s, eof);\n \n     if (! eof && retval.empty ())\n       retval = \"\\n\";\n@@ -1443,3 +1448,32 @@\n }\n \n // #endif\n+\n+DEFUN (current_command_number, args, ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn  {} {@var{val} =} current_command_number ()\n+@deftypefnx {} {@var{old_val} =} current_command_number (@var{new_val})\n+Sets the current command number, which appears in the prompt string.\n+For example, if the prompt says \"octave:1>\", then the current command\n+number is 1.\n+\n+This is a custom function in Octave Online.\n+\n+@example\n+current_command_number(1)\n+@end example\n+@end deftypefn */)\n+{\n+  int nargin = args.length ();\n+  if (nargin == 0) {\n+    int n = octave::command_editor::current_command_number();\n+    return ovl(n);\n+  } else if (nargin > 1) {\n+    print_usage ();\n+    return ovl();\n+  } else {\n+    int n = args(0).int_value ();\n+    octave::command_editor::reset_current_command_number(n);\n+    return ovl(n);\n+  }\n+}\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/interpreter.cc\n--- a/libinterp/corefcn/interpreter.cc\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libinterp/corefcn/interpreter.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -59,6 +59,7 @@\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"interpreter.h\"\n+#include \"json-main.h\"\n #include \"load-path.h\"\n #include \"load-save.h\"\n #include \"octave.h\"\n@@ -600,6 +601,11 @@\n         std::string texi_macros_file = options.texi_macros_file ();\n         if (! texi_macros_file.empty ())\n           Ftexi_macros_file (*this, octave_value (texi_macros_file));\n+\n+        if (!options.json_sock_path().empty ()) {\n+          static json_main _json_main (*this, options.json_sock_path(), options.json_max_message_length());\n+          _json_main.run_loop_on_new_thread();\n+        }\n       }\n \n     // FIXME: we defer creation of the gh_manager object because it\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/json-main.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -0,0 +1,96 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include \"json-main.h\"\r\n+\r\n+#include <iostream>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <unistd.h>\r\n+\r\n+\r\n+// Analog of main-window.cc\r\n+// TODO: Think more about concurrency and null pointer exceptions\r\n+\r\n+void* run_loop_pthread(void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->run_loop();\r\n+  return NULL;\r\n+}\r\n+\r\n+void json_object_cb(std::string name, JSON_OBJECT_T jobj, void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->process_json_object(name, jobj);\r\n+}\r\n+\r\n+json_main::json_main(interpreter& interp, const std::string& json_sock_path, int max_message_length)\r\n+  : _json_sock_path (json_sock_path),\r\n+    _max_message_length (max_message_length),\r\n+    _loop_thread_active (false),\r\n+    _octave_json_link (new octave_json_link(this))\r\n+{\r\n+  // Enable the octave_json_link instance\r\n+  // Note: this passes ownership to octave_link\r\n+  event_manager& evmgr = interp.get_event_manager ();\r\n+  evmgr.connect_link (_octave_json_link);\r\n+  evmgr.enable ();\r\n+\r\n+  // Open UNIX socket file descriptor\r\n+  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\r\n+  struct sockaddr_un addr;\r\n+  memset(&addr, 0, sizeof(addr));\r\n+  addr.sun_family = AF_UNIX;\r\n+  memcpy(&addr.sun_path, _json_sock_path.c_str(), sizeof(addr.sun_path)-1);\r\n+  connect(\r\n+    sockfd,\r\n+    reinterpret_cast<struct sockaddr*>(&addr),\r\n+    sizeof(addr));\r\n+}\r\n+\r\n+json_main::~json_main(void) {\r\n+  close(sockfd);\r\n+\r\n+  // TODO: Stop the _loop_thread\r\n+}\r\n+\r\n+void json_main::publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  std::string jstr = json_util::to_message(name, jobj);\r\n+\r\n+  // Do not send any messages over the socket that exceed the user-specified max length.  Instead, send an error message.  If max_length is 0 (default), do not suppress any messages.\r\n+  // In stress testing, Node may be able to handle as much as 10-20 MB, but I'd prefer to stay on the safe side.  Before this safeguard was implemented, fewer than 5% of plots exceeded 1 MB.\r\n+  int length = jstr.length();\r\n+  int max_length = _max_message_length;\r\n+  if (max_length > 0 && length > max_length) {\r\n+    JSON_MAP_T m;\r\n+    JSON_MAP_SET(m, name, string);\r\n+    JSON_MAP_SET(m, length, int);\r\n+    JSON_MAP_SET(m, max_length, int);\r\n+    jstr = json_util::to_message(\"message-too-long\", json_util::from_map(m));\r\n+  }\r\n+\r\n+  send(sockfd, jstr.c_str(), jstr.length(), 0);\r\n+}\r\n+\r\n+void json_main::run_loop_on_new_thread(void) {\r\n+  if (_loop_thread_active)\r\n+    perror(\"won't run JSON socket loop multiple times\");\r\n+  _loop_thread_active = true;\r\n+\r\n+  pthread_create(\r\n+    &_loop_thread,\r\n+    NULL,\r\n+    run_loop_pthread,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::run_loop(void) {\r\n+  json_util::read_stream(\r\n+    sockfd,\r\n+    json_object_cb,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::process_json_object(std::string name, JSON_OBJECT_T jobj) {\r\n+  _octave_json_link->receive_message(name, jobj);\r\n+}\r\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/json-main.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.h\tThu Jul 02 18:08:38 2020 -0500\n@@ -0,0 +1,32 @@\n+#ifndef json_main_h\r\n+#define json_main_h\r\n+\r\n+#include <queue>\r\n+#include <pthread.h>\r\n+#include <stdio.h>\r\n+\r\n+#include \"octave-json-link.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+class json_main {\r\n+public:\r\n+\tjson_main(interpreter& interp, const std::string& json_sock_path, int max_message_length);\r\n+\t~json_main(void);\r\n+\r\n+\tvoid publish_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\tvoid run_loop_on_new_thread(void);\r\n+\tvoid run_loop(void);\r\n+\tvoid process_json_object(std::string name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+\tstd::string _json_sock_path;\r\n+\tint _max_message_length;\r\n+\tint sockfd;\r\n+\tbool _loop_thread_active;\r\n+\tpthread_t _loop_thread;\r\n+\r\n+\t// Owned by octave_link\r\n+\toctave_json_link* _octave_json_link;\r\n+};\r\n+\r\n+#endif\r\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/json-util.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -0,0 +1,260 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <cstdlib>\r\n+#include <assert.h>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <stdio.h>\r\n+#include <json-c/arraylist.h>\r\n+#include <json-c/json_object.h>\r\n+\r\n+#include \"str-vec.h\"\r\n+\r\n+#include \"json-util.h\"\r\n+\r\n+JSON_OBJECT_T json_util::from_string(const std::string& str) {\r\n+\t// Note: the string is not necesarilly valid UTF-8. The consumers of this stream must be able to handle that situation and substitute replacement characters, etc., where necessary.\r\n+\treturn json_object_new_string_len(str.c_str(), str.length());\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int(int i) {\r\n+\treturn json_object_new_int(i);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float(float flt) {\r\n+\treturn json_object_new_double(flt);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_boolean(bool b) {\r\n+\treturn json_object_new_boolean(b);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::empty() {\r\n+\treturn json_object_new_object();\r\n+}\r\n+\r\n+template<typename T>\r\n+JSON_OBJECT_T json_object_from_list(const std::list<T>& list, JSON_OBJECT_T (*convert)(T)) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\tauto it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, convert(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_list(const std::list<std::string>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_vector(const string_vector& vect) {\r\n+\t// TODO: Make sure this function does what it's supposed to do\r\n+\tstd::list<std::string> list;\r\n+\tfor (int i = 0; i < vect.numel(); ++i) {\r\n+\t\tlist.push_back(vect[i]);\r\n+\t}\r\n+\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int_list(const std::list<int>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_int);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float_list(const std::list<float>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_float);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_symbol_info_list(const octave::symbol_info_list& list) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\tauto it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, json_util::from_symbol_info(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_filter_list(const octave_link::filter_list& list) {\r\n+\treturn json_object_from_list(list, json_util::from_pair);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_value_string(const std::string str) {\r\n+\treturn json_util::from_string(str);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_symbol_info(const octave::symbol_info element) {\r\n+\toctave_value val = element.value();\r\n+\r\n+\tstd::string dims_str = val.get_dims_str();\r\n+\r\n+\tstd::ostringstream display_str;\r\n+\tval.short_disp(display_str);\r\n+\r\n+\tJSON_MAP_T m;\r\n+\t// m[\"scope\"] = json_util::from_int(element.scope());\r\n+\tm[\"symbol\"] = json_util::from_string(element.name());\r\n+\tm[\"class_name\"] = json_util::from_string(val.class_name());\r\n+\tm[\"dimension\"] = json_util::from_string(dims_str);\r\n+\tm[\"value\"] = json_util::from_string(display_str.str());\r\n+\tm[\"complex_flag\"] = json_util::from_boolean(element.is_complex());\r\n+\treturn json_util::from_map(m);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_pair(std::pair<std::string, std::string> pair) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tjson_object_array_add(jobj, json_util::from_string(pair.first.c_str()));\r\n+\tjson_object_array_add(jobj, json_util::from_string(pair.second.c_str()));\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_map(JSON_MAP_T m) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_object();\r\n+\tfor(\r\n+\t\tstd::map<std::string, JSON_OBJECT_T>::iterator it = m.begin();\r\n+\t\tit != m.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_object_add(jobj, it->first.c_str(), it->second);\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+std::string json_util::to_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  JSON_OBJECT_T jmsg = json_object_new_array();\r\n+  json_object_array_add(jmsg, json_util::from_string(name));\r\n+  json_object_array_add(jmsg, jobj);\r\n+  std::string str (json_object_to_json_string(jmsg));\r\n+  return str;\r\n+}\r\n+\r\n+std::string json_util::to_string(JSON_OBJECT_T jobj) {\r\n+  return std::string(json_object_get_string(jobj));\r\n+}\r\n+\r\n+template<typename T>\r\n+std::list<T> json_object_to_list(JSON_OBJECT_T jobj, T (*convert)(JSON_OBJECT_T)) {\r\n+\tstd::list<T> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tfor (size_t i = 0; i < array_list_length(arr); ++i) {\r\n+\t\tJSON_OBJECT_T jsub = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, i));\r\n+\t\tret.push_back(convert(jsub));\r\n+\t}\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> json_util::to_int_list_int_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<std::list<int>, int> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_to_list<int>(first, json_util::to_int);\r\n+\tret.second = json_object_get_int(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<bool, std::string> json_util::to_bool_string_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<bool, std::string> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_get_boolean(first);\r\n+\tret.second = json_object_get_string(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::list<std::string> json_util::to_string_list(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_to_list<std::string>(jobj, json_util::to_string);\r\n+}\r\n+\r\n+int json_util::to_int(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_int(jobj);\r\n+}\r\n+\r\n+bool json_util::to_boolean(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_boolean(jobj);\r\n+}\r\n+\r\n+void json_util::read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+\r\n+\t// Make some local variables\r\n+\tint BUF_LEN = 24;\r\n+\tchar* buf = new char[BUF_LEN];  // buffer for socket read\r\n+\tint buf_len;  // length of new bytes in the buffer\r\n+\tint buf_offset;  // offset of the JSON parser in the buffer\r\n+\tJSON_OBJECT_T jobj;  // pointer to parsed JSON object\r\n+\tjson_tokener* tok = json_tokener_new();  // JSON tokenizer instance\r\n+\tenum json_tokener_error jerr;  // status of JSON tokenizer\r\n+\r\n+\t// Start the blocking I/O loop\r\n+\twhile( (buf_len=recv(sockfd, buf, BUF_LEN, 0)) > 0) {\r\n+\t\tbuf_offset = 0;\r\n+\t\twhile(buf_offset < buf_len){\r\n+\t\t\tjobj = json_tokener_parse_ex(tok, buf + buf_offset, buf_len - buf_offset);\r\n+\t\t\tjerr = json_tokener_get_error(tok);\r\n+\t\t\tbuf_offset += tok->char_offset;\r\n+\r\n+\t\t\t// Do we need more material in order to make JSON?\r\n+\t\t\tif (jerr == json_tokener_continue) {\r\n+\t\t\t\tcontinue;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Make a new tokenizer\r\n+\t\t\tjson_tokener_free(tok);\r\n+\t\t\ttok = json_tokener_new();\r\n+\r\n+\t\t\t// Did we encounter a malformed JSON object?\r\n+\t\t\tif (jerr != json_tokener_success) {\r\n+\t\t\t\tfprintf(stderr,\r\n+\t\t\t\t\t\"JSON parse error: %s: '%.*s'\\n\",\r\n+\t\t\t\t\tjson_tokener_error_desc(jerr),\r\n+\t\t\t\t\t1,\r\n+\t\t\t\t\tbuf + buf_offset);\r\n+\t\t\t\tfflush(stderr);\r\n+\t\t\t\tbreak;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Our object is ready\r\n+\t\t\tprocess_message(jobj, cb, arg);\r\n+\t\t}\r\n+\t}\r\n+\r\n+\tjson_tokener_free(tok);\r\n+\tdelete buf;\r\n+}\r\n+\r\n+void json_util::process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+  if (!json_object_is_type(jobj, json_type_array))\r\n+    return;\r\n+  if (json_object_array_length(jobj) != 2)\r\n+    return;\r\n+\r\n+  cb(\r\n+  \tjson_util::to_string(json_object_array_get_idx(jobj, 0)),\r\n+  \tjson_object_array_get_idx(jobj, 1),\r\n+  \targ\r\n+  );\r\n+}\r\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/json-util.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.h\tThu Jul 02 18:08:38 2020 -0500\n@@ -0,0 +1,60 @@\n+#ifndef json_util_h\r\n+#define json_util_h\r\n+\r\n+#include <json-c/json.h>\r\n+#include <map>\r\n+#include <list>\r\n+\r\n+#include \"syminfo.h\"\r\n+#include \"event-manager.h\"\r\n+\r\n+class string_vector;\r\n+\r\n+// All of the code interacting with the external JSON library should be in\r\n+// the json-util.h and json-util.cc files.  This way, if we want to change\r\n+// the external JSON library, we can do it all in one place.\r\n+\r\n+#define JSON_OBJECT_T json_object*\r\n+#define JSON_MAP_T std::map<std::string, JSON_OBJECT_T>\r\n+\r\n+#define JSON_MAP_SET(M, FIELD, TYPE){ \\\r\n+\tm[#FIELD] = json_util::from_##TYPE (FIELD); \\\r\n+}\r\n+\r\n+class json_util {\r\n+public:\r\n+\tstatic JSON_OBJECT_T from_string(const std::string& str);\r\n+\tstatic JSON_OBJECT_T from_int(int i);\r\n+\tstatic JSON_OBJECT_T from_float(float flt);\r\n+\tstatic JSON_OBJECT_T from_boolean(bool b);\r\n+\tstatic JSON_OBJECT_T empty();\r\n+\r\n+\tstatic JSON_OBJECT_T from_string_list(const std::list<std::string>& list);\r\n+\tstatic JSON_OBJECT_T from_string_vector(const string_vector& list);\r\n+\tstatic JSON_OBJECT_T from_int_list(const std::list<int>& list);\r\n+\tstatic JSON_OBJECT_T from_float_list(const std::list<float>& list);\r\n+\tstatic JSON_OBJECT_T from_symbol_info_list(const octave::symbol_info_list& list);\r\n+\tstatic JSON_OBJECT_T from_filter_list(const event_manager::filter_list& list);\r\n+\r\n+\tstatic JSON_OBJECT_T from_value_string(const std::string str);\r\n+\tstatic JSON_OBJECT_T from_symbol_info(const octave::symbol_info element);\r\n+\tstatic JSON_OBJECT_T from_pair(std::pair<std::string, std::string> pair);\r\n+\r\n+\tstatic JSON_OBJECT_T from_map(JSON_MAP_T m);\r\n+\r\n+\tstatic std::string to_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic std::string to_string(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<std::list<int>, int> to_int_list_int_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<bool, std::string> to_bool_string_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::list<std::string> to_string_list(JSON_OBJECT_T jobj);\r\n+\tstatic int to_int(JSON_OBJECT_T jobj);\r\n+\tstatic bool to_boolean(JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic void read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+\r\n+private:\r\n+\tstatic void process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+};\r\n+\r\n+#endif\r\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/module.mk\n--- a/libinterp/corefcn/module.mk\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libinterp/corefcn/module.mk\tThu Jul 02 18:08:38 2020 -0500\n@@ -45,6 +45,8 @@\n   %reldir%/help.h \\\n   %reldir%/hook-fcn.h \\\n   %reldir%/input.h \\\n+  %reldir%/json-main.h \\\n+  %reldir%/json-util.h \\\n   %reldir%/interpreter.h \\\n   %reldir%/load-path.h \\\n   %reldir%/load-save.h \\\n@@ -73,6 +75,7 @@\n   %reldir%/oct-strstrm.h \\\n   %reldir%/oct.h \\\n   %reldir%/octave-default-image.h \\\n+  %reldir%/octave-json-link.h \\\n   %reldir%/pager.h \\\n   %reldir%/pr-flt-fmt.h \\\n   %reldir%/pr-output.h \\\n@@ -182,6 +185,8 @@\n   %reldir%/hex2num.cc \\\n   %reldir%/hook-fcn.cc \\\n   %reldir%/input.cc \\\n+  %reldir%/json-main.cc \\\n+  %reldir%/json-util.cc \\\n   %reldir%/interpreter-private.cc \\\n   %reldir%/interpreter.cc \\\n   %reldir%/inv.cc \\\n@@ -218,6 +223,7 @@\n   %reldir%/oct-tex-lexer.ll \\\n   %reldir%/oct-tex-parser.h \\\n   %reldir%/oct-tex-parser.yy \\\n+  %reldir%/octave-json-link.cc \\\n   %reldir%/ordschur.cc \\\n   %reldir%/pager.cc \\\n   %reldir%/pinv.cc \\\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/octave-json-link.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -0,0 +1,407 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <iostream>\r\n+#include \"octave-json-link.h\"\r\n+#include \"cmd-edit.h\"\r\n+#include \"json-main.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+octave_json_link::octave_json_link(json_main* __json_main)\r\n+\t: interpreter_events (),\r\n+\t\t_json_main (__json_main)\r\n+{\r\n+\t_request_input_enabled = true;\r\n+\t_plot_destination = STATIC_ONLY;\r\n+}\r\n+\r\n+octave_json_link::~octave_json_link(void) { }\r\n+\r\n+std::string octave_json_link::do_request_input(const std::string& prompt) {\r\n+\t// Triggered whenever the console prompts for user input\r\n+\r\n+\tstd::string value;\r\n+\tif (!request_input_queue.dequeue_to(&value)) {\r\n+\t\t_publish_message(\"request-input\", json_util::from_string(prompt));\r\n+\t\tvalue = request_input_queue.dequeue();\r\n+\t}\r\n+\treturn value;\r\n+}\r\n+\r\n+std::string octave_json_link::do_request_url(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\r\n+\t// Triggered on urlread/urlwrite\r\n+\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, url, string);\r\n+\tJSON_MAP_SET(m, param, string_list);\r\n+\tJSON_MAP_SET(m, action, string);\r\n+\r\n+\t_publish_message(\"request-url\", json_util::from_map(m));\r\n+\tstd::pair<bool, std::string> result = request_url_queue.dequeue();\r\n+\tsuccess = result.first;\r\n+\treturn result.second;\r\n+}\r\n+\r\n+bool octave_json_link::do_confirm_shutdown(void) {\r\n+\t// Triggered when the kernel tries to exit\r\n+\t_publish_message(\"confirm-shutdown\", json_util::empty());\r\n+\r\n+\treturn confirm_shutdown_queue.dequeue();\r\n+}\r\n+\r\n+// do_exit was removed in Octave 5\r\n+// bool octave_json_link::do_exit(int status) {\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, status, int);\r\n+// \t_publish_message(\"exit\", json_util::from_map(m));\r\n+\r\n+// \t// It is our responsibility in octave_link to call exit. If we don't, then\r\n+// \t// the kernel waits for 24 hours expecting us to do something.\r\n+// \t::exit(status);\r\n+\r\n+// \treturn true;\r\n+// }\r\n+\r\n+bool octave_json_link::do_copy_image_to_clipboard(const std::string& file) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"copy-image-to-clipboard\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::do_edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for existing files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::do_prompt_new_edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for new files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"prompt-new-edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn prompt_new_edit_file_queue.dequeue();\r\n+}\r\n+\r\n+// int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {\r\n+// \t// Triggered in \"msgbox\", \"helpdlg\", and \"errordlg\", among others\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\r\n+// \tJSON_MAP_SET(m, msg, string);\r\n+// \tJSON_MAP_SET(m, title, string);\r\n+// \t_publish_message(\"message-dialog\", json_util::from_map(m));\r\n+\r\n+// \treturn message_dialog_queue.dequeue();\r\n+// }\r\n+\r\n+std::string octave_json_link::do_question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n+\t// Triggered in \"questdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, msg, string);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, btn1, string);\r\n+\tJSON_MAP_SET(m, btn2, string);\r\n+\tJSON_MAP_SET(m, btn3, string);\r\n+\tJSON_MAP_SET(m, btndef, string);\r\n+\t_publish_message(\"question-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn question_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> octave_json_link::do_list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {\r\n+\t// Triggered in \"listdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, list, string_list);\r\n+\tJSON_MAP_SET(m, mode, string);\r\n+\tJSON_MAP_SET(m, width, int);\r\n+\tJSON_MAP_SET(m, height, int);\r\n+\tJSON_MAP_SET(m, initial_value, int_list);\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, ok_string, string);\r\n+\tJSON_MAP_SET(m, cancel_string, string);\r\n+\t_publish_message(\"list-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn list_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::do_input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {\r\n+\t// Triggered in \"inputdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, nr, float_list);\r\n+\tJSON_MAP_SET(m, nc, float_list);\r\n+\tJSON_MAP_SET(m, defaults, string_list);\r\n+\t_publish_message(\"input-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn input_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::do_file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {\r\n+\t// Triggered in \"uiputfile\", \"uigetfile\", and \"uigetdir\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, filter, filter_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, filename, string);\r\n+\tJSON_MAP_SET(m, pathname, string);\r\n+\tJSON_MAP_SET(m, multimode, string);\r\n+\t_publish_message(\"file-dialog\", json_util::from_map(m));\r\n+\t\r\n+\treturn file_dialog_queue.dequeue();\r\n+}\r\n+\r\n+int octave_json_link::do_debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\r\n+\t// This endpoint might be unused?  (No references)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\tJSON_MAP_SET(m, addpath_option, boolean);\r\n+\t_publish_message(\"debug-cd-or-addpath-error\", json_util::from_map(m));\r\n+\r\n+\treturn debug_cd_or_addpath_error_queue.dequeue();\r\n+}\r\n+\r\n+void octave_json_link::do_change_directory(const std::string& dir) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\t_publish_message(\"change-directory\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_file_remove (const std::string& old_name, const std::string& new_name) {\r\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, old_name, string);\r\n+\tJSON_MAP_SET(m, new_name, string);\r\n+\t_publish_message(\"file-remove\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_file_renamed (bool status) {\r\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n+\t_publish_message(\"file-renamed\", json_util::from_boolean(status));\r\n+}\r\n+\r\n+void octave_json_link::do_execute_command_in_terminal(const std::string& command) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, command, string);\r\n+\t_publish_message(\"execute-command-in-terminal\", json_util::from_map(m));\r\n+}\r\n+\r\n+uint8NDArray octave_json_link::do_get_named_icon (const std::string& /* icon_name */) {\r\n+\t// Called from msgbox.m\r\n+\t// TODO: Implement request/response for this event\r\n+\tuint8NDArray retval;\r\n+\treturn retval;\r\n+}\r\n+\r\n+void octave_json_link::do_set_workspace(bool top_level, bool debug,\r\n+                         const octave::symbol_info_list& ws,\r\n+                         bool update_variable_editor) {\r\n+\t// Triggered on every new line entry\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, top_level, boolean);\r\n+\tJSON_MAP_SET(m, debug, boolean);\r\n+\tJSON_MAP_SET(m, ws, symbol_info_list);\r\n+\tJSON_MAP_SET(m, update_variable_editor, boolean);\r\n+\t_publish_message(\"set-workspace\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_clear_workspace(void) {\r\n+\t// Triggered on \"clear\" command (but not \"clear all\" or \"clear foo\")\r\n+\t_publish_message(\"clear-workspace\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_set_history(const string_vector& hist) {\r\n+\t// Called at startup, possibly more?\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist, string_vector);\r\n+\t_publish_message(\"set-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_append_history(const std::string& hist_entry) {\r\n+\t// Appears to be tied to readline, if available\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist_entry, string);\r\n+\t_publish_message(\"append-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_clear_history(void) {\r\n+\t// Appears to be tied to readline, if available\r\n+\t_publish_message(\"clear-history\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_clear_screen(void) {\r\n+\t// Triggered by clc\r\n+\t_publish_message(\"clear-screen\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_pre_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::do_post_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::do_enter_debugger_event(const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"enter-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_execute_in_debugger_event(const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"execute-in-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_exit_debugger_event(void) {\r\n+\t_publish_message(\"exit-debugger-event\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::do_update_breakpoint(bool insert, const std::string& file, int line, const std::string& cond) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, insert, boolean);\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\tJSON_MAP_SET(m, cond, string);\r\n+\t_publish_message(\"update-breakpoint\", json_util::from_map(m));\r\n+}\r\n+\r\n+// void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {\r\n+// \t// Triggered upon interpreter startup\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, ps1, string);\r\n+// \tJSON_MAP_SET(m, ps2, string);\r\n+// \tJSON_MAP_SET(m, ps4, string);\r\n+// \t_publish_message(\"set-default-prompts\", json_util::from_map(m));\r\n+// }\r\n+\r\n+void octave_json_link::do_show_preferences(void) {\r\n+\t// Triggered on \"preferences\" command\r\n+\t_publish_message(\"show-preferences\", json_util::empty());\r\n+}\r\n+\r\n+std::string octave_json_link::do_gui_preference (const std::string& /* key */, const std::string& /* value */) {\r\n+\t// Used by Octave GUI?\r\n+\t// TODO: Implement request/response for this event\r\n+\tstd::string retval;\r\n+\treturn retval;\r\n+}\r\n+\r\n+void octave_json_link::do_show_doc(const std::string& file) {\r\n+\t// Triggered on \"doc\" command\r\n+\t_publish_message(\"show-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::do_register_doc (const std::string& file) {\r\n+\t// Triggered by the GUI documentation viewer?\r\n+\t_publish_message(\"register-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::do_unregister_doc (const std::string& file) {\r\n+\t// Triggered by the GUI documentation viewer?\r\n+\t_publish_message(\"unregister-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::do_edit_variable (const std::string& name, const octave_value& /* val */) {\r\n+\t// Triggered on \"openvar\" command\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\t// TODO: val\r\n+\t_publish_message(\"edit-variable\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::do_show_static_plot(const std::string& term, const std::string& content) {\r\n+\t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\r\n+\tint command_number = octave::command_editor::current_command_number();\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, term, string);\r\n+\tJSON_MAP_SET(m, content, string);\r\n+\tJSON_MAP_SET(m, command_number, int);\r\n+\t_publish_message(\"show-static-plot\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::receive_message (const std::string& name, JSON_OBJECT_T jobj) {\r\n+\tif (name == \"cmd\" || name == \"request-input-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\trequest_input_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"request-url-answer\") {\r\n+\t\tstd::pair<bool, std::string> answer = json_util::to_bool_string_pair(jobj);\r\n+\t\trequest_url_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"confirm-shutdown-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tconfirm_shutdown_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"prompt-new-edit-file-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tprompt_new_edit_file_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"message-dialog-answer\"){\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tmessage_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"question-dialog-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\tquestion_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"list-dialog-answer\") {\r\n+\t\tstd::pair<std::list<int>, int> answer = json_util::to_int_list_int_pair(jobj);\r\n+\t\tlist_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"input-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tinput_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"file-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tfile_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"debug-cd-or-addpath-error-answer\") {\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tdebug_cd_or_addpath_error_queue.enqueue(answer);\r\n+\t}\r\n+\telse {\r\n+\t\tstd::cerr << \"warning: received unknown message: \" << name << std::endl;\r\n+\t}\r\n+}\r\n+\r\n+void octave_json_link::_publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+\t_json_main->publish_message(name, jobj);\r\n+}\r\n+\r\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/octave-json-link.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.h\tThu Jul 02 18:08:38 2020 -0500\n@@ -0,0 +1,210 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifndef octave_json_link_h\r\n+#define octave_json_link_h\r\n+\r\n+#include <list>\r\n+#include <string>\r\n+\r\n+#include \"event-manager.h\"\r\n+#include \"json-util.h\"\r\n+#include \"oct-mutex.h\"\r\n+\r\n+// Circular reference\r\n+class json_main;\r\n+\r\n+// Thread-safe queue\r\n+template<typename T> class json_queue {\r\n+public:\r\n+  json_queue();\r\n+  ~json_queue();\r\n+\r\n+  void enqueue(const T& value);\r\n+  T dequeue();\r\n+  bool dequeue_to(T* destination);\r\n+\r\n+private:\r\n+  std::queue<T> _queue;\r\n+  octave::mutex _mutex;\r\n+};\r\n+\r\n+class octave_json_link : public interpreter_events\r\n+{\r\n+\r\n+public:\r\n+\r\n+  octave_json_link (json_main* __json_main);\r\n+\r\n+  ~octave_json_link (void);\r\n+\r\n+  std::string do_request_input (const std::string& prompt) override;\r\n+  std::string do_request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\r\n+\r\n+  bool do_confirm_shutdown (void) override;\r\n+\r\n+  bool do_copy_image_to_clipboard (const std::string& file) override;\r\n+\r\n+  bool do_edit_file (const std::string& file) override;\r\n+  bool do_prompt_new_edit_file (const std::string& file) override;\r\n+\r\n+  std::string\r\n+  do_question_dialog (const std::string& msg, const std::string& title,\r\n+                      const std::string& btn1, const std::string& btn2,\r\n+                      const std::string& btn3, const std::string& btndef) override;\r\n+\r\n+  std::pair<std::list<int>, int>\r\n+  do_list_dialog (const std::list<std::string>& list,\r\n+                  const std::string& mode,\r\n+                  int width, int height,\r\n+                  const std::list<int>& initial_value,\r\n+                  const std::string& name,\r\n+                  const std::list<std::string>& prompt,\r\n+                  const std::string& ok_string,\r\n+                  const std::string& cancel_string) override;\r\n+\r\n+  std::list<std::string>\r\n+  do_input_dialog (const std::list<std::string>& prompt,\r\n+                   const std::string& title,\r\n+                   const std::list<float>& nr,\r\n+                   const std::list<float>& nc,\r\n+                   const std::list<std::string>& defaults) override;\r\n+\r\n+  std::list<std::string>\r\n+  do_file_dialog (const filter_list& filter, const std::string& title,\r\n+                  const std::string &filename, const std::string &pathname,\r\n+                  const std::string& multimode) override;\r\n+\r\n+  int\r\n+  do_debug_cd_or_addpath_error (const std::string& file,\r\n+                                const std::string& dir,\r\n+                                bool addpath_option) override;\r\n+\r\n+  void do_change_directory (const std::string& dir) override;\r\n+\r\n+  void do_file_remove (const std::string& old_name, const std::string& new_name) override;\r\n+  void do_file_renamed (bool) override;\r\n+\r\n+  void do_execute_command_in_terminal (const std::string& command) override;\r\n+\r\n+  uint8NDArray do_get_named_icon (const std::string& icon_name) override;\r\n+\r\n+  void do_set_workspace (bool top_level, bool debug,\r\n+                         const octave::symbol_info_list& ws,\r\n+                         bool update_variable_editor) override;\r\n+\r\n+  void do_clear_workspace (void) override;\r\n+\r\n+  void do_set_history (const string_vector& hist) override;\r\n+  void do_append_history (const std::string& hist_entry) override;\r\n+  void do_clear_history (void) override;\r\n+\r\n+  void do_clear_screen (void) override;\r\n+\r\n+  void do_pre_input_event (void) override;\r\n+  void do_post_input_event (void) override;\r\n+\r\n+  void do_enter_debugger_event (const std::string& file, int line) override;\r\n+  void do_execute_in_debugger_event (const std::string& file, int line) override;\r\n+  void do_exit_debugger_event (void) override;\r\n+\r\n+  void do_update_breakpoint (bool insert,\r\n+                             const std::string& file, int line,\r\n+                             const std::string& cond) override;\r\n+\r\n+  void do_show_preferences (void) override;\r\n+\r\n+  std::string do_gui_preference (const std::string& key, const std::string& value) override;\r\n+\r\n+  void do_show_doc (const std::string& file) override;\r\n+\r\n+  void do_register_doc (const std::string& file) override;\r\n+\r\n+  void do_unregister_doc (const std::string& file) override;\r\n+\r\n+  void do_edit_variable (const std::string& name, const octave_value& val) override;\r\n+\r\n+  void do_show_static_plot (const std::string& term,\r\n+                            const std::string& content) override;\r\n+\r\n+  // Custom methods\r\n+  void receive_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+  json_main* _json_main;\r\n+  void _publish_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+  // Queues\r\n+  json_queue<std::string> request_input_queue;\r\n+  json_queue<std::pair<bool, std::string> > request_url_queue;\r\n+  json_queue<bool> confirm_shutdown_queue;\r\n+  json_queue<bool> prompt_new_edit_file_queue;\r\n+  json_queue<int> message_dialog_queue;\r\n+  json_queue<std::string> question_dialog_queue;\r\n+  json_queue<std::pair<std::list<int>, int> > list_dialog_queue;\r\n+  json_queue<std::list<std::string> > input_dialog_queue;\r\n+  json_queue<std::list<std::string> > file_dialog_queue;\r\n+  json_queue<int> debug_cd_or_addpath_error_queue;\r\n+};\r\n+\r\n+// Template classes require definitions in the header file...\r\n+\r\n+template<typename T>\r\n+json_queue<T>::json_queue() { }\r\n+\r\n+template<typename T>\r\n+json_queue<T>::~json_queue() { }\r\n+\r\n+template<typename T>\r\n+void json_queue<T>::enqueue(const T& value) {\r\n+  _mutex.lock();\r\n+  _queue.push(value);\r\n+  _mutex.cond_signal();\r\n+  _mutex.unlock();\r\n+}\r\n+\r\n+template<typename T>\r\n+T json_queue<T>::dequeue() {\r\n+  _mutex.lock();\r\n+  while (_queue.empty()) {\r\n+    _mutex.cond_wait();\r\n+  }\r\n+  T value = _queue.front();\r\n+  _queue.pop();\r\n+  _mutex.unlock();\r\n+  return value;\r\n+}\r\n+\r\n+template<typename T>\r\n+bool json_queue<T>::dequeue_to(T* destination) {\r\n+  _mutex.lock();\r\n+  bool retval = false;\r\n+  if (!_queue.empty()) {\r\n+    retval = true;\r\n+    *destination = _queue.front();\r\n+    _queue.pop();\r\n+  }\r\n+  _mutex.unlock();\r\n+  return retval;\r\n+}\r\n+\r\n+#endif\r\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/syscalls.cc\n--- a/libinterp/corefcn/syscalls.cc\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libinterp/corefcn/syscalls.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -149,9 +149,8 @@\n @deftypefn {} {[@var{err}, @var{msg}] =} exec (@var{file}, @var{args})\n Replace current process with a new process.\n \n-Calling @code{exec} without first calling @code{fork} will terminate your\n-current Octave process and replace it with the program named by @var{file}.\n-For example,\n+Calling @code{exec} will terminate your current Octave process and replace\n+it with the program named by @var{file}. For example,\n \n @example\n exec (\"ls\", \"-l\")\n@@ -461,42 +460,6 @@\n   return ovl (status, msg);\n }\n \n-DEFMETHODX (\"fork\", Ffork, interp, args, ,\n-            doc: /* -*- texinfo -*-\n-@deftypefn {} {[@var{pid}, @var{msg}] =} fork ()\n-Create a copy of the current process.\n-\n-Fork can return one of the following values:\n-\n-@table @asis\n-@item > 0\n-You are in the parent process.  The value returned from @code{fork} is the\n-process id of the child process.  You should probably arrange to wait for\n-any child processes to exit.\n-\n-@item 0\n-You are in the child process.  You can call @code{exec} to start another\n-process.  If that fails, you should probably call @code{exit}.\n-\n-@item < 0\n-The call to @code{fork} failed for some reason.  You must take evasive\n-action.  A system dependent error message will be waiting in @var{msg}.\n-@end table\n-@end deftypefn */)\n-{\n-  if (args.length () != 0)\n-    print_usage ();\n-\n-  if (interp.at_top_level ())\n-    error (\"fork: cannot be called from command line\");\n-\n-  std::string msg;\n-\n-  pid_t pid = octave::sys::fork (msg);\n-\n-  return ovl (pid, msg);\n-}\n-\n DEFUNX (\"getpgrp\", Fgetpgrp, args, ,\n         doc: /* -*- texinfo -*-\n @deftypefn {} {pgid =} getpgrp ()\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/sysdep.cc\n--- a/libinterp/corefcn/sysdep.cc\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libinterp/corefcn/sysdep.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -75,6 +75,7 @@\n #include \"defun.h\"\n #include \"error.h\"\n #include \"errwarn.h\"\n+#include \"event-manager.h\"\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"octave.h\"\n@@ -651,7 +652,7 @@\n \n   // Read one character from the terminal.\n \n-  int kbhit (bool wait)\n+  int kbhit (const std::string& prompt, bool wait)\n   {\n #if defined (HAVE__KBHIT) && defined (HAVE__GETCH)\n     // This essentially means we are on a Windows system.\n@@ -678,13 +679,24 @@\n \n     set_interrupt_handler (saved_interrupt_handler, false);\n \n-    int c = std::cin.get ();\n+    int c;\n+    event_manager& evmgr = __get_event_manager__ (\"kbhit\");\n+    if (evmgr.request_input_enabled ()) {\n+      std::string line = evmgr.request_input (prompt);\n+      if (line.length() >= 1) {\n+        c = line.at(0);\n+      } else {\n+        c = '\\n';\n+      }\n+    } else {\n+      c = std::cin.get ();\n \n-    if (std::cin.fail () || std::cin.eof ())\n-      {\n-        std::cin.clear ();\n-        clearerr (stdin);\n-      }\n+      if (std::cin.fail () || std::cin.eof ())\n+        {\n+          std::cin.clear ();\n+          clearerr (stdin);\n+        }\n+    }\n \n     // Restore it, enabling system call restarts (if possible).\n     set_interrupt_handler (saved_interrupt_handler, true);\n@@ -743,6 +755,8 @@\n {\n   bool skip_redisplay = true;\n \n+  octave_link::clear_screen ();\n+\n   octave::command_editor::clear_screen (skip_redisplay);\n \n   return ovl ();\n@@ -1169,7 +1183,7 @@\n \n   Fdrawnow (interp);\n \n-  int c = octave::kbhit (args.length () == 0);\n+  int c = octave::kbhit (\"kbhit>\", args.length () == 0);\n \n   if (c == -1)\n     c = 0;\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/sysdep.h\n--- a/libinterp/corefcn/sysdep.h\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libinterp/corefcn/sysdep.h\tThu Jul 02 18:08:38 2020 -0500\n@@ -49,7 +49,7 @@\n \n   extern OCTINTERP_API int pclose (FILE *f);\n \n-  extern OCTINTERP_API int kbhit (bool wait = true);\n+  extern OCTINTERP_API int kbhit (const std::string& prompt, bool wait);\n \n   extern OCTINTERP_API std::string get_P_tmpdir (void);\n \n@@ -107,7 +107,7 @@\n inline int\n octave_kbhit (bool wait = true)\n {\n-  return octave::kbhit (wait);\n+  return octave::kbhit (\"\", wait);\n }\n \n OCTAVE_DEPRECATED (5, \"use 'octave::get_P_tmpdir' instead\")\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/corefcn/utils.cc\n--- a/libinterp/corefcn/utils.cc\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libinterp/corefcn/utils.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -1442,7 +1442,7 @@\n             if (do_graphics_events)\n               gh_mgr.process_events ();\n \n-            c = kbhit (false);\n+            c = kbhit (\"press enter to continue\", false);\n           }\n       }\n     else\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/octave.cc\n--- a/libinterp/octave.cc\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libinterp/octave.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -187,6 +187,16 @@\n           case LINE_EDITING_OPTION:\n             m_forced_line_editing = m_line_editing = true;\n             break;\n+ \n+          case JSON_SOCK_OPTION:\n+            if (octave_optarg_wrapper ())\n+              m_json_sock_path = octave_optarg_wrapper ();\n+            break;\n+\n+          case JSON_MAX_LEN_OPTION:\n+            if (octave_optarg_wrapper ())\n+              m_json_max_message_length = strtol(octave_optarg_wrapper (), NULL, 10);\n+            break;\n \n           case NO_GUI_OPTION:\n             m_gui = false;\n@@ -397,7 +407,7 @@\n   // FIXME: This isn't quite right, it just says that we intended to\n   // start the GUI, not that it is actually running.\n \n-  return ovl (octave::application::is_gui_running ());\n+  return ovl (octave::application::is_link_enabled ());\n }\n \n /*\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/octave.h\n--- a/libinterp/octave.h\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libinterp/octave.h\tThu Jul 02 18:08:38 2020 -0500\n@@ -79,6 +79,8 @@\n     std::string info_file (void) const { return m_info_file; }\n     std::string info_program (void) const { return m_info_program; }\n     std::string texi_macros_file (void) const {return m_texi_macros_file; }\n+    std::string json_sock_path (void) const { return m_json_sock_path; }\n+    int json_max_message_length (void) const { return m_json_max_message_length; }\n     string_vector all_args (void) const { return m_all_args; }\n     string_vector remaining_args (void) const { return m_remaining_args; }\n \n@@ -109,6 +111,8 @@\n     void info_file (const std::string& arg) { m_info_file = arg; }\n     void info_program (const std::string& arg) { m_info_program = arg; }\n     void texi_macros_file (const std::string& arg) { m_texi_macros_file = arg; }\n+    void json_sock_path (const std::string& arg) { m_json_sock_path = arg; }\n+    void json_max_message_length (int arg) { m_json_max_message_length = arg; }\n     void all_args (const string_vector& arg) { m_all_args = arg; }\n     void remaining_args (const string_vector& arg) { m_remaining_args = arg; }\n \n@@ -215,6 +219,14 @@\n     // (--texi-macros-file)\n     std::string m_texi_macros_file;\n \n+    // The value for \"JSON_SOCK\" specified on the command line.\n+    // (--json-sock)\n+    std::string m_json_sock_path;\n+\n+    // The maximum message length; valid only if \"JSON_SOCK\" is specified.\n+    // (--json-max-len)\n+    int m_json_max_message_length = 0;\n+\n     // All arguments passed to the argc, argv constructor.\n     string_vector m_all_args;\n \n@@ -305,6 +317,14 @@\n       return instance ? instance->gui_running () : false;\n     }\n \n+    static bool is_link_enabled (void)\n+    {\n+      if (instance && instance->m_interpreter) {\n+        event_manager& evmgr = instance->m_interpreter->get_event_manager ();\n+        return evmgr.enabled();\n+      } else return false;\n+    }\n+\n     // Convenience functions.\n \n     static bool forced_interactive (void);\ndiff -r 9e7b2625e574 -r d78448f9c483 libinterp/options-usage.h\n--- a/libinterp/options-usage.h\tFri Jun 26 18:44:35 2020 +0200\n+++ b/libinterp/options-usage.h\tThu Jul 02 18:08:38 2020 -0500\n@@ -38,10 +38,10 @@\n        [--echo-commands] [--eval CODE] [--exec-path path]\\n\\\n        [--gui] [--help] [--image-path path]\\n\\\n        [--info-file file] [--info-program prog] [--interactive]\\n\\\n-       [--jit-compiler] [--line-editing] [--no-gui] [--no-history]\\n\\\n-       [--no-init-file] [--no-init-path] [--no-line-editing]\\n\\\n-       [--no-site-file] [--no-window-system] [--norc] [-p path]\\n\\\n-       [--path path] [--persist] [--silent] [--traditional]\\n\\\n+       [--jit-compiler] [--json-sock] [--json-max-len] [--line-editing]\\n\\\n+       [--no-gui] [--no-history][--no-init-file] [--no-init-path]\\n\\\n+       [--no-line-editing] [--no-site-file] [--no-window-system] [--norc]\\n\\\n+       [-p path] [--path path] [--persist] [--silent] [--traditional]\\n\\\n        [--verbose] [--version] [file]\";\n \n // This is here so that it's more likely that the usage message and\n@@ -68,15 +68,17 @@\n #define INFO_PROG_OPTION 8\n #define DEBUG_JIT_OPTION 9\n #define JIT_COMPILER_OPTION 10\n-#define LINE_EDITING_OPTION 11\n-#define NO_GUI_OPTION 12\n-#define NO_INIT_FILE_OPTION 13\n-#define NO_INIT_PATH_OPTION 14\n-#define NO_LINE_EDITING_OPTION 15\n-#define NO_SITE_FILE_OPTION 16\n-#define PERSIST_OPTION 17\n-#define TEXI_MACROS_FILE_OPTION 18\n-#define TRADITIONAL_OPTION 19\n+#define JSON_SOCK_OPTION 11\n+#define JSON_MAX_LEN_OPTION 12\n+#define LINE_EDITING_OPTION 13\n+#define NO_GUI_OPTION 14\n+#define NO_INIT_FILE_OPTION 15\n+#define NO_INIT_PATH_OPTION 16\n+#define NO_LINE_EDITING_OPTION 17\n+#define NO_SITE_FILE_OPTION 18\n+#define PERSIST_OPTION 19\n+#define TEXI_MACROS_FILE_OPTION 20\n+#define TRADITIONAL_OPTION 21\n struct octave_getopt_options long_opts[] =\n {\n   { \"braindead\",                octave_no_arg,       0, TRADITIONAL_OPTION },\n@@ -94,6 +96,8 @@\n   { \"info-program\",             octave_required_arg, 0, INFO_PROG_OPTION },\n   { \"interactive\",              octave_no_arg,       0, 'i' },\n   { \"jit-compiler\",             octave_no_arg,       0, JIT_COMPILER_OPTION },\n+  { \"json-sock\",                octave_required_arg, 0, JSON_SOCK_OPTION },\n+  { \"json-max-len\",             octave_required_arg, 0, JSON_MAX_LEN_OPTION },\n   { \"line-editing\",             octave_no_arg,       0, LINE_EDITING_OPTION },\n   { \"no-gui\",                   octave_no_arg,       0, NO_GUI_OPTION },\n   { \"no-history\",               octave_no_arg,       0, 'H' },\n@@ -145,6 +149,8 @@\n   --info-program PROGRAM  Use PROGRAM for reading info files.\\n\\\n   --interactive, -i       Force interactive behavior.\\n\\\n   --jit-compiler          Enable the JIT compiler.\\n\\\n+  --json-sock PATH        Listen to and publish events on this UNIX socket.\\n\\\n+  --json-max-len LEN      Suppress JSON messages greater than LEN bytes.\\n\\\n   --line-editing          Force readline use for command-line editing.\\n\\\n   --no-gui                Disable the graphical user interface.\\n\\\n   --no-history, -H        Don't save commands to the history list\\n\\\ndiff -r 9e7b2625e574 -r d78448f9c483 liboctave/util/oct-mutex.cc\n--- a/liboctave/util/oct-mutex.cc\tFri Jun 26 18:44:35 2020 +0200\n+++ b/liboctave/util/oct-mutex.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -58,6 +58,18 @@\n     return false;\n   }\n \n+  void\n+  base_mutex::cond_wait (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n+  void\n+  base_mutex::cond_signal (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n #if defined (OCTAVE_USE_WINDOWS_API)\n \n   class\n@@ -68,11 +80,13 @@\n       : base_mutex ()\n     {\n       InitializeCriticalSection (&cs);\n+      InitializeConditionVariable (&cv);\n     }\n \n     ~w32_mutex (void)\n     {\n       DeleteCriticalSection (&cs);\n+      // no need to delete cv: http://stackoverflow.com/a/28981408/1407170\n     }\n \n     void lock (void)\n@@ -90,8 +104,19 @@\n       return (TryEnterCriticalSection (&cs) != 0);\n     }\n \n+    void cond_wait (void)\n+    {\n+      SleepConditionVariableCS (&cv, &cs, INFINITE);\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      WakeConditionVariable (&cv);\n+    }\n+\n   private:\n     CRITICAL_SECTION cs;\n+    CONDITION_VARIABLE cv;\n   };\n \n   static DWORD thread_id = 0;\n@@ -123,11 +148,19 @@\n       pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);\n       pthread_mutex_init (&m_pm, &attr);\n       pthread_mutexattr_destroy (&attr);\n+\n+      pthread_condattr_t condattr;\n+\n+      pthread_condattr_init (&condattr);\n+      pthread_cond_init (&condv, &condattr);\n+      pthread_condattr_destroy (&condattr);\n     }\n \n     ~pthread_mutex (void)\n     {\n       pthread_mutex_destroy (&m_pm);\n+      pthread_mutex_destroy (&m_pm);\n+      pthread_cond_destroy (&condv);\n     }\n \n     void lock (void)\n@@ -145,8 +178,19 @@\n       return (pthread_mutex_trylock (&m_pm) == 0);\n     }\n \n+    void cond_wait (void)\n+    {\n+      pthread_cond_wait (&condv, &pm);\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      pthread_cond_signal (&condv);\n+    }\n+\n   private:\n     pthread_mutex_t m_pm;\n+    pthread_cond_t condv;\n   };\n \n   static pthread_t thread_id = 0;\ndiff -r 9e7b2625e574 -r d78448f9c483 liboctave/util/oct-mutex.h\n--- a/liboctave/util/oct-mutex.h\tFri Jun 26 18:44:35 2020 +0200\n+++ b/liboctave/util/oct-mutex.h\tThu Jul 02 18:08:38 2020 -0500\n@@ -50,6 +50,10 @@\n \n     virtual bool try_lock (void);\n \n+    virtual void cond_wait (void);\n+\n+    virtual void cond_signal (void);\n+\n   private:\n     refcount<octave_idx_type> m_count;\n   };\n@@ -102,6 +106,16 @@\n       return m_rep->try_lock ();\n     }\n \n+    void cond_wait (void)\n+    {\n+      rep->cond_wait ();\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      rep->cond_signal ();\n+    }\n+\n   protected:\n     base_mutex *m_rep;\n   };\ndiff -r 9e7b2625e574 -r d78448f9c483 liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tFri Jun 26 18:44:35 2020 +0200\n+++ b/liboctave/util/url-transfer.cc\tThu Jul 02 18:08:38 2020 -0500\n@@ -36,6 +36,8 @@\n #include \"file-stat.h\"\n #include \"lo-sysdep.h\"\n #include \"oct-env.h\"\n+#include \"libinterp/corefcn/interpreter-private.h\"\n+#include \"gnulib/lib/base64.h\"\n #include \"unwind-prot.h\"\n #include \"url-transfer.h\"\n #include \"version.h\"\n@@ -240,6 +242,87 @@\n     return file_list;\n   }\n \n+\n+class link_transfer : public base_url_transfer\n+{\n+public:\n+\n+  link_transfer (void)\n+      : base_url_transfer () {\n+    valid = true;\n+  }\n+\n+  link_transfer (const std::string& host, const std::string& user_arg,\n+                 const std::string& passwd, std::ostream& os)\n+      : base_url_transfer (host, user_arg, passwd, os) {\n+    valid = true;\n+    // url = \"ftp://\" + host;\n+  }\n+\n+  link_transfer (const std::string& url_str, std::ostream& os)\n+      : base_url_transfer (url_str, os) {\n+    valid = true;\n+  }\n+\n+  ~link_transfer (void) {}\n+\n+  void http_get (const Array<std::string>& param) {\n+    perform (param, \"get\");\n+  }\n+\n+  void http_post (const Array<std::string>& param) {\n+    perform (param, \"post\");\n+  }\n+\n+  void http_action (const Array<std::string>& param, const std::string& action) {\n+    perform (param, action);\n+  }\n+\n+private:\n+  void perform(const Array<std::string>& param, const std::string& action) {\n+    std::string url = host_or_url;\n+\n+    // Convert from Array to std::list\n+    std::list<std::string> paramList;\n+    for (int i = 0; i < param.numel(); i ++) {\n+      std::string value = param(i);\n+      paramList.push_back(value);\n+    }\n+\n+    event_manager& evmgr = __get_event_manager__ (\"link_transfer\");\n+    if (evmgr.request_input_enabled ()) {\n+      bool success;\n+      std::string result = evmgr.request_url (url, paramList, action, success);\n+      if (success) {\n+        process_success(result);\n+      } else {\n+        ok = false;\n+        errmsg = result;\n+      }\n+    } else {\n+      ok = false;\n+      errmsg = \"octave_link not connected for link_transfer\";\n+    }\n+  }\n+\n+  void process_success(const std::string& result) {\n+    // If success, the result is returned as a base64 string, and we need to decode it.\n+    // Use the base64 implementation from gnulib, which is already an Octave dependency.\n+    const char *inc = &(result[0]);\n+    char *out;\n+    size_t outlen;\n+    bool b64_ok = base64_decode_alloc(inc, result.length(), &out, &outlen);\n+    if (!b64_ok) {\n+      ok = false;\n+      errmsg = \"failed decoding base64 from octave_link\";\n+    } else {\n+      curr_ostream->write(out, outlen);\n+      ::free(out);\n+    }\n+  }\n+};\n+\n+\n #if defined (HAVE_CURL)\n \n   static int\n@@ -941,17 +1024,33 @@\n #  define REP_CLASS base_url_transfer\n #endif\n \n-  url_transfer::url_transfer (void) : m_rep (new REP_CLASS ())\n-  { }\n+  url_transfer::url_transfer (void) {\n+    event_manager& evmgr = __get_event_manager__ (\"url_transfer\");\n+    if (evmgr.request_input_enabled()) {\n+      m_rep.reset(new link_transfer());\n+    } else {\n+      m_rep.reset(new REP_CLASS());\n+    }\n+  }\n \n   url_transfer::url_transfer (const std::string& host, const std::string& user,\n-                              const std::string& passwd, std::ostream& os)\n-    : m_rep (new REP_CLASS (host, user, passwd, os))\n-  { }\n+                              const std::string& passwd, std::ostream& os) {\n+    event_manager& evmgr = __get_event_manager__ (\"url_transfer\");\n+    if (evmgr.request_input_enabled()) {\n+      m_rep.reset(new link_transfer(host, user, passwd, os));\n+    } else {\n+      m_rep.reset(new REP_CLASS(host, user, passwd, os));\n+    }\n+  }\n \n-  url_transfer::url_transfer (const std::string& url, std::ostream& os)\n-    : m_rep (new REP_CLASS (url, os))\n-  { }\n+  url_transfer::url_transfer (const std::string& url, std::ostream& os) {\n+    event_manager& evmgr = __get_event_manager__ (\"url_transfer\");\n+    if (evmgr.request_input_enabled()) {\n+      m_rep.reset(new link_transfer(url, os));\n+    } else {\n+      m_rep.reset(new REP_CLASS(url, os));\n+    }\n+  }\n \n #undef REP_CLASS\n \ndiff -r 9e7b2625e574 -r d78448f9c483 scripts/help/__unimplemented__.m\n--- a/scripts/help/__unimplemented__.m\tFri Jun 26 18:44:35 2020 +0200\n+++ b/scripts/help/__unimplemented__.m\tThu Jul 02 18:08:38 2020 -0500\n@@ -45,7 +45,30 @@\n \n   is_matlab_function = true;\n \n+  ## First look at the package metadata\n+  # To generate package_metadata.mat, run: packages={}; for p=pkg('list'); packages={packages{:} pkg('describe', '-verbose', p{1}.name){:}}; endfor; save('/usr/local/share/octave/site/m/package_metadata.mat', 'packages');\n+  found_in_package_metadata = false;\n+  try\n+    vars = load(\"/usr/local/share/octave/site/m/package_metadata.mat\");\n+    for lvl1 = vars.packages\n+      for lvl2 = lvl1{1}.provides\n+        for lvl3 = lvl2{1}.functions\n+          if strcmp(fcn, lvl3{1})\n+            txt = check_package(fcn, lvl1{1}.name);\n+            found_in_package_metadata = true;\n+            break;\n+          endif\n+        endfor\n+        if found_in_package_metadata, break; endif\n+      endfor\n+      if found_in_package_metadata, break; endif\n+    endfor\n+  catch err\n+    warning(err)\n+  end_try_catch\n+\n   ## Some smarter cases, add more as needed.\n+  if !found_in_package_metadata\n   switch (fcn)\n     case {\"avifile\", \"aviinfo\", \"aviread\"}\n       txt = [\"Basic video file support is provided in the video package.  \", ...\n@@ -524,6 +547,7 @@\n         txt = \"\";\n       endif\n   endswitch\n+  endif\n \n   if (is_matlab_function)\n     txt = [txt, \"\\n\\n@noindent\\nPlease read \", ...\n@@ -566,13 +590,13 @@\n       endfor\n       txt = sprintf (\"%s but has not yet been implemented.\", txt);\n     case \"not loaded\",\n-      txt = sprintf ([\"%s which you have installed but not loaded.  To \", ...\n-                      \"load the package, run 'pkg load %s' from the \", ...\n-                      \"Octave prompt.\"], txt, name);\n+      txt = sprintf ([\"%s, which you have installed but not loaded.\\n\\n\", ...\n+                      \"Run `pkg load %s' to use `%s'.\"], ...\n+                     txt, name, fcn);\n     otherwise\n       ## this includes \"not installed\" and anything else if pkg changes\n       ## the output of describe\n-      txt = sprintf (\"%s which seems to not be installed in your system.\", txt);\n+      txt = sprintf (\"%s, which seems to not be installed in your system.\", txt);\n   endswitch\n \n endfunction\ndiff -r 9e7b2625e574 -r d78448f9c483 scripts/plot/util/__gnuplot_drawnow__.m\n--- a/scripts/plot/util/__gnuplot_drawnow__.m\tFri Jun 26 18:44:35 2020 +0200\n+++ b/scripts/plot/util/__gnuplot_drawnow__.m\tThu Jul 02 18:08:38 2020 -0500\n@@ -32,9 +32,84 @@\n \n   if (nargin < 1 || nargin > 4 || nargin == 2)\n     print_usage ();\n+\n+  elseif (nargin >= 3 && nargin <= 4)\n+    ## Write the plot to the given file (e.g., via the \"print\" command)\n+    if (nargin == 5)\n+      __gnuplot_draw_to_file__ (h, term, file, debug_file);\n+    else\n+      __gnuplot_draw_to_file__ (h, term, file);\n+    endif\n+\n+  else  # nargin == 1\n+    ##  Plot to terminal and/or static (e.g., via the \"plot\" command)\n+    plot_stream = get (h, \"__plot_stream__\");\n+    if (isempty (plot_stream))\n+      plot_stream = __gnuplot_open_stream__ (2, h);\n+      new_stream = true;\n+    else\n+      new_stream = false;\n+    endif\n+    term = gnuplot_default_term (plot_stream);\n+\n+    ## There are a few options for how we can proceed.\n+    ## In most cases, we will tell GNUPLOT to put the plot in its terminal.\n+    ## If we have no display, we want to use the \"dumb\" terminal.\n+    ## Octave Link may request that we send the plot as an event.\n+    ## The latter two cases require plotting to a temp file.\n+\n+    should_plot_to_terminal = (\n+      !strcmp (term, \"dumb\") && (\n+        __octave_link_plot_destination__ () == 0 ||\n+        __octave_link_plot_destination__ () == 2\n+      )\n+    );\n+\n+    if (should_plot_to_terminal)\n+      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n+      __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n+      fflush (plot_stream(1));\n+    endif\n+\n+    should_plot_to_temp_file = (\n+      strcmp (term, \"dumb\") ||\n+      __octave_link_plot_destination__ () == 1 ||\n+      __octave_link_plot_destination__ () == 2\n+    );\n+\n+    if (should_plot_to_temp_file)\n+      tmp_file = tempname ();\n+      __gnuplot_draw_to_file__ (h, term, tmp_file);\n+      fflush (plot_stream(1));\n+\n+      ## Read the temp file into memory and then delete it\n+      fid = fopen (tmp_file, 'r');\n+      while (fid < 0)\n+        fprintf (stderr, \"🛈 Waiting for plot to finish… ⏳\\n\");\n+        pause (0.5);\n+        fid = fopen (tmp_file, 'r');\n+      endwhile\n+      [a, count] = fscanf (fid, '%c', Inf);\n+      fclose (fid);\n+      unlink (tmp_file);\n+\n+      ## What to do with the plot data?\n+      if (count > 0)\n+        if (a(1) == 12)\n+          a = a(2:end);  # avoid ^L at the beginning\n+        endif\n+        if strcmp (term, \"dumb\")\n+          puts (a);\n+        else\n+          __octave_link_show_static_plot__ (term, a);\n+        endif\n+      endif\n+    endif\n+\n   endif\n+endfunction\n \n-  if (nargin >= 3 && nargin <= 4)\n+function __gnuplot_draw_to_file__ (h, term, file, debug_file)\n     ## Produce various output formats, or redirect gnuplot stream to a\n     ## debug file.\n     plot_stream = [];\n@@ -70,44 +145,6 @@\n         fclose (fid);\n       endif\n     end_unwind_protect\n-  else  # nargin == 1\n-    ##  Graphics terminal for display.\n-    plot_stream = get (h, \"__plot_stream__\");\n-    if (isempty (plot_stream))\n-      plot_stream = __gnuplot_open_stream__ (2, h);\n-      new_stream = true;\n-    else\n-      new_stream = false;\n-    endif\n-    term = gnuplot_default_term (plot_stream);\n-    if (strcmp (term, \"dumb\"))\n-      ## popen2 eats stdout of gnuplot, use temporary file instead\n-      dumb_tmp_file = tempname ();\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h,\n-                                   term, dumb_tmp_file);\n-    else\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n-    endif\n-    __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n-    fflush (plot_stream(1));\n-    if (strcmp (term, \"dumb\"))\n-      fid = -1;\n-      while (fid < 0)\n-        pause (0.1);\n-        fid = fopen (dumb_tmp_file, 'r');\n-      endwhile\n-      ## reprint the plot on screen\n-      [a, count] = fscanf (fid, '%c', Inf);\n-      fclose (fid);\n-      if (count > 0)\n-        if (a(1) == 12)\n-          a = a(2:end);  # avoid ^L at the beginning\n-        endif\n-        puts (a);\n-      endif\n-      unlink (dumb_tmp_file);\n-    endif\n-  endif\n \n endfunction\n \n"
  },
  {
    "path": "back-octave/oo-changesets/301-97f7d1f4fe83.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1593748718 0\n#      Fri Jul 03 03:58:38 2020 +0000\n# Branch oo-5.2a\n# Node ID 97f7d1f4fe83bd6ebe102eb4cc6268ae23ad8d52\n# Parent  d78448f9c48344a58f5f83d5d52c29bf1ad92cb7\nFixing compiler errors\n\ndiff -r d78448f9c483 -r 97f7d1f4fe83 libinterp/corefcn/event-manager.cc\n--- a/libinterp/corefcn/event-manager.cc\tThu Jul 02 18:08:38 2020 -0500\n+++ b/libinterp/corefcn/event-manager.cc\tFri Jul 03 03:58:38 2020 +0000\n@@ -41,6 +41,17 @@\n \n namespace octave\n {\n+\n+  bool __event_manager_request_input_enabled__() {\n+    event_manager& evmgr = __get_event_manager__ (\"request_input_enabled\");\n+    return evmgr.request_input_enabled();\n+  }\n+\n+  std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\n+    event_manager& evmgr = __get_event_manager__ (\"request_url\");\n+    return evmgr.request_url(url, param, action, success);\n+  }\n+\n   static int readline_event_hook (void)\n   {\n     event_manager& evmgr = __get_event_manager__ (\"octave_readline_hook\");\n@@ -653,18 +664,18 @@\n   return ovl ();\n }\n \n-DEFUN (__octave_link_plot_destination__, , ,\n-       doc: /* -*- texinfo -*-\n-@deftypefn {} {} __octave_link_plot_destination__ ()\n+DEFMETHOD (__event_manager_plot_destination__, interp, , ,\n+           doc: /* -*- texinfo -*-\n+@deftypefn {} {} __event_manager_plot_destination__ ()\n Undocumented internal function.\n @end deftypefn*/)\n {\n-  return ovl (octave_link::plot_destination ());\n+  return ovl (interp.get_event_manager().plot_destination());\n }\n \n-DEFUN (__octave_link_show_static_plot__, args, ,\n-       doc: /* -*- texinfo -*-\n-@deftypefn {} {} __octave_link_show_static_plot__ (@var{term}, @var{content})\n+DEFMETHOD (__event_manager_show_static_plot__, interp, args, ,\n+           doc: /* -*- texinfo -*-\n+@deftypefn {} {} __event_manager_show_static_plot__ (@var{term}, @var{content})\n Undocumented internal function.\n @end deftypefn*/)\n {\n@@ -674,6 +685,6 @@\n \n   std::string term = args(0).string_value();\n   std::string content = args(1).string_value();\n-  return ovl (octave_link::show_static_plot (term, content));\n+  return ovl (interp.get_event_manager().show_static_plot(term, content));\n }\n \ndiff -r d78448f9c483 -r 97f7d1f4fe83 libinterp/corefcn/event-manager.h\n--- a/libinterp/corefcn/event-manager.h\tThu Jul 02 18:08:38 2020 -0500\n+++ b/libinterp/corefcn/event-manager.h\tFri Jul 03 03:58:38 2020 +0000\n@@ -48,6 +48,12 @@\n \n   class symbol_info_list;\n \n+  enum plot_destination_t {\n+    TERMINAL_ONLY = 0,\n+    STATIC_ONLY = 1,\n+    TERMINAL_AND_STATIC = 2\n+  };\n+\n   // The methods in this class provide a way to pass signals to the GUI\n   // thread.  A GUI that wishes to act on these events should derive\n   // from this class and perform actions in a thread-safe way.  In\n@@ -387,12 +393,6 @@\n       return enabled () ? instance->_request_input_enabled : false;\n     }\n \n-    enum plot_destination_t {\n-      TERMINAL_ONLY = 0,\n-      STATIC_ONLY = 1,\n-      TERMINAL_AND_STATIC = 2\n-    };\n-\n     plot_destination_t plot_destination (void)\n     {\n       return enabled () ? instance->_plot_destination : TERMINAL_ONLY;\ndiff -r d78448f9c483 -r 97f7d1f4fe83 libinterp/corefcn/json-main.cc\n--- a/libinterp/corefcn/json-main.cc\tThu Jul 02 18:08:38 2020 -0500\n+++ b/libinterp/corefcn/json-main.cc\tFri Jul 03 03:58:38 2020 +0000\n@@ -3,6 +3,7 @@\n #endif\r\n \r\n #include \"json-main.h\"\r\n+#include \"interpreter.h\"\r\n \r\n #include <iostream>\r\n #include <sys/un.h>\r\n@@ -13,6 +14,8 @@\n // Analog of main-window.cc\r\n // TODO: Think more about concurrency and null pointer exceptions\r\n \r\n+namespace octave {\r\n+\r\n void* run_loop_pthread(void* arg) {\r\n   json_main* _json_main = static_cast<json_main*>(arg);\r\n   _json_main->run_loop();\r\n@@ -94,3 +97,5 @@\n void json_main::process_json_object(std::string name, JSON_OBJECT_T jobj) {\r\n   _octave_json_link->receive_message(name, jobj);\r\n }\r\n+\r\n+} // namespace octave\r\ndiff -r d78448f9c483 -r 97f7d1f4fe83 libinterp/corefcn/json-main.h\n--- a/libinterp/corefcn/json-main.h\tThu Jul 02 18:08:38 2020 -0500\n+++ b/libinterp/corefcn/json-main.h\tFri Jul 03 03:58:38 2020 +0000\n@@ -8,6 +8,10 @@\n #include \"octave-json-link.h\"\r\n #include \"json-util.h\"\r\n \r\n+namespace octave {\r\n+\r\n+class interpreter;\r\n+\r\n class json_main {\r\n public:\r\n \tjson_main(interpreter& interp, const std::string& json_sock_path, int max_message_length);\r\n@@ -25,8 +29,9 @@\n \tbool _loop_thread_active;\r\n \tpthread_t _loop_thread;\r\n \r\n-\t// Owned by octave_link\r\n-\toctave_json_link* _octave_json_link;\r\n+\tstd::shared_ptr<octave_json_link> _octave_json_link;\r\n };\r\n \r\n+} // namespace octave\r\n+\r\n #endif\r\ndiff -r d78448f9c483 -r 97f7d1f4fe83 libinterp/corefcn/json-util.cc\n--- a/libinterp/corefcn/json-util.cc\tThu Jul 02 18:08:38 2020 -0500\n+++ b/libinterp/corefcn/json-util.cc\tFri Jul 03 03:58:38 2020 +0000\n@@ -14,6 +14,8 @@\n \r\n #include \"json-util.h\"\r\n \r\n+namespace octave {\r\n+\r\n JSON_OBJECT_T json_util::from_string(const std::string& str) {\r\n \t// Note: the string is not necesarilly valid UTF-8. The consumers of this stream must be able to handle that situation and substitute replacement characters, etc., where necessary.\r\n \treturn json_object_new_string_len(str.c_str(), str.length());\r\n@@ -70,7 +72,7 @@\n \treturn json_object_from_list(list, json_util::from_float);\r\n }\r\n \r\n-JSON_OBJECT_T json_util::from_symbol_info_list(const octave::symbol_info_list& list) {\r\n+JSON_OBJECT_T json_util::from_symbol_info_list(const symbol_info_list& list) {\r\n \tJSON_OBJECT_T jobj = json_object_new_array();\r\n \tfor (\r\n \t\tauto it = list.begin();\r\n@@ -82,7 +84,7 @@\n \treturn jobj;\r\n }\r\n \r\n-JSON_OBJECT_T json_util::from_filter_list(const octave_link::filter_list& list) {\r\n+JSON_OBJECT_T json_util::from_filter_list(const interpreter_events::filter_list& list) {\r\n \treturn json_object_from_list(list, json_util::from_pair);\r\n }\r\n \r\n@@ -90,7 +92,7 @@\n \treturn json_util::from_string(str);\r\n }\r\n \r\n-JSON_OBJECT_T json_util::from_symbol_info(const octave::symbol_info element) {\r\n+JSON_OBJECT_T json_util::from_symbol_info(const symbol_info element) {\r\n \toctave_value val = element.value();\r\n \r\n \tstd::string dims_str = val.get_dims_str();\r\n@@ -258,3 +260,5 @@\n   \targ\r\n   );\r\n }\r\n+\r\n+} // namespace octave\r\ndiff -r d78448f9c483 -r 97f7d1f4fe83 libinterp/corefcn/json-util.h\n--- a/libinterp/corefcn/json-util.h\tThu Jul 02 18:08:38 2020 -0500\n+++ b/libinterp/corefcn/json-util.h\tFri Jul 03 03:58:38 2020 +0000\n@@ -21,6 +21,8 @@\n \tm[#FIELD] = json_util::from_##TYPE (FIELD); \\\r\n }\r\n \r\n+namespace octave {\r\n+\r\n class json_util {\r\n public:\r\n \tstatic JSON_OBJECT_T from_string(const std::string& str);\r\n@@ -33,11 +35,11 @@\n \tstatic JSON_OBJECT_T from_string_vector(const string_vector& list);\r\n \tstatic JSON_OBJECT_T from_int_list(const std::list<int>& list);\r\n \tstatic JSON_OBJECT_T from_float_list(const std::list<float>& list);\r\n-\tstatic JSON_OBJECT_T from_symbol_info_list(const octave::symbol_info_list& list);\r\n-\tstatic JSON_OBJECT_T from_filter_list(const event_manager::filter_list& list);\r\n+\tstatic JSON_OBJECT_T from_symbol_info_list(const symbol_info_list& list);\r\n+        static JSON_OBJECT_T from_filter_list(const interpreter_events::filter_list& list);\r\n \r\n \tstatic JSON_OBJECT_T from_value_string(const std::string str);\r\n-\tstatic JSON_OBJECT_T from_symbol_info(const octave::symbol_info element);\r\n+\tstatic JSON_OBJECT_T from_symbol_info(const symbol_info element);\r\n \tstatic JSON_OBJECT_T from_pair(std::pair<std::string, std::string> pair);\r\n \r\n \tstatic JSON_OBJECT_T from_map(JSON_MAP_T m);\r\n@@ -57,4 +59,6 @@\n \tstatic void process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n };\r\n \r\n+} // namespace octave\r\n+\r\n #endif\r\ndiff -r d78448f9c483 -r 97f7d1f4fe83 libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tThu Jul 02 18:08:38 2020 -0500\n+++ b/libinterp/corefcn/octave-json-link.cc\tFri Jul 03 03:58:38 2020 +0000\n@@ -30,6 +30,8 @@\n #include \"json-main.h\"\r\n #include \"json-util.h\"\r\n \r\n+namespace octave {\r\n+\r\n octave_json_link::octave_json_link(json_main* __json_main)\r\n \t: interpreter_events (),\r\n \t\t_json_main (__json_main)\r\n@@ -40,7 +42,7 @@\n \r\n octave_json_link::~octave_json_link(void) { }\r\n \r\n-std::string octave_json_link::do_request_input(const std::string& prompt) {\r\n+std::string octave_json_link::request_input(const std::string& prompt) {\r\n \t// Triggered whenever the console prompts for user input\r\n \r\n \tstd::string value;\r\n@@ -51,7 +53,7 @@\n \treturn value;\r\n }\r\n \r\n-std::string octave_json_link::do_request_url(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\r\n+std::string octave_json_link::request_url(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\r\n \t// Triggered on urlread/urlwrite\r\n \r\n \tJSON_MAP_T m;\r\n@@ -65,7 +67,7 @@\n \treturn result.second;\r\n }\r\n \r\n-bool octave_json_link::do_confirm_shutdown(void) {\r\n+bool octave_json_link::confirm_shutdown(void) {\r\n \t// Triggered when the kernel tries to exit\r\n \t_publish_message(\"confirm-shutdown\", json_util::empty());\r\n \r\n@@ -85,7 +87,7 @@\n // \treturn true;\r\n // }\r\n \r\n-bool octave_json_link::do_copy_image_to_clipboard(const std::string& file) {\r\n+bool octave_json_link::copy_image_to_clipboard(const std::string& file) {\r\n \t// This endpoint might be unused?  (References appear only in libgui)\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, file, string);\r\n@@ -94,7 +96,7 @@\n \treturn true;\r\n }\r\n \r\n-bool octave_json_link::do_edit_file(const std::string& file) {\r\n+bool octave_json_link::edit_file(const std::string& file) {\r\n \t// Triggered in \"edit\" for existing files\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, file, string);\r\n@@ -103,7 +105,7 @@\n \treturn true;\r\n }\r\n \r\n-bool octave_json_link::do_prompt_new_edit_file(const std::string& file) {\r\n+bool octave_json_link::prompt_new_edit_file(const std::string& file) {\r\n \t// Triggered in \"edit\" for new files\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, file, string);\r\n@@ -123,7 +125,7 @@\n // \treturn message_dialog_queue.dequeue();\r\n // }\r\n \r\n-std::string octave_json_link::do_question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n+std::string octave_json_link::question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n \t// Triggered in \"questdlg\"\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, msg, string);\r\n@@ -137,7 +139,7 @@\n \treturn question_dialog_queue.dequeue();\r\n }\r\n \r\n-std::pair<std::list<int>, int> octave_json_link::do_list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {\r\n+std::pair<std::list<int>, int> octave_json_link::list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {\r\n \t// Triggered in \"listdlg\"\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, list, string_list);\r\n@@ -154,7 +156,7 @@\n \treturn list_dialog_queue.dequeue();\r\n }\r\n \r\n-std::list<std::string> octave_json_link::do_input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {\r\n+std::list<std::string> octave_json_link::input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {\r\n \t// Triggered in \"inputdlg\"\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, prompt, string_list);\r\n@@ -167,7 +169,7 @@\n \treturn input_dialog_queue.dequeue();\r\n }\r\n \r\n-std::list<std::string> octave_json_link::do_file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {\r\n+std::list<std::string> octave_json_link::file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {\r\n \t// Triggered in \"uiputfile\", \"uigetfile\", and \"uigetdir\"\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, filter, filter_list);\r\n@@ -180,7 +182,7 @@\n \treturn file_dialog_queue.dequeue();\r\n }\r\n \r\n-int octave_json_link::do_debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\r\n+int octave_json_link::debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\r\n \t// This endpoint might be unused?  (No references)\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, file, string);\r\n@@ -191,14 +193,14 @@\n \treturn debug_cd_or_addpath_error_queue.dequeue();\r\n }\r\n \r\n-void octave_json_link::do_change_directory(const std::string& dir) {\r\n+void octave_json_link::directory_changed(const std::string& dir) {\r\n \t// This endpoint might be unused?  (References appear only in libgui)\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, dir, string);\r\n \t_publish_message(\"change-directory\", json_util::from_map(m));\r\n }\r\n \r\n-void octave_json_link::do_file_remove (const std::string& old_name, const std::string& new_name) {\r\n+void octave_json_link::file_remove (const std::string& old_name, const std::string& new_name) {\r\n \t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, old_name, string);\r\n@@ -206,27 +208,27 @@\n \t_publish_message(\"file-remove\", json_util::from_map(m));\r\n }\r\n \r\n-void octave_json_link::do_file_renamed (bool status) {\r\n+void octave_json_link::file_renamed (bool status) {\r\n \t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n \t_publish_message(\"file-renamed\", json_util::from_boolean(status));\r\n }\r\n \r\n-void octave_json_link::do_execute_command_in_terminal(const std::string& command) {\r\n+void octave_json_link::execute_command_in_terminal(const std::string& command) {\r\n \t// This endpoint might be unused?  (References appear only in libgui)\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, command, string);\r\n \t_publish_message(\"execute-command-in-terminal\", json_util::from_map(m));\r\n }\r\n \r\n-uint8NDArray octave_json_link::do_get_named_icon (const std::string& /* icon_name */) {\r\n+uint8NDArray octave_json_link::get_named_icon (const std::string& /* icon_name */) {\r\n \t// Called from msgbox.m\r\n \t// TODO: Implement request/response for this event\r\n \tuint8NDArray retval;\r\n \treturn retval;\r\n }\r\n \r\n-void octave_json_link::do_set_workspace(bool top_level, bool debug,\r\n-                         const octave::symbol_info_list& ws,\r\n+void octave_json_link::set_workspace(bool top_level, bool debug,\r\n+                         const symbol_info_list& ws,\r\n                          bool update_variable_editor) {\r\n \t// Triggered on every new line entry\r\n \tJSON_MAP_T m;\r\n@@ -237,62 +239,63 @@\n \t_publish_message(\"set-workspace\", json_util::from_map(m));\r\n }\r\n \r\n-void octave_json_link::do_clear_workspace(void) {\r\n+void octave_json_link::clear_workspace(void) {\r\n \t// Triggered on \"clear\" command (but not \"clear all\" or \"clear foo\")\r\n \t_publish_message(\"clear-workspace\", json_util::empty());\r\n }\r\n \r\n-void octave_json_link::do_set_history(const string_vector& hist) {\r\n+void octave_json_link::set_history(const string_vector& hist) {\r\n \t// Called at startup, possibly more?\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, hist, string_vector);\r\n \t_publish_message(\"set-history\", json_util::from_map(m));\r\n }\r\n \r\n-void octave_json_link::do_append_history(const std::string& hist_entry) {\r\n+void octave_json_link::append_history(const std::string& hist_entry) {\r\n \t// Appears to be tied to readline, if available\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, hist_entry, string);\r\n \t_publish_message(\"append-history\", json_util::from_map(m));\r\n }\r\n \r\n-void octave_json_link::do_clear_history(void) {\r\n+void octave_json_link::clear_history(void) {\r\n \t// Appears to be tied to readline, if available\r\n \t_publish_message(\"clear-history\", json_util::empty());\r\n }\r\n \r\n-void octave_json_link::do_clear_screen(void) {\r\n+void octave_json_link::clear_screen(void) {\r\n \t// Triggered by clc\r\n \t_publish_message(\"clear-screen\", json_util::empty());\r\n }\r\n \r\n-void octave_json_link::do_pre_input_event(void) {\r\n+void octave_json_link::pre_input_event(void) {\r\n \t// noop\r\n }\r\n \r\n-void octave_json_link::do_post_input_event(void) {\r\n+void octave_json_link::post_input_event(void) {\r\n \t// noop\r\n }\r\n \r\n-void octave_json_link::do_enter_debugger_event(const std::string& file, int line) {\r\n+void octave_json_link::enter_debugger_event(const std::string& fcn_name, const std::string& fcn_file_name, int line) {\r\n \tJSON_MAP_T m;\r\n-\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, fcn_name, string);\r\n+\tJSON_MAP_SET(m, fcn_file_name, string);\r\n \tJSON_MAP_SET(m, line, int);\r\n \t_publish_message(\"enter-debugger-event\", json_util::from_map(m));\r\n }\r\n \r\n-void octave_json_link::do_execute_in_debugger_event(const std::string& file, int line) {\r\n+void octave_json_link::execute_in_debugger_event(const std::string& file, int line) {\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, file, string);\r\n \tJSON_MAP_SET(m, line, int);\r\n \t_publish_message(\"execute-in-debugger-event\", json_util::from_map(m));\r\n }\r\n \r\n-void octave_json_link::do_exit_debugger_event(void) {\r\n+void octave_json_link::exit_debugger_event(void) {\r\n \t_publish_message(\"exit-debugger-event\", json_util::empty());\r\n }\r\n \r\n-void octave_json_link::do_update_breakpoint(bool insert, const std::string& file, int line, const std::string& cond) {\r\n+void octave_json_link::update_breakpoint(bool insert, const std::string& file, int line, const std::string& cond) {\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, insert, boolean);\r\n \tJSON_MAP_SET(m, file, string);\r\n@@ -310,34 +313,34 @@\n // \t_publish_message(\"set-default-prompts\", json_util::from_map(m));\r\n // }\r\n \r\n-void octave_json_link::do_show_preferences(void) {\r\n+void octave_json_link::show_preferences(void) {\r\n \t// Triggered on \"preferences\" command\r\n \t_publish_message(\"show-preferences\", json_util::empty());\r\n }\r\n \r\n-std::string octave_json_link::do_gui_preference (const std::string& /* key */, const std::string& /* value */) {\r\n+std::string octave_json_link::gui_preference (const std::string& /* key */, const std::string& /* value */) {\r\n \t// Used by Octave GUI?\r\n \t// TODO: Implement request/response for this event\r\n \tstd::string retval;\r\n \treturn retval;\r\n }\r\n \r\n-void octave_json_link::do_show_doc(const std::string& file) {\r\n+void octave_json_link::show_doc(const std::string& file) {\r\n \t// Triggered on \"doc\" command\r\n \t_publish_message(\"show-doc\", json_util::from_string(file));\r\n }\r\n \r\n-void octave_json_link::do_register_doc (const std::string& file) {\r\n+void octave_json_link::register_doc (const std::string& file) {\r\n \t// Triggered by the GUI documentation viewer?\r\n \t_publish_message(\"register-doc\", json_util::from_string(file));\r\n }\r\n \r\n-void octave_json_link::do_unregister_doc (const std::string& file) {\r\n+void octave_json_link::unregister_doc (const std::string& file) {\r\n \t// Triggered by the GUI documentation viewer?\r\n \t_publish_message(\"unregister-doc\", json_util::from_string(file));\r\n }\r\n \r\n-void octave_json_link::do_edit_variable (const std::string& name, const octave_value& /* val */) {\r\n+void octave_json_link::edit_variable (const std::string& name, const octave_value& /* val */) {\r\n \t// Triggered on \"openvar\" command\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, name, string);\r\n@@ -345,9 +348,9 @@\n \t_publish_message(\"edit-variable\", json_util::from_map(m));\r\n }\r\n \r\n-void octave_json_link::do_show_static_plot(const std::string& term, const std::string& content) {\r\n+void octave_json_link::show_static_plot(const std::string& term, const std::string& content) {\r\n \t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\r\n-\tint command_number = octave::command_editor::current_command_number();\r\n+\tint command_number = command_editor::current_command_number();\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, term, string);\r\n \tJSON_MAP_SET(m, content, string);\r\n@@ -405,3 +408,4 @@\n \t_json_main->publish_message(name, jobj);\r\n }\r\n \r\n+} // namespace octave\r\ndiff -r d78448f9c483 -r 97f7d1f4fe83 libinterp/corefcn/octave-json-link.h\n--- a/libinterp/corefcn/octave-json-link.h\tThu Jul 02 18:08:38 2020 -0500\n+++ b/libinterp/corefcn/octave-json-link.h\tFri Jul 03 03:58:38 2020 +0000\n@@ -30,6 +30,10 @@\n #include \"json-util.h\"\r\n #include \"oct-mutex.h\"\r\n \r\n+class string_vector;\r\n+\r\n+namespace octave {\r\n+\r\n // Circular reference\r\n class json_main;\r\n \r\n@@ -45,7 +49,7 @@\n \r\n private:\r\n   std::queue<T> _queue;\r\n-  octave::mutex _mutex;\r\n+  mutex _mutex;\r\n };\r\n \r\n class octave_json_link : public interpreter_events\r\n@@ -57,23 +61,23 @@\n \r\n   ~octave_json_link (void);\r\n \r\n-  std::string do_request_input (const std::string& prompt) override;\r\n-  std::string do_request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\r\n+  std::string request_input (const std::string& prompt) override;\r\n+  std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\r\n \r\n-  bool do_confirm_shutdown (void) override;\r\n+  bool confirm_shutdown (void) override;\r\n \r\n-  bool do_copy_image_to_clipboard (const std::string& file) override;\r\n+  bool copy_image_to_clipboard (const std::string& file) override;\r\n \r\n-  bool do_edit_file (const std::string& file) override;\r\n-  bool do_prompt_new_edit_file (const std::string& file) override;\r\n+  bool edit_file (const std::string& file) override;\r\n+  bool prompt_new_edit_file (const std::string& file) override;\r\n \r\n   std::string\r\n-  do_question_dialog (const std::string& msg, const std::string& title,\r\n+  question_dialog (const std::string& msg, const std::string& title,\r\n                       const std::string& btn1, const std::string& btn2,\r\n                       const std::string& btn3, const std::string& btndef) override;\r\n \r\n   std::pair<std::list<int>, int>\r\n-  do_list_dialog (const std::list<std::string>& list,\r\n+  list_dialog (const std::list<std::string>& list,\r\n                   const std::string& mode,\r\n                   int width, int height,\r\n                   const std::list<int>& initial_value,\r\n@@ -83,67 +87,67 @@\n                   const std::string& cancel_string) override;\r\n \r\n   std::list<std::string>\r\n-  do_input_dialog (const std::list<std::string>& prompt,\r\n+  input_dialog (const std::list<std::string>& prompt,\r\n                    const std::string& title,\r\n                    const std::list<float>& nr,\r\n                    const std::list<float>& nc,\r\n                    const std::list<std::string>& defaults) override;\r\n \r\n   std::list<std::string>\r\n-  do_file_dialog (const filter_list& filter, const std::string& title,\r\n+  file_dialog (const filter_list& filter, const std::string& title,\r\n                   const std::string &filename, const std::string &pathname,\r\n                   const std::string& multimode) override;\r\n \r\n   int\r\n-  do_debug_cd_or_addpath_error (const std::string& file,\r\n+  debug_cd_or_addpath_error (const std::string& file,\r\n                                 const std::string& dir,\r\n                                 bool addpath_option) override;\r\n \r\n-  void do_change_directory (const std::string& dir) override;\r\n+  void directory_changed (const std::string& dir) override;\r\n \r\n-  void do_file_remove (const std::string& old_name, const std::string& new_name) override;\r\n-  void do_file_renamed (bool) override;\r\n+  void file_remove (const std::string& old_name, const std::string& new_name) override;\r\n+  void file_renamed (bool) override;\r\n \r\n-  void do_execute_command_in_terminal (const std::string& command) override;\r\n+  void execute_command_in_terminal (const std::string& command) override;\r\n \r\n-  uint8NDArray do_get_named_icon (const std::string& icon_name) override;\r\n+  uint8NDArray get_named_icon (const std::string& icon_name) override;\r\n \r\n-  void do_set_workspace (bool top_level, bool debug,\r\n-                         const octave::symbol_info_list& ws,\r\n+  void set_workspace (bool top_level, bool debug,\r\n+                         const symbol_info_list& ws,\r\n                          bool update_variable_editor) override;\r\n \r\n-  void do_clear_workspace (void) override;\r\n+  void clear_workspace (void) override;\r\n \r\n-  void do_set_history (const string_vector& hist) override;\r\n-  void do_append_history (const std::string& hist_entry) override;\r\n-  void do_clear_history (void) override;\r\n+  void set_history (const string_vector& hist) override;\r\n+  void append_history (const std::string& hist_entry) override;\r\n+  void clear_history (void) override;\r\n \r\n-  void do_clear_screen (void) override;\r\n+  void clear_screen (void) override;\r\n \r\n-  void do_pre_input_event (void) override;\r\n-  void do_post_input_event (void) override;\r\n+  void pre_input_event (void) override;\r\n+  void post_input_event (void) override;\r\n \r\n-  void do_enter_debugger_event (const std::string& file, int line) override;\r\n-  void do_execute_in_debugger_event (const std::string& file, int line) override;\r\n-  void do_exit_debugger_event (void) override;\r\n+  void enter_debugger_event (const std::string& fcn_name, const std::string& fcn_file_name, int line) override;\r\n+  void execute_in_debugger_event (const std::string& file, int line) override;\r\n+  void exit_debugger_event (void) override;\r\n \r\n-  void do_update_breakpoint (bool insert,\r\n+  void update_breakpoint (bool insert,\r\n                              const std::string& file, int line,\r\n                              const std::string& cond) override;\r\n \r\n-  void do_show_preferences (void) override;\r\n+  void show_preferences (void) override;\r\n \r\n-  std::string do_gui_preference (const std::string& key, const std::string& value) override;\r\n+  std::string gui_preference (const std::string& key, const std::string& value) override;\r\n \r\n-  void do_show_doc (const std::string& file) override;\r\n+  void show_doc (const std::string& file) override;\r\n \r\n-  void do_register_doc (const std::string& file) override;\r\n+  void register_doc (const std::string& file) override;\r\n \r\n-  void do_unregister_doc (const std::string& file) override;\r\n+  void unregister_doc (const std::string& file) override;\r\n \r\n-  void do_edit_variable (const std::string& name, const octave_value& val) override;\r\n+  void edit_variable (const std::string& name, const octave_value& val) override;\r\n \r\n-  void do_show_static_plot (const std::string& term,\r\n+  void show_static_plot (const std::string& term,\r\n                             const std::string& content) override;\r\n \r\n   // Custom methods\r\n@@ -207,4 +211,6 @@\n   return retval;\r\n }\r\n \r\n+} // namespace octave\r\n+  \r\n #endif\r\ndiff -r d78448f9c483 -r 97f7d1f4fe83 libinterp/corefcn/sysdep.cc\n--- a/libinterp/corefcn/sysdep.cc\tThu Jul 02 18:08:38 2020 -0500\n+++ b/libinterp/corefcn/sysdep.cc\tFri Jul 03 03:58:38 2020 +0000\n@@ -755,7 +755,8 @@\n {\n   bool skip_redisplay = true;\n \n-  octave_link::clear_screen ();\n+  octave::event_manager& evmgr = octave::__get_event_manager__ (\"clc\");\n+  evmgr.clear_screen();\n \n   octave::command_editor::clear_screen (skip_redisplay);\n \ndiff -r d78448f9c483 -r 97f7d1f4fe83 libinterp/octave.cc\n--- a/libinterp/octave.cc\tThu Jul 02 18:08:38 2020 -0500\n+++ b/libinterp/octave.cc\tFri Jul 03 03:58:38 2020 +0000\n@@ -382,6 +382,14 @@\n     sysdep_init ();\n   }\n \n+  bool application::link_enabled (void) const\n+  {\n+    if (m_interpreter) {\n+      event_manager& evmgr = m_interpreter->get_event_manager ();\n+      return evmgr.enabled();\n+    } else return false;\n+  }\n+\n   int cli_application::execute (void)\n   {\n     interpreter& interp = create_interpreter ();\ndiff -r d78448f9c483 -r 97f7d1f4fe83 libinterp/octave.h\n--- a/libinterp/octave.h\tThu Jul 02 18:08:38 2020 -0500\n+++ b/libinterp/octave.h\tFri Jul 03 03:58:38 2020 +0000\n@@ -240,6 +240,7 @@\n   // both) of them...\n \n   class interpreter;\n+  class event_manager;\n \n   // Base class for an Octave application.\n \n@@ -289,6 +290,8 @@\n     virtual bool gui_running (void) const { return false; }\n     virtual void gui_running (bool) { }\n \n+    bool link_enabled (void) const;\n+\n     void program_invocation_name (const std::string& nm) { m_program_invocation_name = nm; }\n \n     void program_name (const std::string& nm) { m_program_name = nm; }\n@@ -319,10 +322,7 @@\n \n     static bool is_link_enabled (void)\n     {\n-      if (instance && instance->m_interpreter) {\n-        event_manager& evmgr = instance->m_interpreter->get_event_manager ();\n-        return evmgr.enabled();\n-      } else return false;\n+      return instance ? instance->link_enabled () : false;\n     }\n \n     // Convenience functions.\ndiff -r d78448f9c483 -r 97f7d1f4fe83 liboctave/util/oct-mutex.cc\n--- a/liboctave/util/oct-mutex.cc\tThu Jul 02 18:08:38 2020 -0500\n+++ b/liboctave/util/oct-mutex.cc\tFri Jul 03 03:58:38 2020 +0000\n@@ -180,7 +180,7 @@\n \n     void cond_wait (void)\n     {\n-      pthread_cond_wait (&condv, &pm);\n+      pthread_cond_wait (&condv, &m_pm);\n     }\n \n     void cond_signal (void)\ndiff -r d78448f9c483 -r 97f7d1f4fe83 liboctave/util/oct-mutex.h\n--- a/liboctave/util/oct-mutex.h\tThu Jul 02 18:08:38 2020 -0500\n+++ b/liboctave/util/oct-mutex.h\tFri Jul 03 03:58:38 2020 +0000\n@@ -108,12 +108,12 @@\n \n     void cond_wait (void)\n     {\n-      rep->cond_wait ();\n+      m_rep->cond_wait ();\n     }\n \n     void cond_signal (void)\n     {\n-      rep->cond_signal ();\n+      m_rep->cond_signal ();\n     }\n \n   protected:\ndiff -r d78448f9c483 -r 97f7d1f4fe83 liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tThu Jul 02 18:08:38 2020 -0500\n+++ b/liboctave/util/url-transfer.cc\tFri Jul 03 03:58:38 2020 +0000\n@@ -36,7 +36,6 @@\n #include \"file-stat.h\"\n #include \"lo-sysdep.h\"\n #include \"oct-env.h\"\n-#include \"libinterp/corefcn/interpreter-private.h\"\n #include \"gnulib/lib/base64.h\"\n #include \"unwind-prot.h\"\n #include \"url-transfer.h\"\n@@ -50,6 +49,10 @@\n \n namespace octave\n {\n+  // Forward declaration for event_manager\n+  extern bool __event_manager_request_input_enabled__();\n+  extern std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n   base_url_transfer::base_url_transfer (void)\n     : m_host_or_url (), m_valid (false), m_ftp (false),\n       m_ascii_mode (false), m_ok (true), m_errmsg (),\n@@ -249,38 +252,38 @@\n \n   link_transfer (void)\n       : base_url_transfer () {\n-    valid = true;\n+    m_valid = true;\n   }\n \n   link_transfer (const std::string& host, const std::string& user_arg,\n                  const std::string& passwd, std::ostream& os)\n       : base_url_transfer (host, user_arg, passwd, os) {\n-    valid = true;\n+    m_valid = true;\n     // url = \"ftp://\" + host;\n   }\n \n   link_transfer (const std::string& url_str, std::ostream& os)\n       : base_url_transfer (url_str, os) {\n-    valid = true;\n+    m_valid = true;\n   }\n \n   ~link_transfer (void) {}\n \n   void http_get (const Array<std::string>& param) {\n-    perform (param, \"get\");\n+    perform_action (param, \"get\");\n   }\n \n   void http_post (const Array<std::string>& param) {\n-    perform (param, \"post\");\n+    perform_action (param, \"post\");\n   }\n \n   void http_action (const Array<std::string>& param, const std::string& action) {\n-    perform (param, action);\n+    perform_action (param, action);\n   }\n \n private:\n-  void perform(const Array<std::string>& param, const std::string& action) {\n-    std::string url = host_or_url;\n+  void perform_action(const Array<std::string>& param, const std::string& action) {\n+    std::string url = m_host_or_url;\n \n     // Convert from Array to std::list\n     std::list<std::string> paramList;\n@@ -289,19 +292,18 @@\n       paramList.push_back(value);\n     }\n \n-    event_manager& evmgr = __get_event_manager__ (\"link_transfer\");\n-    if (evmgr.request_input_enabled ()) {\n+    if (__event_manager_request_input_enabled__()) {\n       bool success;\n-      std::string result = evmgr.request_url (url, paramList, action, success);\n+      std::string result = __event_manager_request_url__(url, paramList, action, success);\n       if (success) {\n         process_success(result);\n       } else {\n-        ok = false;\n-        errmsg = result;\n+        m_ok = false;\n+        m_errmsg = result;\n       }\n     } else {\n-      ok = false;\n-      errmsg = \"octave_link not connected for link_transfer\";\n+      m_ok = false;\n+      m_errmsg = \"octave_link not connected for link_transfer\";\n     }\n   }\n \n@@ -313,10 +315,10 @@\n     size_t outlen;\n     bool b64_ok = base64_decode_alloc(inc, result.length(), &out, &outlen);\n     if (!b64_ok) {\n-      ok = false;\n-      errmsg = \"failed decoding base64 from octave_link\";\n+      m_ok = false;\n+      m_errmsg = \"failed decoding base64 from octave_link\";\n     } else {\n-      curr_ostream->write(out, outlen);\n+      m_curr_ostream->write(out, outlen);\n       ::free(out);\n     }\n   }\n@@ -1025,8 +1027,7 @@\n #endif\n \n   url_transfer::url_transfer (void) {\n-    event_manager& evmgr = __get_event_manager__ (\"url_transfer\");\n-    if (evmgr.request_input_enabled()) {\n+    if (__event_manager_request_input_enabled__()) {\n       m_rep.reset(new link_transfer());\n     } else {\n       m_rep.reset(new REP_CLASS());\n@@ -1035,8 +1036,7 @@\n \n   url_transfer::url_transfer (const std::string& host, const std::string& user,\n                               const std::string& passwd, std::ostream& os) {\n-    event_manager& evmgr = __get_event_manager__ (\"url_transfer\");\n-    if (evmgr.request_input_enabled()) {\n+    if (__event_manager_request_input_enabled__()) {\n       m_rep.reset(new link_transfer(host, user, passwd, os));\n     } else {\n       m_rep.reset(new REP_CLASS(host, user, passwd, os));\n@@ -1044,8 +1044,7 @@\n   }\n \n   url_transfer::url_transfer (const std::string& url, std::ostream& os) {\n-    event_manager& evmgr = __get_event_manager__ (\"url_transfer\");\n-    if (evmgr.request_input_enabled()) {\n+    if (__event_manager_request_input_enabled__()) {\n       m_rep.reset(new link_transfer(url, os));\n     } else {\n       m_rep.reset(new REP_CLASS(url, os));\n"
  },
  {
    "path": "back-octave/oo-changesets/302-8900d7cf8554.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1593856818 18000\n#      Sat Jul 04 05:00:18 2020 -0500\n# Branch oo-5.2a\n# Node ID 8900d7cf85541d1b927b7d071f541b69cd234f7c\n# Parent  97f7d1f4fe83bd6ebe102eb4cc6268ae23ad8d52\nAnother octave_link to event_manager rename\n\ndiff -r 97f7d1f4fe83 -r 8900d7cf8554 scripts/plot/util/__gnuplot_drawnow__.m\n--- a/scripts/plot/util/__gnuplot_drawnow__.m\tFri Jul 03 03:58:38 2020 +0000\n+++ b/scripts/plot/util/__gnuplot_drawnow__.m\tSat Jul 04 05:00:18 2020 -0500\n@@ -60,8 +60,8 @@\n \n     should_plot_to_terminal = (\n       !strcmp (term, \"dumb\") && (\n-        __octave_link_plot_destination__ () == 0 ||\n-        __octave_link_plot_destination__ () == 2\n+        __event_manager_plot_destination__ () == 0 ||\n+        __event_manager_plot_destination__ () == 2\n       )\n     );\n \n@@ -73,8 +73,8 @@\n \n     should_plot_to_temp_file = (\n       strcmp (term, \"dumb\") ||\n-      __octave_link_plot_destination__ () == 1 ||\n-      __octave_link_plot_destination__ () == 2\n+      __event_manager_plot_destination__ () == 1 ||\n+      __event_manager_plot_destination__ () == 2\n     );\n \n     if (should_plot_to_temp_file)\n@@ -101,7 +101,7 @@\n         if strcmp (term, \"dumb\")\n           puts (a);\n         else\n-          __octave_link_show_static_plot__ (term, a);\n+          __event_manager_show_static_plot__ (term, a);\n         endif\n       endif\n     endif\n"
  },
  {
    "path": "back-octave/oo-changesets/310-1e1c91e6cddc.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1593894938 18000\n#      Sat Jul 04 15:35:38 2020 -0500\n# Branch oo-6.0.1\n# Node ID 1e1c91e6cddc3c48870e390b2730c7b586cc8a89\n# Parent  171a2857d6d1cdcb77eef5cbef40e83962466d21\n# Parent  8900d7cf85541d1b927b7d071f541b69cd234f7c\nMerge oo-5.2a into oo-6.0.1\n\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc configure.ac\n--- a/configure.ac\tSat Jul 04 11:13:35 2020 +0900\n+++ b/configure.ac\tSat Jul 04 15:35:38 2020 -0500\n@@ -2812,7 +2812,7 @@\n AC_SUBST(LIBOCTAVE_LINK_DEPS)\n AC_SUBST(LIBOCTAVE_LINK_OPTS)\n \n-LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $SPARSE_XLIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $LLVM_LIBS $JAVA_LIBS $LAPACK_LIBS\"\n+LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $SPARSE_XLIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $LLVM_LIBS $JAVA_LIBS $LAPACK_LIBS -ljson-c\"\n \n LIBOCTINTERP_LINK_OPTS=\"$FT2_LDFLAGS $HDF5_LDFLAGS $MAGICK_LDFLAGS $Z_LDFLAGS $SPARSE_XLDFLAGS $FFTW_XLDFLAGS $LLVM_LDFLAGS\"\n \ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libgui/src/qt-interpreter-events.cc\n--- a/libgui/src/qt-interpreter-events.cc\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libgui/src/qt-interpreter-events.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -261,6 +261,21 @@\n     emit edit_variable_signal (QString::fromStdString (expr), val);\n   }\n \n+  void qt_interpreter_events::show_static_plot (const std::string&, const std::string&)\n+  {\n+    return;\n+  }\n+\n+  std::string qt_interpreter_events::request_input (const std::string&)\n+  {\n+    return {};\n+  }\n+\n+  std::string qt_interpreter_events::request_url (const std::string&, const std::list<std::string>&, const std::string&, bool&)\n+  {\n+    return {};\n+  }\n+\n   bool qt_interpreter_events::confirm_shutdown (void)\n   {\n     QMutexLocker autolock (&m_mutex);\n@@ -505,6 +520,9 @@\n     emit clear_history_signal ();\n   }\n \n+  void qt_interpreter_events::do_clear_screen (void)\n+  { }\n+\n   void qt_interpreter_events::pre_input_event (void)\n   { }\n \ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libgui/src/qt-interpreter-events.h\n--- a/libgui/src/qt-interpreter-events.h\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libgui/src/qt-interpreter-events.h\tSat Jul 04 15:35:38 2020 -0500\n@@ -118,6 +118,12 @@\n \n     void edit_variable (const std::string& name, const octave_value& val);\n \n+    void show_static_plot (const std::string& term, const std::string& content);\n+\n+    std::string request_input (const std::string&);\n+\n+    std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n     bool confirm_shutdown (void);\n \n     bool prompt_new_edit_file (const std::string& file);\n@@ -160,6 +166,8 @@\n \n     void clear_history (void);\n \n+    void clear_screen (void);\n+\n     void pre_input_event (void);\n \n     void post_input_event (void);\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/event-manager.cc\n--- a/libinterp/corefcn/event-manager.cc\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libinterp/corefcn/event-manager.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -41,6 +41,17 @@\n \n namespace octave\n {\n+\n+  bool __event_manager_request_input_enabled__() {\n+    event_manager& evmgr = __get_event_manager__ (\"request_input_enabled\");\n+    return evmgr.request_input_enabled();\n+  }\n+\n+  std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\n+    event_manager& evmgr = __get_event_manager__ (\"request_url\");\n+    return evmgr.request_url(url, param, action, success);\n+  }\n+\n   static int readline_event_hook (void)\n   {\n     event_manager& evmgr = __get_event_manager__ (\"octave_readline_hook\");\n@@ -652,3 +663,28 @@\n   evmgr.focus_window (\"workspace\");\n   return ovl ();\n }\n+\n+DEFMETHOD (__event_manager_plot_destination__, interp, , ,\n+           doc: /* -*- texinfo -*-\n+@deftypefn {} {} __event_manager_plot_destination__ ()\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  return ovl (interp.get_event_manager().plot_destination());\n+}\n+\n+DEFMETHOD (__event_manager_show_static_plot__, interp, args, ,\n+           doc: /* -*- texinfo -*-\n+@deftypefn {} {} __event_manager_show_static_plot__ (@var{term}, @var{content})\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  if (args.length () != 2) {\n+    return ovl ();\n+  }\n+\n+  std::string term = args(0).string_value();\n+  std::string content = args(1).string_value();\n+  return ovl (interp.get_event_manager().show_static_plot(term, content));\n+}\n+\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/event-manager.h\n--- a/libinterp/corefcn/event-manager.h\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libinterp/corefcn/event-manager.h\tSat Jul 04 15:35:38 2020 -0500\n@@ -48,6 +48,12 @@\n \n   class symbol_info_list;\n \n+  enum plot_destination_t {\n+    TERMINAL_ONLY = 0,\n+    STATIC_ONLY = 1,\n+    TERMINAL_AND_STATIC = 2\n+  };\n+\n   // The methods in this class provide a way to pass signals to the GUI\n   // thread.  A GUI that wishes to act on these events should derive\n   // from this class and perform actions in a thread-safe way.  In\n@@ -147,6 +153,13 @@\n     // confirmation before another action.  Could these be reformulated\n     // using the question_dialog action?\n \n+    bool _request_input_enabled;\n+    virtual std::string request_input (const std::string&) = 0;\n+    virtual std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) = 0;\n+\n+    plot_destination_t _plot_destination;\n+    virtual void show_static_plot (const std::string& term, const std::string& content) = 0;\n+\n     virtual bool confirm_shutdown (void) { return false; }\n \n     virtual bool prompt_new_edit_file (const std::string& /*file*/)\n@@ -220,6 +233,8 @@\n \n     virtual void clear_history (void) { }\n \n+    virtual void clear_screen (void) { }\n+\n     virtual void pre_input_event (void) { }\n \n     virtual void post_input_event (void) { }\n@@ -373,6 +388,28 @@\n         instance->update_path_dialog ();\n     }\n \n+    bool request_input_enabled (void)\n+    {\n+      return enabled () ? instance->_request_input_enabled : false;\n+    }\n+\n+    plot_destination_t plot_destination (void)\n+    {\n+      return enabled () ? instance->_plot_destination : TERMINAL_ONLY;\n+    }\n+\n+    bool\n+    show_static_plot (const std::string& term, const std::string& content)\n+    {\n+      if (enabled ())\n+        {\n+          instance->show_static_plot (term, content);\n+          return true;\n+        }\n+      else\n+        return false;\n+    }\n+\n     bool show_preferences (void)\n     {\n       if (enabled ())\n@@ -551,6 +588,12 @@\n         instance->clear_history ();\n     }\n \n+    void clear_screen (void)\n+    {\n+      if (enabled ())\n+        instance->clear_screen ();\n+    }\n+\n     void pre_input_event (void)\n     {\n       if (enabled ())\n@@ -563,6 +606,21 @@\n         instance->post_input_event ();\n     }\n \n+\n+    std::string request_input (const std::string& prompt)\n+    {\n+      return request_input_enabled ()\n+        ? instance->request_input (prompt)\n+        : std::string ();\n+    }\n+\n+    std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success)\n+    {\n+      return request_input_enabled ()\n+        ? instance->request_url (url, param, action, success)\n+        : std::string ();\n+    }\n+\n     void enter_debugger_event (const std::string& fcn_name,\n                                const std::string& fcn_file_name, int line)\n     {\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/input.cc\n--- a/libinterp/corefcn/input.cc\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libinterp/corefcn/input.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -671,7 +671,12 @@\n \n     eof = false;\n \n-    std::string retval = command_editor::readline (s, eof);\n+    std::string retval;\n+    event_manager& evmgr = m_interpreter.get_event_manager ();\n+    if (evmgr.request_input_enabled ())\n+      retval = evmgr.request_input (s);\n+    else\n+      retval = command_editor::readline (s, eof);\n \n     if (! eof && retval.empty ())\n       retval = \"\\n\";\n@@ -1443,3 +1448,32 @@\n }\n \n // #endif\n+\n+DEFUN (current_command_number, args, ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn  {} {@var{val} =} current_command_number ()\n+@deftypefnx {} {@var{old_val} =} current_command_number (@var{new_val})\n+Sets the current command number, which appears in the prompt string.\n+For example, if the prompt says \"octave:1>\", then the current command\n+number is 1.\n+\n+This is a custom function in Octave Online.\n+\n+@example\n+current_command_number(1)\n+@end example\n+@end deftypefn */)\n+{\n+  int nargin = args.length ();\n+  if (nargin == 0) {\n+    int n = octave::command_editor::current_command_number();\n+    return ovl(n);\n+  } else if (nargin > 1) {\n+    print_usage ();\n+    return ovl();\n+  } else {\n+    int n = args(0).int_value ();\n+    octave::command_editor::reset_current_command_number(n);\n+    return ovl(n);\n+  }\n+}\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/interpreter.cc\n--- a/libinterp/corefcn/interpreter.cc\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libinterp/corefcn/interpreter.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -59,6 +59,7 @@\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"interpreter.h\"\n+#include \"json-main.h\"\n #include \"load-path.h\"\n #include \"load-save.h\"\n #include \"octave.h\"\n@@ -600,6 +601,11 @@\n         std::string texi_macros_file = options.texi_macros_file ();\n         if (! texi_macros_file.empty ())\n           Ftexi_macros_file (*this, octave_value (texi_macros_file));\n+\n+        if (!options.json_sock_path().empty ()) {\n+          static json_main _json_main (*this, options.json_sock_path(), options.json_max_message_length());\n+          _json_main.run_loop_on_new_thread();\n+        }\n       }\n \n     // FIXME: we defer creation of the gh_manager object because it\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/json-main.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -0,0 +1,101 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include \"json-main.h\"\r\n+#include \"interpreter.h\"\r\n+\r\n+#include <iostream>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <unistd.h>\r\n+\r\n+\r\n+// Analog of main-window.cc\r\n+// TODO: Think more about concurrency and null pointer exceptions\r\n+\r\n+namespace octave {\r\n+\r\n+void* run_loop_pthread(void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->run_loop();\r\n+  return NULL;\r\n+}\r\n+\r\n+void json_object_cb(std::string name, JSON_OBJECT_T jobj, void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->process_json_object(name, jobj);\r\n+}\r\n+\r\n+json_main::json_main(interpreter& interp, const std::string& json_sock_path, int max_message_length)\r\n+  : _json_sock_path (json_sock_path),\r\n+    _max_message_length (max_message_length),\r\n+    _loop_thread_active (false),\r\n+    _octave_json_link (new octave_json_link(this))\r\n+{\r\n+  // Enable the octave_json_link instance\r\n+  // Note: this passes ownership to octave_link\r\n+  event_manager& evmgr = interp.get_event_manager ();\r\n+  evmgr.connect_link (_octave_json_link);\r\n+  evmgr.enable ();\r\n+\r\n+  // Open UNIX socket file descriptor\r\n+  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\r\n+  struct sockaddr_un addr;\r\n+  memset(&addr, 0, sizeof(addr));\r\n+  addr.sun_family = AF_UNIX;\r\n+  memcpy(&addr.sun_path, _json_sock_path.c_str(), sizeof(addr.sun_path)-1);\r\n+  connect(\r\n+    sockfd,\r\n+    reinterpret_cast<struct sockaddr*>(&addr),\r\n+    sizeof(addr));\r\n+}\r\n+\r\n+json_main::~json_main(void) {\r\n+  close(sockfd);\r\n+\r\n+  // TODO: Stop the _loop_thread\r\n+}\r\n+\r\n+void json_main::publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  std::string jstr = json_util::to_message(name, jobj);\r\n+\r\n+  // Do not send any messages over the socket that exceed the user-specified max length.  Instead, send an error message.  If max_length is 0 (default), do not suppress any messages.\r\n+  // In stress testing, Node may be able to handle as much as 10-20 MB, but I'd prefer to stay on the safe side.  Before this safeguard was implemented, fewer than 5% of plots exceeded 1 MB.\r\n+  int length = jstr.length();\r\n+  int max_length = _max_message_length;\r\n+  if (max_length > 0 && length > max_length) {\r\n+    JSON_MAP_T m;\r\n+    JSON_MAP_SET(m, name, string);\r\n+    JSON_MAP_SET(m, length, int);\r\n+    JSON_MAP_SET(m, max_length, int);\r\n+    jstr = json_util::to_message(\"message-too-long\", json_util::from_map(m));\r\n+  }\r\n+\r\n+  send(sockfd, jstr.c_str(), jstr.length(), 0);\r\n+}\r\n+\r\n+void json_main::run_loop_on_new_thread(void) {\r\n+  if (_loop_thread_active)\r\n+    perror(\"won't run JSON socket loop multiple times\");\r\n+  _loop_thread_active = true;\r\n+\r\n+  pthread_create(\r\n+    &_loop_thread,\r\n+    NULL,\r\n+    run_loop_pthread,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::run_loop(void) {\r\n+  json_util::read_stream(\r\n+    sockfd,\r\n+    json_object_cb,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::process_json_object(std::string name, JSON_OBJECT_T jobj) {\r\n+  _octave_json_link->receive_message(name, jobj);\r\n+}\r\n+\r\n+} // namespace octave\r\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/json-main.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.h\tSat Jul 04 15:35:38 2020 -0500\n@@ -0,0 +1,37 @@\n+#ifndef json_main_h\r\n+#define json_main_h\r\n+\r\n+#include <queue>\r\n+#include <pthread.h>\r\n+#include <stdio.h>\r\n+\r\n+#include \"octave-json-link.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+namespace octave {\r\n+\r\n+class interpreter;\r\n+\r\n+class json_main {\r\n+public:\r\n+\tjson_main(interpreter& interp, const std::string& json_sock_path, int max_message_length);\r\n+\t~json_main(void);\r\n+\r\n+\tvoid publish_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\tvoid run_loop_on_new_thread(void);\r\n+\tvoid run_loop(void);\r\n+\tvoid process_json_object(std::string name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+\tstd::string _json_sock_path;\r\n+\tint _max_message_length;\r\n+\tint sockfd;\r\n+\tbool _loop_thread_active;\r\n+\tpthread_t _loop_thread;\r\n+\r\n+\tstd::shared_ptr<octave_json_link> _octave_json_link;\r\n+};\r\n+\r\n+} // namespace octave\r\n+\r\n+#endif\r\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/json-util.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -0,0 +1,264 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <cstdlib>\r\n+#include <assert.h>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <stdio.h>\r\n+#include <json-c/arraylist.h>\r\n+#include <json-c/json_object.h>\r\n+\r\n+#include \"str-vec.h\"\r\n+\r\n+#include \"json-util.h\"\r\n+\r\n+namespace octave {\r\n+\r\n+JSON_OBJECT_T json_util::from_string(const std::string& str) {\r\n+\t// Note: the string is not necesarilly valid UTF-8. The consumers of this stream must be able to handle that situation and substitute replacement characters, etc., where necessary.\r\n+\treturn json_object_new_string_len(str.c_str(), str.length());\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int(int i) {\r\n+\treturn json_object_new_int(i);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float(float flt) {\r\n+\treturn json_object_new_double(flt);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_boolean(bool b) {\r\n+\treturn json_object_new_boolean(b);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::empty() {\r\n+\treturn json_object_new_object();\r\n+}\r\n+\r\n+template<typename T>\r\n+JSON_OBJECT_T json_object_from_list(const std::list<T>& list, JSON_OBJECT_T (*convert)(T)) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\tauto it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, convert(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_list(const std::list<std::string>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_vector(const string_vector& vect) {\r\n+\t// TODO: Make sure this function does what it's supposed to do\r\n+\tstd::list<std::string> list;\r\n+\tfor (int i = 0; i < vect.numel(); ++i) {\r\n+\t\tlist.push_back(vect[i]);\r\n+\t}\r\n+\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int_list(const std::list<int>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_int);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float_list(const std::list<float>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_float);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_symbol_info_list(const symbol_info_list& list) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\tauto it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, json_util::from_symbol_info(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_filter_list(const interpreter_events::filter_list& list) {\r\n+\treturn json_object_from_list(list, json_util::from_pair);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_value_string(const std::string str) {\r\n+\treturn json_util::from_string(str);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_symbol_info(const symbol_info element) {\r\n+\toctave_value val = element.value();\r\n+\r\n+\tstd::string dims_str = val.get_dims_str();\r\n+\r\n+\tstd::ostringstream display_str;\r\n+\tval.short_disp(display_str);\r\n+\r\n+\tJSON_MAP_T m;\r\n+\t// m[\"scope\"] = json_util::from_int(element.scope());\r\n+\tm[\"symbol\"] = json_util::from_string(element.name());\r\n+\tm[\"class_name\"] = json_util::from_string(val.class_name());\r\n+\tm[\"dimension\"] = json_util::from_string(dims_str);\r\n+\tm[\"value\"] = json_util::from_string(display_str.str());\r\n+\tm[\"complex_flag\"] = json_util::from_boolean(element.is_complex());\r\n+\treturn json_util::from_map(m);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_pair(std::pair<std::string, std::string> pair) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tjson_object_array_add(jobj, json_util::from_string(pair.first.c_str()));\r\n+\tjson_object_array_add(jobj, json_util::from_string(pair.second.c_str()));\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_map(JSON_MAP_T m) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_object();\r\n+\tfor(\r\n+\t\tstd::map<std::string, JSON_OBJECT_T>::iterator it = m.begin();\r\n+\t\tit != m.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_object_add(jobj, it->first.c_str(), it->second);\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+std::string json_util::to_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  JSON_OBJECT_T jmsg = json_object_new_array();\r\n+  json_object_array_add(jmsg, json_util::from_string(name));\r\n+  json_object_array_add(jmsg, jobj);\r\n+  std::string str (json_object_to_json_string(jmsg));\r\n+  return str;\r\n+}\r\n+\r\n+std::string json_util::to_string(JSON_OBJECT_T jobj) {\r\n+  return std::string(json_object_get_string(jobj));\r\n+}\r\n+\r\n+template<typename T>\r\n+std::list<T> json_object_to_list(JSON_OBJECT_T jobj, T (*convert)(JSON_OBJECT_T)) {\r\n+\tstd::list<T> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tfor (size_t i = 0; i < array_list_length(arr); ++i) {\r\n+\t\tJSON_OBJECT_T jsub = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, i));\r\n+\t\tret.push_back(convert(jsub));\r\n+\t}\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> json_util::to_int_list_int_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<std::list<int>, int> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_to_list<int>(first, json_util::to_int);\r\n+\tret.second = json_object_get_int(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<bool, std::string> json_util::to_bool_string_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<bool, std::string> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_get_boolean(first);\r\n+\tret.second = json_object_get_string(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::list<std::string> json_util::to_string_list(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_to_list<std::string>(jobj, json_util::to_string);\r\n+}\r\n+\r\n+int json_util::to_int(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_int(jobj);\r\n+}\r\n+\r\n+bool json_util::to_boolean(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_boolean(jobj);\r\n+}\r\n+\r\n+void json_util::read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+\r\n+\t// Make some local variables\r\n+\tint BUF_LEN = 24;\r\n+\tchar* buf = new char[BUF_LEN];  // buffer for socket read\r\n+\tint buf_len;  // length of new bytes in the buffer\r\n+\tint buf_offset;  // offset of the JSON parser in the buffer\r\n+\tJSON_OBJECT_T jobj;  // pointer to parsed JSON object\r\n+\tjson_tokener* tok = json_tokener_new();  // JSON tokenizer instance\r\n+\tenum json_tokener_error jerr;  // status of JSON tokenizer\r\n+\r\n+\t// Start the blocking I/O loop\r\n+\twhile( (buf_len=recv(sockfd, buf, BUF_LEN, 0)) > 0) {\r\n+\t\tbuf_offset = 0;\r\n+\t\twhile(buf_offset < buf_len){\r\n+\t\t\tjobj = json_tokener_parse_ex(tok, buf + buf_offset, buf_len - buf_offset);\r\n+\t\t\tjerr = json_tokener_get_error(tok);\r\n+\t\t\tbuf_offset += tok->char_offset;\r\n+\r\n+\t\t\t// Do we need more material in order to make JSON?\r\n+\t\t\tif (jerr == json_tokener_continue) {\r\n+\t\t\t\tcontinue;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Make a new tokenizer\r\n+\t\t\tjson_tokener_free(tok);\r\n+\t\t\ttok = json_tokener_new();\r\n+\r\n+\t\t\t// Did we encounter a malformed JSON object?\r\n+\t\t\tif (jerr != json_tokener_success) {\r\n+\t\t\t\tfprintf(stderr,\r\n+\t\t\t\t\t\"JSON parse error: %s: '%.*s'\\n\",\r\n+\t\t\t\t\tjson_tokener_error_desc(jerr),\r\n+\t\t\t\t\t1,\r\n+\t\t\t\t\tbuf + buf_offset);\r\n+\t\t\t\tfflush(stderr);\r\n+\t\t\t\tbreak;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Our object is ready\r\n+\t\t\tprocess_message(jobj, cb, arg);\r\n+\t\t}\r\n+\t}\r\n+\r\n+\tjson_tokener_free(tok);\r\n+\tdelete buf;\r\n+}\r\n+\r\n+void json_util::process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+  if (!json_object_is_type(jobj, json_type_array))\r\n+    return;\r\n+  if (json_object_array_length(jobj) != 2)\r\n+    return;\r\n+\r\n+  cb(\r\n+  \tjson_util::to_string(json_object_array_get_idx(jobj, 0)),\r\n+  \tjson_object_array_get_idx(jobj, 1),\r\n+  \targ\r\n+  );\r\n+}\r\n+\r\n+} // namespace octave\r\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/json-util.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.h\tSat Jul 04 15:35:38 2020 -0500\n@@ -0,0 +1,64 @@\n+#ifndef json_util_h\r\n+#define json_util_h\r\n+\r\n+#include <json-c/json.h>\r\n+#include <map>\r\n+#include <list>\r\n+\r\n+#include \"syminfo.h\"\r\n+#include \"event-manager.h\"\r\n+\r\n+class string_vector;\r\n+\r\n+// All of the code interacting with the external JSON library should be in\r\n+// the json-util.h and json-util.cc files.  This way, if we want to change\r\n+// the external JSON library, we can do it all in one place.\r\n+\r\n+#define JSON_OBJECT_T json_object*\r\n+#define JSON_MAP_T std::map<std::string, JSON_OBJECT_T>\r\n+\r\n+#define JSON_MAP_SET(M, FIELD, TYPE){ \\\r\n+\tm[#FIELD] = json_util::from_##TYPE (FIELD); \\\r\n+}\r\n+\r\n+namespace octave {\r\n+\r\n+class json_util {\r\n+public:\r\n+\tstatic JSON_OBJECT_T from_string(const std::string& str);\r\n+\tstatic JSON_OBJECT_T from_int(int i);\r\n+\tstatic JSON_OBJECT_T from_float(float flt);\r\n+\tstatic JSON_OBJECT_T from_boolean(bool b);\r\n+\tstatic JSON_OBJECT_T empty();\r\n+\r\n+\tstatic JSON_OBJECT_T from_string_list(const std::list<std::string>& list);\r\n+\tstatic JSON_OBJECT_T from_string_vector(const string_vector& list);\r\n+\tstatic JSON_OBJECT_T from_int_list(const std::list<int>& list);\r\n+\tstatic JSON_OBJECT_T from_float_list(const std::list<float>& list);\r\n+\tstatic JSON_OBJECT_T from_symbol_info_list(const symbol_info_list& list);\r\n+        static JSON_OBJECT_T from_filter_list(const interpreter_events::filter_list& list);\r\n+\r\n+\tstatic JSON_OBJECT_T from_value_string(const std::string str);\r\n+\tstatic JSON_OBJECT_T from_symbol_info(const symbol_info element);\r\n+\tstatic JSON_OBJECT_T from_pair(std::pair<std::string, std::string> pair);\r\n+\r\n+\tstatic JSON_OBJECT_T from_map(JSON_MAP_T m);\r\n+\r\n+\tstatic std::string to_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic std::string to_string(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<std::list<int>, int> to_int_list_int_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<bool, std::string> to_bool_string_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::list<std::string> to_string_list(JSON_OBJECT_T jobj);\r\n+\tstatic int to_int(JSON_OBJECT_T jobj);\r\n+\tstatic bool to_boolean(JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic void read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+\r\n+private:\r\n+\tstatic void process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+};\r\n+\r\n+} // namespace octave\r\n+\r\n+#endif\r\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/module.mk\n--- a/libinterp/corefcn/module.mk\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libinterp/corefcn/module.mk\tSat Jul 04 15:35:38 2020 -0500\n@@ -45,6 +45,8 @@\n   %reldir%/help.h \\\n   %reldir%/hook-fcn.h \\\n   %reldir%/input.h \\\n+  %reldir%/json-main.h \\\n+  %reldir%/json-util.h \\\n   %reldir%/interpreter.h \\\n   %reldir%/load-path.h \\\n   %reldir%/load-save.h \\\n@@ -73,6 +75,7 @@\n   %reldir%/oct-strstrm.h \\\n   %reldir%/oct.h \\\n   %reldir%/octave-default-image.h \\\n+  %reldir%/octave-json-link.h \\\n   %reldir%/pager.h \\\n   %reldir%/pr-flt-fmt.h \\\n   %reldir%/pr-output.h \\\n@@ -182,6 +185,8 @@\n   %reldir%/hex2num.cc \\\n   %reldir%/hook-fcn.cc \\\n   %reldir%/input.cc \\\n+  %reldir%/json-main.cc \\\n+  %reldir%/json-util.cc \\\n   %reldir%/interpreter-private.cc \\\n   %reldir%/interpreter.cc \\\n   %reldir%/inv.cc \\\n@@ -218,6 +223,7 @@\n   %reldir%/oct-tex-lexer.ll \\\n   %reldir%/oct-tex-parser.h \\\n   %reldir%/oct-tex-parser.yy \\\n+  %reldir%/octave-json-link.cc \\\n   %reldir%/ordschur.cc \\\n   %reldir%/pager.cc \\\n   %reldir%/pinv.cc \\\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/octave-json-link.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -0,0 +1,411 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <iostream>\r\n+#include \"octave-json-link.h\"\r\n+#include \"cmd-edit.h\"\r\n+#include \"json-main.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+namespace octave {\r\n+\r\n+octave_json_link::octave_json_link(json_main* __json_main)\r\n+\t: interpreter_events (),\r\n+\t\t_json_main (__json_main)\r\n+{\r\n+\t_request_input_enabled = true;\r\n+\t_plot_destination = STATIC_ONLY;\r\n+}\r\n+\r\n+octave_json_link::~octave_json_link(void) { }\r\n+\r\n+std::string octave_json_link::request_input(const std::string& prompt) {\r\n+\t// Triggered whenever the console prompts for user input\r\n+\r\n+\tstd::string value;\r\n+\tif (!request_input_queue.dequeue_to(&value)) {\r\n+\t\t_publish_message(\"request-input\", json_util::from_string(prompt));\r\n+\t\tvalue = request_input_queue.dequeue();\r\n+\t}\r\n+\treturn value;\r\n+}\r\n+\r\n+std::string octave_json_link::request_url(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\r\n+\t// Triggered on urlread/urlwrite\r\n+\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, url, string);\r\n+\tJSON_MAP_SET(m, param, string_list);\r\n+\tJSON_MAP_SET(m, action, string);\r\n+\r\n+\t_publish_message(\"request-url\", json_util::from_map(m));\r\n+\tstd::pair<bool, std::string> result = request_url_queue.dequeue();\r\n+\tsuccess = result.first;\r\n+\treturn result.second;\r\n+}\r\n+\r\n+bool octave_json_link::confirm_shutdown(void) {\r\n+\t// Triggered when the kernel tries to exit\r\n+\t_publish_message(\"confirm-shutdown\", json_util::empty());\r\n+\r\n+\treturn confirm_shutdown_queue.dequeue();\r\n+}\r\n+\r\n+// do_exit was removed in Octave 5\r\n+// bool octave_json_link::do_exit(int status) {\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, status, int);\r\n+// \t_publish_message(\"exit\", json_util::from_map(m));\r\n+\r\n+// \t// It is our responsibility in octave_link to call exit. If we don't, then\r\n+// \t// the kernel waits for 24 hours expecting us to do something.\r\n+// \t::exit(status);\r\n+\r\n+// \treturn true;\r\n+// }\r\n+\r\n+bool octave_json_link::copy_image_to_clipboard(const std::string& file) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"copy-image-to-clipboard\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for existing files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::prompt_new_edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for new files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"prompt-new-edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn prompt_new_edit_file_queue.dequeue();\r\n+}\r\n+\r\n+// int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {\r\n+// \t// Triggered in \"msgbox\", \"helpdlg\", and \"errordlg\", among others\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\r\n+// \tJSON_MAP_SET(m, msg, string);\r\n+// \tJSON_MAP_SET(m, title, string);\r\n+// \t_publish_message(\"message-dialog\", json_util::from_map(m));\r\n+\r\n+// \treturn message_dialog_queue.dequeue();\r\n+// }\r\n+\r\n+std::string octave_json_link::question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n+\t// Triggered in \"questdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, msg, string);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, btn1, string);\r\n+\tJSON_MAP_SET(m, btn2, string);\r\n+\tJSON_MAP_SET(m, btn3, string);\r\n+\tJSON_MAP_SET(m, btndef, string);\r\n+\t_publish_message(\"question-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn question_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> octave_json_link::list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {\r\n+\t// Triggered in \"listdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, list, string_list);\r\n+\tJSON_MAP_SET(m, mode, string);\r\n+\tJSON_MAP_SET(m, width, int);\r\n+\tJSON_MAP_SET(m, height, int);\r\n+\tJSON_MAP_SET(m, initial_value, int_list);\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, ok_string, string);\r\n+\tJSON_MAP_SET(m, cancel_string, string);\r\n+\t_publish_message(\"list-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn list_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {\r\n+\t// Triggered in \"inputdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, nr, float_list);\r\n+\tJSON_MAP_SET(m, nc, float_list);\r\n+\tJSON_MAP_SET(m, defaults, string_list);\r\n+\t_publish_message(\"input-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn input_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {\r\n+\t// Triggered in \"uiputfile\", \"uigetfile\", and \"uigetdir\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, filter, filter_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, filename, string);\r\n+\tJSON_MAP_SET(m, pathname, string);\r\n+\tJSON_MAP_SET(m, multimode, string);\r\n+\t_publish_message(\"file-dialog\", json_util::from_map(m));\r\n+\t\r\n+\treturn file_dialog_queue.dequeue();\r\n+}\r\n+\r\n+int octave_json_link::debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\r\n+\t// This endpoint might be unused?  (No references)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\tJSON_MAP_SET(m, addpath_option, boolean);\r\n+\t_publish_message(\"debug-cd-or-addpath-error\", json_util::from_map(m));\r\n+\r\n+\treturn debug_cd_or_addpath_error_queue.dequeue();\r\n+}\r\n+\r\n+void octave_json_link::directory_changed(const std::string& dir) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\t_publish_message(\"change-directory\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::file_remove (const std::string& old_name, const std::string& new_name) {\r\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, old_name, string);\r\n+\tJSON_MAP_SET(m, new_name, string);\r\n+\t_publish_message(\"file-remove\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::file_renamed (bool status) {\r\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n+\t_publish_message(\"file-renamed\", json_util::from_boolean(status));\r\n+}\r\n+\r\n+void octave_json_link::execute_command_in_terminal(const std::string& command) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, command, string);\r\n+\t_publish_message(\"execute-command-in-terminal\", json_util::from_map(m));\r\n+}\r\n+\r\n+uint8NDArray octave_json_link::get_named_icon (const std::string& /* icon_name */) {\r\n+\t// Called from msgbox.m\r\n+\t// TODO: Implement request/response for this event\r\n+\tuint8NDArray retval;\r\n+\treturn retval;\r\n+}\r\n+\r\n+void octave_json_link::set_workspace(bool top_level, bool debug,\r\n+                         const symbol_info_list& ws,\r\n+                         bool update_variable_editor) {\r\n+\t// Triggered on every new line entry\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, top_level, boolean);\r\n+\tJSON_MAP_SET(m, debug, boolean);\r\n+\tJSON_MAP_SET(m, ws, symbol_info_list);\r\n+\tJSON_MAP_SET(m, update_variable_editor, boolean);\r\n+\t_publish_message(\"set-workspace\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::clear_workspace(void) {\r\n+\t// Triggered on \"clear\" command (but not \"clear all\" or \"clear foo\")\r\n+\t_publish_message(\"clear-workspace\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::set_history(const string_vector& hist) {\r\n+\t// Called at startup, possibly more?\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist, string_vector);\r\n+\t_publish_message(\"set-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::append_history(const std::string& hist_entry) {\r\n+\t// Appears to be tied to readline, if available\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist_entry, string);\r\n+\t_publish_message(\"append-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::clear_history(void) {\r\n+\t// Appears to be tied to readline, if available\r\n+\t_publish_message(\"clear-history\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::clear_screen(void) {\r\n+\t// Triggered by clc\r\n+\t_publish_message(\"clear-screen\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::pre_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::post_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::enter_debugger_event(const std::string& fcn_name, const std::string& fcn_file_name, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, fcn_name, string);\r\n+\tJSON_MAP_SET(m, fcn_file_name, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"enter-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::execute_in_debugger_event(const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"execute-in-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::exit_debugger_event(void) {\r\n+\t_publish_message(\"exit-debugger-event\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::update_breakpoint(bool insert, const std::string& file, int line, const std::string& cond) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, insert, boolean);\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\tJSON_MAP_SET(m, cond, string);\r\n+\t_publish_message(\"update-breakpoint\", json_util::from_map(m));\r\n+}\r\n+\r\n+// void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {\r\n+// \t// Triggered upon interpreter startup\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, ps1, string);\r\n+// \tJSON_MAP_SET(m, ps2, string);\r\n+// \tJSON_MAP_SET(m, ps4, string);\r\n+// \t_publish_message(\"set-default-prompts\", json_util::from_map(m));\r\n+// }\r\n+\r\n+void octave_json_link::show_preferences(void) {\r\n+\t// Triggered on \"preferences\" command\r\n+\t_publish_message(\"show-preferences\", json_util::empty());\r\n+}\r\n+\r\n+std::string octave_json_link::gui_preference (const std::string& /* key */, const std::string& /* value */) {\r\n+\t// Used by Octave GUI?\r\n+\t// TODO: Implement request/response for this event\r\n+\tstd::string retval;\r\n+\treturn retval;\r\n+}\r\n+\r\n+void octave_json_link::show_doc(const std::string& file) {\r\n+\t// Triggered on \"doc\" command\r\n+\t_publish_message(\"show-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::register_doc (const std::string& file) {\r\n+\t// Triggered by the GUI documentation viewer?\r\n+\t_publish_message(\"register-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::unregister_doc (const std::string& file) {\r\n+\t// Triggered by the GUI documentation viewer?\r\n+\t_publish_message(\"unregister-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::edit_variable (const std::string& name, const octave_value& /* val */) {\r\n+\t// Triggered on \"openvar\" command\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\t// TODO: val\r\n+\t_publish_message(\"edit-variable\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::show_static_plot(const std::string& term, const std::string& content) {\r\n+\t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\r\n+\tint command_number = command_editor::current_command_number();\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, term, string);\r\n+\tJSON_MAP_SET(m, content, string);\r\n+\tJSON_MAP_SET(m, command_number, int);\r\n+\t_publish_message(\"show-static-plot\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::receive_message (const std::string& name, JSON_OBJECT_T jobj) {\r\n+\tif (name == \"cmd\" || name == \"request-input-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\trequest_input_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"request-url-answer\") {\r\n+\t\tstd::pair<bool, std::string> answer = json_util::to_bool_string_pair(jobj);\r\n+\t\trequest_url_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"confirm-shutdown-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tconfirm_shutdown_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"prompt-new-edit-file-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tprompt_new_edit_file_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"message-dialog-answer\"){\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tmessage_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"question-dialog-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\tquestion_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"list-dialog-answer\") {\r\n+\t\tstd::pair<std::list<int>, int> answer = json_util::to_int_list_int_pair(jobj);\r\n+\t\tlist_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"input-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tinput_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"file-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tfile_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"debug-cd-or-addpath-error-answer\") {\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tdebug_cd_or_addpath_error_queue.enqueue(answer);\r\n+\t}\r\n+\telse {\r\n+\t\tstd::cerr << \"warning: received unknown message: \" << name << std::endl;\r\n+\t}\r\n+}\r\n+\r\n+void octave_json_link::_publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+\t_json_main->publish_message(name, jobj);\r\n+}\r\n+\r\n+} // namespace octave\r\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/octave-json-link.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.h\tSat Jul 04 15:35:38 2020 -0500\n@@ -0,0 +1,216 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifndef octave_json_link_h\r\n+#define octave_json_link_h\r\n+\r\n+#include <list>\r\n+#include <string>\r\n+\r\n+#include \"event-manager.h\"\r\n+#include \"json-util.h\"\r\n+#include \"oct-mutex.h\"\r\n+\r\n+class string_vector;\r\n+\r\n+namespace octave {\r\n+\r\n+// Circular reference\r\n+class json_main;\r\n+\r\n+// Thread-safe queue\r\n+template<typename T> class json_queue {\r\n+public:\r\n+  json_queue();\r\n+  ~json_queue();\r\n+\r\n+  void enqueue(const T& value);\r\n+  T dequeue();\r\n+  bool dequeue_to(T* destination);\r\n+\r\n+private:\r\n+  std::queue<T> _queue;\r\n+  mutex _mutex;\r\n+};\r\n+\r\n+class octave_json_link : public interpreter_events\r\n+{\r\n+\r\n+public:\r\n+\r\n+  octave_json_link (json_main* __json_main);\r\n+\r\n+  ~octave_json_link (void);\r\n+\r\n+  std::string request_input (const std::string& prompt) override;\r\n+  std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\r\n+\r\n+  bool confirm_shutdown (void) override;\r\n+\r\n+  bool copy_image_to_clipboard (const std::string& file) override;\r\n+\r\n+  bool edit_file (const std::string& file) override;\r\n+  bool prompt_new_edit_file (const std::string& file) override;\r\n+\r\n+  std::string\r\n+  question_dialog (const std::string& msg, const std::string& title,\r\n+                      const std::string& btn1, const std::string& btn2,\r\n+                      const std::string& btn3, const std::string& btndef) override;\r\n+\r\n+  std::pair<std::list<int>, int>\r\n+  list_dialog (const std::list<std::string>& list,\r\n+                  const std::string& mode,\r\n+                  int width, int height,\r\n+                  const std::list<int>& initial_value,\r\n+                  const std::string& name,\r\n+                  const std::list<std::string>& prompt,\r\n+                  const std::string& ok_string,\r\n+                  const std::string& cancel_string) override;\r\n+\r\n+  std::list<std::string>\r\n+  input_dialog (const std::list<std::string>& prompt,\r\n+                   const std::string& title,\r\n+                   const std::list<float>& nr,\r\n+                   const std::list<float>& nc,\r\n+                   const std::list<std::string>& defaults) override;\r\n+\r\n+  std::list<std::string>\r\n+  file_dialog (const filter_list& filter, const std::string& title,\r\n+                  const std::string &filename, const std::string &pathname,\r\n+                  const std::string& multimode) override;\r\n+\r\n+  int\r\n+  debug_cd_or_addpath_error (const std::string& file,\r\n+                                const std::string& dir,\r\n+                                bool addpath_option) override;\r\n+\r\n+  void directory_changed (const std::string& dir) override;\r\n+\r\n+  void file_remove (const std::string& old_name, const std::string& new_name) override;\r\n+  void file_renamed (bool) override;\r\n+\r\n+  void execute_command_in_terminal (const std::string& command) override;\r\n+\r\n+  uint8NDArray get_named_icon (const std::string& icon_name) override;\r\n+\r\n+  void set_workspace (bool top_level, bool debug,\r\n+                         const symbol_info_list& ws,\r\n+                         bool update_variable_editor) override;\r\n+\r\n+  void clear_workspace (void) override;\r\n+\r\n+  void set_history (const string_vector& hist) override;\r\n+  void append_history (const std::string& hist_entry) override;\r\n+  void clear_history (void) override;\r\n+\r\n+  void clear_screen (void) override;\r\n+\r\n+  void pre_input_event (void) override;\r\n+  void post_input_event (void) override;\r\n+\r\n+  void enter_debugger_event (const std::string& fcn_name, const std::string& fcn_file_name, int line) override;\r\n+  void execute_in_debugger_event (const std::string& file, int line) override;\r\n+  void exit_debugger_event (void) override;\r\n+\r\n+  void update_breakpoint (bool insert,\r\n+                             const std::string& file, int line,\r\n+                             const std::string& cond) override;\r\n+\r\n+  void show_preferences (void) override;\r\n+\r\n+  std::string gui_preference (const std::string& key, const std::string& value) override;\r\n+\r\n+  void show_doc (const std::string& file) override;\r\n+\r\n+  void register_doc (const std::string& file) override;\r\n+\r\n+  void unregister_doc (const std::string& file) override;\r\n+\r\n+  void edit_variable (const std::string& name, const octave_value& val) override;\r\n+\r\n+  void show_static_plot (const std::string& term,\r\n+                            const std::string& content) override;\r\n+\r\n+  // Custom methods\r\n+  void receive_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+  json_main* _json_main;\r\n+  void _publish_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+  // Queues\r\n+  json_queue<std::string> request_input_queue;\r\n+  json_queue<std::pair<bool, std::string> > request_url_queue;\r\n+  json_queue<bool> confirm_shutdown_queue;\r\n+  json_queue<bool> prompt_new_edit_file_queue;\r\n+  json_queue<int> message_dialog_queue;\r\n+  json_queue<std::string> question_dialog_queue;\r\n+  json_queue<std::pair<std::list<int>, int> > list_dialog_queue;\r\n+  json_queue<std::list<std::string> > input_dialog_queue;\r\n+  json_queue<std::list<std::string> > file_dialog_queue;\r\n+  json_queue<int> debug_cd_or_addpath_error_queue;\r\n+};\r\n+\r\n+// Template classes require definitions in the header file...\r\n+\r\n+template<typename T>\r\n+json_queue<T>::json_queue() { }\r\n+\r\n+template<typename T>\r\n+json_queue<T>::~json_queue() { }\r\n+\r\n+template<typename T>\r\n+void json_queue<T>::enqueue(const T& value) {\r\n+  _mutex.lock();\r\n+  _queue.push(value);\r\n+  _mutex.cond_signal();\r\n+  _mutex.unlock();\r\n+}\r\n+\r\n+template<typename T>\r\n+T json_queue<T>::dequeue() {\r\n+  _mutex.lock();\r\n+  while (_queue.empty()) {\r\n+    _mutex.cond_wait();\r\n+  }\r\n+  T value = _queue.front();\r\n+  _queue.pop();\r\n+  _mutex.unlock();\r\n+  return value;\r\n+}\r\n+\r\n+template<typename T>\r\n+bool json_queue<T>::dequeue_to(T* destination) {\r\n+  _mutex.lock();\r\n+  bool retval = false;\r\n+  if (!_queue.empty()) {\r\n+    retval = true;\r\n+    *destination = _queue.front();\r\n+    _queue.pop();\r\n+  }\r\n+  _mutex.unlock();\r\n+  return retval;\r\n+}\r\n+\r\n+} // namespace octave\r\n+  \r\n+#endif\r\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/syscalls.cc\n--- a/libinterp/corefcn/syscalls.cc\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libinterp/corefcn/syscalls.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -149,9 +149,8 @@\n @deftypefn {} {[@var{err}, @var{msg}] =} exec (@var{file}, @var{args})\n Replace current process with a new process.\n \n-Calling @code{exec} without first calling @code{fork} will terminate your\n-current Octave process and replace it with the program named by @var{file}.\n-For example,\n+Calling @code{exec} will terminate your current Octave process and replace\n+it with the program named by @var{file}. For example,\n \n @example\n exec (\"ls\", \"-l\")\n@@ -461,42 +460,6 @@\n   return ovl (status, msg);\n }\n \n-DEFMETHODX (\"fork\", Ffork, interp, args, ,\n-            doc: /* -*- texinfo -*-\n-@deftypefn {} {[@var{pid}, @var{msg}] =} fork ()\n-Create a copy of the current process.\n-\n-Fork can return one of the following values:\n-\n-@table @asis\n-@item > 0\n-You are in the parent process.  The value returned from @code{fork} is the\n-process id of the child process.  You should probably arrange to wait for\n-any child processes to exit.\n-\n-@item 0\n-You are in the child process.  You can call @code{exec} to start another\n-process.  If that fails, you should probably call @code{exit}.\n-\n-@item < 0\n-The call to @code{fork} failed for some reason.  You must take evasive\n-action.  A system dependent error message will be waiting in @var{msg}.\n-@end table\n-@end deftypefn */)\n-{\n-  if (args.length () != 0)\n-    print_usage ();\n-\n-  if (interp.at_top_level ())\n-    error (\"fork: cannot be called from command line\");\n-\n-  std::string msg;\n-\n-  pid_t pid = octave::sys::fork (msg);\n-\n-  return ovl (pid, msg);\n-}\n-\n DEFUNX (\"getpgrp\", Fgetpgrp, args, ,\n         doc: /* -*- texinfo -*-\n @deftypefn {} {pgid =} getpgrp ()\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/sysdep.cc\n--- a/libinterp/corefcn/sysdep.cc\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libinterp/corefcn/sysdep.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -75,6 +75,7 @@\n #include \"defun.h\"\n #include \"error.h\"\n #include \"errwarn.h\"\n+#include \"event-manager.h\"\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"octave.h\"\n@@ -651,7 +652,7 @@\n \n   // Read one character from the terminal.\n \n-  int kbhit (bool wait)\n+  int kbhit (const std::string& prompt, bool wait)\n   {\n #if defined (HAVE__KBHIT) && defined (HAVE__GETCH)\n     // This essentially means we are on a Windows system.\n@@ -678,13 +679,24 @@\n \n     set_interrupt_handler (saved_interrupt_handler, false);\n \n-    int c = std::cin.get ();\n+    int c;\n+    event_manager& evmgr = __get_event_manager__ (\"kbhit\");\n+    if (evmgr.request_input_enabled ()) {\n+      std::string line = evmgr.request_input (prompt);\n+      if (line.length() >= 1) {\n+        c = line.at(0);\n+      } else {\n+        c = '\\n';\n+      }\n+    } else {\n+      c = std::cin.get ();\n \n-    if (std::cin.fail () || std::cin.eof ())\n-      {\n-        std::cin.clear ();\n-        clearerr (stdin);\n-      }\n+      if (std::cin.fail () || std::cin.eof ())\n+        {\n+          std::cin.clear ();\n+          clearerr (stdin);\n+        }\n+    }\n \n     // Restore it, enabling system call restarts (if possible).\n     set_interrupt_handler (saved_interrupt_handler, true);\n@@ -743,6 +755,9 @@\n {\n   bool skip_redisplay = true;\n \n+  octave::event_manager& evmgr = octave::__get_event_manager__ (\"clc\");\n+  evmgr.clear_screen();\n+\n   octave::command_editor::clear_screen (skip_redisplay);\n \n   return ovl ();\n@@ -1169,7 +1184,7 @@\n \n   Fdrawnow (interp);\n \n-  int c = octave::kbhit (args.length () == 0);\n+  int c = octave::kbhit (\"kbhit>\", args.length () == 0);\n \n   if (c == -1)\n     c = 0;\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/sysdep.h\n--- a/libinterp/corefcn/sysdep.h\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libinterp/corefcn/sysdep.h\tSat Jul 04 15:35:38 2020 -0500\n@@ -49,7 +49,7 @@\n \n   extern OCTINTERP_API int pclose (FILE *f);\n \n-  extern OCTINTERP_API int kbhit (bool wait = true);\n+  extern OCTINTERP_API int kbhit (const std::string& prompt, bool wait);\n \n   extern OCTINTERP_API std::string get_P_tmpdir (void);\n \n@@ -107,7 +107,7 @@\n inline int\n octave_kbhit (bool wait = true)\n {\n-  return octave::kbhit (wait);\n+  return octave::kbhit (\"\", wait);\n }\n \n OCTAVE_DEPRECATED (5, \"use 'octave::get_P_tmpdir' instead\")\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/corefcn/utils.cc\n--- a/libinterp/corefcn/utils.cc\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libinterp/corefcn/utils.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -1442,7 +1442,7 @@\n             if (do_graphics_events)\n               gh_mgr.process_events ();\n \n-            c = kbhit (false);\n+            c = kbhit (\"press enter to continue\", false);\n           }\n       }\n     else\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/octave.cc\n--- a/libinterp/octave.cc\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libinterp/octave.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -187,6 +187,16 @@\n           case LINE_EDITING_OPTION:\n             m_forced_line_editing = m_line_editing = true;\n             break;\n+ \n+          case JSON_SOCK_OPTION:\n+            if (octave_optarg_wrapper ())\n+              m_json_sock_path = octave_optarg_wrapper ();\n+            break;\n+\n+          case JSON_MAX_LEN_OPTION:\n+            if (octave_optarg_wrapper ())\n+              m_json_max_message_length = strtol(octave_optarg_wrapper (), NULL, 10);\n+            break;\n \n           case NO_GUI_OPTION:\n             m_gui = false;\n@@ -372,6 +382,14 @@\n     sysdep_init ();\n   }\n \n+  bool application::link_enabled (void) const\n+  {\n+    if (m_interpreter) {\n+      event_manager& evmgr = m_interpreter->get_event_manager ();\n+      return evmgr.enabled();\n+    } else return false;\n+  }\n+\n   int cli_application::execute (void)\n   {\n     interpreter& interp = create_interpreter ();\n@@ -397,7 +415,7 @@\n   // FIXME: This isn't quite right, it just says that we intended to\n   // start the GUI, not that it is actually running.\n \n-  return ovl (octave::application::is_gui_running ());\n+  return ovl (octave::application::is_link_enabled ());\n }\n \n /*\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/octave.h\n--- a/libinterp/octave.h\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libinterp/octave.h\tSat Jul 04 15:35:38 2020 -0500\n@@ -79,6 +79,8 @@\n     std::string info_file (void) const { return m_info_file; }\n     std::string info_program (void) const { return m_info_program; }\n     std::string texi_macros_file (void) const {return m_texi_macros_file; }\n+    std::string json_sock_path (void) const { return m_json_sock_path; }\n+    int json_max_message_length (void) const { return m_json_max_message_length; }\n     string_vector all_args (void) const { return m_all_args; }\n     string_vector remaining_args (void) const { return m_remaining_args; }\n \n@@ -109,6 +111,8 @@\n     void info_file (const std::string& arg) { m_info_file = arg; }\n     void info_program (const std::string& arg) { m_info_program = arg; }\n     void texi_macros_file (const std::string& arg) { m_texi_macros_file = arg; }\n+    void json_sock_path (const std::string& arg) { m_json_sock_path = arg; }\n+    void json_max_message_length (int arg) { m_json_max_message_length = arg; }\n     void all_args (const string_vector& arg) { m_all_args = arg; }\n     void remaining_args (const string_vector& arg) { m_remaining_args = arg; }\n \n@@ -215,6 +219,14 @@\n     // (--texi-macros-file)\n     std::string m_texi_macros_file;\n \n+    // The value for \"JSON_SOCK\" specified on the command line.\n+    // (--json-sock)\n+    std::string m_json_sock_path;\n+\n+    // The maximum message length; valid only if \"JSON_SOCK\" is specified.\n+    // (--json-max-len)\n+    int m_json_max_message_length = 0;\n+\n     // All arguments passed to the argc, argv constructor.\n     string_vector m_all_args;\n \n@@ -228,6 +240,7 @@\n   // both) of them...\n \n   class interpreter;\n+  class event_manager;\n \n   // Base class for an Octave application.\n \n@@ -277,6 +290,8 @@\n     virtual bool gui_running (void) const { return false; }\n     virtual void gui_running (bool) { }\n \n+    bool link_enabled (void) const;\n+\n     void program_invocation_name (const std::string& nm) { m_program_invocation_name = nm; }\n \n     void program_name (const std::string& nm) { m_program_name = nm; }\n@@ -305,6 +320,11 @@\n       return instance ? instance->gui_running () : false;\n     }\n \n+    static bool is_link_enabled (void)\n+    {\n+      return instance ? instance->link_enabled () : false;\n+    }\n+\n     // Convenience functions.\n \n     static bool forced_interactive (void);\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc libinterp/options-usage.h\n--- a/libinterp/options-usage.h\tSat Jul 04 11:13:35 2020 +0900\n+++ b/libinterp/options-usage.h\tSat Jul 04 15:35:38 2020 -0500\n@@ -38,10 +38,10 @@\n        [--echo-commands] [--eval CODE] [--exec-path path]\\n\\\n        [--gui] [--help] [--image-path path]\\n\\\n        [--info-file file] [--info-program prog] [--interactive]\\n\\\n-       [--jit-compiler] [--line-editing] [--no-gui] [--no-history]\\n\\\n-       [--no-init-file] [--no-init-path] [--no-line-editing]\\n\\\n-       [--no-site-file] [--no-window-system] [--norc] [-p path]\\n\\\n-       [--path path] [--persist] [--silent] [--traditional]\\n\\\n+       [--jit-compiler] [--json-sock] [--json-max-len] [--line-editing]\\n\\\n+       [--no-gui] [--no-history][--no-init-file] [--no-init-path]\\n\\\n+       [--no-line-editing] [--no-site-file] [--no-window-system] [--norc]\\n\\\n+       [-p path] [--path path] [--persist] [--silent] [--traditional]\\n\\\n        [--verbose] [--version] [file]\";\n \n // This is here so that it's more likely that the usage message and\n@@ -68,15 +68,17 @@\n #define INFO_PROG_OPTION 8\n #define DEBUG_JIT_OPTION 9\n #define JIT_COMPILER_OPTION 10\n-#define LINE_EDITING_OPTION 11\n-#define NO_GUI_OPTION 12\n-#define NO_INIT_FILE_OPTION 13\n-#define NO_INIT_PATH_OPTION 14\n-#define NO_LINE_EDITING_OPTION 15\n-#define NO_SITE_FILE_OPTION 16\n-#define PERSIST_OPTION 17\n-#define TEXI_MACROS_FILE_OPTION 18\n-#define TRADITIONAL_OPTION 19\n+#define JSON_SOCK_OPTION 11\n+#define JSON_MAX_LEN_OPTION 12\n+#define LINE_EDITING_OPTION 13\n+#define NO_GUI_OPTION 14\n+#define NO_INIT_FILE_OPTION 15\n+#define NO_INIT_PATH_OPTION 16\n+#define NO_LINE_EDITING_OPTION 17\n+#define NO_SITE_FILE_OPTION 18\n+#define PERSIST_OPTION 19\n+#define TEXI_MACROS_FILE_OPTION 20\n+#define TRADITIONAL_OPTION 21\n struct octave_getopt_options long_opts[] =\n {\n   { \"braindead\",                octave_no_arg,       0, TRADITIONAL_OPTION },\n@@ -94,6 +96,8 @@\n   { \"info-program\",             octave_required_arg, 0, INFO_PROG_OPTION },\n   { \"interactive\",              octave_no_arg,       0, 'i' },\n   { \"jit-compiler\",             octave_no_arg,       0, JIT_COMPILER_OPTION },\n+  { \"json-sock\",                octave_required_arg, 0, JSON_SOCK_OPTION },\n+  { \"json-max-len\",             octave_required_arg, 0, JSON_MAX_LEN_OPTION },\n   { \"line-editing\",             octave_no_arg,       0, LINE_EDITING_OPTION },\n   { \"no-gui\",                   octave_no_arg,       0, NO_GUI_OPTION },\n   { \"no-history\",               octave_no_arg,       0, 'H' },\n@@ -145,6 +149,8 @@\n   --info-program PROGRAM  Use PROGRAM for reading info files.\\n\\\n   --interactive, -i       Force interactive behavior.\\n\\\n   --jit-compiler          Enable the JIT compiler.\\n\\\n+  --json-sock PATH        Listen to and publish events on this UNIX socket.\\n\\\n+  --json-max-len LEN      Suppress JSON messages greater than LEN bytes.\\n\\\n   --line-editing          Force readline use for command-line editing.\\n\\\n   --no-gui                Disable the graphical user interface.\\n\\\n   --no-history, -H        Don't save commands to the history list\\n\\\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc liboctave/util/oct-mutex.cc\n--- a/liboctave/util/oct-mutex.cc\tSat Jul 04 11:13:35 2020 +0900\n+++ b/liboctave/util/oct-mutex.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -58,6 +58,18 @@\n     return false;\n   }\n \n+  void\n+  base_mutex::cond_wait (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n+  void\n+  base_mutex::cond_signal (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n #if defined (OCTAVE_USE_WINDOWS_API)\n \n   class\n@@ -68,11 +80,13 @@\n       : base_mutex ()\n     {\n       InitializeCriticalSection (&cs);\n+      InitializeConditionVariable (&cv);\n     }\n \n     ~w32_mutex (void)\n     {\n       DeleteCriticalSection (&cs);\n+      // no need to delete cv: http://stackoverflow.com/a/28981408/1407170\n     }\n \n     void lock (void)\n@@ -90,8 +104,19 @@\n       return (TryEnterCriticalSection (&cs) != 0);\n     }\n \n+    void cond_wait (void)\n+    {\n+      SleepConditionVariableCS (&cv, &cs, INFINITE);\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      WakeConditionVariable (&cv);\n+    }\n+\n   private:\n     CRITICAL_SECTION cs;\n+    CONDITION_VARIABLE cv;\n   };\n \n   static DWORD thread_id = 0;\n@@ -123,11 +148,19 @@\n       pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);\n       pthread_mutex_init (&m_pm, &attr);\n       pthread_mutexattr_destroy (&attr);\n+\n+      pthread_condattr_t condattr;\n+\n+      pthread_condattr_init (&condattr);\n+      pthread_cond_init (&condv, &condattr);\n+      pthread_condattr_destroy (&condattr);\n     }\n \n     ~pthread_mutex (void)\n     {\n       pthread_mutex_destroy (&m_pm);\n+      pthread_mutex_destroy (&m_pm);\n+      pthread_cond_destroy (&condv);\n     }\n \n     void lock (void)\n@@ -145,8 +178,19 @@\n       return (pthread_mutex_trylock (&m_pm) == 0);\n     }\n \n+    void cond_wait (void)\n+    {\n+      pthread_cond_wait (&condv, &m_pm);\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      pthread_cond_signal (&condv);\n+    }\n+\n   private:\n     pthread_mutex_t m_pm;\n+    pthread_cond_t condv;\n   };\n \n   static pthread_t thread_id = 0;\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc liboctave/util/oct-mutex.h\n--- a/liboctave/util/oct-mutex.h\tSat Jul 04 11:13:35 2020 +0900\n+++ b/liboctave/util/oct-mutex.h\tSat Jul 04 15:35:38 2020 -0500\n@@ -50,6 +50,10 @@\n \n     virtual bool try_lock (void);\n \n+    virtual void cond_wait (void);\n+\n+    virtual void cond_signal (void);\n+\n   private:\n     refcount<octave_idx_type> m_count;\n   };\n@@ -102,6 +106,16 @@\n       return m_rep->try_lock ();\n     }\n \n+    void cond_wait (void)\n+    {\n+      m_rep->cond_wait ();\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      m_rep->cond_signal ();\n+    }\n+\n   protected:\n     base_mutex *m_rep;\n   };\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tSat Jul 04 11:13:35 2020 +0900\n+++ b/liboctave/util/url-transfer.cc\tSat Jul 04 15:35:38 2020 -0500\n@@ -36,6 +36,7 @@\n #include \"file-stat.h\"\n #include \"lo-sysdep.h\"\n #include \"oct-env.h\"\n+#include \"gnulib/lib/base64.h\"\n #include \"unwind-prot.h\"\n #include \"url-transfer.h\"\n #include \"version.h\"\n@@ -48,6 +49,10 @@\n \n namespace octave\n {\n+  // Forward declaration for event_manager\n+  extern bool __event_manager_request_input_enabled__();\n+  extern std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n   base_url_transfer::base_url_transfer (void)\n     : m_host_or_url (), m_valid (false), m_ftp (false),\n       m_ascii_mode (false), m_ok (true), m_errmsg (),\n@@ -240,6 +245,86 @@\n     return file_list;\n   }\n \n+\n+class link_transfer : public base_url_transfer\n+{\n+public:\n+\n+  link_transfer (void)\n+      : base_url_transfer () {\n+    m_valid = true;\n+  }\n+\n+  link_transfer (const std::string& host, const std::string& user_arg,\n+                 const std::string& passwd, std::ostream& os)\n+      : base_url_transfer (host, user_arg, passwd, os) {\n+    m_valid = true;\n+    // url = \"ftp://\" + host;\n+  }\n+\n+  link_transfer (const std::string& url_str, std::ostream& os)\n+      : base_url_transfer (url_str, os) {\n+    m_valid = true;\n+  }\n+\n+  ~link_transfer (void) {}\n+\n+  void http_get (const Array<std::string>& param) {\n+    perform_action (param, \"get\");\n+  }\n+\n+  void http_post (const Array<std::string>& param) {\n+    perform_action (param, \"post\");\n+  }\n+\n+  void http_action (const Array<std::string>& param, const std::string& action) {\n+    perform_action (param, action);\n+  }\n+\n+private:\n+  void perform_action(const Array<std::string>& param, const std::string& action) {\n+    std::string url = m_host_or_url;\n+\n+    // Convert from Array to std::list\n+    std::list<std::string> paramList;\n+    for (int i = 0; i < param.numel(); i ++) {\n+      std::string value = param(i);\n+      paramList.push_back(value);\n+    }\n+\n+    if (__event_manager_request_input_enabled__()) {\n+      bool success;\n+      std::string result = __event_manager_request_url__(url, paramList, action, success);\n+      if (success) {\n+        process_success(result);\n+      } else {\n+        m_ok = false;\n+        m_errmsg = result;\n+      }\n+    } else {\n+      m_ok = false;\n+      m_errmsg = \"octave_link not connected for link_transfer\";\n+    }\n+  }\n+\n+  void process_success(const std::string& result) {\n+    // If success, the result is returned as a base64 string, and we need to decode it.\n+    // Use the base64 implementation from gnulib, which is already an Octave dependency.\n+    const char *inc = &(result[0]);\n+    char *out;\n+    size_t outlen;\n+    bool b64_ok = base64_decode_alloc(inc, result.length(), &out, &outlen);\n+    if (!b64_ok) {\n+      m_ok = false;\n+      m_errmsg = \"failed decoding base64 from octave_link\";\n+    } else {\n+      m_curr_ostream->write(out, outlen);\n+      ::free(out);\n+    }\n+  }\n+};\n+\n+\n #if defined (HAVE_CURL)\n \n   static int\n@@ -941,17 +1026,30 @@\n #  define REP_CLASS base_url_transfer\n #endif\n \n-  url_transfer::url_transfer (void) : m_rep (new REP_CLASS ())\n-  { }\n+  url_transfer::url_transfer (void) {\n+    if (__event_manager_request_input_enabled__()) {\n+      m_rep.reset(new link_transfer());\n+    } else {\n+      m_rep.reset(new REP_CLASS());\n+    }\n+  }\n \n   url_transfer::url_transfer (const std::string& host, const std::string& user,\n-                              const std::string& passwd, std::ostream& os)\n-    : m_rep (new REP_CLASS (host, user, passwd, os))\n-  { }\n+                              const std::string& passwd, std::ostream& os) {\n+    if (__event_manager_request_input_enabled__()) {\n+      m_rep.reset(new link_transfer(host, user, passwd, os));\n+    } else {\n+      m_rep.reset(new REP_CLASS(host, user, passwd, os));\n+    }\n+  }\n \n-  url_transfer::url_transfer (const std::string& url, std::ostream& os)\n-    : m_rep (new REP_CLASS (url, os))\n-  { }\n+  url_transfer::url_transfer (const std::string& url, std::ostream& os) {\n+    if (__event_manager_request_input_enabled__()) {\n+      m_rep.reset(new link_transfer(url, os));\n+    } else {\n+      m_rep.reset(new REP_CLASS(url, os));\n+    }\n+  }\n \n #undef REP_CLASS\n \ndiff -r 171a2857d6d1 -r 1e1c91e6cddc scripts/help/__unimplemented__.m\n--- a/scripts/help/__unimplemented__.m\tSat Jul 04 11:13:35 2020 +0900\n+++ b/scripts/help/__unimplemented__.m\tSat Jul 04 15:35:38 2020 -0500\n@@ -45,7 +45,30 @@\n \n   is_matlab_function = true;\n \n+  ## First look at the package metadata\n+  # To generate package_metadata.mat, run: packages={}; for p=pkg('list'); packages={packages{:} pkg('describe', '-verbose', p{1}.name){:}}; endfor; save('/usr/local/share/octave/site/m/package_metadata.mat', 'packages');\n+  found_in_package_metadata = false;\n+  try\n+    vars = load(\"/usr/local/share/octave/site/m/package_metadata.mat\");\n+    for lvl1 = vars.packages\n+      for lvl2 = lvl1{1}.provides\n+        for lvl3 = lvl2{1}.functions\n+          if strcmp(fcn, lvl3{1})\n+            txt = check_package(fcn, lvl1{1}.name);\n+            found_in_package_metadata = true;\n+            break;\n+          endif\n+        endfor\n+        if found_in_package_metadata, break; endif\n+      endfor\n+      if found_in_package_metadata, break; endif\n+    endfor\n+  catch err\n+    warning(err)\n+  end_try_catch\n+\n   ## Some smarter cases, add more as needed.\n+  if !found_in_package_metadata\n   switch (fcn)\n     case {\"avifile\", \"aviinfo\", \"aviread\"}\n       txt = [\"Basic video file support is provided in the video package.  \", ...\n@@ -524,6 +547,7 @@\n         txt = \"\";\n       endif\n   endswitch\n+  endif\n \n   if (is_matlab_function)\n     txt = [txt, \"\\n\\n@noindent\\nPlease read \", ...\n@@ -566,13 +590,13 @@\n       endfor\n       txt = sprintf (\"%s but has not yet been implemented.\", txt);\n     case \"not loaded\",\n-      txt = sprintf ([\"%s which you have installed but not loaded.  To \", ...\n-                      \"load the package, run 'pkg load %s' from the \", ...\n-                      \"Octave prompt.\"], txt, name);\n+      txt = sprintf ([\"%s, which you have installed but not loaded.\\n\\n\", ...\n+                      \"Run `pkg load %s' to use `%s'.\"], ...\n+                     txt, name, fcn);\n     otherwise\n       ## this includes \"not installed\" and anything else if pkg changes\n       ## the output of describe\n-      txt = sprintf (\"%s which seems to not be installed in your system.\", txt);\n+      txt = sprintf (\"%s, which seems to not be installed in your system.\", txt);\n   endswitch\n \n endfunction\ndiff -r 171a2857d6d1 -r 1e1c91e6cddc scripts/plot/util/__gnuplot_drawnow__.m\n--- a/scripts/plot/util/__gnuplot_drawnow__.m\tSat Jul 04 11:13:35 2020 +0900\n+++ b/scripts/plot/util/__gnuplot_drawnow__.m\tSat Jul 04 15:35:38 2020 -0500\n@@ -32,9 +32,84 @@\n \n   if (nargin < 1 || nargin > 4 || nargin == 2)\n     print_usage ();\n+\n+  elseif (nargin >= 3 && nargin <= 4)\n+    ## Write the plot to the given file (e.g., via the \"print\" command)\n+    if (nargin == 5)\n+      __gnuplot_draw_to_file__ (h, term, file, debug_file);\n+    else\n+      __gnuplot_draw_to_file__ (h, term, file);\n+    endif\n+\n+  else  # nargin == 1\n+    ##  Plot to terminal and/or static (e.g., via the \"plot\" command)\n+    plot_stream = get (h, \"__plot_stream__\");\n+    if (isempty (plot_stream))\n+      plot_stream = __gnuplot_open_stream__ (2, h);\n+      new_stream = true;\n+    else\n+      new_stream = false;\n+    endif\n+    term = gnuplot_default_term (plot_stream);\n+\n+    ## There are a few options for how we can proceed.\n+    ## In most cases, we will tell GNUPLOT to put the plot in its terminal.\n+    ## If we have no display, we want to use the \"dumb\" terminal.\n+    ## Octave Link may request that we send the plot as an event.\n+    ## The latter two cases require plotting to a temp file.\n+\n+    should_plot_to_terminal = (\n+      !strcmp (term, \"dumb\") && (\n+        __event_manager_plot_destination__ () == 0 ||\n+        __event_manager_plot_destination__ () == 2\n+      )\n+    );\n+\n+    if (should_plot_to_terminal)\n+      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n+      __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n+      fflush (plot_stream(1));\n+    endif\n+\n+    should_plot_to_temp_file = (\n+      strcmp (term, \"dumb\") ||\n+      __event_manager_plot_destination__ () == 1 ||\n+      __event_manager_plot_destination__ () == 2\n+    );\n+\n+    if (should_plot_to_temp_file)\n+      tmp_file = tempname ();\n+      __gnuplot_draw_to_file__ (h, term, tmp_file);\n+      fflush (plot_stream(1));\n+\n+      ## Read the temp file into memory and then delete it\n+      fid = fopen (tmp_file, 'r');\n+      while (fid < 0)\n+        fprintf (stderr, \"🛈 Waiting for plot to finish… ⏳\\n\");\n+        pause (0.5);\n+        fid = fopen (tmp_file, 'r');\n+      endwhile\n+      [a, count] = fscanf (fid, '%c', Inf);\n+      fclose (fid);\n+      unlink (tmp_file);\n+\n+      ## What to do with the plot data?\n+      if (count > 0)\n+        if (a(1) == 12)\n+          a = a(2:end);  # avoid ^L at the beginning\n+        endif\n+        if strcmp (term, \"dumb\")\n+          puts (a);\n+        else\n+          __event_manager_show_static_plot__ (term, a);\n+        endif\n+      endif\n+    endif\n+\n   endif\n+endfunction\n \n-  if (nargin >= 3 && nargin <= 4)\n+function __gnuplot_draw_to_file__ (h, term, file, debug_file)\n     ## Produce various output formats, or redirect gnuplot stream to a\n     ## debug file.\n     plot_stream = [];\n@@ -70,44 +145,6 @@\n         fclose (fid);\n       endif\n     end_unwind_protect\n-  else  # nargin == 1\n-    ##  Graphics terminal for display.\n-    plot_stream = get (h, \"__plot_stream__\");\n-    if (isempty (plot_stream))\n-      plot_stream = __gnuplot_open_stream__ (2, h);\n-      new_stream = true;\n-    else\n-      new_stream = false;\n-    endif\n-    term = gnuplot_default_term (plot_stream);\n-    if (strcmp (term, \"dumb\"))\n-      ## popen2 eats stdout of gnuplot, use temporary file instead\n-      dumb_tmp_file = tempname ();\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h,\n-                                   term, dumb_tmp_file);\n-    else\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n-    endif\n-    __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n-    fflush (plot_stream(1));\n-    if (strcmp (term, \"dumb\"))\n-      fid = -1;\n-      while (fid < 0)\n-        pause (0.1);\n-        fid = fopen (dumb_tmp_file, 'r');\n-      endwhile\n-      ## reprint the plot on screen\n-      [a, count] = fscanf (fid, '%c', Inf);\n-      fclose (fid);\n-      if (count > 0)\n-        if (a(1) == 12)\n-          a = a(2:end);  # avoid ^L at the beginning\n-        endif\n-        puts (a);\n-      endif\n-      unlink (dumb_tmp_file);\n-    endif\n-  endif\n \n endfunction\n \n"
  },
  {
    "path": "back-octave/oo-changesets/320-8d4683a83238.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1640589439 21600\n#      Mon Dec 27 01:17:19 2021 -0600\n# Branch oo-6.4.0\n# Node ID 8d4683a83238d9f41c16f6b9138c72530f0cc9a6\n# Parent  8d7671609955afabf79ceff678cc41eea61583f2\n# Parent  1e1c91e6cddc3c48870e390b2730c7b586cc8a89\nMerge oo-6.0.1 into oo-6.4.0\n\ndiff -r 8d7671609955 -r 8d4683a83238 configure.ac\n--- a/configure.ac\tSat Oct 30 10:20:24 2021 -0400\n+++ b/configure.ac\tMon Dec 27 01:17:19 2021 -0600\n@@ -2833,7 +2833,7 @@\n AC_SUBST(LIBOCTAVE_LINK_DEPS)\n AC_SUBST(LIBOCTAVE_LINK_OPTS)\n \n-LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $SPARSE_XLIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $LLVM_LIBS $JAVA_LIBS $LAPACK_LIBS\"\n+LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $SPARSE_XLIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $LLVM_LIBS $JAVA_LIBS $LAPACK_LIBS -ljson-c\"\n \n LIBOCTINTERP_LINK_OPTS=\"$FT2_LDFLAGS $HDF5_LDFLAGS $MAGICK_LDFLAGS $Z_LDFLAGS $SPARSE_XLDFLAGS $FFTW_XLDFLAGS $LLVM_LDFLAGS\"\n \ndiff -r 8d7671609955 -r 8d4683a83238 libgui/src/qt-interpreter-events.cc\n--- a/libgui/src/qt-interpreter-events.cc\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libgui/src/qt-interpreter-events.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -264,6 +264,21 @@\n     emit edit_variable_signal (QString::fromStdString (expr), val);\n   }\n \n+  void qt_interpreter_events::show_static_plot (const std::string&, const std::string&)\n+  {\n+    return;\n+  }\n+\n+  std::string qt_interpreter_events::request_input (const std::string&)\n+  {\n+    return {};\n+  }\n+\n+  std::string qt_interpreter_events::request_url (const std::string&, const std::list<std::string>&, const std::string&, bool&)\n+  {\n+    return {};\n+  }\n+\n   bool qt_interpreter_events::confirm_shutdown (void)\n   {\n     QMutexLocker autolock (&m_mutex);\n@@ -508,6 +523,9 @@\n     emit clear_history_signal ();\n   }\n \n+  void qt_interpreter_events::do_clear_screen (void)\n+  { }\n+\n   void qt_interpreter_events::pre_input_event (void)\n   { }\n \ndiff -r 8d7671609955 -r 8d4683a83238 libgui/src/qt-interpreter-events.h\n--- a/libgui/src/qt-interpreter-events.h\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libgui/src/qt-interpreter-events.h\tMon Dec 27 01:17:19 2021 -0600\n@@ -118,6 +118,12 @@\n \n     void edit_variable (const std::string& name, const octave_value& val);\n \n+    void show_static_plot (const std::string& term, const std::string& content);\n+\n+    std::string request_input (const std::string&);\n+\n+    std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n     bool confirm_shutdown (void);\n \n     bool prompt_new_edit_file (const std::string& file);\n@@ -160,6 +166,8 @@\n \n     void clear_history (void);\n \n+    void clear_screen (void);\n+\n     void pre_input_event (void);\n \n     void post_input_event (void);\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/event-manager.cc\n--- a/libinterp/corefcn/event-manager.cc\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/corefcn/event-manager.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -41,6 +41,17 @@\n \n namespace octave\n {\n+\n+  bool __event_manager_request_input_enabled__() {\n+    event_manager& evmgr = __get_event_manager__ (\"request_input_enabled\");\n+    return evmgr.request_input_enabled();\n+  }\n+\n+  std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\n+    event_manager& evmgr = __get_event_manager__ (\"request_url\");\n+    return evmgr.request_url(url, param, action, success);\n+  }\n+\n   static int readline_event_hook (void)\n   {\n     event_manager& evmgr = __get_event_manager__ (\"octave_readline_hook\");\n@@ -652,3 +663,28 @@\n   evmgr.focus_window (\"workspace\");\n   return ovl ();\n }\n+\n+DEFMETHOD (__event_manager_plot_destination__, interp, , ,\n+           doc: /* -*- texinfo -*-\n+@deftypefn {} {} __event_manager_plot_destination__ ()\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  return ovl (interp.get_event_manager().plot_destination());\n+}\n+\n+DEFMETHOD (__event_manager_show_static_plot__, interp, args, ,\n+           doc: /* -*- texinfo -*-\n+@deftypefn {} {} __event_manager_show_static_plot__ (@var{term}, @var{content})\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  if (args.length () != 2) {\n+    return ovl ();\n+  }\n+\n+  std::string term = args(0).string_value();\n+  std::string content = args(1).string_value();\n+  return ovl (interp.get_event_manager().show_static_plot(term, content));\n+}\n+\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/event-manager.h\n--- a/libinterp/corefcn/event-manager.h\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/corefcn/event-manager.h\tMon Dec 27 01:17:19 2021 -0600\n@@ -48,6 +48,12 @@\n \n   class symbol_info_list;\n \n+  enum plot_destination_t {\n+    TERMINAL_ONLY = 0,\n+    STATIC_ONLY = 1,\n+    TERMINAL_AND_STATIC = 2\n+  };\n+\n   // The methods in this class provide a way to pass signals to the GUI\n   // thread.  A GUI that wishes to act on these events should derive\n   // from this class and perform actions in a thread-safe way.  In\n@@ -147,6 +153,13 @@\n     // confirmation before another action.  Could these be reformulated\n     // using the question_dialog action?\n \n+    bool _request_input_enabled;\n+    virtual std::string request_input (const std::string&) = 0;\n+    virtual std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) = 0;\n+\n+    plot_destination_t _plot_destination;\n+    virtual void show_static_plot (const std::string& term, const std::string& content) = 0;\n+\n     virtual bool confirm_shutdown (void) { return false; }\n \n     virtual bool prompt_new_edit_file (const std::string& /*file*/)\n@@ -220,6 +233,8 @@\n \n     virtual void clear_history (void) { }\n \n+    virtual void clear_screen (void) { }\n+\n     virtual void pre_input_event (void) { }\n \n     virtual void post_input_event (void) { }\n@@ -373,6 +388,28 @@\n         instance->update_path_dialog ();\n     }\n \n+    bool request_input_enabled (void)\n+    {\n+      return enabled () ? instance->_request_input_enabled : false;\n+    }\n+\n+    plot_destination_t plot_destination (void)\n+    {\n+      return enabled () ? instance->_plot_destination : TERMINAL_ONLY;\n+    }\n+\n+    bool\n+    show_static_plot (const std::string& term, const std::string& content)\n+    {\n+      if (enabled ())\n+        {\n+          instance->show_static_plot (term, content);\n+          return true;\n+        }\n+      else\n+        return false;\n+    }\n+\n     bool show_preferences (void)\n     {\n       if (enabled ())\n@@ -551,6 +588,12 @@\n         instance->clear_history ();\n     }\n \n+    void clear_screen (void)\n+    {\n+      if (enabled ())\n+        instance->clear_screen ();\n+    }\n+\n     void pre_input_event (void)\n     {\n       if (enabled ())\n@@ -563,6 +606,21 @@\n         instance->post_input_event ();\n     }\n \n+\n+    std::string request_input (const std::string& prompt)\n+    {\n+      return request_input_enabled ()\n+        ? instance->request_input (prompt)\n+        : std::string ();\n+    }\n+\n+    std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success)\n+    {\n+      return request_input_enabled ()\n+        ? instance->request_url (url, param, action, success)\n+        : std::string ();\n+    }\n+\n     void enter_debugger_event (const std::string& fcn_name,\n                                const std::string& fcn_file_name, int line)\n     {\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/input.cc\n--- a/libinterp/corefcn/input.cc\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/corefcn/input.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -699,7 +699,12 @@\n \n     eof = false;\n \n-    std::string retval = command_editor::readline (s, eof);\n+    std::string retval;\n+    event_manager& evmgr = m_interpreter.get_event_manager ();\n+    if (evmgr.request_input_enabled ())\n+      retval = evmgr.request_input (s);\n+    else\n+      retval = command_editor::readline (s, eof);\n \n     if (! eof && retval.empty ())\n       retval = \"\\n\";\n@@ -1525,3 +1530,32 @@\n }\n \n // #endif\n+\n+DEFUN (current_command_number, args, ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn  {} {@var{val} =} current_command_number ()\n+@deftypefnx {} {@var{old_val} =} current_command_number (@var{new_val})\n+Sets the current command number, which appears in the prompt string.\n+For example, if the prompt says \"octave:1>\", then the current command\n+number is 1.\n+\n+This is a custom function in Octave Online.\n+\n+@example\n+current_command_number(1)\n+@end example\n+@end deftypefn */)\n+{\n+  int nargin = args.length ();\n+  if (nargin == 0) {\n+    int n = octave::command_editor::current_command_number();\n+    return ovl(n);\n+  } else if (nargin > 1) {\n+    print_usage ();\n+    return ovl();\n+  } else {\n+    int n = args(0).int_value ();\n+    octave::command_editor::reset_current_command_number(n);\n+    return ovl(n);\n+  }\n+}\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/interpreter.cc\n--- a/libinterp/corefcn/interpreter.cc\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/corefcn/interpreter.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -59,6 +59,7 @@\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"interpreter.h\"\n+#include \"json-main.h\"\n #include \"load-path.h\"\n #include \"load-save.h\"\n #include \"octave.h\"\n@@ -599,6 +600,11 @@\n         std::string texi_macros_file = options.texi_macros_file ();\n         if (! texi_macros_file.empty ())\n           Ftexi_macros_file (*this, octave_value (texi_macros_file));\n+\n+        if (!options.json_sock_path().empty ()) {\n+          static json_main _json_main (*this, options.json_sock_path(), options.json_max_message_length());\n+          _json_main.run_loop_on_new_thread();\n+        }\n       }\n \n     // FIXME: we defer creation of the gh_manager object because it\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/json-main.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -0,0 +1,101 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include \"json-main.h\"\r\n+#include \"interpreter.h\"\r\n+\r\n+#include <iostream>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <unistd.h>\r\n+\r\n+\r\n+// Analog of main-window.cc\r\n+// TODO: Think more about concurrency and null pointer exceptions\r\n+\r\n+namespace octave {\r\n+\r\n+void* run_loop_pthread(void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->run_loop();\r\n+  return NULL;\r\n+}\r\n+\r\n+void json_object_cb(std::string name, JSON_OBJECT_T jobj, void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->process_json_object(name, jobj);\r\n+}\r\n+\r\n+json_main::json_main(interpreter& interp, const std::string& json_sock_path, int max_message_length)\r\n+  : _json_sock_path (json_sock_path),\r\n+    _max_message_length (max_message_length),\r\n+    _loop_thread_active (false),\r\n+    _octave_json_link (new octave_json_link(this))\r\n+{\r\n+  // Enable the octave_json_link instance\r\n+  // Note: this passes ownership to octave_link\r\n+  event_manager& evmgr = interp.get_event_manager ();\r\n+  evmgr.connect_link (_octave_json_link);\r\n+  evmgr.enable ();\r\n+\r\n+  // Open UNIX socket file descriptor\r\n+  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\r\n+  struct sockaddr_un addr;\r\n+  memset(&addr, 0, sizeof(addr));\r\n+  addr.sun_family = AF_UNIX;\r\n+  memcpy(&addr.sun_path, _json_sock_path.c_str(), sizeof(addr.sun_path)-1);\r\n+  connect(\r\n+    sockfd,\r\n+    reinterpret_cast<struct sockaddr*>(&addr),\r\n+    sizeof(addr));\r\n+}\r\n+\r\n+json_main::~json_main(void) {\r\n+  close(sockfd);\r\n+\r\n+  // TODO: Stop the _loop_thread\r\n+}\r\n+\r\n+void json_main::publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  std::string jstr = json_util::to_message(name, jobj);\r\n+\r\n+  // Do not send any messages over the socket that exceed the user-specified max length.  Instead, send an error message.  If max_length is 0 (default), do not suppress any messages.\r\n+  // In stress testing, Node may be able to handle as much as 10-20 MB, but I'd prefer to stay on the safe side.  Before this safeguard was implemented, fewer than 5% of plots exceeded 1 MB.\r\n+  int length = jstr.length();\r\n+  int max_length = _max_message_length;\r\n+  if (max_length > 0 && length > max_length) {\r\n+    JSON_MAP_T m;\r\n+    JSON_MAP_SET(m, name, string);\r\n+    JSON_MAP_SET(m, length, int);\r\n+    JSON_MAP_SET(m, max_length, int);\r\n+    jstr = json_util::to_message(\"message-too-long\", json_util::from_map(m));\r\n+  }\r\n+\r\n+  send(sockfd, jstr.c_str(), jstr.length(), 0);\r\n+}\r\n+\r\n+void json_main::run_loop_on_new_thread(void) {\r\n+  if (_loop_thread_active)\r\n+    perror(\"won't run JSON socket loop multiple times\");\r\n+  _loop_thread_active = true;\r\n+\r\n+  pthread_create(\r\n+    &_loop_thread,\r\n+    NULL,\r\n+    run_loop_pthread,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::run_loop(void) {\r\n+  json_util::read_stream(\r\n+    sockfd,\r\n+    json_object_cb,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::process_json_object(std::string name, JSON_OBJECT_T jobj) {\r\n+  _octave_json_link->receive_message(name, jobj);\r\n+}\r\n+\r\n+} // namespace octave\r\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/json-main.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.h\tMon Dec 27 01:17:19 2021 -0600\n@@ -0,0 +1,37 @@\n+#ifndef json_main_h\r\n+#define json_main_h\r\n+\r\n+#include <queue>\r\n+#include <pthread.h>\r\n+#include <stdio.h>\r\n+\r\n+#include \"octave-json-link.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+namespace octave {\r\n+\r\n+class interpreter;\r\n+\r\n+class json_main {\r\n+public:\r\n+\tjson_main(interpreter& interp, const std::string& json_sock_path, int max_message_length);\r\n+\t~json_main(void);\r\n+\r\n+\tvoid publish_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\tvoid run_loop_on_new_thread(void);\r\n+\tvoid run_loop(void);\r\n+\tvoid process_json_object(std::string name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+\tstd::string _json_sock_path;\r\n+\tint _max_message_length;\r\n+\tint sockfd;\r\n+\tbool _loop_thread_active;\r\n+\tpthread_t _loop_thread;\r\n+\r\n+\tstd::shared_ptr<octave_json_link> _octave_json_link;\r\n+};\r\n+\r\n+} // namespace octave\r\n+\r\n+#endif\r\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/json-util.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -0,0 +1,264 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <cstdlib>\r\n+#include <assert.h>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <stdio.h>\r\n+#include <json-c/arraylist.h>\r\n+#include <json-c/json_object.h>\r\n+\r\n+#include \"str-vec.h\"\r\n+\r\n+#include \"json-util.h\"\r\n+\r\n+namespace octave {\r\n+\r\n+JSON_OBJECT_T json_util::from_string(const std::string& str) {\r\n+\t// Note: the string is not necesarilly valid UTF-8. The consumers of this stream must be able to handle that situation and substitute replacement characters, etc., where necessary.\r\n+\treturn json_object_new_string_len(str.c_str(), str.length());\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int(int i) {\r\n+\treturn json_object_new_int(i);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float(float flt) {\r\n+\treturn json_object_new_double(flt);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_boolean(bool b) {\r\n+\treturn json_object_new_boolean(b);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::empty() {\r\n+\treturn json_object_new_object();\r\n+}\r\n+\r\n+template<typename T>\r\n+JSON_OBJECT_T json_object_from_list(const std::list<T>& list, JSON_OBJECT_T (*convert)(T)) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\tauto it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, convert(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_list(const std::list<std::string>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_vector(const string_vector& vect) {\r\n+\t// TODO: Make sure this function does what it's supposed to do\r\n+\tstd::list<std::string> list;\r\n+\tfor (int i = 0; i < vect.numel(); ++i) {\r\n+\t\tlist.push_back(vect[i]);\r\n+\t}\r\n+\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int_list(const std::list<int>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_int);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float_list(const std::list<float>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_float);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_symbol_info_list(const symbol_info_list& list) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\tauto it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, json_util::from_symbol_info(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_filter_list(const interpreter_events::filter_list& list) {\r\n+\treturn json_object_from_list(list, json_util::from_pair);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_value_string(const std::string str) {\r\n+\treturn json_util::from_string(str);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_symbol_info(const symbol_info element) {\r\n+\toctave_value val = element.value();\r\n+\r\n+\tstd::string dims_str = val.get_dims_str();\r\n+\r\n+\tstd::ostringstream display_str;\r\n+\tval.short_disp(display_str);\r\n+\r\n+\tJSON_MAP_T m;\r\n+\t// m[\"scope\"] = json_util::from_int(element.scope());\r\n+\tm[\"symbol\"] = json_util::from_string(element.name());\r\n+\tm[\"class_name\"] = json_util::from_string(val.class_name());\r\n+\tm[\"dimension\"] = json_util::from_string(dims_str);\r\n+\tm[\"value\"] = json_util::from_string(display_str.str());\r\n+\tm[\"complex_flag\"] = json_util::from_boolean(element.is_complex());\r\n+\treturn json_util::from_map(m);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_pair(std::pair<std::string, std::string> pair) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tjson_object_array_add(jobj, json_util::from_string(pair.first.c_str()));\r\n+\tjson_object_array_add(jobj, json_util::from_string(pair.second.c_str()));\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_map(JSON_MAP_T m) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_object();\r\n+\tfor(\r\n+\t\tstd::map<std::string, JSON_OBJECT_T>::iterator it = m.begin();\r\n+\t\tit != m.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_object_add(jobj, it->first.c_str(), it->second);\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+std::string json_util::to_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  JSON_OBJECT_T jmsg = json_object_new_array();\r\n+  json_object_array_add(jmsg, json_util::from_string(name));\r\n+  json_object_array_add(jmsg, jobj);\r\n+  std::string str (json_object_to_json_string(jmsg));\r\n+  return str;\r\n+}\r\n+\r\n+std::string json_util::to_string(JSON_OBJECT_T jobj) {\r\n+  return std::string(json_object_get_string(jobj));\r\n+}\r\n+\r\n+template<typename T>\r\n+std::list<T> json_object_to_list(JSON_OBJECT_T jobj, T (*convert)(JSON_OBJECT_T)) {\r\n+\tstd::list<T> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tfor (size_t i = 0; i < array_list_length(arr); ++i) {\r\n+\t\tJSON_OBJECT_T jsub = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, i));\r\n+\t\tret.push_back(convert(jsub));\r\n+\t}\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> json_util::to_int_list_int_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<std::list<int>, int> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_to_list<int>(first, json_util::to_int);\r\n+\tret.second = json_object_get_int(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<bool, std::string> json_util::to_bool_string_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<bool, std::string> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_get_boolean(first);\r\n+\tret.second = json_object_get_string(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::list<std::string> json_util::to_string_list(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_to_list<std::string>(jobj, json_util::to_string);\r\n+}\r\n+\r\n+int json_util::to_int(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_int(jobj);\r\n+}\r\n+\r\n+bool json_util::to_boolean(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_boolean(jobj);\r\n+}\r\n+\r\n+void json_util::read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+\r\n+\t// Make some local variables\r\n+\tint BUF_LEN = 24;\r\n+\tchar* buf = new char[BUF_LEN];  // buffer for socket read\r\n+\tint buf_len;  // length of new bytes in the buffer\r\n+\tint buf_offset;  // offset of the JSON parser in the buffer\r\n+\tJSON_OBJECT_T jobj;  // pointer to parsed JSON object\r\n+\tjson_tokener* tok = json_tokener_new();  // JSON tokenizer instance\r\n+\tenum json_tokener_error jerr;  // status of JSON tokenizer\r\n+\r\n+\t// Start the blocking I/O loop\r\n+\twhile( (buf_len=recv(sockfd, buf, BUF_LEN, 0)) > 0) {\r\n+\t\tbuf_offset = 0;\r\n+\t\twhile(buf_offset < buf_len){\r\n+\t\t\tjobj = json_tokener_parse_ex(tok, buf + buf_offset, buf_len - buf_offset);\r\n+\t\t\tjerr = json_tokener_get_error(tok);\r\n+\t\t\tbuf_offset += tok->char_offset;\r\n+\r\n+\t\t\t// Do we need more material in order to make JSON?\r\n+\t\t\tif (jerr == json_tokener_continue) {\r\n+\t\t\t\tcontinue;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Make a new tokenizer\r\n+\t\t\tjson_tokener_free(tok);\r\n+\t\t\ttok = json_tokener_new();\r\n+\r\n+\t\t\t// Did we encounter a malformed JSON object?\r\n+\t\t\tif (jerr != json_tokener_success) {\r\n+\t\t\t\tfprintf(stderr,\r\n+\t\t\t\t\t\"JSON parse error: %s: '%.*s'\\n\",\r\n+\t\t\t\t\tjson_tokener_error_desc(jerr),\r\n+\t\t\t\t\t1,\r\n+\t\t\t\t\tbuf + buf_offset);\r\n+\t\t\t\tfflush(stderr);\r\n+\t\t\t\tbreak;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Our object is ready\r\n+\t\t\tprocess_message(jobj, cb, arg);\r\n+\t\t}\r\n+\t}\r\n+\r\n+\tjson_tokener_free(tok);\r\n+\tdelete buf;\r\n+}\r\n+\r\n+void json_util::process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+  if (!json_object_is_type(jobj, json_type_array))\r\n+    return;\r\n+  if (json_object_array_length(jobj) != 2)\r\n+    return;\r\n+\r\n+  cb(\r\n+  \tjson_util::to_string(json_object_array_get_idx(jobj, 0)),\r\n+  \tjson_object_array_get_idx(jobj, 1),\r\n+  \targ\r\n+  );\r\n+}\r\n+\r\n+} // namespace octave\r\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/json-util.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.h\tMon Dec 27 01:17:19 2021 -0600\n@@ -0,0 +1,64 @@\n+#ifndef json_util_h\r\n+#define json_util_h\r\n+\r\n+#include <json-c/json.h>\r\n+#include <map>\r\n+#include <list>\r\n+\r\n+#include \"syminfo.h\"\r\n+#include \"event-manager.h\"\r\n+\r\n+class string_vector;\r\n+\r\n+// All of the code interacting with the external JSON library should be in\r\n+// the json-util.h and json-util.cc files.  This way, if we want to change\r\n+// the external JSON library, we can do it all in one place.\r\n+\r\n+#define JSON_OBJECT_T json_object*\r\n+#define JSON_MAP_T std::map<std::string, JSON_OBJECT_T>\r\n+\r\n+#define JSON_MAP_SET(M, FIELD, TYPE){ \\\r\n+\tm[#FIELD] = json_util::from_##TYPE (FIELD); \\\r\n+}\r\n+\r\n+namespace octave {\r\n+\r\n+class json_util {\r\n+public:\r\n+\tstatic JSON_OBJECT_T from_string(const std::string& str);\r\n+\tstatic JSON_OBJECT_T from_int(int i);\r\n+\tstatic JSON_OBJECT_T from_float(float flt);\r\n+\tstatic JSON_OBJECT_T from_boolean(bool b);\r\n+\tstatic JSON_OBJECT_T empty();\r\n+\r\n+\tstatic JSON_OBJECT_T from_string_list(const std::list<std::string>& list);\r\n+\tstatic JSON_OBJECT_T from_string_vector(const string_vector& list);\r\n+\tstatic JSON_OBJECT_T from_int_list(const std::list<int>& list);\r\n+\tstatic JSON_OBJECT_T from_float_list(const std::list<float>& list);\r\n+\tstatic JSON_OBJECT_T from_symbol_info_list(const symbol_info_list& list);\r\n+        static JSON_OBJECT_T from_filter_list(const interpreter_events::filter_list& list);\r\n+\r\n+\tstatic JSON_OBJECT_T from_value_string(const std::string str);\r\n+\tstatic JSON_OBJECT_T from_symbol_info(const symbol_info element);\r\n+\tstatic JSON_OBJECT_T from_pair(std::pair<std::string, std::string> pair);\r\n+\r\n+\tstatic JSON_OBJECT_T from_map(JSON_MAP_T m);\r\n+\r\n+\tstatic std::string to_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic std::string to_string(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<std::list<int>, int> to_int_list_int_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<bool, std::string> to_bool_string_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::list<std::string> to_string_list(JSON_OBJECT_T jobj);\r\n+\tstatic int to_int(JSON_OBJECT_T jobj);\r\n+\tstatic bool to_boolean(JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic void read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+\r\n+private:\r\n+\tstatic void process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+};\r\n+\r\n+} // namespace octave\r\n+\r\n+#endif\r\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/module.mk\n--- a/libinterp/corefcn/module.mk\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/corefcn/module.mk\tMon Dec 27 01:17:19 2021 -0600\n@@ -45,6 +45,8 @@\n   %reldir%/help.h \\\n   %reldir%/hook-fcn.h \\\n   %reldir%/input.h \\\n+  %reldir%/json-main.h \\\n+  %reldir%/json-util.h \\\n   %reldir%/interpreter.h \\\n   %reldir%/load-path.h \\\n   %reldir%/load-save.h \\\n@@ -73,6 +75,7 @@\n   %reldir%/oct-strstrm.h \\\n   %reldir%/oct.h \\\n   %reldir%/octave-default-image.h \\\n+  %reldir%/octave-json-link.h \\\n   %reldir%/pager.h \\\n   %reldir%/pr-flt-fmt.h \\\n   %reldir%/pr-output.h \\\n@@ -182,6 +185,8 @@\n   %reldir%/hex2num.cc \\\n   %reldir%/hook-fcn.cc \\\n   %reldir%/input.cc \\\n+  %reldir%/json-main.cc \\\n+  %reldir%/json-util.cc \\\n   %reldir%/interpreter-private.cc \\\n   %reldir%/interpreter.cc \\\n   %reldir%/inv.cc \\\n@@ -218,6 +223,7 @@\n   %reldir%/oct-tex-lexer.ll \\\n   %reldir%/oct-tex-parser.h \\\n   %reldir%/oct-tex-parser.yy \\\n+  %reldir%/octave-json-link.cc \\\n   %reldir%/ordschur.cc \\\n   %reldir%/pager.cc \\\n   %reldir%/pinv.cc \\\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/octave-json-link.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -0,0 +1,411 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <iostream>\r\n+#include \"octave-json-link.h\"\r\n+#include \"cmd-edit.h\"\r\n+#include \"json-main.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+namespace octave {\r\n+\r\n+octave_json_link::octave_json_link(json_main* __json_main)\r\n+\t: interpreter_events (),\r\n+\t\t_json_main (__json_main)\r\n+{\r\n+\t_request_input_enabled = true;\r\n+\t_plot_destination = STATIC_ONLY;\r\n+}\r\n+\r\n+octave_json_link::~octave_json_link(void) { }\r\n+\r\n+std::string octave_json_link::request_input(const std::string& prompt) {\r\n+\t// Triggered whenever the console prompts for user input\r\n+\r\n+\tstd::string value;\r\n+\tif (!request_input_queue.dequeue_to(&value)) {\r\n+\t\t_publish_message(\"request-input\", json_util::from_string(prompt));\r\n+\t\tvalue = request_input_queue.dequeue();\r\n+\t}\r\n+\treturn value;\r\n+}\r\n+\r\n+std::string octave_json_link::request_url(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\r\n+\t// Triggered on urlread/urlwrite\r\n+\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, url, string);\r\n+\tJSON_MAP_SET(m, param, string_list);\r\n+\tJSON_MAP_SET(m, action, string);\r\n+\r\n+\t_publish_message(\"request-url\", json_util::from_map(m));\r\n+\tstd::pair<bool, std::string> result = request_url_queue.dequeue();\r\n+\tsuccess = result.first;\r\n+\treturn result.second;\r\n+}\r\n+\r\n+bool octave_json_link::confirm_shutdown(void) {\r\n+\t// Triggered when the kernel tries to exit\r\n+\t_publish_message(\"confirm-shutdown\", json_util::empty());\r\n+\r\n+\treturn confirm_shutdown_queue.dequeue();\r\n+}\r\n+\r\n+// do_exit was removed in Octave 5\r\n+// bool octave_json_link::do_exit(int status) {\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, status, int);\r\n+// \t_publish_message(\"exit\", json_util::from_map(m));\r\n+\r\n+// \t// It is our responsibility in octave_link to call exit. If we don't, then\r\n+// \t// the kernel waits for 24 hours expecting us to do something.\r\n+// \t::exit(status);\r\n+\r\n+// \treturn true;\r\n+// }\r\n+\r\n+bool octave_json_link::copy_image_to_clipboard(const std::string& file) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"copy-image-to-clipboard\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for existing files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::prompt_new_edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for new files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"prompt-new-edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn prompt_new_edit_file_queue.dequeue();\r\n+}\r\n+\r\n+// int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {\r\n+// \t// Triggered in \"msgbox\", \"helpdlg\", and \"errordlg\", among others\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\r\n+// \tJSON_MAP_SET(m, msg, string);\r\n+// \tJSON_MAP_SET(m, title, string);\r\n+// \t_publish_message(\"message-dialog\", json_util::from_map(m));\r\n+\r\n+// \treturn message_dialog_queue.dequeue();\r\n+// }\r\n+\r\n+std::string octave_json_link::question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n+\t// Triggered in \"questdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, msg, string);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, btn1, string);\r\n+\tJSON_MAP_SET(m, btn2, string);\r\n+\tJSON_MAP_SET(m, btn3, string);\r\n+\tJSON_MAP_SET(m, btndef, string);\r\n+\t_publish_message(\"question-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn question_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> octave_json_link::list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {\r\n+\t// Triggered in \"listdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, list, string_list);\r\n+\tJSON_MAP_SET(m, mode, string);\r\n+\tJSON_MAP_SET(m, width, int);\r\n+\tJSON_MAP_SET(m, height, int);\r\n+\tJSON_MAP_SET(m, initial_value, int_list);\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, ok_string, string);\r\n+\tJSON_MAP_SET(m, cancel_string, string);\r\n+\t_publish_message(\"list-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn list_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {\r\n+\t// Triggered in \"inputdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, nr, float_list);\r\n+\tJSON_MAP_SET(m, nc, float_list);\r\n+\tJSON_MAP_SET(m, defaults, string_list);\r\n+\t_publish_message(\"input-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn input_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {\r\n+\t// Triggered in \"uiputfile\", \"uigetfile\", and \"uigetdir\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, filter, filter_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, filename, string);\r\n+\tJSON_MAP_SET(m, pathname, string);\r\n+\tJSON_MAP_SET(m, multimode, string);\r\n+\t_publish_message(\"file-dialog\", json_util::from_map(m));\r\n+\t\r\n+\treturn file_dialog_queue.dequeue();\r\n+}\r\n+\r\n+int octave_json_link::debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\r\n+\t// This endpoint might be unused?  (No references)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\tJSON_MAP_SET(m, addpath_option, boolean);\r\n+\t_publish_message(\"debug-cd-or-addpath-error\", json_util::from_map(m));\r\n+\r\n+\treturn debug_cd_or_addpath_error_queue.dequeue();\r\n+}\r\n+\r\n+void octave_json_link::directory_changed(const std::string& dir) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\t_publish_message(\"change-directory\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::file_remove (const std::string& old_name, const std::string& new_name) {\r\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, old_name, string);\r\n+\tJSON_MAP_SET(m, new_name, string);\r\n+\t_publish_message(\"file-remove\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::file_renamed (bool status) {\r\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n+\t_publish_message(\"file-renamed\", json_util::from_boolean(status));\r\n+}\r\n+\r\n+void octave_json_link::execute_command_in_terminal(const std::string& command) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, command, string);\r\n+\t_publish_message(\"execute-command-in-terminal\", json_util::from_map(m));\r\n+}\r\n+\r\n+uint8NDArray octave_json_link::get_named_icon (const std::string& /* icon_name */) {\r\n+\t// Called from msgbox.m\r\n+\t// TODO: Implement request/response for this event\r\n+\tuint8NDArray retval;\r\n+\treturn retval;\r\n+}\r\n+\r\n+void octave_json_link::set_workspace(bool top_level, bool debug,\r\n+                         const symbol_info_list& ws,\r\n+                         bool update_variable_editor) {\r\n+\t// Triggered on every new line entry\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, top_level, boolean);\r\n+\tJSON_MAP_SET(m, debug, boolean);\r\n+\tJSON_MAP_SET(m, ws, symbol_info_list);\r\n+\tJSON_MAP_SET(m, update_variable_editor, boolean);\r\n+\t_publish_message(\"set-workspace\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::clear_workspace(void) {\r\n+\t// Triggered on \"clear\" command (but not \"clear all\" or \"clear foo\")\r\n+\t_publish_message(\"clear-workspace\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::set_history(const string_vector& hist) {\r\n+\t// Called at startup, possibly more?\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist, string_vector);\r\n+\t_publish_message(\"set-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::append_history(const std::string& hist_entry) {\r\n+\t// Appears to be tied to readline, if available\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist_entry, string);\r\n+\t_publish_message(\"append-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::clear_history(void) {\r\n+\t// Appears to be tied to readline, if available\r\n+\t_publish_message(\"clear-history\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::clear_screen(void) {\r\n+\t// Triggered by clc\r\n+\t_publish_message(\"clear-screen\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::pre_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::post_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::enter_debugger_event(const std::string& fcn_name, const std::string& fcn_file_name, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, fcn_name, string);\r\n+\tJSON_MAP_SET(m, fcn_file_name, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"enter-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::execute_in_debugger_event(const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"execute-in-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::exit_debugger_event(void) {\r\n+\t_publish_message(\"exit-debugger-event\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::update_breakpoint(bool insert, const std::string& file, int line, const std::string& cond) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, insert, boolean);\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\tJSON_MAP_SET(m, cond, string);\r\n+\t_publish_message(\"update-breakpoint\", json_util::from_map(m));\r\n+}\r\n+\r\n+// void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {\r\n+// \t// Triggered upon interpreter startup\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, ps1, string);\r\n+// \tJSON_MAP_SET(m, ps2, string);\r\n+// \tJSON_MAP_SET(m, ps4, string);\r\n+// \t_publish_message(\"set-default-prompts\", json_util::from_map(m));\r\n+// }\r\n+\r\n+void octave_json_link::show_preferences(void) {\r\n+\t// Triggered on \"preferences\" command\r\n+\t_publish_message(\"show-preferences\", json_util::empty());\r\n+}\r\n+\r\n+std::string octave_json_link::gui_preference (const std::string& /* key */, const std::string& /* value */) {\r\n+\t// Used by Octave GUI?\r\n+\t// TODO: Implement request/response for this event\r\n+\tstd::string retval;\r\n+\treturn retval;\r\n+}\r\n+\r\n+void octave_json_link::show_doc(const std::string& file) {\r\n+\t// Triggered on \"doc\" command\r\n+\t_publish_message(\"show-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::register_doc (const std::string& file) {\r\n+\t// Triggered by the GUI documentation viewer?\r\n+\t_publish_message(\"register-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::unregister_doc (const std::string& file) {\r\n+\t// Triggered by the GUI documentation viewer?\r\n+\t_publish_message(\"unregister-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::edit_variable (const std::string& name, const octave_value& /* val */) {\r\n+\t// Triggered on \"openvar\" command\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\t// TODO: val\r\n+\t_publish_message(\"edit-variable\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::show_static_plot(const std::string& term, const std::string& content) {\r\n+\t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\r\n+\tint command_number = command_editor::current_command_number();\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, term, string);\r\n+\tJSON_MAP_SET(m, content, string);\r\n+\tJSON_MAP_SET(m, command_number, int);\r\n+\t_publish_message(\"show-static-plot\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::receive_message (const std::string& name, JSON_OBJECT_T jobj) {\r\n+\tif (name == \"cmd\" || name == \"request-input-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\trequest_input_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"request-url-answer\") {\r\n+\t\tstd::pair<bool, std::string> answer = json_util::to_bool_string_pair(jobj);\r\n+\t\trequest_url_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"confirm-shutdown-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tconfirm_shutdown_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"prompt-new-edit-file-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tprompt_new_edit_file_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"message-dialog-answer\"){\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tmessage_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"question-dialog-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\tquestion_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"list-dialog-answer\") {\r\n+\t\tstd::pair<std::list<int>, int> answer = json_util::to_int_list_int_pair(jobj);\r\n+\t\tlist_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"input-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tinput_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"file-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tfile_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"debug-cd-or-addpath-error-answer\") {\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tdebug_cd_or_addpath_error_queue.enqueue(answer);\r\n+\t}\r\n+\telse {\r\n+\t\tstd::cerr << \"warning: received unknown message: \" << name << std::endl;\r\n+\t}\r\n+}\r\n+\r\n+void octave_json_link::_publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+\t_json_main->publish_message(name, jobj);\r\n+}\r\n+\r\n+} // namespace octave\r\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/octave-json-link.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.h\tMon Dec 27 01:17:19 2021 -0600\n@@ -0,0 +1,216 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifndef octave_json_link_h\r\n+#define octave_json_link_h\r\n+\r\n+#include <list>\r\n+#include <string>\r\n+\r\n+#include \"event-manager.h\"\r\n+#include \"json-util.h\"\r\n+#include \"oct-mutex.h\"\r\n+\r\n+class string_vector;\r\n+\r\n+namespace octave {\r\n+\r\n+// Circular reference\r\n+class json_main;\r\n+\r\n+// Thread-safe queue\r\n+template<typename T> class json_queue {\r\n+public:\r\n+  json_queue();\r\n+  ~json_queue();\r\n+\r\n+  void enqueue(const T& value);\r\n+  T dequeue();\r\n+  bool dequeue_to(T* destination);\r\n+\r\n+private:\r\n+  std::queue<T> _queue;\r\n+  mutex _mutex;\r\n+};\r\n+\r\n+class octave_json_link : public interpreter_events\r\n+{\r\n+\r\n+public:\r\n+\r\n+  octave_json_link (json_main* __json_main);\r\n+\r\n+  ~octave_json_link (void);\r\n+\r\n+  std::string request_input (const std::string& prompt) override;\r\n+  std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\r\n+\r\n+  bool confirm_shutdown (void) override;\r\n+\r\n+  bool copy_image_to_clipboard (const std::string& file) override;\r\n+\r\n+  bool edit_file (const std::string& file) override;\r\n+  bool prompt_new_edit_file (const std::string& file) override;\r\n+\r\n+  std::string\r\n+  question_dialog (const std::string& msg, const std::string& title,\r\n+                      const std::string& btn1, const std::string& btn2,\r\n+                      const std::string& btn3, const std::string& btndef) override;\r\n+\r\n+  std::pair<std::list<int>, int>\r\n+  list_dialog (const std::list<std::string>& list,\r\n+                  const std::string& mode,\r\n+                  int width, int height,\r\n+                  const std::list<int>& initial_value,\r\n+                  const std::string& name,\r\n+                  const std::list<std::string>& prompt,\r\n+                  const std::string& ok_string,\r\n+                  const std::string& cancel_string) override;\r\n+\r\n+  std::list<std::string>\r\n+  input_dialog (const std::list<std::string>& prompt,\r\n+                   const std::string& title,\r\n+                   const std::list<float>& nr,\r\n+                   const std::list<float>& nc,\r\n+                   const std::list<std::string>& defaults) override;\r\n+\r\n+  std::list<std::string>\r\n+  file_dialog (const filter_list& filter, const std::string& title,\r\n+                  const std::string &filename, const std::string &pathname,\r\n+                  const std::string& multimode) override;\r\n+\r\n+  int\r\n+  debug_cd_or_addpath_error (const std::string& file,\r\n+                                const std::string& dir,\r\n+                                bool addpath_option) override;\r\n+\r\n+  void directory_changed (const std::string& dir) override;\r\n+\r\n+  void file_remove (const std::string& old_name, const std::string& new_name) override;\r\n+  void file_renamed (bool) override;\r\n+\r\n+  void execute_command_in_terminal (const std::string& command) override;\r\n+\r\n+  uint8NDArray get_named_icon (const std::string& icon_name) override;\r\n+\r\n+  void set_workspace (bool top_level, bool debug,\r\n+                         const symbol_info_list& ws,\r\n+                         bool update_variable_editor) override;\r\n+\r\n+  void clear_workspace (void) override;\r\n+\r\n+  void set_history (const string_vector& hist) override;\r\n+  void append_history (const std::string& hist_entry) override;\r\n+  void clear_history (void) override;\r\n+\r\n+  void clear_screen (void) override;\r\n+\r\n+  void pre_input_event (void) override;\r\n+  void post_input_event (void) override;\r\n+\r\n+  void enter_debugger_event (const std::string& fcn_name, const std::string& fcn_file_name, int line) override;\r\n+  void execute_in_debugger_event (const std::string& file, int line) override;\r\n+  void exit_debugger_event (void) override;\r\n+\r\n+  void update_breakpoint (bool insert,\r\n+                             const std::string& file, int line,\r\n+                             const std::string& cond) override;\r\n+\r\n+  void show_preferences (void) override;\r\n+\r\n+  std::string gui_preference (const std::string& key, const std::string& value) override;\r\n+\r\n+  void show_doc (const std::string& file) override;\r\n+\r\n+  void register_doc (const std::string& file) override;\r\n+\r\n+  void unregister_doc (const std::string& file) override;\r\n+\r\n+  void edit_variable (const std::string& name, const octave_value& val) override;\r\n+\r\n+  void show_static_plot (const std::string& term,\r\n+                            const std::string& content) override;\r\n+\r\n+  // Custom methods\r\n+  void receive_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+  json_main* _json_main;\r\n+  void _publish_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+  // Queues\r\n+  json_queue<std::string> request_input_queue;\r\n+  json_queue<std::pair<bool, std::string> > request_url_queue;\r\n+  json_queue<bool> confirm_shutdown_queue;\r\n+  json_queue<bool> prompt_new_edit_file_queue;\r\n+  json_queue<int> message_dialog_queue;\r\n+  json_queue<std::string> question_dialog_queue;\r\n+  json_queue<std::pair<std::list<int>, int> > list_dialog_queue;\r\n+  json_queue<std::list<std::string> > input_dialog_queue;\r\n+  json_queue<std::list<std::string> > file_dialog_queue;\r\n+  json_queue<int> debug_cd_or_addpath_error_queue;\r\n+};\r\n+\r\n+// Template classes require definitions in the header file...\r\n+\r\n+template<typename T>\r\n+json_queue<T>::json_queue() { }\r\n+\r\n+template<typename T>\r\n+json_queue<T>::~json_queue() { }\r\n+\r\n+template<typename T>\r\n+void json_queue<T>::enqueue(const T& value) {\r\n+  _mutex.lock();\r\n+  _queue.push(value);\r\n+  _mutex.cond_signal();\r\n+  _mutex.unlock();\r\n+}\r\n+\r\n+template<typename T>\r\n+T json_queue<T>::dequeue() {\r\n+  _mutex.lock();\r\n+  while (_queue.empty()) {\r\n+    _mutex.cond_wait();\r\n+  }\r\n+  T value = _queue.front();\r\n+  _queue.pop();\r\n+  _mutex.unlock();\r\n+  return value;\r\n+}\r\n+\r\n+template<typename T>\r\n+bool json_queue<T>::dequeue_to(T* destination) {\r\n+  _mutex.lock();\r\n+  bool retval = false;\r\n+  if (!_queue.empty()) {\r\n+    retval = true;\r\n+    *destination = _queue.front();\r\n+    _queue.pop();\r\n+  }\r\n+  _mutex.unlock();\r\n+  return retval;\r\n+}\r\n+\r\n+} // namespace octave\r\n+  \r\n+#endif\r\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/syscalls.cc\n--- a/libinterp/corefcn/syscalls.cc\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/corefcn/syscalls.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -149,9 +149,8 @@\n @deftypefn {} {[@var{err}, @var{msg}] =} exec (@var{file}, @var{args})\n Replace current process with a new process.\n \n-Calling @code{exec} without first calling @code{fork} will terminate your\n-current Octave process and replace it with the program named by @var{file}.\n-For example,\n+Calling @code{exec} will terminate your current Octave process and replace\n+it with the program named by @var{file}. For example,\n \n @example\n exec (\"ls\", \"-l\")\n@@ -461,42 +460,6 @@\n   return ovl (status, msg);\n }\n \n-DEFMETHODX (\"fork\", Ffork, interp, args, ,\n-            doc: /* -*- texinfo -*-\n-@deftypefn {} {[@var{pid}, @var{msg}] =} fork ()\n-Create a copy of the current process.\n-\n-Fork can return one of the following values:\n-\n-@table @asis\n-@item > 0\n-You are in the parent process.  The value returned from @code{fork} is the\n-process id of the child process.  You should probably arrange to wait for\n-any child processes to exit.\n-\n-@item 0\n-You are in the child process.  You can call @code{exec} to start another\n-process.  If that fails, you should probably call @code{exit}.\n-\n-@item < 0\n-The call to @code{fork} failed for some reason.  You must take evasive\n-action.  A system dependent error message will be waiting in @var{msg}.\n-@end table\n-@end deftypefn */)\n-{\n-  if (args.length () != 0)\n-    print_usage ();\n-\n-  if (interp.at_top_level ())\n-    error (\"fork: cannot be called from command line\");\n-\n-  std::string msg;\n-\n-  pid_t pid = octave::sys::fork (msg);\n-\n-  return ovl (pid, msg);\n-}\n-\n DEFUNX (\"getpgrp\", Fgetpgrp, args, ,\n         doc: /* -*- texinfo -*-\n @deftypefn {} {pgid =} getpgrp ()\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/sysdep.cc\n--- a/libinterp/corefcn/sysdep.cc\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/corefcn/sysdep.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -75,6 +75,7 @@\n #include \"defun.h\"\n #include \"error.h\"\n #include \"errwarn.h\"\n+#include \"event-manager.h\"\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"octave.h\"\n@@ -651,7 +652,7 @@\n \n   // Read one character from the terminal.\n \n-  int kbhit (bool wait)\n+  int kbhit (const std::string& prompt, bool wait)\n   {\n #if defined (HAVE__KBHIT) && defined (HAVE__GETCH)\n     // This essentially means we are on a Windows system.\n@@ -678,13 +679,24 @@\n \n     set_interrupt_handler (saved_interrupt_handler, false);\n \n-    int c = std::cin.get ();\n+    int c;\n+    event_manager& evmgr = __get_event_manager__ (\"kbhit\");\n+    if (evmgr.request_input_enabled ()) {\n+      std::string line = evmgr.request_input (prompt);\n+      if (line.length() >= 1) {\n+        c = line.at(0);\n+      } else {\n+        c = '\\n';\n+      }\n+    } else {\n+      c = std::cin.get ();\n \n-    if (std::cin.fail () || std::cin.eof ())\n-      {\n-        std::cin.clear ();\n-        clearerr (stdin);\n-      }\n+      if (std::cin.fail () || std::cin.eof ())\n+        {\n+          std::cin.clear ();\n+          clearerr (stdin);\n+        }\n+    }\n \n     // Restore it, enabling system call restarts (if possible).\n     set_interrupt_handler (saved_interrupt_handler, true);\n@@ -743,6 +755,9 @@\n {\n   bool skip_redisplay = true;\n \n+  octave::event_manager& evmgr = octave::__get_event_manager__ (\"clc\");\n+  evmgr.clear_screen();\n+\n   octave::command_editor::clear_screen (skip_redisplay);\n \n   return ovl ();\n@@ -1169,7 +1184,7 @@\n \n   Fdrawnow (interp);\n \n-  int c = octave::kbhit (args.length () == 0);\n+  int c = octave::kbhit (\"kbhit>\", args.length () == 0);\n \n   if (c == -1)\n     c = 0;\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/sysdep.h\n--- a/libinterp/corefcn/sysdep.h\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/corefcn/sysdep.h\tMon Dec 27 01:17:19 2021 -0600\n@@ -49,7 +49,7 @@\n \n   extern OCTINTERP_API int pclose (FILE *f);\n \n-  extern OCTINTERP_API int kbhit (bool wait = true);\n+  extern OCTINTERP_API int kbhit (const std::string& prompt, bool wait);\n \n   extern OCTINTERP_API std::string get_P_tmpdir (void);\n \n@@ -107,7 +107,7 @@\n inline int\n octave_kbhit (bool wait = true)\n {\n-  return octave::kbhit (wait);\n+  return octave::kbhit (\"\", wait);\n }\n \n OCTAVE_DEPRECATED (5, \"use 'octave::get_P_tmpdir' instead\")\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/corefcn/utils.cc\n--- a/libinterp/corefcn/utils.cc\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/corefcn/utils.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -1442,7 +1442,7 @@\n             if (do_graphics_events)\n               gh_mgr.process_events ();\n \n-            c = kbhit (false);\n+            c = kbhit (\"press enter to continue\", false);\n           }\n       }\n     else\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/octave.cc\n--- a/libinterp/octave.cc\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/octave.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -188,6 +188,16 @@\n           case LINE_EDITING_OPTION:\n             m_forced_line_editing = m_line_editing = true;\n             break;\n+ \n+          case JSON_SOCK_OPTION:\n+            if (octave_optarg_wrapper ())\n+              m_json_sock_path = octave_optarg_wrapper ();\n+            break;\n+\n+          case JSON_MAX_LEN_OPTION:\n+            if (octave_optarg_wrapper ())\n+              m_json_max_message_length = strtol(octave_optarg_wrapper (), NULL, 10);\n+            break;\n \n           case NO_GUI_OPTION:\n             m_gui = false;\n@@ -373,6 +383,14 @@\n     sysdep_init ();\n   }\n \n+  bool application::link_enabled (void) const\n+  {\n+    if (m_interpreter) {\n+      event_manager& evmgr = m_interpreter->get_event_manager ();\n+      return evmgr.enabled();\n+    } else return false;\n+  }\n+\n   int cli_application::execute (void)\n   {\n     interpreter& interp = create_interpreter ();\n@@ -396,7 +414,7 @@\n   // FIXME: This isn't quite right, it just says that we intended to\n   // start the GUI, not that it is actually running.\n \n-  return ovl (octave::application::is_gui_running ());\n+  return ovl (octave::application::is_link_enabled ());\n }\n \n /*\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/octave.h\n--- a/libinterp/octave.h\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/octave.h\tMon Dec 27 01:17:19 2021 -0600\n@@ -79,6 +79,8 @@\n     std::string info_file (void) const { return m_info_file; }\n     std::string info_program (void) const { return m_info_program; }\n     std::string texi_macros_file (void) const {return m_texi_macros_file; }\n+    std::string json_sock_path (void) const { return m_json_sock_path; }\n+    int json_max_message_length (void) const { return m_json_max_message_length; }\n     string_vector all_args (void) const { return m_all_args; }\n     string_vector remaining_args (void) const { return m_remaining_args; }\n \n@@ -109,6 +111,8 @@\n     void info_file (const std::string& arg) { m_info_file = arg; }\n     void info_program (const std::string& arg) { m_info_program = arg; }\n     void texi_macros_file (const std::string& arg) { m_texi_macros_file = arg; }\n+    void json_sock_path (const std::string& arg) { m_json_sock_path = arg; }\n+    void json_max_message_length (int arg) { m_json_max_message_length = arg; }\n     void all_args (const string_vector& arg) { m_all_args = arg; }\n     void remaining_args (const string_vector& arg) { m_remaining_args = arg; }\n \n@@ -215,6 +219,14 @@\n     // (--texi-macros-file)\n     std::string m_texi_macros_file;\n \n+    // The value for \"JSON_SOCK\" specified on the command line.\n+    // (--json-sock)\n+    std::string m_json_sock_path;\n+\n+    // The maximum message length; valid only if \"JSON_SOCK\" is specified.\n+    // (--json-max-len)\n+    int m_json_max_message_length = 0;\n+\n     // All arguments passed to the argc, argv constructor.\n     string_vector m_all_args;\n \n@@ -228,6 +240,7 @@\n   // both) of them...\n \n   class interpreter;\n+  class event_manager;\n \n   // Base class for an Octave application.\n \n@@ -277,6 +290,8 @@\n     virtual bool gui_running (void) const { return false; }\n     virtual void gui_running (bool) { }\n \n+    bool link_enabled (void) const;\n+\n     void program_invocation_name (const std::string& nm) { m_program_invocation_name = nm; }\n \n     void program_name (const std::string& nm) { m_program_name = nm; }\n@@ -305,6 +320,11 @@\n       return instance ? instance->gui_running () : false;\n     }\n \n+    static bool is_link_enabled (void)\n+    {\n+      return instance ? instance->link_enabled () : false;\n+    }\n+\n     // Convenience functions.\n \n     static bool forced_interactive (void);\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/options.h\n--- a/libinterp/options.h\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/options.h\tMon Dec 27 01:17:19 2021 -0600\n@@ -54,16 +54,18 @@\n #define INFO_PROG_OPTION 8\n #define DEBUG_JIT_OPTION 9\n #define JIT_COMPILER_OPTION 10\n-#define LINE_EDITING_OPTION 11\n-#define NO_GUI_OPTION 12\n-#define NO_GUI_LIBS_OPTION 13\n-#define NO_INIT_FILE_OPTION 14\n-#define NO_INIT_PATH_OPTION 15\n-#define NO_LINE_EDITING_OPTION 16\n-#define NO_SITE_FILE_OPTION 17\n-#define PERSIST_OPTION 18\n-#define TEXI_MACROS_FILE_OPTION 19\n-#define TRADITIONAL_OPTION 20\n+#define JSON_SOCK_OPTION 11\n+#define JSON_MAX_LEN_OPTION 12\n+#define LINE_EDITING_OPTION 13\n+#define NO_GUI_OPTION 14\n+#define NO_GUI_LIBS_OPTION 15\n+#define NO_INIT_FILE_OPTION 16\n+#define NO_INIT_PATH_OPTION 17\n+#define NO_LINE_EDITING_OPTION 18\n+#define NO_SITE_FILE_OPTION 19\n+#define PERSIST_OPTION 20\n+#define TEXI_MACROS_FILE_OPTION 21\n+#define TRADITIONAL_OPTION 22\n struct octave_getopt_options long_opts[] =\n {\n   { \"braindead\",                octave_no_arg,       0, TRADITIONAL_OPTION },\n@@ -82,6 +84,8 @@\n   { \"info-program\",             octave_required_arg, 0, INFO_PROG_OPTION },\n   { \"interactive\",              octave_no_arg,       0, 'i' },\n   { \"jit-compiler\",             octave_no_arg,       0, JIT_COMPILER_OPTION },\n+  { \"json-sock\",                octave_required_arg, 0, JSON_SOCK_OPTION },\n+  { \"json-max-len\",             octave_required_arg, 0, JSON_MAX_LEN_OPTION },\n   { \"line-editing\",             octave_no_arg,       0, LINE_EDITING_OPTION },\n   { \"no-gui\",                   octave_no_arg,       0, NO_GUI_OPTION },\n   { \"no-gui-libs\",              octave_no_arg,       0, NO_GUI_LIBS_OPTION },\ndiff -r 8d7671609955 -r 8d4683a83238 libinterp/usage.h\n--- a/libinterp/usage.h\tSat Oct 30 10:20:24 2021 -0400\n+++ b/libinterp/usage.h\tMon Dec 27 01:17:19 2021 -0600\n@@ -38,10 +38,10 @@\n        [--echo-commands] [--eval CODE] [--exec-path path]\\n\\\n        [--gui] [--help] [--image-path path]\\n\\\n        [--info-file file] [--info-program prog] [--interactive]\\n\\\n-       [--jit-compiler] [--line-editing] [--no-gui] [--no-history]\\n\\\n-       [--no-init-file] [--no-init-path] [--no-line-editing]\\n\\\n-       [--no-site-file] [--no-window-system] [--norc] [-p path]\\n\\\n-       [--path path] [--persist] [--silent] [--traditional]\\n\\\n+       [--jit-compiler] [--json-sock] [--json-max-len] [--line-editing]\\n\\\n+       [--no-gui] [--no-history][--no-init-file] [--no-init-path]\\n\\\n+       [--no-line-editing] [--no-site-file] [--no-window-system] [--norc]\\n\\\n+       [-p path] [--path path] [--persist] [--silent] [--traditional]\\n\\\n        [--verbose] [--version] [file]\";\n \n // Usage message with extra help.\n@@ -70,6 +70,8 @@\n   --info-program PROGRAM  Use PROGRAM for reading info files.\\n\\\n   --interactive, -i       Force interactive behavior.\\n\\\n   --jit-compiler          Enable the JIT compiler.\\n\\\n+  --json-sock PATH        Listen to and publish events on this UNIX socket.\\n\\\n+  --json-max-len LEN      Suppress JSON messages greater than LEN bytes.\\n\\\n   --line-editing          Force readline use for command-line editing.\\n\\\n   --no-gui                Disable the graphical user interface.\\n\\\n   --no-history, -H        Don't save commands to the history list\\n\\\ndiff -r 8d7671609955 -r 8d4683a83238 liboctave/util/oct-mutex.cc\n--- a/liboctave/util/oct-mutex.cc\tSat Oct 30 10:20:24 2021 -0400\n+++ b/liboctave/util/oct-mutex.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -58,6 +58,18 @@\n     return false;\n   }\n \n+  void\n+  base_mutex::cond_wait (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n+  void\n+  base_mutex::cond_signal (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n #if defined (OCTAVE_USE_WINDOWS_API)\n \n   class\n@@ -68,11 +80,13 @@\n       : base_mutex ()\n     {\n       InitializeCriticalSection (&cs);\n+      InitializeConditionVariable (&cv);\n     }\n \n     ~w32_mutex (void)\n     {\n       DeleteCriticalSection (&cs);\n+      // no need to delete cv: http://stackoverflow.com/a/28981408/1407170\n     }\n \n     void lock (void)\n@@ -90,8 +104,19 @@\n       return (TryEnterCriticalSection (&cs) != 0);\n     }\n \n+    void cond_wait (void)\n+    {\n+      SleepConditionVariableCS (&cv, &cs, INFINITE);\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      WakeConditionVariable (&cv);\n+    }\n+\n   private:\n     CRITICAL_SECTION cs;\n+    CONDITION_VARIABLE cv;\n   };\n \n   static DWORD thread_id = 0;\n@@ -123,11 +148,19 @@\n       pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);\n       pthread_mutex_init (&m_pm, &attr);\n       pthread_mutexattr_destroy (&attr);\n+\n+      pthread_condattr_t condattr;\n+\n+      pthread_condattr_init (&condattr);\n+      pthread_cond_init (&condv, &condattr);\n+      pthread_condattr_destroy (&condattr);\n     }\n \n     ~pthread_mutex (void)\n     {\n       pthread_mutex_destroy (&m_pm);\n+      pthread_mutex_destroy (&m_pm);\n+      pthread_cond_destroy (&condv);\n     }\n \n     void lock (void)\n@@ -145,8 +178,19 @@\n       return (pthread_mutex_trylock (&m_pm) == 0);\n     }\n \n+    void cond_wait (void)\n+    {\n+      pthread_cond_wait (&condv, &m_pm);\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      pthread_cond_signal (&condv);\n+    }\n+\n   private:\n     pthread_mutex_t m_pm;\n+    pthread_cond_t condv;\n   };\n \n   static pthread_t thread_id = 0;\ndiff -r 8d7671609955 -r 8d4683a83238 liboctave/util/oct-mutex.h\n--- a/liboctave/util/oct-mutex.h\tSat Oct 30 10:20:24 2021 -0400\n+++ b/liboctave/util/oct-mutex.h\tMon Dec 27 01:17:19 2021 -0600\n@@ -50,6 +50,10 @@\n \n     virtual bool try_lock (void);\n \n+    virtual void cond_wait (void);\n+\n+    virtual void cond_signal (void);\n+\n   private:\n     refcount<octave_idx_type> m_count;\n   };\n@@ -102,6 +106,16 @@\n       return m_rep->try_lock ();\n     }\n \n+    void cond_wait (void)\n+    {\n+      m_rep->cond_wait ();\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      m_rep->cond_signal ();\n+    }\n+\n   protected:\n     base_mutex *m_rep;\n   };\ndiff -r 8d7671609955 -r 8d4683a83238 liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tSat Oct 30 10:20:24 2021 -0400\n+++ b/liboctave/util/url-transfer.cc\tMon Dec 27 01:17:19 2021 -0600\n@@ -36,6 +36,7 @@\n #include \"file-stat.h\"\n #include \"lo-sysdep.h\"\n #include \"oct-env.h\"\n+#include \"gnulib/lib/base64.h\"\n #include \"unwind-prot.h\"\n #include \"url-transfer.h\"\n #include \"version.h\"\n@@ -48,6 +49,10 @@\n \n namespace octave\n {\n+  // Forward declaration for event_manager\n+  extern bool __event_manager_request_input_enabled__();\n+  extern std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n   base_url_transfer::base_url_transfer (void)\n     : m_host_or_url (), m_valid (false), m_ftp (false),\n       m_ascii_mode (false), m_ok (true), m_errmsg (),\n@@ -240,6 +245,86 @@\n     return file_list;\n   }\n \n+\n+class link_transfer : public base_url_transfer\n+{\n+public:\n+\n+  link_transfer (void)\n+      : base_url_transfer () {\n+    m_valid = true;\n+  }\n+\n+  link_transfer (const std::string& host, const std::string& user_arg,\n+                 const std::string& passwd, std::ostream& os)\n+      : base_url_transfer (host, user_arg, passwd, os) {\n+    m_valid = true;\n+    // url = \"ftp://\" + host;\n+  }\n+\n+  link_transfer (const std::string& url_str, std::ostream& os)\n+      : base_url_transfer (url_str, os) {\n+    m_valid = true;\n+  }\n+\n+  ~link_transfer (void) {}\n+\n+  void http_get (const Array<std::string>& param) {\n+    perform_action (param, \"get\");\n+  }\n+\n+  void http_post (const Array<std::string>& param) {\n+    perform_action (param, \"post\");\n+  }\n+\n+  void http_action (const Array<std::string>& param, const std::string& action) {\n+    perform_action (param, action);\n+  }\n+\n+private:\n+  void perform_action(const Array<std::string>& param, const std::string& action) {\n+    std::string url = m_host_or_url;\n+\n+    // Convert from Array to std::list\n+    std::list<std::string> paramList;\n+    for (int i = 0; i < param.numel(); i ++) {\n+      std::string value = param(i);\n+      paramList.push_back(value);\n+    }\n+\n+    if (__event_manager_request_input_enabled__()) {\n+      bool success;\n+      std::string result = __event_manager_request_url__(url, paramList, action, success);\n+      if (success) {\n+        process_success(result);\n+      } else {\n+        m_ok = false;\n+        m_errmsg = result;\n+      }\n+    } else {\n+      m_ok = false;\n+      m_errmsg = \"octave_link not connected for link_transfer\";\n+    }\n+  }\n+\n+  void process_success(const std::string& result) {\n+    // If success, the result is returned as a base64 string, and we need to decode it.\n+    // Use the base64 implementation from gnulib, which is already an Octave dependency.\n+    const char *inc = &(result[0]);\n+    char *out;\n+    size_t outlen;\n+    bool b64_ok = base64_decode_alloc(inc, result.length(), &out, &outlen);\n+    if (!b64_ok) {\n+      m_ok = false;\n+      m_errmsg = \"failed decoding base64 from octave_link\";\n+    } else {\n+      m_curr_ostream->write(out, outlen);\n+      ::free(out);\n+    }\n+  }\n+};\n+\n+\n #if defined (HAVE_CURL)\n \n   static int\n@@ -941,17 +1026,30 @@\n #  define REP_CLASS base_url_transfer\n #endif\n \n-  url_transfer::url_transfer (void) : m_rep (new REP_CLASS ())\n-  { }\n+  url_transfer::url_transfer (void) {\n+    if (__event_manager_request_input_enabled__()) {\n+      m_rep.reset(new link_transfer());\n+    } else {\n+      m_rep.reset(new REP_CLASS());\n+    }\n+  }\n \n   url_transfer::url_transfer (const std::string& host, const std::string& user,\n-                              const std::string& passwd, std::ostream& os)\n-    : m_rep (new REP_CLASS (host, user, passwd, os))\n-  { }\n+                              const std::string& passwd, std::ostream& os) {\n+    if (__event_manager_request_input_enabled__()) {\n+      m_rep.reset(new link_transfer(host, user, passwd, os));\n+    } else {\n+      m_rep.reset(new REP_CLASS(host, user, passwd, os));\n+    }\n+  }\n \n-  url_transfer::url_transfer (const std::string& url, std::ostream& os)\n-    : m_rep (new REP_CLASS (url, os))\n-  { }\n+  url_transfer::url_transfer (const std::string& url, std::ostream& os) {\n+    if (__event_manager_request_input_enabled__()) {\n+      m_rep.reset(new link_transfer(url, os));\n+    } else {\n+      m_rep.reset(new REP_CLASS(url, os));\n+    }\n+  }\n \n #undef REP_CLASS\n \ndiff -r 8d7671609955 -r 8d4683a83238 scripts/help/__unimplemented__.m\n--- a/scripts/help/__unimplemented__.m\tSat Oct 30 10:20:24 2021 -0400\n+++ b/scripts/help/__unimplemented__.m\tMon Dec 27 01:17:19 2021 -0600\n@@ -45,7 +45,30 @@\n \n   is_matlab_function = true;\n \n+  ## First look at the package metadata\n+  # To generate package_metadata.mat, run: packages={}; for p=pkg('list'); packages={packages{:} pkg('describe', '-verbose', p{1}.name){:}}; endfor; save('/usr/local/share/octave/site/m/package_metadata.mat', 'packages');\n+  found_in_package_metadata = false;\n+  try\n+    vars = load(\"/usr/local/share/octave/site/m/package_metadata.mat\");\n+    for lvl1 = vars.packages\n+      for lvl2 = lvl1{1}.provides\n+        for lvl3 = lvl2{1}.functions\n+          if strcmp(fcn, lvl3{1})\n+            txt = check_package(fcn, lvl1{1}.name);\n+            found_in_package_metadata = true;\n+            break;\n+          endif\n+        endfor\n+        if found_in_package_metadata, break; endif\n+      endfor\n+      if found_in_package_metadata, break; endif\n+    endfor\n+  catch err\n+    warning(err)\n+  end_try_catch\n+\n   ## Some smarter cases, add more as needed.\n+  if !found_in_package_metadata\n   switch (fcn)\n     case {\"avifile\", \"aviinfo\", \"aviread\"}\n       txt = [\"Basic video file support is provided in the video package.  \", ...\n@@ -524,6 +547,7 @@\n         txt = \"\";\n       endif\n   endswitch\n+  endif\n \n   if (is_matlab_function)\n     txt = [txt, \"\\n\\n@noindent\\nPlease read \", ...\n@@ -566,13 +590,13 @@\n       endfor\n       txt = sprintf (\"%s but has not yet been implemented.\", txt);\n     case \"not loaded\",\n-      txt = sprintf ([\"%s which you have installed but not loaded.  To \", ...\n-                      \"load the package, run 'pkg load %s' from the \", ...\n-                      \"Octave prompt.\"], txt, name);\n+      txt = sprintf ([\"%s, which you have installed but not loaded.\\n\\n\", ...\n+                      \"Run `pkg load %s' to use `%s'.\"], ...\n+                     txt, name, fcn);\n     otherwise\n       ## this includes \"not installed\" and anything else if pkg changes\n       ## the output of describe\n-      txt = sprintf (\"%s which seems to not be installed in your system.\", txt);\n+      txt = sprintf (\"%s, which seems to not be installed in your system.\", txt);\n   endswitch\n \n endfunction\ndiff -r 8d7671609955 -r 8d4683a83238 scripts/plot/util/__gnuplot_drawnow__.m\n--- a/scripts/plot/util/__gnuplot_drawnow__.m\tSat Oct 30 10:20:24 2021 -0400\n+++ b/scripts/plot/util/__gnuplot_drawnow__.m\tMon Dec 27 01:17:19 2021 -0600\n@@ -32,9 +32,84 @@\n \n   if (nargin < 1 || nargin > 4 || nargin == 2)\n     print_usage ();\n-  endif\n+\n+  elseif (nargin >= 3 && nargin <= 4)\n+    ## Write the plot to the given file (e.g., via the \"print\" command)\n+    if (nargin == 5)\n+      __gnuplot_draw_to_file__ (h, term, file, debug_file);\n+    else\n+      __gnuplot_draw_to_file__ (h, term, file);\n+    endif\n+\n+  else  # nargin == 1\n+    ##  Plot to terminal and/or static (e.g., via the \"plot\" command)\n+    plot_stream = get (h, \"__plot_stream__\");\n+    if (isempty (plot_stream))\n+      plot_stream = __gnuplot_open_stream__ (2, h);\n+      new_stream = true;\n+    else\n+      new_stream = false;\n+    endif\n+    term = gnuplot_default_term (plot_stream);\n+\n+    ## There are a few options for how we can proceed.\n+    ## In most cases, we will tell GNUPLOT to put the plot in its terminal.\n+    ## If we have no display, we want to use the \"dumb\" terminal.\n+    ## Octave Link may request that we send the plot as an event.\n+    ## The latter two cases require plotting to a temp file.\n+\n+    should_plot_to_terminal = (\n+      !strcmp (term, \"dumb\") && (\n+        __event_manager_plot_destination__ () == 0 ||\n+        __event_manager_plot_destination__ () == 2\n+      )\n+    );\n+\n+    if (should_plot_to_terminal)\n+      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n+      __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n+      fflush (plot_stream(1));\n+    endif\n \n-  if (nargin >= 3 && nargin <= 4)\n+    should_plot_to_temp_file = (\n+      strcmp (term, \"dumb\") ||\n+      __event_manager_plot_destination__ () == 1 ||\n+      __event_manager_plot_destination__ () == 2\n+    );\n+\n+    if (should_plot_to_temp_file)\n+      tmp_file = tempname ();\n+      __gnuplot_draw_to_file__ (h, term, tmp_file);\n+      fflush (plot_stream(1));\n+\n+      ## Read the temp file into memory and then delete it\n+      fid = fopen (tmp_file, 'r');\n+      while (fid < 0)\n+        fprintf (stderr, \"🛈 Waiting for plot to finish… ⏳\\n\");\n+        pause (0.5);\n+        fid = fopen (tmp_file, 'r');\n+      endwhile\n+      [a, count] = fscanf (fid, '%c', Inf);\n+      fclose (fid);\n+      unlink (tmp_file);\n+\n+      ## What to do with the plot data?\n+      if (count > 0)\n+        if (a(1) == 12)\n+          a = a(2:end);  # avoid ^L at the beginning\n+        endif\n+        if strcmp (term, \"dumb\")\n+          puts (a);\n+        else\n+          __event_manager_show_static_plot__ (term, a);\n+        endif\n+      endif\n+    endif\n+\n+  endif\n+endfunction\n+\n+function __gnuplot_draw_to_file__ (h, term, file, debug_file)\n     ## Produce various output formats, or redirect gnuplot stream to a\n     ## debug file.\n     plot_stream = [];\n@@ -70,44 +145,6 @@\n         fclose (fid);\n       endif\n     end_unwind_protect\n-  else  # nargin == 1\n-    ##  Graphics terminal for display.\n-    plot_stream = get (h, \"__plot_stream__\");\n-    if (isempty (plot_stream))\n-      plot_stream = __gnuplot_open_stream__ (2, h);\n-      new_stream = true;\n-    else\n-      new_stream = false;\n-    endif\n-    term = gnuplot_default_term (plot_stream);\n-    if (strcmp (term, \"dumb\"))\n-      ## popen2 eats stdout of gnuplot, use temporary file instead\n-      dumb_tmp_file = tempname ();\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h,\n-                                   term, dumb_tmp_file);\n-    else\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n-    endif\n-    __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n-    fflush (plot_stream(1));\n-    if (strcmp (term, \"dumb\"))\n-      fid = -1;\n-      while (fid < 0)\n-        pause (0.1);\n-        fid = fopen (dumb_tmp_file, 'r');\n-      endwhile\n-      ## reprint the plot on screen\n-      [a, count] = fscanf (fid, '%c', Inf);\n-      fclose (fid);\n-      if (count > 0)\n-        if (a(1) == 12)\n-          a = a(2:end);  # avoid ^L at the beginning\n-        endif\n-        puts (a);\n-      endif\n-      unlink (dumb_tmp_file);\n-    endif\n-  endif\n \n endfunction\n \n"
  },
  {
    "path": "back-octave/oo-changesets/321-faad58416a3a.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1640591257 21600\n#      Mon Dec 27 01:47:37 2021 -0600\n# Branch oo-6.4.0\n# Node ID faad58416a3a44c493ee817964bd11407895a60f\n# Parent  8d4683a83238d9f41c16f6b9138c72530f0cc9a6\nRemove gnuplot warning\n\ndiff -r 8d4683a83238 -r faad58416a3a libinterp/dldfcn/__init_gnuplot__.cc\n--- a/libinterp/dldfcn/__init_gnuplot__.cc\tMon Dec 27 01:17:19 2021 -0600\n+++ b/libinterp/dldfcn/__init_gnuplot__.cc\tMon Dec 27 01:47:37 2021 -0600\n@@ -63,25 +63,6 @@\n   gnuplot_graphics_toolkit (octave::interpreter& interp)\n     : octave::base_graphics_toolkit (\"gnuplot\"), m_interpreter (interp)\n   {\n-    static bool warned = false;\n-\n-    if (! warned)\n-      {\n-        warning_with_id\n-          (\"Octave:gnuplot-graphics\",\n-           \"using the gnuplot graphics toolkit is discouraged\\n\\\n-\\n\\\n-The gnuplot graphics toolkit is not actively maintained and has a number\\n\\\n-of limitations that are ulikely to be fixed.  Communication with gnuplot\\n\\\n-uses a one-directional pipe and limited information is passed back to the\\n\\\n-Octave interpreter so most changes made interactively in the plot window\\n\\\n-will not be reflected in the graphics properties managed by Octave.  For\\n\\\n-example, if the plot window is closed with a mouse click, Octave will not\\n\\\n-be notified and will not update it's internal list of open figure windows.\\n\\\n-We recommend using the qt toolkit instead.\\n\");\n-\n-          warned = true;\n-      }\n   }\n \n   ~gnuplot_graphics_toolkit (void) = default;\n"
  },
  {
    "path": "back-octave/oo-changesets/400-7ade2492e023.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1640564444 21600\n#      Sun Dec 26 18:20:44 2021 -0600\n# Branch oo-7.0.1\n# Node ID 7ade2492e0237c2557ae6e67ee1be88cce8e6982\n# Parent  117ebe363f56509f909190fc12aa321e55bea711\n# Parent  1e1c91e6cddc3c48870e390b2730c7b586cc8a89\nMerge oo-6.0.1 into oo-7.0.1\n\ndiff -r 117ebe363f56 -r 7ade2492e023 configure.ac\n--- a/configure.ac\tSat Dec 25 19:16:44 2021 -0800\n+++ b/configure.ac\tSun Dec 26 18:20:44 2021 -0600\n@@ -2909,7 +2909,7 @@\n AC_SUBST(LIBOCTAVE_LINK_DEPS)\n AC_SUBST(LIBOCTAVE_LINK_OPTS)\n \n-LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $SPARSE_XLIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $JAVA_LIBS $LAPACK_LIBS\"\n+LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $SPARSE_XLIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $JAVA_LIBS $LAPACK_LIBS -ljson-c\"\n \n LIBOCTINTERP_LINK_OPTS=\"$FT2_LDFLAGS $HDF5_LDFLAGS $MAGICK_LDFLAGS $Z_LDFLAGS $SPARSE_XLDFLAGS $FFTW_XLDFLAGS\"\n \ndiff -r 117ebe363f56 -r 7ade2492e023 libgui/src/qt-interpreter-events.cc\n--- a/libgui/src/qt-interpreter-events.cc\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libgui/src/qt-interpreter-events.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -309,6 +309,21 @@\n     emit edit_variable_signal (QString::fromStdString (expr), val);\n   }\n \n+  void qt_interpreter_events::show_static_plot (const std::string&, const std::string&)\n+  {\n+    return;\n+  }\n+\n+  std::string qt_interpreter_events::request_input (const std::string&)\n+  {\n+    return {};\n+  }\n+\n+  std::string qt_interpreter_events::request_url (const std::string&, const std::list<std::string>&, const std::string&, bool&)\n+  {\n+    return {};\n+  }\n+\n   bool qt_interpreter_events::confirm_shutdown (void)\n   {\n     QMutexLocker autolock (&m_mutex);\n@@ -601,6 +616,9 @@\n     emit clear_history_signal ();\n   }\n \n+  void qt_interpreter_events::do_clear_screen (void)\n+  { }\n+\n   void qt_interpreter_events::pre_input_event (void)\n   { }\n \ndiff -r 117ebe363f56 -r 7ade2492e023 libgui/src/qt-interpreter-events.h\n--- a/libgui/src/qt-interpreter-events.h\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libgui/src/qt-interpreter-events.h\tSun Dec 26 18:20:44 2021 -0600\n@@ -136,6 +136,12 @@\n \n     void edit_variable (const std::string& name, const octave_value& val);\n \n+    void show_static_plot (const std::string& term, const std::string& content);\n+\n+    std::string request_input (const std::string&);\n+\n+    std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n     bool confirm_shutdown (void);\n \n     bool prompt_new_edit_file (const std::string& file);\n@@ -190,6 +196,8 @@\n \n     void clear_history (void);\n \n+    void clear_screen (void);\n+\n     void pre_input_event (void);\n \n     void post_input_event (void);\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/event-manager.cc\n--- a/libinterp/corefcn/event-manager.cc\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/corefcn/event-manager.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -46,6 +46,16 @@\n \n OCTAVE_NAMESPACE_BEGIN\n \n+  bool __event_manager_request_input_enabled__() {\n+    event_manager& evmgr = __get_event_manager__ (\"request_input_enabled\");\n+    return evmgr.request_input_enabled();\n+  }\n+\n+  std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\n+    event_manager& evmgr = __get_event_manager__ (\"request_url\");\n+    return evmgr.request_url(url, param, action, success);\n+  }\n+\n   static int readline_event_hook (void)\n   {\n     event_manager& evmgr = __get_event_manager__ (\"octave_readline_hook\");\n@@ -878,3 +888,27 @@\n }\n \n OCTAVE_NAMESPACE_END\n+\n+DEFMETHOD (__event_manager_plot_destination__, interp, , ,\n+           doc: /* -*- texinfo -*-\n+@deftypefn {} {} __event_manager_plot_destination__ ()\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  return ovl (interp.get_event_manager().plot_destination());\n+}\n+\n+DEFMETHOD (__event_manager_show_static_plot__, interp, args, ,\n+           doc: /* -*- texinfo -*-\n+@deftypefn {} {} __event_manager_show_static_plot__ (@var{term}, @var{content})\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  if (args.length () != 2) {\n+    return ovl ();\n+  }\n+\n+  std::string term = args(0).string_value();\n+  std::string content = args(1).string_value();\n+  return ovl (interp.get_event_manager().show_static_plot(term, content));\n+}\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/event-manager.h\n--- a/libinterp/corefcn/event-manager.h\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/corefcn/event-manager.h\tSun Dec 26 18:20:44 2021 -0600\n@@ -50,6 +50,12 @@\n   class execution_exception;\n   class symbol_info_list;\n \n+  enum plot_destination_t {\n+    TERMINAL_ONLY = 0,\n+    STATIC_ONLY = 1,\n+    TERMINAL_AND_STATIC = 2\n+  };\n+\n   // The methods in this class provide a way to pass signals to the GUI\n   // thread.  A GUI that wishes to act on these events should derive\n   // from this class and perform actions in a thread-safe way.  In\n@@ -176,6 +182,13 @@\n     // confirmation before another action.  Could these be reformulated\n     // using the question_dialog action?\n \n+    bool _request_input_enabled;\n+    virtual std::string request_input (const std::string&) = 0;\n+    virtual std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) = 0;\n+\n+    plot_destination_t _plot_destination;\n+    virtual void show_static_plot (const std::string& term, const std::string& content) = 0;\n+\n     virtual bool confirm_shutdown (void) { return true; }\n \n     virtual bool prompt_new_edit_file (const std::string& /*file*/)\n@@ -260,6 +273,8 @@\n \n     virtual void clear_history (void) { }\n \n+    virtual void clear_screen (void) { }\n+\n     virtual void pre_input_event (void) { }\n \n     virtual void post_input_event (void) { }\n@@ -446,6 +461,28 @@\n         m_instance->update_path_dialog ();\n     }\n \n+    bool request_input_enabled (void)\n+    {\n+      return enabled () ? instance->_request_input_enabled : false;\n+    }\n+\n+    plot_destination_t plot_destination (void)\n+    {\n+      return enabled () ? instance->_plot_destination : TERMINAL_ONLY;\n+    }\n+\n+    bool\n+    show_static_plot (const std::string& term, const std::string& content)\n+    {\n+      if (enabled ())\n+        {\n+          instance->show_static_plot (term, content);\n+          return true;\n+        }\n+      else\n+        return false;\n+    }\n+\n     bool show_preferences (void)\n     {\n       if (enabled ())\n@@ -706,6 +743,12 @@\n         m_instance->clear_history ();\n     }\n \n+    void clear_screen (void)\n+    {\n+      if (enabled ())\n+        instance->clear_screen ();\n+    }\n+\n     void pre_input_event (void)\n     {\n       if (enabled ())\n@@ -718,6 +761,21 @@\n         m_instance->post_input_event ();\n     }\n \n+\n+    std::string request_input (const std::string& prompt)\n+    {\n+      return request_input_enabled ()\n+        ? instance->request_input (prompt)\n+        : std::string ();\n+    }\n+\n+    std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success)\n+    {\n+      return request_input_enabled ()\n+        ? instance->request_url (url, param, action, success)\n+        : std::string ();\n+    }\n+\n     void enter_debugger_event (const std::string& fcn_name,\n                                const std::string& fcn_file_name, int line)\n     {\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/input.cc\n--- a/libinterp/corefcn/input.cc\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/corefcn/input.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -791,7 +791,12 @@\n \n     eof = false;\n \n-    std::string retval = command_editor::readline (s, eof);\n+    std::string retval;\n+    event_manager& evmgr = m_interpreter.get_event_manager ();\n+    if (evmgr.request_input_enabled ())\n+      retval = evmgr.request_input (s);\n+    else\n+      retval = command_editor::readline (s, eof);\n \n     if (! eof && retval.empty ())\n       retval = \"\\n\";\n@@ -1666,3 +1671,32 @@\n }\n \n OCTAVE_NAMESPACE_END\n+\n+DEFUN (current_command_number, args, ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn  {} {@var{val} =} current_command_number ()\n+@deftypefnx {} {@var{old_val} =} current_command_number (@var{new_val})\n+Sets the current command number, which appears in the prompt string.\n+For example, if the prompt says \"octave:1>\", then the current command\n+number is 1.\n+\n+This is a custom function in Octave Online.\n+\n+@example\n+current_command_number(1)\n+@end example\n+@end deftypefn */)\n+{\n+  int nargin = args.length ();\n+  if (nargin == 0) {\n+    int n = octave::command_editor::current_command_number();\n+    return ovl(n);\n+  } else if (nargin > 1) {\n+    print_usage ();\n+    return ovl();\n+  } else {\n+    int n = args(0).int_value ();\n+    octave::command_editor::reset_current_command_number(n);\n+    return ovl(n);\n+  }\n+}\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/interpreter.cc\n--- a/libinterp/corefcn/interpreter.cc\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/corefcn/interpreter.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -62,6 +62,7 @@\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"interpreter.h\"\n+#include \"json-main.h\"\n #include \"load-path.h\"\n #include \"load-save.h\"\n #include \"octave.h\"\n@@ -619,6 +620,11 @@\n         std::string texi_macros_file = options.texi_macros_file ();\n         if (! texi_macros_file.empty ())\n           Ftexi_macros_file (*this, octave_value (texi_macros_file));\n+\n+        if (!options.json_sock_path().empty ()) {\n+          static json_main _json_main (*this, options.json_sock_path(), options.json_max_message_length());\n+          _json_main.run_loop_on_new_thread();\n+        }\n       }\n \n     // FIXME: we defer creation of the gh_manager object because it\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/json-main.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -0,0 +1,101 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include \"json-main.h\"\r\n+#include \"interpreter.h\"\r\n+\r\n+#include <iostream>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <unistd.h>\r\n+\r\n+\r\n+// Analog of main-window.cc\r\n+// TODO: Think more about concurrency and null pointer exceptions\r\n+\r\n+namespace octave {\r\n+\r\n+void* run_loop_pthread(void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->run_loop();\r\n+  return NULL;\r\n+}\r\n+\r\n+void json_object_cb(std::string name, JSON_OBJECT_T jobj, void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->process_json_object(name, jobj);\r\n+}\r\n+\r\n+json_main::json_main(interpreter& interp, const std::string& json_sock_path, int max_message_length)\r\n+  : _json_sock_path (json_sock_path),\r\n+    _max_message_length (max_message_length),\r\n+    _loop_thread_active (false),\r\n+    _octave_json_link (new octave_json_link(this))\r\n+{\r\n+  // Enable the octave_json_link instance\r\n+  // Note: this passes ownership to octave_link\r\n+  event_manager& evmgr = interp.get_event_manager ();\r\n+  evmgr.connect_link (_octave_json_link);\r\n+  evmgr.enable ();\r\n+\r\n+  // Open UNIX socket file descriptor\r\n+  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\r\n+  struct sockaddr_un addr;\r\n+  memset(&addr, 0, sizeof(addr));\r\n+  addr.sun_family = AF_UNIX;\r\n+  memcpy(&addr.sun_path, _json_sock_path.c_str(), sizeof(addr.sun_path)-1);\r\n+  connect(\r\n+    sockfd,\r\n+    reinterpret_cast<struct sockaddr*>(&addr),\r\n+    sizeof(addr));\r\n+}\r\n+\r\n+json_main::~json_main(void) {\r\n+  close(sockfd);\r\n+\r\n+  // TODO: Stop the _loop_thread\r\n+}\r\n+\r\n+void json_main::publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  std::string jstr = json_util::to_message(name, jobj);\r\n+\r\n+  // Do not send any messages over the socket that exceed the user-specified max length.  Instead, send an error message.  If max_length is 0 (default), do not suppress any messages.\r\n+  // In stress testing, Node may be able to handle as much as 10-20 MB, but I'd prefer to stay on the safe side.  Before this safeguard was implemented, fewer than 5% of plots exceeded 1 MB.\r\n+  int length = jstr.length();\r\n+  int max_length = _max_message_length;\r\n+  if (max_length > 0 && length > max_length) {\r\n+    JSON_MAP_T m;\r\n+    JSON_MAP_SET(m, name, string);\r\n+    JSON_MAP_SET(m, length, int);\r\n+    JSON_MAP_SET(m, max_length, int);\r\n+    jstr = json_util::to_message(\"message-too-long\", json_util::from_map(m));\r\n+  }\r\n+\r\n+  send(sockfd, jstr.c_str(), jstr.length(), 0);\r\n+}\r\n+\r\n+void json_main::run_loop_on_new_thread(void) {\r\n+  if (_loop_thread_active)\r\n+    perror(\"won't run JSON socket loop multiple times\");\r\n+  _loop_thread_active = true;\r\n+\r\n+  pthread_create(\r\n+    &_loop_thread,\r\n+    NULL,\r\n+    run_loop_pthread,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::run_loop(void) {\r\n+  json_util::read_stream(\r\n+    sockfd,\r\n+    json_object_cb,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::process_json_object(std::string name, JSON_OBJECT_T jobj) {\r\n+  _octave_json_link->receive_message(name, jobj);\r\n+}\r\n+\r\n+} // namespace octave\r\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/json-main.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.h\tSun Dec 26 18:20:44 2021 -0600\n@@ -0,0 +1,37 @@\n+#ifndef json_main_h\r\n+#define json_main_h\r\n+\r\n+#include <queue>\r\n+#include <pthread.h>\r\n+#include <stdio.h>\r\n+\r\n+#include \"octave-json-link.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+namespace octave {\r\n+\r\n+class interpreter;\r\n+\r\n+class json_main {\r\n+public:\r\n+\tjson_main(interpreter& interp, const std::string& json_sock_path, int max_message_length);\r\n+\t~json_main(void);\r\n+\r\n+\tvoid publish_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\tvoid run_loop_on_new_thread(void);\r\n+\tvoid run_loop(void);\r\n+\tvoid process_json_object(std::string name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+\tstd::string _json_sock_path;\r\n+\tint _max_message_length;\r\n+\tint sockfd;\r\n+\tbool _loop_thread_active;\r\n+\tpthread_t _loop_thread;\r\n+\r\n+\tstd::shared_ptr<octave_json_link> _octave_json_link;\r\n+};\r\n+\r\n+} // namespace octave\r\n+\r\n+#endif\r\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/json-util.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -0,0 +1,264 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <cstdlib>\r\n+#include <assert.h>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <stdio.h>\r\n+#include <json-c/arraylist.h>\r\n+#include <json-c/json_object.h>\r\n+\r\n+#include \"str-vec.h\"\r\n+\r\n+#include \"json-util.h\"\r\n+\r\n+namespace octave {\r\n+\r\n+JSON_OBJECT_T json_util::from_string(const std::string& str) {\r\n+\t// Note: the string is not necesarilly valid UTF-8. The consumers of this stream must be able to handle that situation and substitute replacement characters, etc., where necessary.\r\n+\treturn json_object_new_string_len(str.c_str(), str.length());\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int(int i) {\r\n+\treturn json_object_new_int(i);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float(float flt) {\r\n+\treturn json_object_new_double(flt);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_boolean(bool b) {\r\n+\treturn json_object_new_boolean(b);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::empty() {\r\n+\treturn json_object_new_object();\r\n+}\r\n+\r\n+template<typename T>\r\n+JSON_OBJECT_T json_object_from_list(const std::list<T>& list, JSON_OBJECT_T (*convert)(T)) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\tauto it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, convert(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_list(const std::list<std::string>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_vector(const string_vector& vect) {\r\n+\t// TODO: Make sure this function does what it's supposed to do\r\n+\tstd::list<std::string> list;\r\n+\tfor (int i = 0; i < vect.numel(); ++i) {\r\n+\t\tlist.push_back(vect[i]);\r\n+\t}\r\n+\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int_list(const std::list<int>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_int);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float_list(const std::list<float>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_float);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_symbol_info_list(const symbol_info_list& list) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\tauto it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, json_util::from_symbol_info(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_filter_list(const interpreter_events::filter_list& list) {\r\n+\treturn json_object_from_list(list, json_util::from_pair);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_value_string(const std::string str) {\r\n+\treturn json_util::from_string(str);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_symbol_info(const symbol_info element) {\r\n+\toctave_value val = element.value();\r\n+\r\n+\tstd::string dims_str = val.get_dims_str();\r\n+\r\n+\tstd::ostringstream display_str;\r\n+\tval.short_disp(display_str);\r\n+\r\n+\tJSON_MAP_T m;\r\n+\t// m[\"scope\"] = json_util::from_int(element.scope());\r\n+\tm[\"symbol\"] = json_util::from_string(element.name());\r\n+\tm[\"class_name\"] = json_util::from_string(val.class_name());\r\n+\tm[\"dimension\"] = json_util::from_string(dims_str);\r\n+\tm[\"value\"] = json_util::from_string(display_str.str());\r\n+\tm[\"complex_flag\"] = json_util::from_boolean(element.is_complex());\r\n+\treturn json_util::from_map(m);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_pair(std::pair<std::string, std::string> pair) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tjson_object_array_add(jobj, json_util::from_string(pair.first.c_str()));\r\n+\tjson_object_array_add(jobj, json_util::from_string(pair.second.c_str()));\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_map(JSON_MAP_T m) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_object();\r\n+\tfor(\r\n+\t\tstd::map<std::string, JSON_OBJECT_T>::iterator it = m.begin();\r\n+\t\tit != m.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_object_add(jobj, it->first.c_str(), it->second);\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+std::string json_util::to_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  JSON_OBJECT_T jmsg = json_object_new_array();\r\n+  json_object_array_add(jmsg, json_util::from_string(name));\r\n+  json_object_array_add(jmsg, jobj);\r\n+  std::string str (json_object_to_json_string(jmsg));\r\n+  return str;\r\n+}\r\n+\r\n+std::string json_util::to_string(JSON_OBJECT_T jobj) {\r\n+  return std::string(json_object_get_string(jobj));\r\n+}\r\n+\r\n+template<typename T>\r\n+std::list<T> json_object_to_list(JSON_OBJECT_T jobj, T (*convert)(JSON_OBJECT_T)) {\r\n+\tstd::list<T> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tfor (size_t i = 0; i < array_list_length(arr); ++i) {\r\n+\t\tJSON_OBJECT_T jsub = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, i));\r\n+\t\tret.push_back(convert(jsub));\r\n+\t}\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> json_util::to_int_list_int_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<std::list<int>, int> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_to_list<int>(first, json_util::to_int);\r\n+\tret.second = json_object_get_int(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<bool, std::string> json_util::to_bool_string_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<bool, std::string> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_get_boolean(first);\r\n+\tret.second = json_object_get_string(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::list<std::string> json_util::to_string_list(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_to_list<std::string>(jobj, json_util::to_string);\r\n+}\r\n+\r\n+int json_util::to_int(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_int(jobj);\r\n+}\r\n+\r\n+bool json_util::to_boolean(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_boolean(jobj);\r\n+}\r\n+\r\n+void json_util::read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+\r\n+\t// Make some local variables\r\n+\tint BUF_LEN = 24;\r\n+\tchar* buf = new char[BUF_LEN];  // buffer for socket read\r\n+\tint buf_len;  // length of new bytes in the buffer\r\n+\tint buf_offset;  // offset of the JSON parser in the buffer\r\n+\tJSON_OBJECT_T jobj;  // pointer to parsed JSON object\r\n+\tjson_tokener* tok = json_tokener_new();  // JSON tokenizer instance\r\n+\tenum json_tokener_error jerr;  // status of JSON tokenizer\r\n+\r\n+\t// Start the blocking I/O loop\r\n+\twhile( (buf_len=recv(sockfd, buf, BUF_LEN, 0)) > 0) {\r\n+\t\tbuf_offset = 0;\r\n+\t\twhile(buf_offset < buf_len){\r\n+\t\t\tjobj = json_tokener_parse_ex(tok, buf + buf_offset, buf_len - buf_offset);\r\n+\t\t\tjerr = json_tokener_get_error(tok);\r\n+\t\t\tbuf_offset += tok->char_offset;\r\n+\r\n+\t\t\t// Do we need more material in order to make JSON?\r\n+\t\t\tif (jerr == json_tokener_continue) {\r\n+\t\t\t\tcontinue;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Make a new tokenizer\r\n+\t\t\tjson_tokener_free(tok);\r\n+\t\t\ttok = json_tokener_new();\r\n+\r\n+\t\t\t// Did we encounter a malformed JSON object?\r\n+\t\t\tif (jerr != json_tokener_success) {\r\n+\t\t\t\tfprintf(stderr,\r\n+\t\t\t\t\t\"JSON parse error: %s: '%.*s'\\n\",\r\n+\t\t\t\t\tjson_tokener_error_desc(jerr),\r\n+\t\t\t\t\t1,\r\n+\t\t\t\t\tbuf + buf_offset);\r\n+\t\t\t\tfflush(stderr);\r\n+\t\t\t\tbreak;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Our object is ready\r\n+\t\t\tprocess_message(jobj, cb, arg);\r\n+\t\t}\r\n+\t}\r\n+\r\n+\tjson_tokener_free(tok);\r\n+\tdelete buf;\r\n+}\r\n+\r\n+void json_util::process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+  if (!json_object_is_type(jobj, json_type_array))\r\n+    return;\r\n+  if (json_object_array_length(jobj) != 2)\r\n+    return;\r\n+\r\n+  cb(\r\n+  \tjson_util::to_string(json_object_array_get_idx(jobj, 0)),\r\n+  \tjson_object_array_get_idx(jobj, 1),\r\n+  \targ\r\n+  );\r\n+}\r\n+\r\n+} // namespace octave\r\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/json-util.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.h\tSun Dec 26 18:20:44 2021 -0600\n@@ -0,0 +1,64 @@\n+#ifndef json_util_h\r\n+#define json_util_h\r\n+\r\n+#include <json-c/json.h>\r\n+#include <map>\r\n+#include <list>\r\n+\r\n+#include \"syminfo.h\"\r\n+#include \"event-manager.h\"\r\n+\r\n+class string_vector;\r\n+\r\n+// All of the code interacting with the external JSON library should be in\r\n+// the json-util.h and json-util.cc files.  This way, if we want to change\r\n+// the external JSON library, we can do it all in one place.\r\n+\r\n+#define JSON_OBJECT_T json_object*\r\n+#define JSON_MAP_T std::map<std::string, JSON_OBJECT_T>\r\n+\r\n+#define JSON_MAP_SET(M, FIELD, TYPE){ \\\r\n+\tm[#FIELD] = json_util::from_##TYPE (FIELD); \\\r\n+}\r\n+\r\n+namespace octave {\r\n+\r\n+class json_util {\r\n+public:\r\n+\tstatic JSON_OBJECT_T from_string(const std::string& str);\r\n+\tstatic JSON_OBJECT_T from_int(int i);\r\n+\tstatic JSON_OBJECT_T from_float(float flt);\r\n+\tstatic JSON_OBJECT_T from_boolean(bool b);\r\n+\tstatic JSON_OBJECT_T empty();\r\n+\r\n+\tstatic JSON_OBJECT_T from_string_list(const std::list<std::string>& list);\r\n+\tstatic JSON_OBJECT_T from_string_vector(const string_vector& list);\r\n+\tstatic JSON_OBJECT_T from_int_list(const std::list<int>& list);\r\n+\tstatic JSON_OBJECT_T from_float_list(const std::list<float>& list);\r\n+\tstatic JSON_OBJECT_T from_symbol_info_list(const symbol_info_list& list);\r\n+        static JSON_OBJECT_T from_filter_list(const interpreter_events::filter_list& list);\r\n+\r\n+\tstatic JSON_OBJECT_T from_value_string(const std::string str);\r\n+\tstatic JSON_OBJECT_T from_symbol_info(const symbol_info element);\r\n+\tstatic JSON_OBJECT_T from_pair(std::pair<std::string, std::string> pair);\r\n+\r\n+\tstatic JSON_OBJECT_T from_map(JSON_MAP_T m);\r\n+\r\n+\tstatic std::string to_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic std::string to_string(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<std::list<int>, int> to_int_list_int_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<bool, std::string> to_bool_string_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::list<std::string> to_string_list(JSON_OBJECT_T jobj);\r\n+\tstatic int to_int(JSON_OBJECT_T jobj);\r\n+\tstatic bool to_boolean(JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic void read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+\r\n+private:\r\n+\tstatic void process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+};\r\n+\r\n+} // namespace octave\r\n+\r\n+#endif\r\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/module.mk\n--- a/libinterp/corefcn/module.mk\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/corefcn/module.mk\tSun Dec 26 18:20:44 2021 -0600\n@@ -45,6 +45,8 @@\n   %reldir%/help.h \\\n   %reldir%/hook-fcn.h \\\n   %reldir%/input.h \\\n+  %reldir%/json-main.h \\\n+  %reldir%/json-util.h \\\n   %reldir%/interpreter.h \\\n   %reldir%/latex-text-renderer.h \\\n   %reldir%/load-path.h \\\n@@ -76,6 +78,7 @@\n   %reldir%/oct-strstrm.h \\\n   %reldir%/oct.h \\\n   %reldir%/octave-default-image.h \\\n+  %reldir%/octave-json-link.h \\\n   %reldir%/pager.h \\\n   %reldir%/pr-flt-fmt.h \\\n   %reldir%/pr-output.h \\\n@@ -186,6 +189,8 @@\n   %reldir%/hex2num.cc \\\n   %reldir%/hook-fcn.cc \\\n   %reldir%/input.cc \\\n+  %reldir%/json-main.cc \\\n+  %reldir%/json-util.cc \\\n   %reldir%/interpreter-private.cc \\\n   %reldir%/interpreter.cc \\\n   %reldir%/inv.cc \\\n@@ -225,6 +230,7 @@\n   %reldir%/oct-tex-lexer.ll \\\n   %reldir%/oct-tex-parser.h \\\n   %reldir%/oct-tex-parser.yy \\\n+  %reldir%/octave-json-link.cc \\\n   %reldir%/ordqz.cc \\\n   %reldir%/ordschur.cc \\\n   %reldir%/pager.cc \\\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/octave-json-link.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -0,0 +1,411 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <iostream>\r\n+#include \"octave-json-link.h\"\r\n+#include \"cmd-edit.h\"\r\n+#include \"json-main.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+namespace octave {\r\n+\r\n+octave_json_link::octave_json_link(json_main* __json_main)\r\n+\t: interpreter_events (),\r\n+\t\t_json_main (__json_main)\r\n+{\r\n+\t_request_input_enabled = true;\r\n+\t_plot_destination = STATIC_ONLY;\r\n+}\r\n+\r\n+octave_json_link::~octave_json_link(void) { }\r\n+\r\n+std::string octave_json_link::request_input(const std::string& prompt) {\r\n+\t// Triggered whenever the console prompts for user input\r\n+\r\n+\tstd::string value;\r\n+\tif (!request_input_queue.dequeue_to(&value)) {\r\n+\t\t_publish_message(\"request-input\", json_util::from_string(prompt));\r\n+\t\tvalue = request_input_queue.dequeue();\r\n+\t}\r\n+\treturn value;\r\n+}\r\n+\r\n+std::string octave_json_link::request_url(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\r\n+\t// Triggered on urlread/urlwrite\r\n+\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, url, string);\r\n+\tJSON_MAP_SET(m, param, string_list);\r\n+\tJSON_MAP_SET(m, action, string);\r\n+\r\n+\t_publish_message(\"request-url\", json_util::from_map(m));\r\n+\tstd::pair<bool, std::string> result = request_url_queue.dequeue();\r\n+\tsuccess = result.first;\r\n+\treturn result.second;\r\n+}\r\n+\r\n+bool octave_json_link::confirm_shutdown(void) {\r\n+\t// Triggered when the kernel tries to exit\r\n+\t_publish_message(\"confirm-shutdown\", json_util::empty());\r\n+\r\n+\treturn confirm_shutdown_queue.dequeue();\r\n+}\r\n+\r\n+// do_exit was removed in Octave 5\r\n+// bool octave_json_link::do_exit(int status) {\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, status, int);\r\n+// \t_publish_message(\"exit\", json_util::from_map(m));\r\n+\r\n+// \t// It is our responsibility in octave_link to call exit. If we don't, then\r\n+// \t// the kernel waits for 24 hours expecting us to do something.\r\n+// \t::exit(status);\r\n+\r\n+// \treturn true;\r\n+// }\r\n+\r\n+bool octave_json_link::copy_image_to_clipboard(const std::string& file) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"copy-image-to-clipboard\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for existing files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::prompt_new_edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for new files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"prompt-new-edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn prompt_new_edit_file_queue.dequeue();\r\n+}\r\n+\r\n+// int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {\r\n+// \t// Triggered in \"msgbox\", \"helpdlg\", and \"errordlg\", among others\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\r\n+// \tJSON_MAP_SET(m, msg, string);\r\n+// \tJSON_MAP_SET(m, title, string);\r\n+// \t_publish_message(\"message-dialog\", json_util::from_map(m));\r\n+\r\n+// \treturn message_dialog_queue.dequeue();\r\n+// }\r\n+\r\n+std::string octave_json_link::question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n+\t// Triggered in \"questdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, msg, string);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, btn1, string);\r\n+\tJSON_MAP_SET(m, btn2, string);\r\n+\tJSON_MAP_SET(m, btn3, string);\r\n+\tJSON_MAP_SET(m, btndef, string);\r\n+\t_publish_message(\"question-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn question_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> octave_json_link::list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {\r\n+\t// Triggered in \"listdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, list, string_list);\r\n+\tJSON_MAP_SET(m, mode, string);\r\n+\tJSON_MAP_SET(m, width, int);\r\n+\tJSON_MAP_SET(m, height, int);\r\n+\tJSON_MAP_SET(m, initial_value, int_list);\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, ok_string, string);\r\n+\tJSON_MAP_SET(m, cancel_string, string);\r\n+\t_publish_message(\"list-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn list_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {\r\n+\t// Triggered in \"inputdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, nr, float_list);\r\n+\tJSON_MAP_SET(m, nc, float_list);\r\n+\tJSON_MAP_SET(m, defaults, string_list);\r\n+\t_publish_message(\"input-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn input_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {\r\n+\t// Triggered in \"uiputfile\", \"uigetfile\", and \"uigetdir\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, filter, filter_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, filename, string);\r\n+\tJSON_MAP_SET(m, pathname, string);\r\n+\tJSON_MAP_SET(m, multimode, string);\r\n+\t_publish_message(\"file-dialog\", json_util::from_map(m));\r\n+\t\r\n+\treturn file_dialog_queue.dequeue();\r\n+}\r\n+\r\n+int octave_json_link::debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\r\n+\t// This endpoint might be unused?  (No references)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\tJSON_MAP_SET(m, addpath_option, boolean);\r\n+\t_publish_message(\"debug-cd-or-addpath-error\", json_util::from_map(m));\r\n+\r\n+\treturn debug_cd_or_addpath_error_queue.dequeue();\r\n+}\r\n+\r\n+void octave_json_link::directory_changed(const std::string& dir) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\t_publish_message(\"change-directory\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::file_remove (const std::string& old_name, const std::string& new_name) {\r\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, old_name, string);\r\n+\tJSON_MAP_SET(m, new_name, string);\r\n+\t_publish_message(\"file-remove\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::file_renamed (bool status) {\r\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n+\t_publish_message(\"file-renamed\", json_util::from_boolean(status));\r\n+}\r\n+\r\n+void octave_json_link::execute_command_in_terminal(const std::string& command) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, command, string);\r\n+\t_publish_message(\"execute-command-in-terminal\", json_util::from_map(m));\r\n+}\r\n+\r\n+uint8NDArray octave_json_link::get_named_icon (const std::string& /* icon_name */) {\r\n+\t// Called from msgbox.m\r\n+\t// TODO: Implement request/response for this event\r\n+\tuint8NDArray retval;\r\n+\treturn retval;\r\n+}\r\n+\r\n+void octave_json_link::set_workspace(bool top_level, bool debug,\r\n+                         const symbol_info_list& ws,\r\n+                         bool update_variable_editor) {\r\n+\t// Triggered on every new line entry\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, top_level, boolean);\r\n+\tJSON_MAP_SET(m, debug, boolean);\r\n+\tJSON_MAP_SET(m, ws, symbol_info_list);\r\n+\tJSON_MAP_SET(m, update_variable_editor, boolean);\r\n+\t_publish_message(\"set-workspace\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::clear_workspace(void) {\r\n+\t// Triggered on \"clear\" command (but not \"clear all\" or \"clear foo\")\r\n+\t_publish_message(\"clear-workspace\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::set_history(const string_vector& hist) {\r\n+\t// Called at startup, possibly more?\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist, string_vector);\r\n+\t_publish_message(\"set-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::append_history(const std::string& hist_entry) {\r\n+\t// Appears to be tied to readline, if available\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist_entry, string);\r\n+\t_publish_message(\"append-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::clear_history(void) {\r\n+\t// Appears to be tied to readline, if available\r\n+\t_publish_message(\"clear-history\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::clear_screen(void) {\r\n+\t// Triggered by clc\r\n+\t_publish_message(\"clear-screen\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::pre_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::post_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::enter_debugger_event(const std::string& fcn_name, const std::string& fcn_file_name, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, fcn_name, string);\r\n+\tJSON_MAP_SET(m, fcn_file_name, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"enter-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::execute_in_debugger_event(const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"execute-in-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::exit_debugger_event(void) {\r\n+\t_publish_message(\"exit-debugger-event\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::update_breakpoint(bool insert, const std::string& file, int line, const std::string& cond) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, insert, boolean);\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\tJSON_MAP_SET(m, cond, string);\r\n+\t_publish_message(\"update-breakpoint\", json_util::from_map(m));\r\n+}\r\n+\r\n+// void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {\r\n+// \t// Triggered upon interpreter startup\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, ps1, string);\r\n+// \tJSON_MAP_SET(m, ps2, string);\r\n+// \tJSON_MAP_SET(m, ps4, string);\r\n+// \t_publish_message(\"set-default-prompts\", json_util::from_map(m));\r\n+// }\r\n+\r\n+void octave_json_link::show_preferences(void) {\r\n+\t// Triggered on \"preferences\" command\r\n+\t_publish_message(\"show-preferences\", json_util::empty());\r\n+}\r\n+\r\n+std::string octave_json_link::gui_preference (const std::string& /* key */, const std::string& /* value */) {\r\n+\t// Used by Octave GUI?\r\n+\t// TODO: Implement request/response for this event\r\n+\tstd::string retval;\r\n+\treturn retval;\r\n+}\r\n+\r\n+void octave_json_link::show_doc(const std::string& file) {\r\n+\t// Triggered on \"doc\" command\r\n+\t_publish_message(\"show-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::register_doc (const std::string& file) {\r\n+\t// Triggered by the GUI documentation viewer?\r\n+\t_publish_message(\"register-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::unregister_doc (const std::string& file) {\r\n+\t// Triggered by the GUI documentation viewer?\r\n+\t_publish_message(\"unregister-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::edit_variable (const std::string& name, const octave_value& /* val */) {\r\n+\t// Triggered on \"openvar\" command\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\t// TODO: val\r\n+\t_publish_message(\"edit-variable\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::show_static_plot(const std::string& term, const std::string& content) {\r\n+\t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\r\n+\tint command_number = command_editor::current_command_number();\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, term, string);\r\n+\tJSON_MAP_SET(m, content, string);\r\n+\tJSON_MAP_SET(m, command_number, int);\r\n+\t_publish_message(\"show-static-plot\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::receive_message (const std::string& name, JSON_OBJECT_T jobj) {\r\n+\tif (name == \"cmd\" || name == \"request-input-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\trequest_input_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"request-url-answer\") {\r\n+\t\tstd::pair<bool, std::string> answer = json_util::to_bool_string_pair(jobj);\r\n+\t\trequest_url_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"confirm-shutdown-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tconfirm_shutdown_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"prompt-new-edit-file-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tprompt_new_edit_file_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"message-dialog-answer\"){\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tmessage_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"question-dialog-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\tquestion_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"list-dialog-answer\") {\r\n+\t\tstd::pair<std::list<int>, int> answer = json_util::to_int_list_int_pair(jobj);\r\n+\t\tlist_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"input-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tinput_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"file-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tfile_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"debug-cd-or-addpath-error-answer\") {\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tdebug_cd_or_addpath_error_queue.enqueue(answer);\r\n+\t}\r\n+\telse {\r\n+\t\tstd::cerr << \"warning: received unknown message: \" << name << std::endl;\r\n+\t}\r\n+}\r\n+\r\n+void octave_json_link::_publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+\t_json_main->publish_message(name, jobj);\r\n+}\r\n+\r\n+} // namespace octave\r\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/octave-json-link.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.h\tSun Dec 26 18:20:44 2021 -0600\n@@ -0,0 +1,216 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifndef octave_json_link_h\r\n+#define octave_json_link_h\r\n+\r\n+#include <list>\r\n+#include <string>\r\n+\r\n+#include \"event-manager.h\"\r\n+#include \"json-util.h\"\r\n+#include \"oct-mutex.h\"\r\n+\r\n+class string_vector;\r\n+\r\n+namespace octave {\r\n+\r\n+// Circular reference\r\n+class json_main;\r\n+\r\n+// Thread-safe queue\r\n+template<typename T> class json_queue {\r\n+public:\r\n+  json_queue();\r\n+  ~json_queue();\r\n+\r\n+  void enqueue(const T& value);\r\n+  T dequeue();\r\n+  bool dequeue_to(T* destination);\r\n+\r\n+private:\r\n+  std::queue<T> _queue;\r\n+  mutex _mutex;\r\n+};\r\n+\r\n+class octave_json_link : public interpreter_events\r\n+{\r\n+\r\n+public:\r\n+\r\n+  octave_json_link (json_main* __json_main);\r\n+\r\n+  ~octave_json_link (void);\r\n+\r\n+  std::string request_input (const std::string& prompt) override;\r\n+  std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\r\n+\r\n+  bool confirm_shutdown (void) override;\r\n+\r\n+  bool copy_image_to_clipboard (const std::string& file) override;\r\n+\r\n+  bool edit_file (const std::string& file) override;\r\n+  bool prompt_new_edit_file (const std::string& file) override;\r\n+\r\n+  std::string\r\n+  question_dialog (const std::string& msg, const std::string& title,\r\n+                      const std::string& btn1, const std::string& btn2,\r\n+                      const std::string& btn3, const std::string& btndef) override;\r\n+\r\n+  std::pair<std::list<int>, int>\r\n+  list_dialog (const std::list<std::string>& list,\r\n+                  const std::string& mode,\r\n+                  int width, int height,\r\n+                  const std::list<int>& initial_value,\r\n+                  const std::string& name,\r\n+                  const std::list<std::string>& prompt,\r\n+                  const std::string& ok_string,\r\n+                  const std::string& cancel_string) override;\r\n+\r\n+  std::list<std::string>\r\n+  input_dialog (const std::list<std::string>& prompt,\r\n+                   const std::string& title,\r\n+                   const std::list<float>& nr,\r\n+                   const std::list<float>& nc,\r\n+                   const std::list<std::string>& defaults) override;\r\n+\r\n+  std::list<std::string>\r\n+  file_dialog (const filter_list& filter, const std::string& title,\r\n+                  const std::string &filename, const std::string &pathname,\r\n+                  const std::string& multimode) override;\r\n+\r\n+  int\r\n+  debug_cd_or_addpath_error (const std::string& file,\r\n+                                const std::string& dir,\r\n+                                bool addpath_option) override;\r\n+\r\n+  void directory_changed (const std::string& dir) override;\r\n+\r\n+  void file_remove (const std::string& old_name, const std::string& new_name) override;\r\n+  void file_renamed (bool) override;\r\n+\r\n+  void execute_command_in_terminal (const std::string& command) override;\r\n+\r\n+  uint8NDArray get_named_icon (const std::string& icon_name) override;\r\n+\r\n+  void set_workspace (bool top_level, bool debug,\r\n+                         const symbol_info_list& ws,\r\n+                         bool update_variable_editor) override;\r\n+\r\n+  void clear_workspace (void) override;\r\n+\r\n+  void set_history (const string_vector& hist) override;\r\n+  void append_history (const std::string& hist_entry) override;\r\n+  void clear_history (void) override;\r\n+\r\n+  void clear_screen (void) override;\r\n+\r\n+  void pre_input_event (void) override;\r\n+  void post_input_event (void) override;\r\n+\r\n+  void enter_debugger_event (const std::string& fcn_name, const std::string& fcn_file_name, int line) override;\r\n+  void execute_in_debugger_event (const std::string& file, int line) override;\r\n+  void exit_debugger_event (void) override;\r\n+\r\n+  void update_breakpoint (bool insert,\r\n+                             const std::string& file, int line,\r\n+                             const std::string& cond) override;\r\n+\r\n+  void show_preferences (void) override;\r\n+\r\n+  std::string gui_preference (const std::string& key, const std::string& value) override;\r\n+\r\n+  void show_doc (const std::string& file) override;\r\n+\r\n+  void register_doc (const std::string& file) override;\r\n+\r\n+  void unregister_doc (const std::string& file) override;\r\n+\r\n+  void edit_variable (const std::string& name, const octave_value& val) override;\r\n+\r\n+  void show_static_plot (const std::string& term,\r\n+                            const std::string& content) override;\r\n+\r\n+  // Custom methods\r\n+  void receive_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+  json_main* _json_main;\r\n+  void _publish_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+  // Queues\r\n+  json_queue<std::string> request_input_queue;\r\n+  json_queue<std::pair<bool, std::string> > request_url_queue;\r\n+  json_queue<bool> confirm_shutdown_queue;\r\n+  json_queue<bool> prompt_new_edit_file_queue;\r\n+  json_queue<int> message_dialog_queue;\r\n+  json_queue<std::string> question_dialog_queue;\r\n+  json_queue<std::pair<std::list<int>, int> > list_dialog_queue;\r\n+  json_queue<std::list<std::string> > input_dialog_queue;\r\n+  json_queue<std::list<std::string> > file_dialog_queue;\r\n+  json_queue<int> debug_cd_or_addpath_error_queue;\r\n+};\r\n+\r\n+// Template classes require definitions in the header file...\r\n+\r\n+template<typename T>\r\n+json_queue<T>::json_queue() { }\r\n+\r\n+template<typename T>\r\n+json_queue<T>::~json_queue() { }\r\n+\r\n+template<typename T>\r\n+void json_queue<T>::enqueue(const T& value) {\r\n+  _mutex.lock();\r\n+  _queue.push(value);\r\n+  _mutex.cond_signal();\r\n+  _mutex.unlock();\r\n+}\r\n+\r\n+template<typename T>\r\n+T json_queue<T>::dequeue() {\r\n+  _mutex.lock();\r\n+  while (_queue.empty()) {\r\n+    _mutex.cond_wait();\r\n+  }\r\n+  T value = _queue.front();\r\n+  _queue.pop();\r\n+  _mutex.unlock();\r\n+  return value;\r\n+}\r\n+\r\n+template<typename T>\r\n+bool json_queue<T>::dequeue_to(T* destination) {\r\n+  _mutex.lock();\r\n+  bool retval = false;\r\n+  if (!_queue.empty()) {\r\n+    retval = true;\r\n+    *destination = _queue.front();\r\n+    _queue.pop();\r\n+  }\r\n+  _mutex.unlock();\r\n+  return retval;\r\n+}\r\n+\r\n+} // namespace octave\r\n+  \r\n+#endif\r\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/syscalls.cc\n--- a/libinterp/corefcn/syscalls.cc\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/corefcn/syscalls.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -151,9 +151,8 @@\n @deftypefn {} {[@var{err}, @var{msg}] =} exec (@var{file}, @var{args})\n Replace current process with a new process.\n \n-Calling @code{exec} without first calling @code{fork} will terminate your\n-current Octave process and replace it with the program named by @var{file}.\n-For example,\n+Calling @code{exec} will terminate your current Octave process and replace\n+it with the program named by @var{file}. For example,\n \n @example\n exec (\"ls\", \"-l\")\n@@ -476,42 +475,6 @@\n   return retval;\n }\n \n-DEFMETHODX (\"fork\", Ffork, interp, args, ,\n-            doc: /* -*- texinfo -*-\n-@deftypefn {} {[@var{pid}, @var{msg}] =} fork ()\n-Create a copy of the current process.\n-\n-Fork can return one of the following values:\n-\n-@table @asis\n-@item > 0\n-You are in the parent process.  The value returned from @code{fork} is the\n-process id of the child process.  You should probably arrange to wait for\n-any child processes to exit.\n-\n-@item 0\n-You are in the child process.  You can call @code{exec} to start another\n-process.  If that fails, you should probably call @code{exit}.\n-\n-@item < 0\n-The call to @code{fork} failed for some reason.  You must take evasive\n-action.  A system dependent error message will be waiting in @var{msg}.\n-@end table\n-@end deftypefn */)\n-{\n-  if (args.length () != 0)\n-    print_usage ();\n-\n-  if (interp.at_top_level ())\n-    error (\"fork: cannot be called from command line\");\n-\n-  std::string msg;\n-\n-  pid_t pid = sys::fork (msg);\n-\n-  return ovl (pid, msg);\n-}\n-\n DEFUNX (\"getpgrp\", Fgetpgrp, args, ,\n         doc: /* -*- texinfo -*-\n @deftypefn {} {pgid =} getpgrp ()\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/sysdep.cc\n--- a/libinterp/corefcn/sysdep.cc\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/corefcn/sysdep.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -75,6 +75,7 @@\n #include \"defun.h\"\n #include \"error.h\"\n #include \"errwarn.h\"\n+#include \"event-manager.h\"\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"octave.h\"\n@@ -719,7 +720,7 @@\n \n   // Read one character from the terminal.\n \n-  int kbhit (bool wait)\n+  int kbhit (const std::string& prompt, bool wait)\n   {\n #if defined (HAVE__KBHIT) && defined (HAVE__GETCH)\n     // This essentially means we are on a Windows system.\n@@ -746,13 +747,24 @@\n \n     set_interrupt_handler (saved_interrupt_handler, false);\n \n-    int c = std::cin.get ();\n+    int c;\n+    event_manager& evmgr = __get_event_manager__ (\"kbhit\");\n+    if (evmgr.request_input_enabled ()) {\n+      std::string line = evmgr.request_input (prompt);\n+      if (line.length() >= 1) {\n+        c = line.at(0);\n+      } else {\n+        c = '\\n';\n+      }\n+    } else {\n+      c = std::cin.get ();\n \n-    if (std::cin.fail () || std::cin.eof ())\n-      {\n-        std::cin.clear ();\n-        clearerr (stdin);\n-      }\n+      if (std::cin.fail () || std::cin.eof ())\n+        {\n+          std::cin.clear ();\n+          clearerr (stdin);\n+        }\n+    }\n \n     // Restore it, enabling system call restarts (if possible).\n     set_interrupt_handler (saved_interrupt_handler, true);\n@@ -810,6 +822,9 @@\n {\n   bool skip_redisplay = true;\n \n+  octave::event_manager& evmgr = octave::__get_event_manager__ (\"clc\");\n+  evmgr.clear_screen();\n+\n   command_editor::clear_screen (skip_redisplay);\n \n   return ovl ();\n@@ -1238,7 +1253,7 @@\n \n   Fdrawnow (interp);\n \n-  int c = kbhit (args.length () == 0);\n+  int c = kbhit (\"kbhit>\", args.length () == 0);\n \n   if (c == -1)\n     c = 0;\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/sysdep.h\n--- a/libinterp/corefcn/sysdep.h\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/corefcn/sysdep.h\tSun Dec 26 18:20:44 2021 -0600\n@@ -49,7 +49,7 @@\n \n   extern OCTINTERP_API int pclose (FILE *f);\n \n-  extern OCTINTERP_API int kbhit (bool wait = true);\n+  extern OCTINTERP_API int kbhit (const std::string& prompt, bool wait);\n \n   extern OCTINTERP_API std::string get_P_tmpdir (void);\n \ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/corefcn/utils.cc\n--- a/libinterp/corefcn/utils.cc\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/corefcn/utils.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -1584,7 +1584,7 @@\n             if (do_graphics_events)\n               gh_mgr.process_events ();\n \n-            c = kbhit (false);\n+            c = kbhit (\"press enter to continue\", false);\n           }\n       }\n     else\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/octave.cc\n--- a/libinterp/octave.cc\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/octave.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -184,6 +184,16 @@\n           case LINE_EDITING_OPTION:\n             m_forced_line_editing = m_line_editing = true;\n             break;\n+ \n+          case JSON_SOCK_OPTION:\n+            if (octave_optarg_wrapper ())\n+              m_json_sock_path = octave_optarg_wrapper ();\n+            break;\n+\n+          case JSON_MAX_LEN_OPTION:\n+            if (octave_optarg_wrapper ())\n+              m_json_max_message_length = strtol(octave_optarg_wrapper (), NULL, 10);\n+            break;\n \n           case NO_GUI_OPTION:\n             m_gui = false;\n@@ -418,6 +428,14 @@\n     sysdep_init ();\n   }\n \n+  bool application::link_enabled (void) const\n+  {\n+    if (m_interpreter) {\n+      event_manager& evmgr = m_interpreter->get_event_manager ();\n+      return evmgr.enabled();\n+    } else return false;\n+  }\n+\n   int cli_application::execute (void)\n   {\n     interpreter& interp = create_interpreter ();\n@@ -440,7 +458,7 @@\n   // FIXME: This isn't quite right, it just says that we intended to\n   // start the GUI, not that it is actually running.\n \n-  return ovl (application::is_gui_running ());\n+  return ovl (application::is_link_enabled ());\n }\n \n /*\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/octave.h\n--- a/libinterp/octave.h\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/octave.h\tSun Dec 26 18:20:44 2021 -0600\n@@ -82,6 +82,8 @@\n     std::string info_file (void) const { return m_info_file; }\n     std::string info_program (void) const { return m_info_program; }\n     std::string texi_macros_file (void) const {return m_texi_macros_file; }\n+    std::string json_sock_path (void) const { return m_json_sock_path; }\n+    int json_max_message_length (void) const { return m_json_max_message_length; }\n     string_vector all_args (void) const { return m_all_args; }\n     string_vector remaining_args (void) const { return m_remaining_args; }\n \n@@ -112,6 +114,8 @@\n     void info_file (const std::string& arg) { m_info_file = arg; }\n     void info_program (const std::string& arg) { m_info_program = arg; }\n     void texi_macros_file (const std::string& arg) { m_texi_macros_file = arg; }\n+    void json_sock_path (const std::string& arg) { m_json_sock_path = arg; }\n+    void json_max_message_length (int arg) { m_json_max_message_length = arg; }\n     void all_args (const string_vector& arg) { m_all_args = arg; }\n     void remaining_args (const string_vector& arg) { m_remaining_args = arg; }\n \n@@ -220,6 +224,14 @@\n     // (--texi-macros-file)\n     std::string m_texi_macros_file;\n \n+    // The value for \"JSON_SOCK\" specified on the command line.\n+    // (--json-sock)\n+    std::string m_json_sock_path;\n+\n+    // The maximum message length; valid only if \"JSON_SOCK\" is specified.\n+    // (--json-max-len)\n+    int m_json_max_message_length = 0;\n+\n     // All arguments passed to the argc, argv constructor.\n     string_vector m_all_args;\n \n@@ -233,6 +245,7 @@\n   // both) of them...\n \n   class interpreter;\n+  class event_manager;\n \n   // Base class for an Octave application.\n \n@@ -282,6 +295,8 @@\n     virtual bool gui_running (void) const { return false; }\n     virtual void gui_running (bool) { }\n \n+    bool link_enabled (void) const;\n+\n     void program_invocation_name (const std::string& nm) { m_program_invocation_name = nm; }\n \n     void program_name (const std::string& nm) { m_program_name = nm; }\n@@ -314,6 +329,11 @@\n       return s_instance ? s_instance->gui_running () : false;\n     }\n \n+    static bool is_link_enabled (void)\n+    {\n+      return instance ? instance->link_enabled () : false;\n+    }\n+\n     // Convenience functions.\n \n     static bool forced_interactive (void);\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/options.h\n--- a/libinterp/options.h\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/options.h\tSun Dec 26 18:20:44 2021 -0600\n@@ -46,17 +46,19 @@\n #define IMAGE_PATH_OPTION 7\n #define INFO_FILE_OPTION 8\n #define INFO_PROG_OPTION 9\n-#define LINE_EDITING_OPTION 10\n-#define NO_GUI_OPTION 11\n-#define NO_GUI_LIBS_OPTION 12\n-#define NO_INIT_FILE_OPTION 13\n-#define NO_INIT_PATH_OPTION 14\n-#define NO_LINE_EDITING_OPTION 15\n-#define NO_SITE_FILE_OPTION 16\n-#define PERSIST_OPTION 17\n-#define SERVER_OPTION 18\n-#define TEXI_MACROS_FILE_OPTION 19\n-#define TRADITIONAL_OPTION 20\n+#define JSON_SOCK_OPTION 10\n+#define JSON_MAX_LEN_OPTION 11\n+#define LINE_EDITING_OPTION 12\n+#define NO_GUI_OPTION 13\n+#define NO_GUI_LIBS_OPTION 14\n+#define NO_INIT_FILE_OPTION 15\n+#define NO_INIT_PATH_OPTION 16\n+#define NO_LINE_EDITING_OPTION 17\n+#define NO_SITE_FILE_OPTION 18\n+#define PERSIST_OPTION 19\n+#define SERVER_OPTION 20\n+#define TEXI_MACROS_FILE_OPTION 21\n+#define TRADITIONAL_OPTION 22\n struct octave_getopt_options long_opts[] =\n {\n   { \"braindead\",                octave_no_arg,       nullptr, TRADITIONAL_OPTION },\n@@ -74,6 +76,8 @@\n   { \"info-file\",                octave_required_arg, nullptr, INFO_FILE_OPTION },\n   { \"info-program\",             octave_required_arg, nullptr, INFO_PROG_OPTION },\n   { \"interactive\",              octave_no_arg,       nullptr, 'i' },\n+  { \"json-sock\",                octave_required_arg, nullptr, JSON_SOCK_OPTION },\n+  { \"json-max-len\",             octave_required_arg, nullptr, JSON_MAX_LEN_OPTION },\n   { \"line-editing\",             octave_no_arg,       nullptr, LINE_EDITING_OPTION },\n   { \"no-gui\",                   octave_no_arg,       nullptr, NO_GUI_OPTION },\n   { \"no-gui-libs\",              octave_no_arg,       nullptr, NO_GUI_LIBS_OPTION },\ndiff -r 117ebe363f56 -r 7ade2492e023 libinterp/usage.h\n--- a/libinterp/usage.h\tSat Dec 25 19:16:44 2021 -0800\n+++ b/libinterp/usage.h\tSun Dec 26 18:20:44 2021 -0600\n@@ -37,11 +37,11 @@\n   \"octave [-HVWdfhiqvx] [--debug] [--doc-cache-file file] [--echo-commands]\\n\\\n        [--eval CODE] [--exec-path path] [--experimental-terminal-widget]\\n\\\n        [--gui] [--help] [--image-path path] [--info-file file]\\n\\\n-       [--info-program prog] [--interactive] [--line-editing] [--no-gui]\\n\\\n-       [--no-history] [--no-init-file] [--no-init-path] [--no-line-editing]\\n\\\n-       [--no-site-file] [--no-window-system] [--norc] [-p path]\\n\\\n-       [--path path] [--persist] [--server] [--silent] [--traditional]\\n\\\n-       [--verbose] [--version] [file]\";\n+       [--info-program prog] [--interactive] [--json-sock] [--json-max-len] \\n\\\n+       [--line-editing] [--no-gui] [--no-history] [--no-init-file] \\n\\\n+       [--no-init-path] [--no-line-editing] [--no-site-file] \\n\\\n+       [--no-window-system] [--norc] [-p path] [--path path] [--persist] \\n\\\n+       [--server] [--silent] [--traditional] [--verbose] [--version] [file]\";\n \n // Usage message with extra help.\n \n@@ -69,6 +69,8 @@\n   --info-file FILE        Use top-level info file FILE.\\n\\\n   --info-program PROGRAM  Use PROGRAM for reading info files.\\n\\\n   --interactive, -i       Force interactive behavior.\\n\\\n+  --json-sock PATH        Listen to and publish events on this UNIX socket.\\n\\\n+  --json-max-len LEN      Suppress JSON messages greater than LEN bytes.\\n\\\n   --line-editing          Force readline use for command-line editing.\\n\\\n   --no-gui                Disable the graphical user interface.\\n\\\n   --no-history, -H        Don't save commands to the history list\\n\\\ndiff -r 117ebe363f56 -r 7ade2492e023 liboctave/util/oct-mutex.cc\n--- a/liboctave/util/oct-mutex.cc\tSat Dec 25 19:16:44 2021 -0800\n+++ b/liboctave/util/oct-mutex.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -58,6 +58,18 @@\n     return false;\n   }\n \n+  void\n+  base_mutex::cond_wait (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n+  void\n+  base_mutex::cond_signal (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n #if defined (OCTAVE_USE_WINDOWS_API)\n \n   class\n@@ -68,11 +80,13 @@\n       : base_mutex ()\n     {\n       InitializeCriticalSection (&cs);\n+      InitializeConditionVariable (&cv);\n     }\n \n     ~w32_mutex (void)\n     {\n       DeleteCriticalSection (&cs);\n+      // no need to delete cv: http://stackoverflow.com/a/28981408/1407170\n     }\n \n     void lock (void)\n@@ -90,8 +104,19 @@\n       return (TryEnterCriticalSection (&cs) != 0);\n     }\n \n+    void cond_wait (void)\n+    {\n+      SleepConditionVariableCS (&cv, &cs, INFINITE);\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      WakeConditionVariable (&cv);\n+    }\n+\n   private:\n     CRITICAL_SECTION cs;\n+    CONDITION_VARIABLE cv;\n   };\n \n   static DWORD thread_id = 0;\n@@ -123,11 +148,19 @@\n       pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);\n       pthread_mutex_init (&m_pm, &attr);\n       pthread_mutexattr_destroy (&attr);\n+\n+      pthread_condattr_t condattr;\n+\n+      pthread_condattr_init (&condattr);\n+      pthread_cond_init (&condv, &condattr);\n+      pthread_condattr_destroy (&condattr);\n     }\n \n     ~pthread_mutex (void)\n     {\n       pthread_mutex_destroy (&m_pm);\n+      pthread_mutex_destroy (&m_pm);\n+      pthread_cond_destroy (&condv);\n     }\n \n     void lock (void)\n@@ -145,8 +178,19 @@\n       return (pthread_mutex_trylock (&m_pm) == 0);\n     }\n \n+    void cond_wait (void)\n+    {\n+      pthread_cond_wait (&condv, &m_pm);\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      pthread_cond_signal (&condv);\n+    }\n+\n   private:\n     pthread_mutex_t m_pm;\n+    pthread_cond_t condv;\n   };\n \n   static pthread_t thread_id = 0;\ndiff -r 117ebe363f56 -r 7ade2492e023 liboctave/util/oct-mutex.h\n--- a/liboctave/util/oct-mutex.h\tSat Dec 25 19:16:44 2021 -0800\n+++ b/liboctave/util/oct-mutex.h\tSun Dec 26 18:20:44 2021 -0600\n@@ -49,6 +49,10 @@\n     virtual void unlock (void);\n \n     virtual bool try_lock (void);\n+\n+    virtual void cond_wait (void);\n+\n+    virtual void cond_signal (void);\n   };\n \n   class\n@@ -79,6 +83,16 @@\n       return m_rep->try_lock ();\n     }\n \n+    void cond_wait (void)\n+    {\n+      m_rep->cond_wait ();\n+    }\n+\n+    void cond_signal (void)\n+    {\n+      m_rep->cond_signal ();\n+    }\n+\n   protected:\n     std::shared_ptr<base_mutex> m_rep;\n   };\ndiff -r 117ebe363f56 -r 7ade2492e023 liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tSat Dec 25 19:16:44 2021 -0800\n+++ b/liboctave/util/url-transfer.cc\tSun Dec 26 18:20:44 2021 -0600\n@@ -36,6 +36,7 @@\n #include \"file-stat.h\"\n #include \"lo-sysdep.h\"\n #include \"oct-env.h\"\n+#include \"gnulib/lib/base64.h\"\n #include \"unwind-prot.h\"\n #include \"url-transfer.h\"\n #include \"version.h\"\n@@ -48,6 +49,10 @@\n \n namespace octave\n {\n+  // Forward declaration for event_manager\n+  extern bool __event_manager_request_input_enabled__();\n+  extern std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n   base_url_transfer::base_url_transfer (void)\n     : m_host_or_url (), m_valid (false), m_ftp (false),\n       m_ascii_mode (false), m_ok (true), m_errmsg (),\n@@ -228,6 +233,86 @@\n     return file_list;\n   }\n \n+\n+class link_transfer : public base_url_transfer\n+{\n+public:\n+\n+  link_transfer (void)\n+      : base_url_transfer () {\n+    m_valid = true;\n+  }\n+\n+  link_transfer (const std::string& host, const std::string& user_arg,\n+                 const std::string& passwd, std::ostream& os)\n+      : base_url_transfer (host, user_arg, passwd, os) {\n+    m_valid = true;\n+    // url = \"ftp://\" + host;\n+  }\n+\n+  link_transfer (const std::string& url_str, std::ostream& os)\n+      : base_url_transfer (url_str, os) {\n+    m_valid = true;\n+  }\n+\n+  ~link_transfer (void) {}\n+\n+  void http_get (const Array<std::string>& param) {\n+    perform_action (param, \"get\");\n+  }\n+\n+  void http_post (const Array<std::string>& param) {\n+    perform_action (param, \"post\");\n+  }\n+\n+  void http_action (const Array<std::string>& param, const std::string& action) {\n+    perform_action (param, action);\n+  }\n+\n+private:\n+  void perform_action(const Array<std::string>& param, const std::string& action) {\n+    std::string url = m_host_or_url;\n+\n+    // Convert from Array to std::list\n+    std::list<std::string> paramList;\n+    for (int i = 0; i < param.numel(); i ++) {\n+      std::string value = param(i);\n+      paramList.push_back(value);\n+    }\n+\n+    if (__event_manager_request_input_enabled__()) {\n+      bool success;\n+      std::string result = __event_manager_request_url__(url, paramList, action, success);\n+      if (success) {\n+        process_success(result);\n+      } else {\n+        m_ok = false;\n+        m_errmsg = result;\n+      }\n+    } else {\n+      m_ok = false;\n+      m_errmsg = \"octave_link not connected for link_transfer\";\n+    }\n+  }\n+\n+  void process_success(const std::string& result) {\n+    // If success, the result is returned as a base64 string, and we need to decode it.\n+    // Use the base64 implementation from gnulib, which is already an Octave dependency.\n+    const char *inc = &(result[0]);\n+    char *out;\n+    size_t outlen;\n+    bool b64_ok = base64_decode_alloc(inc, result.length(), &out, &outlen);\n+    if (!b64_ok) {\n+      m_ok = false;\n+      m_errmsg = \"failed decoding base64 from octave_link\";\n+    } else {\n+      m_curr_ostream->write(out, outlen);\n+      ::free(out);\n+    }\n+  }\n+};\n+\n+\n #if defined (HAVE_CURL)\n \n   static int\n@@ -918,17 +1003,30 @@\n #  define REP_CLASS base_url_transfer\n #endif\n \n-  url_transfer::url_transfer (void) : m_rep (new REP_CLASS ())\n-  { }\n+  url_transfer::url_transfer (void) {\n+    if (__event_manager_request_input_enabled__()) {\n+      m_rep.reset(new link_transfer());\n+    } else {\n+      m_rep.reset(new REP_CLASS());\n+    }\n+  }\n \n   url_transfer::url_transfer (const std::string& host, const std::string& user,\n-                              const std::string& passwd, std::ostream& os)\n-    : m_rep (new REP_CLASS (host, user, passwd, os))\n-  { }\n+                              const std::string& passwd, std::ostream& os) {\n+    if (__event_manager_request_input_enabled__()) {\n+      m_rep.reset(new link_transfer(host, user, passwd, os));\n+    } else {\n+      m_rep.reset(new REP_CLASS(host, user, passwd, os));\n+    }\n+  }\n \n-  url_transfer::url_transfer (const std::string& url, std::ostream& os)\n-    : m_rep (new REP_CLASS (url, os))\n-  { }\n+  url_transfer::url_transfer (const std::string& url, std::ostream& os) {\n+    if (__event_manager_request_input_enabled__()) {\n+      m_rep.reset(new link_transfer(url, os));\n+    } else {\n+      m_rep.reset(new REP_CLASS(url, os));\n+    }\n+  }\n \n #undef REP_CLASS\n \ndiff -r 117ebe363f56 -r 7ade2492e023 scripts/help/__unimplemented__.m\n--- a/scripts/help/__unimplemented__.m\tSat Dec 25 19:16:44 2021 -0800\n+++ b/scripts/help/__unimplemented__.m\tSun Dec 26 18:20:44 2021 -0600\n@@ -45,7 +45,30 @@\n \n   is_matlab_function = true;\n \n+  ## First look at the package metadata\n+  # To generate package_metadata.mat, run: packages={}; for p=pkg('list'); packages={packages{:} pkg('describe', '-verbose', p{1}.name){:}}; endfor; save('/usr/local/share/octave/site/m/package_metadata.mat', 'packages');\n+  found_in_package_metadata = false;\n+  try\n+    vars = load(\"/usr/local/share/octave/site/m/package_metadata.mat\");\n+    for lvl1 = vars.packages\n+      for lvl2 = lvl1{1}.provides\n+        for lvl3 = lvl2{1}.functions\n+          if strcmp(fcn, lvl3{1})\n+            txt = check_package(fcn, lvl1{1}.name);\n+            found_in_package_metadata = true;\n+            break;\n+          endif\n+        endfor\n+        if found_in_package_metadata, break; endif\n+      endfor\n+      if found_in_package_metadata, break; endif\n+    endfor\n+  catch err\n+    warning(err)\n+  end_try_catch\n+\n   ## Some smarter cases, add more as needed.\n+  if !found_in_package_metadata\n   switch (fcn)\n     case {\"avifile\", \"aviinfo\", \"aviread\"}\n       txt = [\"Basic video file support is provided in the video package.  \", ...\n@@ -524,6 +547,7 @@\n         txt = \"\";\n       endif\n   endswitch\n+  endif\n \n   if (is_matlab_function)\n     txt = [txt, \"\\n\\n@noindent\\nPlease read \", ...\n@@ -566,13 +590,13 @@\n       endfor\n       txt = sprintf (\"%s but has not yet been implemented.\", txt);\n     case \"not loaded\",\n-      txt = sprintf ([\"%s which you have installed but not loaded.  To \", ...\n-                      \"load the package, run 'pkg load %s' from the \", ...\n-                      \"Octave prompt.\"], txt, name);\n+      txt = sprintf ([\"%s, which you have installed but not loaded.\\n\\n\", ...\n+                      \"Run `pkg load %s' to use `%s'.\"], ...\n+                     txt, name, fcn);\n     otherwise\n       ## this includes \"not installed\" and anything else if pkg changes\n       ## the output of describe\n-      txt = sprintf (\"%s which seems to not be installed in your system.\", txt);\n+      txt = sprintf (\"%s, which seems to not be installed in your system.\", txt);\n   endswitch\n \n endfunction\ndiff -r 117ebe363f56 -r 7ade2492e023 scripts/plot/util/__gnuplot_drawnow__.m\n--- a/scripts/plot/util/__gnuplot_drawnow__.m\tSat Dec 25 19:16:44 2021 -0800\n+++ b/scripts/plot/util/__gnuplot_drawnow__.m\tSun Dec 26 18:20:44 2021 -0600\n@@ -32,9 +32,84 @@\n \n   if (nargin < 1 || nargin == 2)\n     print_usage ();\n-  endif\n+\n+  elseif (nargin >= 3 && nargin <= 4)\n+    ## Write the plot to the given file (e.g., via the \"print\" command)\n+    if (nargin == 5)\n+      __gnuplot_draw_to_file__ (h, term, file, debug_file);\n+    else\n+      __gnuplot_draw_to_file__ (h, term, file);\n+    endif\n+\n+  else  # nargin == 1\n+    ##  Plot to terminal and/or static (e.g., via the \"plot\" command)\n+    plot_stream = get (h, \"__plot_stream__\");\n+    if (isempty (plot_stream))\n+      plot_stream = __gnuplot_open_stream__ (2, h);\n+      new_stream = true;\n+    else\n+      new_stream = false;\n+    endif\n+    term = gnuplot_default_term (plot_stream);\n+\n+    ## There are a few options for how we can proceed.\n+    ## In most cases, we will tell GNUPLOT to put the plot in its terminal.\n+    ## If we have no display, we want to use the \"dumb\" terminal.\n+    ## Octave Link may request that we send the plot as an event.\n+    ## The latter two cases require plotting to a temp file.\n+\n+    should_plot_to_terminal = (\n+      !strcmp (term, \"dumb\") && (\n+        __event_manager_plot_destination__ () == 0 ||\n+        __event_manager_plot_destination__ () == 2\n+      )\n+    );\n+\n+    if (should_plot_to_terminal)\n+      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n+      __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n+      fflush (plot_stream(1));\n+    endif\n \n-  if (nargin >= 3 && nargin <= 4)\n+    should_plot_to_temp_file = (\n+      strcmp (term, \"dumb\") ||\n+      __event_manager_plot_destination__ () == 1 ||\n+      __event_manager_plot_destination__ () == 2\n+    );\n+\n+    if (should_plot_to_temp_file)\n+      tmp_file = tempname ();\n+      __gnuplot_draw_to_file__ (h, term, tmp_file);\n+      fflush (plot_stream(1));\n+\n+      ## Read the temp file into memory and then delete it\n+      fid = fopen (tmp_file, 'r');\n+      while (fid < 0)\n+        fprintf (stderr, \"🛈 Waiting for plot to finish… ⏳\\n\");\n+        pause (0.5);\n+        fid = fopen (tmp_file, 'r');\n+      endwhile\n+      [a, count] = fscanf (fid, '%c', Inf);\n+      fclose (fid);\n+      unlink (tmp_file);\n+\n+      ## What to do with the plot data?\n+      if (count > 0)\n+        if (a(1) == 12)\n+          a = a(2:end);  # avoid ^L at the beginning\n+        endif\n+        if strcmp (term, \"dumb\")\n+          puts (a);\n+        else\n+          __event_manager_show_static_plot__ (term, a);\n+        endif\n+      endif\n+    endif\n+\n+  endif\n+endfunction\n+\n+function __gnuplot_draw_to_file__ (h, term, file, debug_file)\n     ## Produce various output formats, or redirect gnuplot stream to a\n     ## debug file.\n     plot_stream = [];\n@@ -70,44 +145,6 @@\n         fclose (fid);\n       endif\n     end_unwind_protect\n-  else  # nargin == 1\n-    ##  Graphics terminal for display.\n-    plot_stream = get (h, \"__plot_stream__\");\n-    if (isempty (plot_stream))\n-      plot_stream = __gnuplot_open_stream__ (2, h);\n-      new_stream = true;\n-    else\n-      new_stream = false;\n-    endif\n-    term = gnuplot_default_term (plot_stream);\n-    if (strcmp (term, \"dumb\"))\n-      ## popen2 eats stdout of gnuplot, use temporary file instead\n-      dumb_tmp_file = tempname ();\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h,\n-                                   term, dumb_tmp_file);\n-    else\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n-    endif\n-    __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n-    fflush (plot_stream(1));\n-    if (strcmp (term, \"dumb\"))\n-      fid = -1;\n-      while (fid < 0)\n-        pause (0.1);\n-        fid = fopen (dumb_tmp_file, 'r');\n-      endwhile\n-      ## reprint the plot on screen\n-      [a, count] = fscanf (fid, '%c', Inf);\n-      fclose (fid);\n-      if (count > 0)\n-        if (a(1) == 12)\n-          a = a(2:end);  # avoid ^L at the beginning\n-        endif\n-        puts (a);\n-      endif\n-      unlink (dumb_tmp_file);\n-    endif\n-  endif\n \n endfunction\n \n"
  },
  {
    "path": "back-octave/oo-changesets/401-1b33dc797ec9.hg.txt",
    "content": "# HG changeset patch\n# User Octave Online Team <webmaster@octave-online.net>\n# Date 1640567764 21600\n#      Sun Dec 26 19:16:04 2021 -0600\n# Branch oo-7.0.1\n# Node ID 1b33dc797ec9b06aa971361d838f656fb7cc41ac\n# Parent  7ade2492e0237c2557ae6e67ee1be88cce8e6982\nFix compile errors\n\ndiff -r 7ade2492e023 -r 1b33dc797ec9 libinterp/corefcn/event-manager.h\n--- a/libinterp/corefcn/event-manager.h\tSun Dec 26 18:20:44 2021 -0600\n+++ b/libinterp/corefcn/event-manager.h\tSun Dec 26 19:16:04 2021 -0600\n@@ -183,11 +183,16 @@\n     // using the question_dialog action?\n \n     bool _request_input_enabled;\n-    virtual std::string request_input (const std::string&) = 0;\n-    virtual std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) = 0;\n+    virtual std::string request_input (const std::string&)\n+    {\n+      return \"\";\n+    }\n+    virtual std::string request_url (const std::string& /*url*/, const std::list<std::string>& /*param*/, const std::string& /*action*/, bool& /*success*/) {\n+      return \"\";\n+    }\n \n     plot_destination_t _plot_destination;\n-    virtual void show_static_plot (const std::string& term, const std::string& content) = 0;\n+    virtual void show_static_plot (const std::string& /*term*/, const std::string& /*content*/) { }\n \n     virtual bool confirm_shutdown (void) { return true; }\n \n@@ -463,12 +468,12 @@\n \n     bool request_input_enabled (void)\n     {\n-      return enabled () ? instance->_request_input_enabled : false;\n+      return enabled () ? m_instance->_request_input_enabled : false;\n     }\n \n     plot_destination_t plot_destination (void)\n     {\n-      return enabled () ? instance->_plot_destination : TERMINAL_ONLY;\n+      return enabled () ? m_instance->_plot_destination : TERMINAL_ONLY;\n     }\n \n     bool\n@@ -476,7 +481,7 @@\n     {\n       if (enabled ())\n         {\n-          instance->show_static_plot (term, content);\n+          m_instance->show_static_plot (term, content);\n           return true;\n         }\n       else\n@@ -746,7 +751,7 @@\n     void clear_screen (void)\n     {\n       if (enabled ())\n-        instance->clear_screen ();\n+        m_instance->clear_screen ();\n     }\n \n     void pre_input_event (void)\n@@ -765,14 +770,14 @@\n     std::string request_input (const std::string& prompt)\n     {\n       return request_input_enabled ()\n-        ? instance->request_input (prompt)\n+        ? m_instance->request_input (prompt)\n         : std::string ();\n     }\n \n     std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success)\n     {\n       return request_input_enabled ()\n-        ? instance->request_url (url, param, action, success)\n+        ? m_instance->request_url (url, param, action, success)\n         : std::string ();\n     }\n \ndiff -r 7ade2492e023 -r 1b33dc797ec9 libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 18:20:44 2021 -0600\n+++ b/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 19:16:04 2021 -0600\n@@ -325,17 +325,18 @@\n \treturn retval;\r\n }\r\n \r\n-void octave_json_link::show_doc(const std::string& file) {\r\n+bool octave_json_link::show_documentation(const std::string& file) {\r\n \t// Triggered on \"doc\" command\r\n \t_publish_message(\"show-doc\", json_util::from_string(file));\r\n+\treturn true;\r\n }\r\n \r\n-void octave_json_link::register_doc (const std::string& file) {\r\n+void octave_json_link::register_documentation (const std::string& file) {\r\n \t// Triggered by the GUI documentation viewer?\r\n \t_publish_message(\"register-doc\", json_util::from_string(file));\r\n }\r\n \r\n-void octave_json_link::unregister_doc (const std::string& file) {\r\n+void octave_json_link::unregister_documentation (const std::string& file) {\r\n \t// Triggered by the GUI documentation viewer?\r\n \t_publish_message(\"unregister-doc\", json_util::from_string(file));\r\n }\r\ndiff -r 7ade2492e023 -r 1b33dc797ec9 libinterp/corefcn/octave-json-link.h\n--- a/libinterp/corefcn/octave-json-link.h\tSun Dec 26 18:20:44 2021 -0600\n+++ b/libinterp/corefcn/octave-json-link.h\tSun Dec 26 19:16:04 2021 -0600\n@@ -139,11 +139,11 @@\n \r\n   std::string gui_preference (const std::string& key, const std::string& value) override;\r\n \r\n-  void show_doc (const std::string& file) override;\r\n+  bool show_documentation (const std::string& file) override;\r\n \r\n-  void register_doc (const std::string& file) override;\r\n+  void register_documentation (const std::string& file) override;\r\n \r\n-  void unregister_doc (const std::string& file) override;\r\n+  void unregister_documentation (const std::string& file) override;\r\n \r\n   void edit_variable (const std::string& name, const octave_value& val) override;\r\n \r\ndiff -r 7ade2492e023 -r 1b33dc797ec9 libinterp/octave.h\n--- a/libinterp/octave.h\tSun Dec 26 18:20:44 2021 -0600\n+++ b/libinterp/octave.h\tSun Dec 26 19:16:04 2021 -0600\n@@ -331,7 +331,7 @@\n \n     static bool is_link_enabled (void)\n     {\n-      return instance ? instance->link_enabled () : false;\n+      return s_instance ? s_instance->link_enabled () : false;\n     }\n \n     // Convenience functions.\ndiff -r 7ade2492e023 -r 1b33dc797ec9 liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tSun Dec 26 18:20:44 2021 -0600\n+++ b/liboctave/util/url-transfer.cc\tSun Dec 26 19:16:04 2021 -0600\n@@ -31,12 +31,12 @@\n #include <iomanip>\n #include <iostream>\n \n+#include \"base64-wrappers.h\"\n #include \"dir-ops.h\"\n #include \"file-ops.h\"\n #include \"file-stat.h\"\n #include \"lo-sysdep.h\"\n #include \"oct-env.h\"\n-#include \"gnulib/lib/base64.h\"\n #include \"unwind-prot.h\"\n #include \"url-transfer.h\"\n #include \"version.h\"\n@@ -300,8 +300,8 @@\n     // Use the base64 implementation from gnulib, which is already an Octave dependency.\n     const char *inc = &(result[0]);\n     char *out;\n-    size_t outlen;\n-    bool b64_ok = base64_decode_alloc(inc, result.length(), &out, &outlen);\n+    std::ptrdiff_t outlen;\n+    bool b64_ok = octave_base64_decode_alloc_wrapper(inc, result.length(), &out, &outlen);\n     if (!b64_ok) {\n       m_ok = false;\n       m_errmsg = \"failed decoding base64 from octave_link\";\n"
  },
  {
    "path": "back-octave/oo-changesets/402-b01fa2864d4d.hg.txt",
    "content": "# HG changeset patch\n# User Octave Online Team <webmaster@octave-online.net>\n# Date 1640569694 21600\n#      Sun Dec 26 19:48:14 2021 -0600\n# Branch oo-7.0.1\n# Node ID b01fa2864d4d45ba10bcb1e59fe84bb54330ab17\n# Parent  1b33dc797ec9b06aa971361d838f656fb7cc41ac\nFix linker errors\n\ndiff -r 1b33dc797ec9 -r b01fa2864d4d libinterp/corefcn/event-manager.cc\n--- a/libinterp/corefcn/event-manager.cc\tSun Dec 26 19:16:04 2021 -0600\n+++ b/libinterp/corefcn/event-manager.cc\tSun Dec 26 19:48:14 2021 -0600\n@@ -887,8 +887,6 @@\n   return ovl ();\n }\n \n-OCTAVE_NAMESPACE_END\n-\n DEFMETHOD (__event_manager_plot_destination__, interp, , ,\n            doc: /* -*- texinfo -*-\n @deftypefn {} {} __event_manager_plot_destination__ ()\n@@ -912,3 +910,5 @@\n   std::string content = args(1).string_value();\n   return ovl (interp.get_event_manager().show_static_plot(term, content));\n }\n+\n+OCTAVE_NAMESPACE_END\ndiff -r 1b33dc797ec9 -r b01fa2864d4d libinterp/corefcn/input.cc\n--- a/libinterp/corefcn/input.cc\tSun Dec 26 19:16:04 2021 -0600\n+++ b/libinterp/corefcn/input.cc\tSun Dec 26 19:48:14 2021 -0600\n@@ -1670,8 +1670,6 @@\n   return input_sys.auto_repeat_debug_command (args, nargout);\n }\n \n-OCTAVE_NAMESPACE_END\n-\n DEFUN (current_command_number, args, ,\n        doc: /* -*- texinfo -*-\n @deftypefn  {} {@var{val} =} current_command_number ()\n@@ -1700,3 +1698,5 @@\n     return ovl(n);\n   }\n }\n+\n+OCTAVE_NAMESPACE_END\n"
  },
  {
    "path": "back-octave/oo-changesets/403-2813cb96e10f.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1640570532 21600\n#      Sun Dec 26 20:02:12 2021 -0600\n# Branch oo-7.0.1\n# Node ID 2813cb96e10f0a2b1ec6903a6dbf7c368ac323a1\n# Parent  b01fa2864d4d45ba10bcb1e59fe84bb54330ab17\nAdd have_dialogs\n\ndiff -r b01fa2864d4d -r 2813cb96e10f libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 19:48:14 2021 -0600\n+++ b/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 20:02:12 2021 -0600\n@@ -125,6 +125,13 @@\n // \treturn message_dialog_queue.dequeue();\r\n // }\r\n \r\n+bool octave_json_link::have_dialogs() const {\r\n+\t// Triggered in \"inputdlg\" and similar functions to check for dialog support\r\n+\t_publish_message(\"have-dialog\", json_util::empty());\r\n+\r\n+\treturn have_dialogs_queue.dequeue();\r\n+}\r\n+\r\n std::string octave_json_link::question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n \t// Triggered in \"questdlg\"\r\n \tJSON_MAP_T m;\r\n@@ -372,6 +379,10 @@\n \t\tbool answer = json_util::to_boolean(jobj);\r\n \t\tconfirm_shutdown_queue.enqueue(answer);\r\n \t}\r\n+\telse if (name == \"have-dialogs-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\thave_dialogs_queue.enqueue(answer);\r\n+\t}\r\n \telse if (name == \"prompt-new-edit-file-answer\"){\r\n \t\tbool answer = json_util::to_boolean(jobj);\r\n \t\tprompt_new_edit_file_queue.enqueue(answer);\r\ndiff -r b01fa2864d4d -r 2813cb96e10f libinterp/corefcn/octave-json-link.h\n--- a/libinterp/corefcn/octave-json-link.h\tSun Dec 26 19:48:14 2021 -0600\n+++ b/libinterp/corefcn/octave-json-link.h\tSun Dec 26 20:02:12 2021 -0600\n@@ -71,6 +71,8 @@\n   bool edit_file (const std::string& file) override;\r\n   bool prompt_new_edit_file (const std::string& file) override;\r\n \r\n+  bool have_dialogs (void) override;\r\n+\r\n   std::string\r\n   question_dialog (const std::string& msg, const std::string& title,\r\n                       const std::string& btn1, const std::string& btn2,\r\n@@ -161,6 +163,7 @@\n   json_queue<std::string> request_input_queue;\r\n   json_queue<std::pair<bool, std::string> > request_url_queue;\r\n   json_queue<bool> confirm_shutdown_queue;\r\n+  json_queue<bool> have_dialogs_queue;\r\n   json_queue<bool> prompt_new_edit_file_queue;\r\n   json_queue<int> message_dialog_queue;\r\n   json_queue<std::string> question_dialog_queue;\r\n"
  },
  {
    "path": "back-octave/oo-changesets/404-acb523f25bb9.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1640575083 21600\n#      Sun Dec 26 21:18:03 2021 -0600\n# Branch oo-7.0.1\n# Node ID acb523f25bb964dbc67a871ea27b7b7366203f26\n# Parent  2813cb96e10f0a2b1ec6903a6dbf7c368ac323a1\nUpdate methods in octave-json-link.h\n\ndiff -r 2813cb96e10f -r acb523f25bb9 libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 20:02:12 2021 -0600\n+++ b/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 21:18:03 2021 -0600\n@@ -189,6 +189,11 @@\n \treturn file_dialog_queue.dequeue();\r\n }\r\n \r\n+void octave_json_link::update_path_dialog(void) {\r\n+\t// Triggered in \"rehash\"\r\n+\t_publish_message(\"update-path-dialog\", json_util::empty());\r\n+}\r\n+\r\n int octave_json_link::debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\r\n \t// This endpoint might be unused?  (No references)\r\n \tJSON_MAP_T m;\r\ndiff -r 2813cb96e10f -r acb523f25bb9 libinterp/corefcn/octave-json-link.h\n--- a/libinterp/corefcn/octave-json-link.h\tSun Dec 26 20:02:12 2021 -0600\n+++ b/libinterp/corefcn/octave-json-link.h\tSun Dec 26 21:18:03 2021 -0600\n@@ -61,15 +61,9 @@\n \r\n   ~octave_json_link (void);\r\n \r\n-  std::string request_input (const std::string& prompt) override;\r\n-  std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\r\n-\r\n-  bool confirm_shutdown (void) override;\r\n-\r\n-  bool copy_image_to_clipboard (const std::string& file) override;\r\n-\r\n-  bool edit_file (const std::string& file) override;\r\n-  bool prompt_new_edit_file (const std::string& file) override;\r\n+  // TODO\r\n+  // void start_gui (bool gui_app = false);\r\n+  // void close_gui (void);\r\n \r\n   bool have_dialogs (void) override;\r\n \r\n@@ -100,57 +94,115 @@\n                   const std::string &filename, const std::string &pathname,\r\n                   const std::string& multimode) override;\r\n \r\n+  void update_path_dialog (void) override;\r\n+\r\n+  void show_preferences (void) override;\r\n+\r\n+  // TODO:\r\n+  // void apply_preferences (void) override;\r\n+\r\n+  // TODO:\r\n+  // void show_terminal_window (void) override;\r\n+\r\n+  bool show_documentation (const std::string& file) override;\r\n+\r\n+  // TODO:\r\n+  // void show_file_browser (void);\r\n+\r\n+  // TODO:\r\n+  // void show_command_history (void);\r\n+\r\n+  void show_workspace (void);\r\n+\r\n+  // TODO:\r\n+  // void show_community_news (int serial);\r\n+  // void show_release_notes (void);\r\n+\r\n+  bool edit_file (const std::string& file) override;\r\n+\r\n+  void edit_variable (const std::string& name, const octave_value& val) override;\r\n+\r\n+  std::string request_input (const std::string& prompt) override;\r\n+\r\n+  std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\r\n+\r\n+  void show_static_plot (const std::string& term, const std::string& content) override;\r\n+\r\n+  bool confirm_shutdown (void) override;\r\n+\r\n+  bool prompt_new_edit_file (const std::string& file) override;\r\n+\r\n   int\r\n   debug_cd_or_addpath_error (const std::string& file,\r\n                                 const std::string& dir,\r\n                                 bool addpath_option) override;\r\n \r\n+  uint8NDArray get_named_icon (const std::string& icon_name) override;\r\n+\r\n+  std::string gui_preference (const std::string& key, const std::string& value) override;\r\n+\r\n+  bool copy_image_to_clipboard (const std::string& file) override;\r\n+\r\n+  // TODO:\r\n+  // void focus_window (const std::string win_name);\r\n+\r\n+  void execute_command_in_terminal (const std::string& command) override;\r\n+\r\n+  void register_documentation (const std::string& file) override;\r\n+\r\n+  void unregister_documentation (const std::string& file) override;\r\n+\r\n+  // TODO:\r\n+  // void interpreter_output (const std::string& msg);\r\n+\r\n+  // TODO:\r\n+  // void display_exception (const execution_exception& ee, bool beep);\r\n+\r\n+  // TODO:\r\n+  // void gui_status_update (const std::string& feature, const std::string& status);\r\n+\r\n+  // TODO:\r\n+  // void update_gui_lexer (void);\r\n+\r\n   void directory_changed (const std::string& dir) override;\r\n \r\n   void file_remove (const std::string& old_name, const std::string& new_name) override;\r\n+\r\n   void file_renamed (bool) override;\r\n \r\n-  void execute_command_in_terminal (const std::string& command) override;\r\n-\r\n-  uint8NDArray get_named_icon (const std::string& icon_name) override;\r\n-\r\n   void set_workspace (bool top_level, bool debug,\r\n                          const symbol_info_list& ws,\r\n                          bool update_variable_editor) override;\r\n \r\n   void clear_workspace (void) override;\r\n \r\n+  // TODO:\r\n+  // void update_prompt (const std::string& prompt);\r\n+\r\n   void set_history (const string_vector& hist) override;\r\n+\r\n   void append_history (const std::string& hist_entry) override;\r\n+\r\n   void clear_history (void) override;\r\n \r\n   void clear_screen (void) override;\r\n \r\n   void pre_input_event (void) override;\r\n+\r\n   void post_input_event (void) override;\r\n \r\n   void enter_debugger_event (const std::string& fcn_name, const std::string& fcn_file_name, int line) override;\r\n+\r\n   void execute_in_debugger_event (const std::string& file, int line) override;\r\n+\r\n   void exit_debugger_event (void) override;\r\n \r\n   void update_breakpoint (bool insert,\r\n                              const std::string& file, int line,\r\n                              const std::string& cond) override;\r\n \r\n-  void show_preferences (void) override;\r\n-\r\n-  std::string gui_preference (const std::string& key, const std::string& value) override;\r\n-\r\n-  bool show_documentation (const std::string& file) override;\r\n-\r\n-  void register_documentation (const std::string& file) override;\r\n-\r\n-  void unregister_documentation (const std::string& file) override;\r\n-\r\n-  void edit_variable (const std::string& name, const octave_value& val) override;\r\n-\r\n-  void show_static_plot (const std::string& term,\r\n-                            const std::string& content) override;\r\n+  // TODO:\r\n+  // void interpreter_interrupted (void) override;\r\n \r\n   // Custom methods\r\n   void receive_message (const std::string& name, JSON_OBJECT_T jobj);\r\n"
  },
  {
    "path": "back-octave/oo-changesets/405-6ad34b0b69e1.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1640576162 21600\n#      Sun Dec 26 21:36:02 2021 -0600\n# Branch oo-7.0.1\n# Node ID 6ad34b0b69e19c2ce3086daf148fce7bc7d51b2e\n# Parent  acb523f25bb964dbc67a871ea27b7b7366203f26\nSupport more methods in octave-json-link\n\ndiff -r acb523f25bb9 -r 6ad34b0b69e1 libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 21:18:03 2021 -0600\n+++ b/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 21:36:02 2021 -0600\n@@ -205,6 +205,34 @@\n \treturn debug_cd_or_addpath_error_queue.dequeue();\r\n }\r\n \r\n+void octave_json_link::focus_window(const std::string& win_name) {\r\n+\t// Triggered in \"commandhistory\", \"commandwindow\", \"filebrowser\", \"workspace\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, win_name, string);\r\n+\t_publish_message(\"focus-window\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::display_exception(const execution_exception& ee, bool beep) {\r\n+\t// Triggered in various places in libinterp\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, ee, string);\r\n+\tJSON_MAP_SET(m, beep, boolean);\r\n+\t_publish_message(\"display-exception\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::gui_status_update(const std::string& feature, const std::string& status) {\r\n+\t// Triggered in __profiler_enable__\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, feature, string);\r\n+\tJSON_MAP_SET(m, status, string);\r\n+\t_publish_message(\"gui-status-update\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::update_gui_lexer(void) {\r\n+\t// Triggered in \"load_packages_and_dependencies\"\r\n+\t_publish_message(\"update-gui-lexer\", json_util::empty());\r\n+}\r\n+\r\n void octave_json_link::directory_changed(const std::string& dir) {\r\n \t// This endpoint might be unused?  (References appear only in libgui)\r\n \tJSON_MAP_T m;\r\ndiff -r acb523f25bb9 -r 6ad34b0b69e1 libinterp/corefcn/octave-json-link.h\n--- a/libinterp/corefcn/octave-json-link.h\tSun Dec 26 21:18:03 2021 -0600\n+++ b/libinterp/corefcn/octave-json-link.h\tSun Dec 26 21:36:02 2021 -0600\n@@ -112,7 +112,7 @@\n   // TODO:\r\n   // void show_command_history (void);\r\n \r\n-  void show_workspace (void);\r\n+  void show_workspace (void) override;\r\n \r\n   // TODO:\r\n   // void show_community_news (int serial);\r\n@@ -143,8 +143,7 @@\n \r\n   bool copy_image_to_clipboard (const std::string& file) override;\r\n \r\n-  // TODO:\r\n-  // void focus_window (const std::string win_name);\r\n+  void focus_window (const std::string win_name);\r\n \r\n   void execute_command_in_terminal (const std::string& command) override;\r\n \r\n@@ -155,14 +154,11 @@\n   // TODO:\r\n   // void interpreter_output (const std::string& msg);\r\n \r\n-  // TODO:\r\n-  // void display_exception (const execution_exception& ee, bool beep);\r\n+  void display_exception (const execution_exception& ee, bool beep) override;\r\n \r\n-  // TODO:\r\n-  // void gui_status_update (const std::string& feature, const std::string& status);\r\n+  void gui_status_update (const std::string& feature, const std::string& status);\r\n \r\n-  // TODO:\r\n-  // void update_gui_lexer (void);\r\n+  void update_gui_lexer (void);\r\n \r\n   void directory_changed (const std::string& dir) override;\r\n \r\n"
  },
  {
    "path": "back-octave/oo-changesets/406-d0df6f16f41e.hg.txt",
    "content": "# HG changeset patch\n# User Octave Online Team <webmaster@octave-online.net>\n# Date 1640581133 21600\n#      Sun Dec 26 22:58:53 2021 -0600\n# Branch oo-7.0.1\n# Node ID d0df6f16f41ed3d39e4e40bf8080d143e4726098\n# Parent  6ad34b0b69e19c2ce3086daf148fce7bc7d51b2e\nFixing build issues\n\ndiff -r 6ad34b0b69e1 -r d0df6f16f41e libinterp/corefcn/json-main.cc\n--- a/libinterp/corefcn/json-main.cc\tSun Dec 26 21:36:02 2021 -0600\n+++ b/libinterp/corefcn/json-main.cc\tSun Dec 26 22:58:53 2021 -0600\n@@ -37,6 +37,7 @@\n   // Note: this passes ownership to octave_link\r\n   event_manager& evmgr = interp.get_event_manager ();\r\n   evmgr.connect_link (_octave_json_link);\r\n+  evmgr.install_qt_event_handlers (_octave_json_link);\r\n   evmgr.enable ();\r\n \r\n   // Open UNIX socket file descriptor\r\ndiff -r 6ad34b0b69e1 -r d0df6f16f41e libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 21:36:02 2021 -0600\n+++ b/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 22:58:53 2021 -0600\n@@ -127,9 +127,7 @@\n \r\n bool octave_json_link::have_dialogs() const {\r\n \t// Triggered in \"inputdlg\" and similar functions to check for dialog support\r\n-\t_publish_message(\"have-dialog\", json_util::empty());\r\n-\r\n-\treturn have_dialogs_queue.dequeue();\r\n+\treturn true;\r\n }\r\n \r\n std::string octave_json_link::question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n@@ -205,7 +203,7 @@\n \treturn debug_cd_or_addpath_error_queue.dequeue();\r\n }\r\n \r\n-void octave_json_link::focus_window(const std::string& win_name) {\r\n+void octave_json_link::focus_window(const std::string win_name) {\r\n \t// Triggered in \"commandhistory\", \"commandwindow\", \"filebrowser\", \"workspace\"\r\n \tJSON_MAP_T m;\r\n \tJSON_MAP_SET(m, win_name, string);\r\n@@ -214,8 +212,11 @@\n \r\n void octave_json_link::display_exception(const execution_exception& ee, bool beep) {\r\n \t// Triggered in various places in libinterp\r\n+        std::ostringstream buf;\r\n+        ee.display (buf);\r\n+        std::string ee_str = buf.str();\r\n \tJSON_MAP_T m;\r\n-\tJSON_MAP_SET(m, ee, string);\r\n+\tJSON_MAP_SET(m, ee_str, string);\r\n \tJSON_MAP_SET(m, beep, boolean);\r\n \t_publish_message(\"display-exception\", json_util::from_map(m));\r\n }\r\ndiff -r 6ad34b0b69e1 -r d0df6f16f41e libinterp/corefcn/octave-json-link.h\n--- a/libinterp/corefcn/octave-json-link.h\tSun Dec 26 21:36:02 2021 -0600\n+++ b/libinterp/corefcn/octave-json-link.h\tSun Dec 26 22:58:53 2021 -0600\n@@ -62,10 +62,10 @@\n   ~octave_json_link (void);\r\n \r\n   // TODO\r\n-  // void start_gui (bool gui_app = false);\r\n-  // void close_gui (void);\r\n+  // void start_gui (bool gui_app = false) override;\r\n+  // void close_gui (void) override;\r\n \r\n-  bool have_dialogs (void) override;\r\n+  bool have_dialogs (void) const override;\r\n \r\n   std::string\r\n   question_dialog (const std::string& msg, const std::string& title,\r\n@@ -107,16 +107,17 @@\n   bool show_documentation (const std::string& file) override;\r\n \r\n   // TODO:\r\n-  // void show_file_browser (void);\r\n+  // void show_file_browser (void) override;\r\n+\r\n+  // TODO:\r\n+  // void show_command_history (void) override;\r\n \r\n   // TODO:\r\n-  // void show_command_history (void);\r\n-\r\n-  void show_workspace (void) override;\r\n+  // void show_workspace (void) override;\r\n \r\n   // TODO:\r\n-  // void show_community_news (int serial);\r\n-  // void show_release_notes (void);\r\n+  // void show_community_news (int serial) override;\r\n+  // void show_release_notes (void) override;\r\n \r\n   bool edit_file (const std::string& file) override;\r\n \r\n@@ -143,7 +144,7 @@\n \r\n   bool copy_image_to_clipboard (const std::string& file) override;\r\n \r\n-  void focus_window (const std::string win_name);\r\n+  void focus_window (const std::string win_name) override;\r\n \r\n   void execute_command_in_terminal (const std::string& command) override;\r\n \r\n@@ -152,13 +153,13 @@\n   void unregister_documentation (const std::string& file) override;\r\n \r\n   // TODO:\r\n-  // void interpreter_output (const std::string& msg);\r\n+  // void interpreter_output (const std::string& msg) override;\r\n \r\n   void display_exception (const execution_exception& ee, bool beep) override;\r\n \r\n-  void gui_status_update (const std::string& feature, const std::string& status);\r\n+  void gui_status_update (const std::string& feature, const std::string& status) override;\r\n \r\n-  void update_gui_lexer (void);\r\n+  void update_gui_lexer (void) override;\r\n \r\n   void directory_changed (const std::string& dir) override;\r\n \r\n@@ -173,7 +174,7 @@\n   void clear_workspace (void) override;\r\n \r\n   // TODO:\r\n-  // void update_prompt (const std::string& prompt);\r\n+  // void update_prompt (const std::string& prompt) override;\r\n \r\n   void set_history (const string_vector& hist) override;\r\n \r\n"
  },
  {
    "path": "back-octave/oo-changesets/407-df206dd11399.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1640581766 21600\n#      Sun Dec 26 23:09:26 2021 -0600\n# Branch oo-7.0.1\n# Node ID df206dd113996f60a2559ad898b96b1467a596b5\n# Parent  d0df6f16f41ed3d39e4e40bf8080d143e4726098\nRemove unused queue\n\ndiff -r d0df6f16f41e -r df206dd11399 libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 22:58:53 2021 -0600\n+++ b/libinterp/corefcn/octave-json-link.cc\tSun Dec 26 23:09:26 2021 -0600\n@@ -413,10 +413,6 @@\n \t\tbool answer = json_util::to_boolean(jobj);\r\n \t\tconfirm_shutdown_queue.enqueue(answer);\r\n \t}\r\n-\telse if (name == \"have-dialogs-answer\"){\r\n-\t\tbool answer = json_util::to_boolean(jobj);\r\n-\t\thave_dialogs_queue.enqueue(answer);\r\n-\t}\r\n \telse if (name == \"prompt-new-edit-file-answer\"){\r\n \t\tbool answer = json_util::to_boolean(jobj);\r\n \t\tprompt_new_edit_file_queue.enqueue(answer);\r\ndiff -r d0df6f16f41e -r df206dd11399 libinterp/corefcn/octave-json-link.h\n--- a/libinterp/corefcn/octave-json-link.h\tSun Dec 26 22:58:53 2021 -0600\n+++ b/libinterp/corefcn/octave-json-link.h\tSun Dec 26 23:09:26 2021 -0600\n@@ -212,7 +212,6 @@\n   json_queue<std::string> request_input_queue;\r\n   json_queue<std::pair<bool, std::string> > request_url_queue;\r\n   json_queue<bool> confirm_shutdown_queue;\r\n-  json_queue<bool> have_dialogs_queue;\r\n   json_queue<bool> prompt_new_edit_file_queue;\r\n   json_queue<int> message_dialog_queue;\r\n   json_queue<std::string> question_dialog_queue;\r\n"
  },
  {
    "path": "back-octave/oo-changesets/408-8184a51579f3.hg.txt",
    "content": "# HG changeset patch\n# User Shane F. Carr <shane.carr@wustl.edu>\n# Date 1640581778 21600\n#      Sun Dec 26 23:09:38 2021 -0600\n# Branch oo-7.0.1\n# Node ID 8184a51579f351cfe3809445b795d601143cd3c6\n# Parent  df206dd113996f60a2559ad898b96b1467a596b5\nRemove gnuplot warning\n\ndiff -r df206dd11399 -r 8184a51579f3 libinterp/dldfcn/__init_gnuplot__.cc\n--- a/libinterp/dldfcn/__init_gnuplot__.cc\tSun Dec 26 23:09:26 2021 -0600\n+++ b/libinterp/dldfcn/__init_gnuplot__.cc\tSun Dec 26 23:09:38 2021 -0600\n@@ -65,25 +65,6 @@\n   gnuplot_graphics_toolkit (octave::interpreter& interp)\n     : octave::base_graphics_toolkit (\"gnuplot\"), m_interpreter (interp)\n   {\n-    static bool warned = false;\n-\n-    if (! warned)\n-      {\n-        warning_with_id\n-          (\"Octave:gnuplot-graphics\",\n-           \"using the gnuplot graphics toolkit is discouraged\\n\\\n-\\n\\\n-The gnuplot graphics toolkit is not actively maintained and has a number\\n\\\n-of limitations that are unlikely to be fixed.  Communication with gnuplot\\n\\\n-uses a one-directional pipe and limited information is passed back to the\\n\\\n-Octave interpreter so most changes made interactively in the plot window\\n\\\n-will not be reflected in the graphics properties managed by Octave.  For\\n\\\n-example, if the plot window is closed with a mouse click, Octave will not\\n\\\n-be notified and will not update its internal list of open figure windows.\\n\\\n-The qt toolkit is recommended instead.\\n\");\n-\n-        warned = true;\n-      }\n   }\n \n   ~gnuplot_graphics_toolkit (void) = default;\n"
  },
  {
    "path": "back-octave/oo-changesets/420-4c3d80dd9e65.hg.txt",
    "content": "# HG changeset patch\n# User username = Octave Online Team <webmaster@octave-online.net>\n# Date 1672382380 21600\n#      Fri Dec 30 00:39:40 2022 -0600\n# Branch stable\n# Node ID 4c3d80dd9e65ced0f06ce25e13cf7ef9d481f954\n# Parent  601e7a142a15c7995dde159d96df317367c8d85f\n# Parent  48927d553c9d60228cc8e181add56e84c37cbd6d\nMerge oo-7.0.1 into oo-7.4\n\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 configure.ac\n--- a/configure.ac\tThu Dec 29 16:10:07 2022 +0100\n+++ b/configure.ac\tFri Dec 30 00:39:40 2022 -0600\n@@ -2979,7 +2979,7 @@\n AC_SUBST(LIBOCTAVE_LINK_DEPS)\n AC_SUBST(LIBOCTAVE_LINK_OPTS)\n \n-LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $SPARSE_XLIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $JAVA_LIBS $LAPACK_LIBS\"\n+LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $SPARSE_XLIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $JAVA_LIBS $LAPACK_LIBS -ljson-c\"\n \n LIBOCTINTERP_LINK_OPTS=\"$FT2_LDFLAGS $HDF5_LDFLAGS $MAGICK_LDFLAGS $Z_LDFLAGS $SPARSE_XLDFLAGS $FFTW_XLDFLAGS\"\n \ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libgui/src/qt-interpreter-events.cc\n--- a/libgui/src/qt-interpreter-events.cc\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libgui/src/qt-interpreter-events.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -309,6 +309,21 @@\n   emit edit_variable_signal (QString::fromStdString (expr), val);\n }\n \n+void qt_interpreter_events::show_static_plot (const std::string&, const std::string&)\n+{\n+  return;\n+}\n+\n+std::string qt_interpreter_events::request_input (const std::string&)\n+{\n+  return {};\n+}\n+\n+std::string qt_interpreter_events::request_url (const std::string&, const std::list<std::string>&, const std::string&, bool&)\n+{\n+  return {};\n+}\n+\n bool qt_interpreter_events::confirm_shutdown (void)\n {\n   QMutexLocker autolock (&m_mutex);\n@@ -604,6 +619,9 @@\n   emit clear_history_signal ();\n }\n \n+void qt_interpreter_events::do_clear_screen (void)\n+{ }\n+\n void qt_interpreter_events::pre_input_event (void)\n { }\n \ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libgui/src/qt-interpreter-events.h\n--- a/libgui/src/qt-interpreter-events.h\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libgui/src/qt-interpreter-events.h\tFri Dec 30 00:39:40 2022 -0600\n@@ -136,6 +136,12 @@\n \n   void edit_variable (const std::string& name, const octave_value& val);\n \n+  void show_static_plot (const std::string& term, const std::string& content);\n+\n+  std::string request_input (const std::string&);\n+\n+  std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n   bool confirm_shutdown (void);\n \n   bool prompt_new_edit_file (const std::string& file);\n@@ -190,6 +196,8 @@\n \n   void clear_history (void);\n \n+  void clear_screen (void);\n+\n   void pre_input_event (void);\n \n   void post_input_event (void);\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/event-manager.cc\n--- a/libinterp/corefcn/event-manager.cc\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/corefcn/event-manager.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -46,6 +46,16 @@\n \n OCTAVE_BEGIN_NAMESPACE(octave)\n \n+bool __event_manager_request_input_enabled__() {\n+  event_manager& evmgr = __get_event_manager__ (\"request_input_enabled\");\n+  return evmgr.request_input_enabled();\n+}\n+\n+std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\n+  event_manager& evmgr = __get_event_manager__ (\"request_url\");\n+  return evmgr.request_url(url, param, action, success);\n+}\n+\n static int readline_event_hook (void)\n {\n   event_manager& evmgr = __get_event_manager__ ();\n@@ -880,3 +890,27 @@\n }\n \n OCTAVE_END_NAMESPACE(octave)\n+\n+DEFMETHOD (__event_manager_plot_destination__, interp, , ,\n+           doc: /* -*- texinfo -*-\n+@deftypefn {} {} __event_manager_plot_destination__ ()\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  return ovl (interp.get_event_manager().plot_destination());\n+}\n+\n+DEFMETHOD (__event_manager_show_static_plot__, interp, args, ,\n+           doc: /* -*- texinfo -*-\n+@deftypefn {} {} __event_manager_show_static_plot__ (@var{term}, @var{content})\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  if (args.length () != 2) {\n+    return ovl ();\n+  }\n+\n+  std::string term = args(0).string_value();\n+  std::string content = args(1).string_value();\n+  return ovl (interp.get_event_manager().show_static_plot(term, content));\n+}\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/event-manager.h\n--- a/libinterp/corefcn/event-manager.h\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/corefcn/event-manager.h\tFri Dec 30 00:39:40 2022 -0600\n@@ -50,6 +50,12 @@\n class execution_exception;\n class symbol_info_list;\n \n+enum plot_destination_t {\n+  TERMINAL_ONLY = 0,\n+  STATIC_ONLY = 1,\n+  TERMINAL_AND_STATIC = 2\n+};\n+\n // The methods in this class provide a way to pass signals to the GUI\n // thread.  A GUI that wishes to act on these events should derive\n // from this class and perform actions in a thread-safe way.  In\n@@ -176,6 +182,18 @@\n   // confirmation before another action.  Could these be reformulated\n   // using the question_dialog action?\n \n+  bool _request_input_enabled;\n+  virtual std::string request_input (const std::string&)\n+  {\n+    return \"\";\n+  }\n+  virtual std::string request_url (const std::string& /*url*/, const std::list<std::string>& /*param*/, const std::string& /*action*/, bool& /*success*/) {\n+    return \"\";\n+  }\n+\n+  plot_destination_t _plot_destination;\n+  virtual void show_static_plot (const std::string& /*term*/, const std::string& /*content*/) { }\n+\n   virtual bool confirm_shutdown (void) { return true; }\n \n   virtual bool prompt_new_edit_file (const std::string& /*file*/)\n@@ -260,6 +278,8 @@\n \n   virtual void clear_history (void) { }\n \n+  virtual void clear_screen (void) { }\n+\n   virtual void pre_input_event (void) { }\n \n   virtual void post_input_event (void) { }\n@@ -448,6 +468,28 @@\n       m_instance->update_path_dialog ();\n   }\n \n+  bool request_input_enabled (void)\n+  {\n+    return enabled () ? m_instance->_request_input_enabled : false;\n+  }\n+\n+  plot_destination_t plot_destination (void)\n+  {\n+    return enabled () ? m_instance->_plot_destination : TERMINAL_ONLY;\n+  }\n+\n+  bool\n+  show_static_plot (const std::string& term, const std::string& content)\n+  {\n+    if (enabled ())\n+      {\n+        m_instance->show_static_plot (term, content);\n+        return true;\n+      }\n+    else\n+      return false;\n+  }\n+\n   bool show_preferences (void)\n   {\n     if (enabled ())\n@@ -709,6 +751,12 @@\n       m_instance->clear_history ();\n   }\n \n+  void clear_screen (void)\n+  {\n+    if (enabled ())\n+      m_instance->clear_screen ();\n+  }\n+\n   void pre_input_event (void)\n   {\n     if (enabled ())\n@@ -721,6 +769,20 @@\n       m_instance->post_input_event ();\n   }\n \n+  std::string request_input (const std::string& prompt)\n+  {\n+    return request_input_enabled ()\n+      ? m_instance->request_input (prompt)\n+      : std::string ();\n+  }\n+\n+  std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success)\n+  {\n+    return request_input_enabled ()\n+      ? m_instance->request_url (url, param, action, success)\n+      : std::string ();\n+  }\n+\n   void enter_debugger_event (const std::string& fcn_name,\n                              const std::string& fcn_file_name, int line)\n   {\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/input.cc\n--- a/libinterp/corefcn/input.cc\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/corefcn/input.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -786,7 +786,12 @@\n \n   eof = false;\n \n-  std::string retval = command_editor::readline (s, eof);\n+  std::string retval;\n+  event_manager& evmgr = m_interpreter.get_event_manager ();\n+  if (evmgr.request_input_enabled ())\n+    retval = evmgr.request_input (s);\n+  else\n+    retval = command_editor::readline (s, eof);\n \n   if (! eof && retval.empty ())\n     retval = \"\\n\";\n@@ -1679,3 +1684,32 @@\n }\n \n OCTAVE_END_NAMESPACE(octave)\n+\n+DEFUN (current_command_number, args, ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn  {} {@var{val} =} current_command_number ()\n+@deftypefnx {} {@var{old_val} =} current_command_number (@var{new_val})\n+Sets the current command number, which appears in the prompt string.\n+For example, if the prompt says \"octave:1>\", then the current command\n+number is 1.\n+\n+This is a custom function in Octave Online.\n+\n+@example\n+current_command_number(1)\n+@end example\n+@end deftypefn */)\n+{\n+  int nargin = args.length ();\n+  if (nargin == 0) {\n+    int n = octave::command_editor::current_command_number();\n+    return ovl(n);\n+  } else if (nargin > 1) {\n+    print_usage ();\n+    return ovl();\n+  } else {\n+    int n = args(0).int_value ();\n+    octave::command_editor::reset_current_command_number(n);\n+    return ovl(n);\n+  }\n+}\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/interpreter.cc\n--- a/libinterp/corefcn/interpreter.cc\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/corefcn/interpreter.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -61,6 +61,7 @@\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"interpreter.h\"\n+#include \"json-main.h\"\n #include \"load-path.h\"\n #include \"load-save.h\"\n #include \"octave.h\"\n@@ -624,6 +625,11 @@\n       std::string texi_macros_file = options.texi_macros_file ();\n       if (! texi_macros_file.empty ())\n         Ftexi_macros_file (*this, octave_value (texi_macros_file));\n+\n+      if (!options.json_sock_path().empty ()) {\n+        static json_main _json_main (*this, options.json_sock_path(), options.json_max_message_length());\n+        _json_main.run_loop_on_new_thread();\n+      }\n     }\n \n   // FIXME: we defer creation of the gh_manager object because it\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/json-main.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -0,0 +1,102 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include \"json-main.h\"\r\n+#include \"interpreter.h\"\r\n+\r\n+#include <iostream>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <unistd.h>\r\n+\r\n+\r\n+// Analog of main-window.cc\r\n+// TODO: Think more about concurrency and null pointer exceptions\r\n+\r\n+namespace octave {\r\n+\r\n+void* run_loop_pthread(void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->run_loop();\r\n+  return NULL;\r\n+}\r\n+\r\n+void json_object_cb(std::string name, JSON_OBJECT_T jobj, void* arg) {\r\n+  json_main* _json_main = static_cast<json_main*>(arg);\r\n+  _json_main->process_json_object(name, jobj);\r\n+}\r\n+\r\n+json_main::json_main(interpreter& interp, const std::string& json_sock_path, int max_message_length)\r\n+  : _json_sock_path (json_sock_path),\r\n+    _max_message_length (max_message_length),\r\n+    _loop_thread_active (false),\r\n+    _octave_json_link (new octave_json_link(this))\r\n+{\r\n+  // Enable the octave_json_link instance\r\n+  // Note: this passes ownership to octave_link\r\n+  event_manager& evmgr = interp.get_event_manager ();\r\n+  evmgr.connect_link (_octave_json_link);\r\n+  evmgr.install_qt_event_handlers (_octave_json_link);\r\n+  evmgr.enable ();\r\n+\r\n+  // Open UNIX socket file descriptor\r\n+  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\r\n+  struct sockaddr_un addr;\r\n+  memset(&addr, 0, sizeof(addr));\r\n+  addr.sun_family = AF_UNIX;\r\n+  memcpy(&addr.sun_path, _json_sock_path.c_str(), sizeof(addr.sun_path)-1);\r\n+  connect(\r\n+    sockfd,\r\n+    reinterpret_cast<struct sockaddr*>(&addr),\r\n+    sizeof(addr));\r\n+}\r\n+\r\n+json_main::~json_main(void) {\r\n+  close(sockfd);\r\n+\r\n+  // TODO: Stop the _loop_thread\r\n+}\r\n+\r\n+void json_main::publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  std::string jstr = json_util::to_message(name, jobj);\r\n+\r\n+  // Do not send any messages over the socket that exceed the user-specified max length.  Instead, send an error message.  If max_length is 0 (default), do not suppress any messages.\r\n+  // In stress testing, Node may be able to handle as much as 10-20 MB, but I'd prefer to stay on the safe side.  Before this safeguard was implemented, fewer than 5% of plots exceeded 1 MB.\r\n+  int length = jstr.length();\r\n+  int max_length = _max_message_length;\r\n+  if (max_length > 0 && length > max_length) {\r\n+    JSON_MAP_T m;\r\n+    JSON_MAP_SET(m, name, string);\r\n+    JSON_MAP_SET(m, length, int);\r\n+    JSON_MAP_SET(m, max_length, int);\r\n+    jstr = json_util::to_message(\"message-too-long\", json_util::from_map(m));\r\n+  }\r\n+\r\n+  send(sockfd, jstr.c_str(), jstr.length(), 0);\r\n+}\r\n+\r\n+void json_main::run_loop_on_new_thread(void) {\r\n+  if (_loop_thread_active)\r\n+    perror(\"won't run JSON socket loop multiple times\");\r\n+  _loop_thread_active = true;\r\n+\r\n+  pthread_create(\r\n+    &_loop_thread,\r\n+    NULL,\r\n+    run_loop_pthread,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::run_loop(void) {\r\n+  json_util::read_stream(\r\n+    sockfd,\r\n+    json_object_cb,\r\n+    static_cast<void*>(this));\r\n+}\r\n+\r\n+void json_main::process_json_object(std::string name, JSON_OBJECT_T jobj) {\r\n+  _octave_json_link->receive_message(name, jobj);\r\n+}\r\n+\r\n+} // namespace octave\r\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/json-main.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.h\tFri Dec 30 00:39:40 2022 -0600\n@@ -0,0 +1,37 @@\n+#ifndef json_main_h\r\n+#define json_main_h\r\n+\r\n+#include <queue>\r\n+#include <pthread.h>\r\n+#include <stdio.h>\r\n+\r\n+#include \"octave-json-link.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+namespace octave {\r\n+\r\n+class interpreter;\r\n+\r\n+class json_main {\r\n+public:\r\n+\tjson_main(interpreter& interp, const std::string& json_sock_path, int max_message_length);\r\n+\t~json_main(void);\r\n+\r\n+\tvoid publish_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\tvoid run_loop_on_new_thread(void);\r\n+\tvoid run_loop(void);\r\n+\tvoid process_json_object(std::string name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+\tstd::string _json_sock_path;\r\n+\tint _max_message_length;\r\n+\tint sockfd;\r\n+\tbool _loop_thread_active;\r\n+\tpthread_t _loop_thread;\r\n+\r\n+\tstd::shared_ptr<octave_json_link> _octave_json_link;\r\n+};\r\n+\r\n+} // namespace octave\r\n+\r\n+#endif\r\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/json-util.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -0,0 +1,264 @@\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <cstdlib>\r\n+#include <assert.h>\r\n+#include <sys/un.h>\r\n+#include <sys/socket.h>\r\n+#include <stdio.h>\r\n+#include <json-c/arraylist.h>\r\n+#include <json-c/json_object.h>\r\n+\r\n+#include \"str-vec.h\"\r\n+\r\n+#include \"json-util.h\"\r\n+\r\n+namespace octave {\r\n+\r\n+JSON_OBJECT_T json_util::from_string(const std::string& str) {\r\n+\t// Note: the string is not necesarilly valid UTF-8. The consumers of this stream must be able to handle that situation and substitute replacement characters, etc., where necessary.\r\n+\treturn json_object_new_string_len(str.c_str(), str.length());\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int(int i) {\r\n+\treturn json_object_new_int(i);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float(float flt) {\r\n+\treturn json_object_new_double(flt);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_boolean(bool b) {\r\n+\treturn json_object_new_boolean(b);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::empty() {\r\n+\treturn json_object_new_object();\r\n+}\r\n+\r\n+template<typename T>\r\n+JSON_OBJECT_T json_object_from_list(const std::list<T>& list, JSON_OBJECT_T (*convert)(T)) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\tauto it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, convert(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_list(const std::list<std::string>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_string_vector(const string_vector& vect) {\r\n+\t// TODO: Make sure this function does what it's supposed to do\r\n+\tstd::list<std::string> list;\r\n+\tfor (int i = 0; i < vect.numel(); ++i) {\r\n+\t\tlist.push_back(vect[i]);\r\n+\t}\r\n+\r\n+\treturn json_object_from_list(list, json_util::from_value_string);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_int_list(const std::list<int>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_int);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_float_list(const std::list<float>& list) {\r\n+\treturn json_object_from_list(list, json_util::from_float);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_symbol_info_list(const symbol_info_list& list) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tfor (\r\n+\t\tauto it = list.begin();\r\n+\t\tit != list.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_array_add(jobj, json_util::from_symbol_info(*it));\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_filter_list(const interpreter_events::filter_list& list) {\r\n+\treturn json_object_from_list(list, json_util::from_pair);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_value_string(const std::string str) {\r\n+\treturn json_util::from_string(str);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_symbol_info(const symbol_info element) {\r\n+\toctave_value val = element.value();\r\n+\r\n+\tstd::string dims_str = val.get_dims_str();\r\n+\r\n+\tstd::ostringstream display_str;\r\n+\tval.short_disp(display_str);\r\n+\r\n+\tJSON_MAP_T m;\r\n+\t// m[\"scope\"] = json_util::from_int(element.scope());\r\n+\tm[\"symbol\"] = json_util::from_string(element.name());\r\n+\tm[\"class_name\"] = json_util::from_string(val.class_name());\r\n+\tm[\"dimension\"] = json_util::from_string(dims_str);\r\n+\tm[\"value\"] = json_util::from_string(display_str.str());\r\n+\tm[\"complex_flag\"] = json_util::from_boolean(element.is_complex());\r\n+\treturn json_util::from_map(m);\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_pair(std::pair<std::string, std::string> pair) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_array();\r\n+\tjson_object_array_add(jobj, json_util::from_string(pair.first.c_str()));\r\n+\tjson_object_array_add(jobj, json_util::from_string(pair.second.c_str()));\r\n+\treturn jobj;\r\n+}\r\n+\r\n+JSON_OBJECT_T json_util::from_map(JSON_MAP_T m) {\r\n+\tJSON_OBJECT_T jobj = json_object_new_object();\r\n+\tfor(\r\n+\t\tstd::map<std::string, JSON_OBJECT_T>::iterator it = m.begin();\r\n+\t\tit != m.end();\r\n+\t\t++it\r\n+\t){\r\n+\t\tjson_object_object_add(jobj, it->first.c_str(), it->second);\r\n+\t}\r\n+\treturn jobj;\r\n+}\r\n+\r\n+std::string json_util::to_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+  JSON_OBJECT_T jmsg = json_object_new_array();\r\n+  json_object_array_add(jmsg, json_util::from_string(name));\r\n+  json_object_array_add(jmsg, jobj);\r\n+  std::string str (json_object_to_json_string(jmsg));\r\n+  return str;\r\n+}\r\n+\r\n+std::string json_util::to_string(JSON_OBJECT_T jobj) {\r\n+  return std::string(json_object_get_string(jobj));\r\n+}\r\n+\r\n+template<typename T>\r\n+std::list<T> json_object_to_list(JSON_OBJECT_T jobj, T (*convert)(JSON_OBJECT_T)) {\r\n+\tstd::list<T> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tfor (size_t i = 0; i < array_list_length(arr); ++i) {\r\n+\t\tJSON_OBJECT_T jsub = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, i));\r\n+\t\tret.push_back(convert(jsub));\r\n+\t}\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> json_util::to_int_list_int_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<std::list<int>, int> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_to_list<int>(first, json_util::to_int);\r\n+\tret.second = json_object_get_int(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::pair<bool, std::string> json_util::to_bool_string_pair(JSON_OBJECT_T jobj) {\r\n+\tstd::pair<bool, std::string> ret;\r\n+\r\n+\tstruct array_list* arr = json_object_get_array(jobj);\r\n+\tif (arr == NULL)\r\n+\t\treturn ret;\r\n+\r\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\r\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\r\n+\r\n+\tret.first = json_object_get_boolean(first);\r\n+\tret.second = json_object_get_string(second);\r\n+\r\n+\treturn ret;\r\n+}\r\n+\r\n+std::list<std::string> json_util::to_string_list(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_to_list<std::string>(jobj, json_util::to_string);\r\n+}\r\n+\r\n+int json_util::to_int(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_int(jobj);\r\n+}\r\n+\r\n+bool json_util::to_boolean(JSON_OBJECT_T jobj) {\r\n+\treturn json_object_get_boolean(jobj);\r\n+}\r\n+\r\n+void json_util::read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+\r\n+\t// Make some local variables\r\n+\tint BUF_LEN = 24;\r\n+\tchar* buf = new char[BUF_LEN];  // buffer for socket read\r\n+\tint buf_len;  // length of new bytes in the buffer\r\n+\tint buf_offset;  // offset of the JSON parser in the buffer\r\n+\tJSON_OBJECT_T jobj;  // pointer to parsed JSON object\r\n+\tjson_tokener* tok = json_tokener_new();  // JSON tokenizer instance\r\n+\tenum json_tokener_error jerr;  // status of JSON tokenizer\r\n+\r\n+\t// Start the blocking I/O loop\r\n+\twhile( (buf_len=recv(sockfd, buf, BUF_LEN, 0)) > 0) {\r\n+\t\tbuf_offset = 0;\r\n+\t\twhile(buf_offset < buf_len){\r\n+\t\t\tjobj = json_tokener_parse_ex(tok, buf + buf_offset, buf_len - buf_offset);\r\n+\t\t\tjerr = json_tokener_get_error(tok);\r\n+\t\t\tbuf_offset += tok->char_offset;\r\n+\r\n+\t\t\t// Do we need more material in order to make JSON?\r\n+\t\t\tif (jerr == json_tokener_continue) {\r\n+\t\t\t\tcontinue;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Make a new tokenizer\r\n+\t\t\tjson_tokener_free(tok);\r\n+\t\t\ttok = json_tokener_new();\r\n+\r\n+\t\t\t// Did we encounter a malformed JSON object?\r\n+\t\t\tif (jerr != json_tokener_success) {\r\n+\t\t\t\tfprintf(stderr,\r\n+\t\t\t\t\t\"JSON parse error: %s: '%.*s'\\n\",\r\n+\t\t\t\t\tjson_tokener_error_desc(jerr),\r\n+\t\t\t\t\t1,\r\n+\t\t\t\t\tbuf + buf_offset);\r\n+\t\t\t\tfflush(stderr);\r\n+\t\t\t\tbreak;\r\n+\t\t\t}\r\n+\r\n+\t\t\t// Our object is ready\r\n+\t\t\tprocess_message(jobj, cb, arg);\r\n+\t\t}\r\n+\t}\r\n+\r\n+\tjson_tokener_free(tok);\r\n+\tdelete buf;\r\n+}\r\n+\r\n+void json_util::process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\n+  if (!json_object_is_type(jobj, json_type_array))\r\n+    return;\r\n+  if (json_object_array_length(jobj) != 2)\r\n+    return;\r\n+\r\n+  cb(\r\n+  \tjson_util::to_string(json_object_array_get_idx(jobj, 0)),\r\n+  \tjson_object_array_get_idx(jobj, 1),\r\n+  \targ\r\n+  );\r\n+}\r\n+\r\n+} // namespace octave\r\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/json-util.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.h\tFri Dec 30 00:39:40 2022 -0600\n@@ -0,0 +1,64 @@\n+#ifndef json_util_h\r\n+#define json_util_h\r\n+\r\n+#include <json-c/json.h>\r\n+#include <map>\r\n+#include <list>\r\n+\r\n+#include \"syminfo.h\"\r\n+#include \"event-manager.h\"\r\n+\r\n+class string_vector;\r\n+\r\n+// All of the code interacting with the external JSON library should be in\r\n+// the json-util.h and json-util.cc files.  This way, if we want to change\r\n+// the external JSON library, we can do it all in one place.\r\n+\r\n+#define JSON_OBJECT_T json_object*\r\n+#define JSON_MAP_T std::map<std::string, JSON_OBJECT_T>\r\n+\r\n+#define JSON_MAP_SET(M, FIELD, TYPE){ \\\r\n+\tm[#FIELD] = json_util::from_##TYPE (FIELD); \\\r\n+}\r\n+\r\n+namespace octave {\r\n+\r\n+class json_util {\r\n+public:\r\n+\tstatic JSON_OBJECT_T from_string(const std::string& str);\r\n+\tstatic JSON_OBJECT_T from_int(int i);\r\n+\tstatic JSON_OBJECT_T from_float(float flt);\r\n+\tstatic JSON_OBJECT_T from_boolean(bool b);\r\n+\tstatic JSON_OBJECT_T empty();\r\n+\r\n+\tstatic JSON_OBJECT_T from_string_list(const std::list<std::string>& list);\r\n+\tstatic JSON_OBJECT_T from_string_vector(const string_vector& list);\r\n+\tstatic JSON_OBJECT_T from_int_list(const std::list<int>& list);\r\n+\tstatic JSON_OBJECT_T from_float_list(const std::list<float>& list);\r\n+\tstatic JSON_OBJECT_T from_symbol_info_list(const symbol_info_list& list);\r\n+        static JSON_OBJECT_T from_filter_list(const interpreter_events::filter_list& list);\r\n+\r\n+\tstatic JSON_OBJECT_T from_value_string(const std::string str);\r\n+\tstatic JSON_OBJECT_T from_symbol_info(const symbol_info element);\r\n+\tstatic JSON_OBJECT_T from_pair(std::pair<std::string, std::string> pair);\r\n+\r\n+\tstatic JSON_OBJECT_T from_map(JSON_MAP_T m);\r\n+\r\n+\tstatic std::string to_message(const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic std::string to_string(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<std::list<int>, int> to_int_list_int_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::pair<bool, std::string> to_bool_string_pair(JSON_OBJECT_T jobj);\r\n+\tstatic std::list<std::string> to_string_list(JSON_OBJECT_T jobj);\r\n+\tstatic int to_int(JSON_OBJECT_T jobj);\r\n+\tstatic bool to_boolean(JSON_OBJECT_T jobj);\r\n+\r\n+\tstatic void read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+\r\n+private:\r\n+\tstatic void process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\r\n+};\r\n+\r\n+} // namespace octave\r\n+\r\n+#endif\r\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/module.mk\n--- a/libinterp/corefcn/module.mk\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/corefcn/module.mk\tFri Dec 30 00:39:40 2022 -0600\n@@ -46,6 +46,8 @@\n   %reldir%/help.h \\\n   %reldir%/hook-fcn.h \\\n   %reldir%/input.h \\\n+  %reldir%/json-main.h \\\n+  %reldir%/json-util.h \\\n   %reldir%/interpreter.h \\\n   %reldir%/latex-text-renderer.h \\\n   %reldir%/load-path.h \\\n@@ -77,6 +79,7 @@\n   %reldir%/oct-strstrm.h \\\n   %reldir%/oct.h \\\n   %reldir%/octave-default-image.h \\\n+  %reldir%/octave-json-link.h \\\n   %reldir%/pager.h \\\n   %reldir%/pr-flt-fmt.h \\\n   %reldir%/pr-output.h \\\n@@ -189,6 +192,8 @@\n   %reldir%/hex2num.cc \\\n   %reldir%/hook-fcn.cc \\\n   %reldir%/input.cc \\\n+  %reldir%/json-main.cc \\\n+  %reldir%/json-util.cc \\\n   %reldir%/interpreter-private.cc \\\n   %reldir%/interpreter.cc \\\n   %reldir%/inv.cc \\\n@@ -228,6 +233,7 @@\n   %reldir%/oct-tex-lexer.ll \\\n   %reldir%/oct-tex-parser.h \\\n   %reldir%/oct-tex-parser.yy \\\n+  %reldir%/octave-json-link.cc \\\n   %reldir%/ordqz.cc \\\n   %reldir%/ordschur.cc \\\n   %reldir%/pager.cc \\\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/octave-json-link.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -0,0 +1,453 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifdef HAVE_CONFIG_H\r\n+#include <config.h>\r\n+#endif\r\n+\r\n+#include <iostream>\r\n+#include \"octave-json-link.h\"\r\n+#include \"cmd-edit.h\"\r\n+#include \"json-main.h\"\r\n+#include \"json-util.h\"\r\n+\r\n+namespace octave {\r\n+\r\n+octave_json_link::octave_json_link(json_main* __json_main)\r\n+\t: interpreter_events (),\r\n+\t\t_json_main (__json_main)\r\n+{\r\n+\t_request_input_enabled = true;\r\n+\t_plot_destination = STATIC_ONLY;\r\n+}\r\n+\r\n+octave_json_link::~octave_json_link(void) { }\r\n+\r\n+std::string octave_json_link::request_input(const std::string& prompt) {\r\n+\t// Triggered whenever the console prompts for user input\r\n+\r\n+\tstd::string value;\r\n+\tif (!request_input_queue.dequeue_to(&value)) {\r\n+\t\t_publish_message(\"request-input\", json_util::from_string(prompt));\r\n+\t\tvalue = request_input_queue.dequeue();\r\n+\t}\r\n+\treturn value;\r\n+}\r\n+\r\n+std::string octave_json_link::request_url(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\r\n+\t// Triggered on urlread/urlwrite\r\n+\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, url, string);\r\n+\tJSON_MAP_SET(m, param, string_list);\r\n+\tJSON_MAP_SET(m, action, string);\r\n+\r\n+\t_publish_message(\"request-url\", json_util::from_map(m));\r\n+\tstd::pair<bool, std::string> result = request_url_queue.dequeue();\r\n+\tsuccess = result.first;\r\n+\treturn result.second;\r\n+}\r\n+\r\n+bool octave_json_link::confirm_shutdown(void) {\r\n+\t// Triggered when the kernel tries to exit\r\n+\t_publish_message(\"confirm-shutdown\", json_util::empty());\r\n+\r\n+\treturn confirm_shutdown_queue.dequeue();\r\n+}\r\n+\r\n+// do_exit was removed in Octave 5\r\n+// bool octave_json_link::do_exit(int status) {\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, status, int);\r\n+// \t_publish_message(\"exit\", json_util::from_map(m));\r\n+\r\n+// \t// It is our responsibility in octave_link to call exit. If we don't, then\r\n+// \t// the kernel waits for 24 hours expecting us to do something.\r\n+// \t::exit(status);\r\n+\r\n+// \treturn true;\r\n+// }\r\n+\r\n+bool octave_json_link::copy_image_to_clipboard(const std::string& file) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"copy-image-to-clipboard\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for existing files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn true;\r\n+}\r\n+\r\n+bool octave_json_link::prompt_new_edit_file(const std::string& file) {\r\n+\t// Triggered in \"edit\" for new files\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\t_publish_message(\"prompt-new-edit-file\", json_util::from_map(m));\r\n+\r\n+\treturn prompt_new_edit_file_queue.dequeue();\r\n+}\r\n+\r\n+// int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {\r\n+// \t// Triggered in \"msgbox\", \"helpdlg\", and \"errordlg\", among others\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\r\n+// \tJSON_MAP_SET(m, msg, string);\r\n+// \tJSON_MAP_SET(m, title, string);\r\n+// \t_publish_message(\"message-dialog\", json_util::from_map(m));\r\n+\r\n+// \treturn message_dialog_queue.dequeue();\r\n+// }\r\n+\r\n+bool octave_json_link::have_dialogs() const {\r\n+\t// Triggered in \"inputdlg\" and similar functions to check for dialog support\r\n+\treturn true;\r\n+}\r\n+\r\n+std::string octave_json_link::question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\r\n+\t// Triggered in \"questdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, msg, string);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, btn1, string);\r\n+\tJSON_MAP_SET(m, btn2, string);\r\n+\tJSON_MAP_SET(m, btn3, string);\r\n+\tJSON_MAP_SET(m, btndef, string);\r\n+\t_publish_message(\"question-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn question_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::pair<std::list<int>, int> octave_json_link::list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {\r\n+\t// Triggered in \"listdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, list, string_list);\r\n+\tJSON_MAP_SET(m, mode, string);\r\n+\tJSON_MAP_SET(m, width, int);\r\n+\tJSON_MAP_SET(m, height, int);\r\n+\tJSON_MAP_SET(m, initial_value, int_list);\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, ok_string, string);\r\n+\tJSON_MAP_SET(m, cancel_string, string);\r\n+\t_publish_message(\"list-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn list_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {\r\n+\t// Triggered in \"inputdlg\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, prompt, string_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, nr, float_list);\r\n+\tJSON_MAP_SET(m, nc, float_list);\r\n+\tJSON_MAP_SET(m, defaults, string_list);\r\n+\t_publish_message(\"input-dialog\", json_util::from_map(m));\r\n+\r\n+\treturn input_dialog_queue.dequeue();\r\n+}\r\n+\r\n+std::list<std::string> octave_json_link::file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {\r\n+\t// Triggered in \"uiputfile\", \"uigetfile\", and \"uigetdir\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, filter, filter_list);\r\n+\tJSON_MAP_SET(m, title, string);\r\n+\tJSON_MAP_SET(m, filename, string);\r\n+\tJSON_MAP_SET(m, pathname, string);\r\n+\tJSON_MAP_SET(m, multimode, string);\r\n+\t_publish_message(\"file-dialog\", json_util::from_map(m));\r\n+\t\r\n+\treturn file_dialog_queue.dequeue();\r\n+}\r\n+\r\n+void octave_json_link::update_path_dialog(void) {\r\n+\t// Triggered in \"rehash\"\r\n+\t_publish_message(\"update-path-dialog\", json_util::empty());\r\n+}\r\n+\r\n+int octave_json_link::debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\r\n+\t// This endpoint might be unused?  (No references)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\tJSON_MAP_SET(m, addpath_option, boolean);\r\n+\t_publish_message(\"debug-cd-or-addpath-error\", json_util::from_map(m));\r\n+\r\n+\treturn debug_cd_or_addpath_error_queue.dequeue();\r\n+}\r\n+\r\n+void octave_json_link::focus_window(const std::string win_name) {\r\n+\t// Triggered in \"commandhistory\", \"commandwindow\", \"filebrowser\", \"workspace\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, win_name, string);\r\n+\t_publish_message(\"focus-window\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::display_exception(const execution_exception& ee, bool beep) {\r\n+\t// Triggered in various places in libinterp\r\n+        std::ostringstream buf;\r\n+        ee.display (buf);\r\n+        std::string ee_str = buf.str();\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, ee_str, string);\r\n+\tJSON_MAP_SET(m, beep, boolean);\r\n+\t_publish_message(\"display-exception\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::gui_status_update(const std::string& feature, const std::string& status) {\r\n+\t// Triggered in __profiler_enable__\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, feature, string);\r\n+\tJSON_MAP_SET(m, status, string);\r\n+\t_publish_message(\"gui-status-update\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::update_gui_lexer(void) {\r\n+\t// Triggered in \"load_packages_and_dependencies\"\r\n+\t_publish_message(\"update-gui-lexer\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::directory_changed(const std::string& dir) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, dir, string);\r\n+\t_publish_message(\"change-directory\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::file_remove (const std::string& old_name, const std::string& new_name) {\r\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, old_name, string);\r\n+\tJSON_MAP_SET(m, new_name, string);\r\n+\t_publish_message(\"file-remove\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::file_renamed (bool status) {\r\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\r\n+\t_publish_message(\"file-renamed\", json_util::from_boolean(status));\r\n+}\r\n+\r\n+void octave_json_link::execute_command_in_terminal(const std::string& command) {\r\n+\t// This endpoint might be unused?  (References appear only in libgui)\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, command, string);\r\n+\t_publish_message(\"execute-command-in-terminal\", json_util::from_map(m));\r\n+}\r\n+\r\n+uint8NDArray octave_json_link::get_named_icon (const std::string& /* icon_name */) {\r\n+\t// Called from msgbox.m\r\n+\t// TODO: Implement request/response for this event\r\n+\tuint8NDArray retval;\r\n+\treturn retval;\r\n+}\r\n+\r\n+void octave_json_link::set_workspace(bool top_level, bool debug,\r\n+                         const symbol_info_list& ws,\r\n+                         bool update_variable_editor) {\r\n+\t// Triggered on every new line entry\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, top_level, boolean);\r\n+\tJSON_MAP_SET(m, debug, boolean);\r\n+\tJSON_MAP_SET(m, ws, symbol_info_list);\r\n+\tJSON_MAP_SET(m, update_variable_editor, boolean);\r\n+\t_publish_message(\"set-workspace\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::clear_workspace(void) {\r\n+\t// Triggered on \"clear\" command (but not \"clear all\" or \"clear foo\")\r\n+\t_publish_message(\"clear-workspace\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::set_history(const string_vector& hist) {\r\n+\t// Called at startup, possibly more?\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist, string_vector);\r\n+\t_publish_message(\"set-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::append_history(const std::string& hist_entry) {\r\n+\t// Appears to be tied to readline, if available\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, hist_entry, string);\r\n+\t_publish_message(\"append-history\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::clear_history(void) {\r\n+\t// Appears to be tied to readline, if available\r\n+\t_publish_message(\"clear-history\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::clear_screen(void) {\r\n+\t// Triggered by clc\r\n+\t_publish_message(\"clear-screen\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::pre_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::post_input_event(void) {\r\n+\t// noop\r\n+}\r\n+\r\n+void octave_json_link::enter_debugger_event(const std::string& fcn_name, const std::string& fcn_file_name, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, fcn_name, string);\r\n+\tJSON_MAP_SET(m, fcn_file_name, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"enter-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::execute_in_debugger_event(const std::string& file, int line) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\t_publish_message(\"execute-in-debugger-event\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::exit_debugger_event(void) {\r\n+\t_publish_message(\"exit-debugger-event\", json_util::empty());\r\n+}\r\n+\r\n+void octave_json_link::update_breakpoint(bool insert, const std::string& file, int line, const std::string& cond) {\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, insert, boolean);\r\n+\tJSON_MAP_SET(m, file, string);\r\n+\tJSON_MAP_SET(m, line, int);\r\n+\tJSON_MAP_SET(m, cond, string);\r\n+\t_publish_message(\"update-breakpoint\", json_util::from_map(m));\r\n+}\r\n+\r\n+// void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {\r\n+// \t// Triggered upon interpreter startup\r\n+// \tJSON_MAP_T m;\r\n+// \tJSON_MAP_SET(m, ps1, string);\r\n+// \tJSON_MAP_SET(m, ps2, string);\r\n+// \tJSON_MAP_SET(m, ps4, string);\r\n+// \t_publish_message(\"set-default-prompts\", json_util::from_map(m));\r\n+// }\r\n+\r\n+void octave_json_link::show_preferences(void) {\r\n+\t// Triggered on \"preferences\" command\r\n+\t_publish_message(\"show-preferences\", json_util::empty());\r\n+}\r\n+\r\n+std::string octave_json_link::gui_preference (const std::string& /* key */, const std::string& /* value */) {\r\n+\t// Used by Octave GUI?\r\n+\t// TODO: Implement request/response for this event\r\n+\tstd::string retval;\r\n+\treturn retval;\r\n+}\r\n+\r\n+bool octave_json_link::show_documentation(const std::string& file) {\r\n+\t// Triggered on \"doc\" command\r\n+\t_publish_message(\"show-doc\", json_util::from_string(file));\r\n+\treturn true;\r\n+}\r\n+\r\n+void octave_json_link::register_documentation (const std::string& file) {\r\n+\t// Triggered by the GUI documentation viewer?\r\n+\t_publish_message(\"register-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::unregister_documentation (const std::string& file) {\r\n+\t// Triggered by the GUI documentation viewer?\r\n+\t_publish_message(\"unregister-doc\", json_util::from_string(file));\r\n+}\r\n+\r\n+void octave_json_link::edit_variable (const std::string& name, const octave_value& /* val */) {\r\n+\t// Triggered on \"openvar\" command\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, name, string);\r\n+\t// TODO: val\r\n+\t_publish_message(\"edit-variable\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::show_static_plot(const std::string& term, const std::string& content) {\r\n+\t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\r\n+\tint command_number = command_editor::current_command_number();\r\n+\tJSON_MAP_T m;\r\n+\tJSON_MAP_SET(m, term, string);\r\n+\tJSON_MAP_SET(m, content, string);\r\n+\tJSON_MAP_SET(m, command_number, int);\r\n+\t_publish_message(\"show-static-plot\", json_util::from_map(m));\r\n+}\r\n+\r\n+void octave_json_link::receive_message (const std::string& name, JSON_OBJECT_T jobj) {\r\n+\tif (name == \"cmd\" || name == \"request-input-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\trequest_input_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"request-url-answer\") {\r\n+\t\tstd::pair<bool, std::string> answer = json_util::to_bool_string_pair(jobj);\r\n+\t\trequest_url_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"confirm-shutdown-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tconfirm_shutdown_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"prompt-new-edit-file-answer\"){\r\n+\t\tbool answer = json_util::to_boolean(jobj);\r\n+\t\tprompt_new_edit_file_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"message-dialog-answer\"){\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tmessage_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"question-dialog-answer\") {\r\n+\t\tstd::string answer = json_util::to_string(jobj);\r\n+\t\tquestion_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"list-dialog-answer\") {\r\n+\t\tstd::pair<std::list<int>, int> answer = json_util::to_int_list_int_pair(jobj);\r\n+\t\tlist_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"input-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tinput_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"file-dialog-answer\") {\r\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\r\n+\t\tfile_dialog_queue.enqueue(answer);\r\n+\t}\r\n+\telse if (name == \"debug-cd-or-addpath-error-answer\") {\r\n+\t\tint answer = json_util::to_int(jobj);\r\n+\t\tdebug_cd_or_addpath_error_queue.enqueue(answer);\r\n+\t}\r\n+\telse {\r\n+\t\tstd::cerr << \"warning: received unknown message: \" << name << std::endl;\r\n+\t}\r\n+}\r\n+\r\n+void octave_json_link::_publish_message(const std::string& name, JSON_OBJECT_T jobj) {\r\n+\t_json_main->publish_message(name, jobj);\r\n+}\r\n+\r\n+} // namespace octave\r\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/octave-json-link.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.h\tFri Dec 30 00:39:40 2022 -0600\n@@ -0,0 +1,267 @@\n+/*\r\n+\r\n+Copyright (C) 2015-2016 Shane Carr\r\n+\r\n+This file is part of Octave.\r\n+\r\n+Octave is free software; you can redistribute it and/or modify it\r\n+under the terms of the GNU General Public License as published by the\r\n+Free Software Foundation; either version 3 of the License, or (at your\r\n+option) any later version.\r\n+\r\n+Octave is distributed in the hope that it will be useful, but WITHOUT\r\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\r\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\r\n+for more details.\r\n+\r\n+You should have received a copy of the GNU General Public License\r\n+along with Octave; see the file COPYING.  If not, see\r\n+<http://www.gnu.org/licenses/>.\r\n+\r\n+*/\r\n+\r\n+#ifndef octave_json_link_h\r\n+#define octave_json_link_h\r\n+\r\n+#include <list>\r\n+#include <string>\r\n+\r\n+#include \"event-manager.h\"\r\n+#include \"json-util.h\"\r\n+#include \"oct-mutex.h\"\r\n+\r\n+class string_vector;\r\n+\r\n+namespace octave {\r\n+\r\n+// Circular reference\r\n+class json_main;\r\n+\r\n+// Thread-safe queue\r\n+template<typename T> class json_queue {\r\n+public:\r\n+  json_queue();\r\n+  ~json_queue();\r\n+\r\n+  void enqueue(const T& value);\r\n+  T dequeue();\r\n+  bool dequeue_to(T* destination);\r\n+\r\n+private:\r\n+  std::queue<T> _queue;\r\n+  mutex _mutex;\r\n+};\r\n+\r\n+class octave_json_link : public interpreter_events\r\n+{\r\n+\r\n+public:\r\n+\r\n+  octave_json_link (json_main* __json_main);\r\n+\r\n+  ~octave_json_link (void);\r\n+\r\n+  // TODO\r\n+  // void start_gui (bool gui_app = false) override;\r\n+  // void close_gui (void) override;\r\n+\r\n+  bool have_dialogs (void) const override;\r\n+\r\n+  std::string\r\n+  question_dialog (const std::string& msg, const std::string& title,\r\n+                      const std::string& btn1, const std::string& btn2,\r\n+                      const std::string& btn3, const std::string& btndef) override;\r\n+\r\n+  std::pair<std::list<int>, int>\r\n+  list_dialog (const std::list<std::string>& list,\r\n+                  const std::string& mode,\r\n+                  int width, int height,\r\n+                  const std::list<int>& initial_value,\r\n+                  const std::string& name,\r\n+                  const std::list<std::string>& prompt,\r\n+                  const std::string& ok_string,\r\n+                  const std::string& cancel_string) override;\r\n+\r\n+  std::list<std::string>\r\n+  input_dialog (const std::list<std::string>& prompt,\r\n+                   const std::string& title,\r\n+                   const std::list<float>& nr,\r\n+                   const std::list<float>& nc,\r\n+                   const std::list<std::string>& defaults) override;\r\n+\r\n+  std::list<std::string>\r\n+  file_dialog (const filter_list& filter, const std::string& title,\r\n+                  const std::string &filename, const std::string &pathname,\r\n+                  const std::string& multimode) override;\r\n+\r\n+  void update_path_dialog (void) override;\r\n+\r\n+  void show_preferences (void) override;\r\n+\r\n+  // TODO:\r\n+  // void apply_preferences (void) override;\r\n+\r\n+  // TODO:\r\n+  // void show_terminal_window (void) override;\r\n+\r\n+  bool show_documentation (const std::string& file) override;\r\n+\r\n+  // TODO:\r\n+  // void show_file_browser (void) override;\r\n+\r\n+  // TODO:\r\n+  // void show_command_history (void) override;\r\n+\r\n+  // TODO:\r\n+  // void show_workspace (void) override;\r\n+\r\n+  // TODO:\r\n+  // void show_community_news (int serial) override;\r\n+  // void show_release_notes (void) override;\r\n+\r\n+  bool edit_file (const std::string& file) override;\r\n+\r\n+  void edit_variable (const std::string& name, const octave_value& val) override;\r\n+\r\n+  std::string request_input (const std::string& prompt) override;\r\n+\r\n+  std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\r\n+\r\n+  void show_static_plot (const std::string& term, const std::string& content) override;\r\n+\r\n+  bool confirm_shutdown (void) override;\r\n+\r\n+  bool prompt_new_edit_file (const std::string& file) override;\r\n+\r\n+  int\r\n+  debug_cd_or_addpath_error (const std::string& file,\r\n+                                const std::string& dir,\r\n+                                bool addpath_option) override;\r\n+\r\n+  uint8NDArray get_named_icon (const std::string& icon_name) override;\r\n+\r\n+  std::string gui_preference (const std::string& key, const std::string& value) override;\r\n+\r\n+  bool copy_image_to_clipboard (const std::string& file) override;\r\n+\r\n+  void focus_window (const std::string win_name) override;\r\n+\r\n+  void execute_command_in_terminal (const std::string& command) override;\r\n+\r\n+  void register_documentation (const std::string& file) override;\r\n+\r\n+  void unregister_documentation (const std::string& file) override;\r\n+\r\n+  // TODO:\r\n+  // void interpreter_output (const std::string& msg) override;\r\n+\r\n+  void display_exception (const execution_exception& ee, bool beep) override;\r\n+\r\n+  void gui_status_update (const std::string& feature, const std::string& status) override;\r\n+\r\n+  void update_gui_lexer (void) override;\r\n+\r\n+  void directory_changed (const std::string& dir) override;\r\n+\r\n+  void file_remove (const std::string& old_name, const std::string& new_name) override;\r\n+\r\n+  void file_renamed (bool) override;\r\n+\r\n+  void set_workspace (bool top_level, bool debug,\r\n+                         const symbol_info_list& ws,\r\n+                         bool update_variable_editor) override;\r\n+\r\n+  void clear_workspace (void) override;\r\n+\r\n+  // TODO:\r\n+  // void update_prompt (const std::string& prompt) override;\r\n+\r\n+  void set_history (const string_vector& hist) override;\r\n+\r\n+  void append_history (const std::string& hist_entry) override;\r\n+\r\n+  void clear_history (void) override;\r\n+\r\n+  void clear_screen (void) override;\r\n+\r\n+  void pre_input_event (void) override;\r\n+\r\n+  void post_input_event (void) override;\r\n+\r\n+  void enter_debugger_event (const std::string& fcn_name, const std::string& fcn_file_name, int line) override;\r\n+\r\n+  void execute_in_debugger_event (const std::string& file, int line) override;\r\n+\r\n+  void exit_debugger_event (void) override;\r\n+\r\n+  void update_breakpoint (bool insert,\r\n+                             const std::string& file, int line,\r\n+                             const std::string& cond) override;\r\n+\r\n+  // TODO:\r\n+  // void interpreter_interrupted (void) override;\r\n+\r\n+  // Custom methods\r\n+  void receive_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+private:\r\n+  json_main* _json_main;\r\n+  void _publish_message (const std::string& name, JSON_OBJECT_T jobj);\r\n+\r\n+  // Queues\r\n+  json_queue<std::string> request_input_queue;\r\n+  json_queue<std::pair<bool, std::string> > request_url_queue;\r\n+  json_queue<bool> confirm_shutdown_queue;\r\n+  json_queue<bool> prompt_new_edit_file_queue;\r\n+  json_queue<int> message_dialog_queue;\r\n+  json_queue<std::string> question_dialog_queue;\r\n+  json_queue<std::pair<std::list<int>, int> > list_dialog_queue;\r\n+  json_queue<std::list<std::string> > input_dialog_queue;\r\n+  json_queue<std::list<std::string> > file_dialog_queue;\r\n+  json_queue<int> debug_cd_or_addpath_error_queue;\r\n+};\r\n+\r\n+// Template classes require definitions in the header file...\r\n+\r\n+template<typename T>\r\n+json_queue<T>::json_queue() { }\r\n+\r\n+template<typename T>\r\n+json_queue<T>::~json_queue() { }\r\n+\r\n+template<typename T>\r\n+void json_queue<T>::enqueue(const T& value) {\r\n+  _mutex.lock();\r\n+  _queue.push(value);\r\n+  _mutex.cond_signal();\r\n+  _mutex.unlock();\r\n+}\r\n+\r\n+template<typename T>\r\n+T json_queue<T>::dequeue() {\r\n+  _mutex.lock();\r\n+  while (_queue.empty()) {\r\n+    _mutex.cond_wait();\r\n+  }\r\n+  T value = _queue.front();\r\n+  _queue.pop();\r\n+  _mutex.unlock();\r\n+  return value;\r\n+}\r\n+\r\n+template<typename T>\r\n+bool json_queue<T>::dequeue_to(T* destination) {\r\n+  _mutex.lock();\r\n+  bool retval = false;\r\n+  if (!_queue.empty()) {\r\n+    retval = true;\r\n+    *destination = _queue.front();\r\n+    _queue.pop();\r\n+  }\r\n+  _mutex.unlock();\r\n+  return retval;\r\n+}\r\n+\r\n+} // namespace octave\r\n+  \r\n+#endif\r\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/syscalls.cc\n--- a/libinterp/corefcn/syscalls.cc\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/corefcn/syscalls.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -150,9 +150,8 @@\n @deftypefn {} {[@var{err}, @var{msg}] =} exec (@var{file}, @var{args})\n Replace current process with a new process.\n \n-Calling @code{exec} without first calling @code{fork} will terminate your\n-current Octave process and replace it with the program named by @var{file}.\n-For example,\n+Calling @code{exec} will terminate your current Octave process and replace\n+it with the program named by @var{file}. For example,\n \n @example\n exec (\"ls\", \"-l\")\n@@ -475,42 +474,6 @@\n   return retval;\n }\n \n-DEFMETHODX (\"fork\", Ffork, interp, args, ,\n-            doc: /* -*- texinfo -*-\n-@deftypefn {} {[@var{pid}, @var{msg}] =} fork ()\n-Create a copy of the current process.\n-\n-Fork can return one of the following values:\n-\n-@table @asis\n-@item > 0\n-You are in the parent process.  The value returned from @code{fork} is the\n-process id of the child process.  You should probably arrange to wait for\n-any child processes to exit.\n-\n-@item 0\n-You are in the child process.  You can call @code{exec} to start another\n-process.  If that fails, you should probably call @code{exit}.\n-\n-@item < 0\n-The call to @code{fork} failed for some reason.  You must take evasive\n-action.  A system dependent error message will be waiting in @var{msg}.\n-@end table\n-@end deftypefn */)\n-{\n-  if (args.length () != 0)\n-    print_usage ();\n-\n-  if (interp.at_top_level ())\n-    error (\"fork: cannot be called from command line\");\n-\n-  std::string msg;\n-\n-  pid_t pid = sys::fork (msg);\n-\n-  return ovl (pid, msg);\n-}\n-\n DEFUNX (\"getpgrp\", Fgetpgrp, args, ,\n         doc: /* -*- texinfo -*-\n @deftypefn {} {pgid =} getpgrp ()\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/sysdep.cc\n--- a/libinterp/corefcn/sysdep.cc\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/corefcn/sysdep.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -75,6 +75,7 @@\n #include \"defun.h\"\n #include \"error.h\"\n #include \"errwarn.h\"\n+#include \"event-manager.h\"\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"octave.h\"\n@@ -719,7 +720,7 @@\n \n // Read one character from the terminal.\n \n-int kbhit (bool wait)\n+int kbhit (const std::string& prompt, bool wait)\n {\n #if defined (HAVE__KBHIT) && defined (HAVE__GETCH)\n   // This essentially means we are on a Windows system.\n@@ -746,13 +747,24 @@\n \n   set_interrupt_handler (saved_interrupt_handler, false);\n \n-  int c = std::cin.get ();\n+  int c;\n+  event_manager& evmgr = __get_event_manager__ (\"kbhit\");\n+  if (evmgr.request_input_enabled ()) {\n+    std::string line = evmgr.request_input (prompt);\n+    if (line.length() >= 1) {\n+      c = line.at(0);\n+    } else {\n+      c = '\\n';\n+    }\n+  } else {\n+    c = std::cin.get ();\n \n-  if (std::cin.fail () || std::cin.eof ())\n-    {\n-      std::cin.clear ();\n-      clearerr (stdin);\n-    }\n+    if (std::cin.fail () || std::cin.eof ())\n+      {\n+        std::cin.clear ();\n+        clearerr (stdin);\n+      }\n+  }\n \n   // Restore it, enabling system call restarts (if possible).\n   set_interrupt_handler (saved_interrupt_handler, true);\n@@ -810,6 +822,9 @@\n {\n   bool skip_redisplay = true;\n \n+  octave::event_manager& evmgr = octave::__get_event_manager__ (\"clc\");\n+  evmgr.clear_screen();\n+\n   command_editor::clear_screen (skip_redisplay);\n \n   return ovl ();\n@@ -1244,7 +1259,7 @@\n \n   Fdrawnow (interp);\n \n-  int c = kbhit (args.length () == 0);\n+  int c = kbhit (\"kbhit>\", args.length () == 0);\n \n   if (c == -1)\n     c = 0;\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/sysdep.h\n--- a/libinterp/corefcn/sysdep.h\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/corefcn/sysdep.h\tFri Dec 30 00:39:40 2022 -0600\n@@ -49,7 +49,7 @@\n \n extern OCTINTERP_API int pclose (FILE *f);\n \n-extern OCTINTERP_API int kbhit (bool wait = true);\n+extern OCTINTERP_API int kbhit (const std::string& prompt, bool wait);\n \n extern OCTINTERP_API std::string get_P_tmpdir (void);\n \ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/corefcn/utils.cc\n--- a/libinterp/corefcn/utils.cc\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/corefcn/utils.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -1559,7 +1559,7 @@\n           if (do_graphics_events)\n             gh_mgr.process_events ();\n \n-          c = kbhit (false);\n+          c = kbhit (\"press enter to continue\", false);\n         }\n     }\n   else\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/dldfcn/__init_gnuplot__.cc\n--- a/libinterp/dldfcn/__init_gnuplot__.cc\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/dldfcn/__init_gnuplot__.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -65,25 +65,6 @@\n   gnuplot_graphics_toolkit (octave::interpreter& interp)\n     : octave::base_graphics_toolkit (\"gnuplot\"), m_interpreter (interp)\n   {\n-    static bool warned = false;\n-\n-    if (! warned)\n-      {\n-        warning_with_id\n-        (\"Octave:gnuplot-graphics\",\n-         \"using the gnuplot graphics toolkit is discouraged\\n\\\n-\\n\\\n-The gnuplot graphics toolkit is not actively maintained and has a number\\n\\\n-of limitations that are unlikely to be fixed.  Communication with gnuplot\\n\\\n-uses a one-directional pipe and limited information is passed back to the\\n\\\n-Octave interpreter so most changes made interactively in the plot window\\n\\\n-will not be reflected in the graphics properties managed by Octave.  For\\n\\\n-example, if the plot window is closed with a mouse click, Octave will not\\n\\\n-be notified and will not update its internal list of open figure windows.\\n\\\n-The qt toolkit is recommended instead.\\n\");\n-\n-        warned = true;\n-      }\n   }\n \n   ~gnuplot_graphics_toolkit (void) = default;\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/octave.cc\n--- a/libinterp/octave.cc\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/octave.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -186,6 +186,16 @@\n         case LINE_EDITING_OPTION:\n           m_forced_line_editing = m_line_editing = true;\n           break;\n+ \n+        case JSON_SOCK_OPTION:\n+          if (octave_optarg_wrapper ())\n+            m_json_sock_path = octave_optarg_wrapper ();\n+          break;\n+\n+        case JSON_MAX_LEN_OPTION:\n+          if (octave_optarg_wrapper ())\n+            m_json_max_message_length = strtol(octave_optarg_wrapper (), NULL, 10);\n+          break;\n \n         case NO_GUI_OPTION:\n           m_gui = false;\n@@ -420,6 +430,14 @@\n   sysdep_init ();\n }\n \n+bool application::link_enabled (void) const\n+{\n+  if (m_interpreter) {\n+    event_manager& evmgr = m_interpreter->get_event_manager ();\n+    return evmgr.enabled();\n+  } else return false;\n+}\n+\n int cli_application::execute (void)\n {\n   interpreter& interp = create_interpreter ();\n@@ -442,7 +460,7 @@\n   // FIXME: This isn't quite right, it just says that we intended to\n   // start the GUI, not that it is actually running.\n \n-  return ovl (application::is_gui_running ());\n+  return ovl (application::is_link_enabled ());\n }\n \n /*\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/octave.h\n--- a/libinterp/octave.h\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/octave.h\tFri Dec 30 00:39:40 2022 -0600\n@@ -85,6 +85,8 @@\n   std::string info_file (void) const { return m_info_file; }\n   std::string info_program (void) const { return m_info_program; }\n   std::string texi_macros_file (void) const {return m_texi_macros_file; }\n+  std::string json_sock_path (void) const { return m_json_sock_path; }\n+  int json_max_message_length (void) const { return m_json_max_message_length; }\n   string_vector all_args (void) const { return m_all_args; }\n   string_vector remaining_args (void) const { return m_remaining_args; }\n \n@@ -117,6 +119,8 @@\n   void info_file (const std::string& arg) { m_info_file = arg; }\n   void info_program (const std::string& arg) { m_info_program = arg; }\n   void texi_macros_file (const std::string& arg) { m_texi_macros_file = arg; }\n+  void json_sock_path (const std::string& arg) { m_json_sock_path = arg; }\n+  void json_max_message_length (int arg) { m_json_max_message_length = arg; }\n   void all_args (const string_vector& arg) { m_all_args = arg; }\n   void remaining_args (const string_vector& arg) { m_remaining_args = arg; }\n \n@@ -225,6 +229,14 @@\n   // (--texi-macros-file)\n   std::string m_texi_macros_file;\n \n+  // The value for \"JSON_SOCK\" specified on the command line.\n+  // (--json-sock)\n+  std::string m_json_sock_path;\n+\n+  // The maximum message length; valid only if \"JSON_SOCK\" is specified.\n+  // (--json-max-len)\n+  int m_json_max_message_length = 0;\n+\n   // All arguments passed to the argc, argv constructor.\n   string_vector m_all_args;\n \n@@ -238,6 +250,7 @@\n // both) of them...\n \n class interpreter;\n+class event_manager;\n \n // Base class for an Octave application.\n \n@@ -287,6 +300,8 @@\n   virtual bool gui_running (void) const { return false; }\n   virtual void gui_running (bool) { }\n \n+  bool link_enabled (void) const;\n+\n   void program_invocation_name (const std::string& nm)\n   { m_program_invocation_name = nm; }\n \n@@ -320,6 +335,11 @@\n     return s_instance ? s_instance->gui_running () : false;\n   }\n \n+  static bool is_link_enabled (void)\n+  {\n+    return s_instance ? s_instance->link_enabled () : false;\n+  }\n+\n   // Convenience functions.\n \n   static bool forced_interactive (void);\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/options.h\n--- a/libinterp/options.h\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/options.h\tFri Dec 30 00:39:40 2022 -0600\n@@ -46,17 +46,19 @@\n #define IMAGE_PATH_OPTION 7\n #define INFO_FILE_OPTION 8\n #define INFO_PROG_OPTION 9\n-#define LINE_EDITING_OPTION 10\n-#define NO_GUI_OPTION 11\n-#define NO_GUI_LIBS_OPTION 12\n-#define NO_INIT_FILE_OPTION 13\n-#define NO_INIT_PATH_OPTION 14\n-#define NO_LINE_EDITING_OPTION 15\n-#define NO_SITE_FILE_OPTION 16\n-#define PERSIST_OPTION 17\n-#define SERVER_OPTION 18\n-#define TEXI_MACROS_FILE_OPTION 19\n-#define TRADITIONAL_OPTION 20\n+#define JSON_SOCK_OPTION 10\n+#define JSON_MAX_LEN_OPTION 11\n+#define LINE_EDITING_OPTION 12\n+#define NO_GUI_OPTION 13\n+#define NO_GUI_LIBS_OPTION 14\n+#define NO_INIT_FILE_OPTION 15\n+#define NO_INIT_PATH_OPTION 16\n+#define NO_LINE_EDITING_OPTION 17\n+#define NO_SITE_FILE_OPTION 18\n+#define PERSIST_OPTION 19\n+#define SERVER_OPTION 20\n+#define TEXI_MACROS_FILE_OPTION 21\n+#define TRADITIONAL_OPTION 22\n struct octave_getopt_options long_opts[] =\n {\n   { \"braindead\",                octave_no_arg,       nullptr, TRADITIONAL_OPTION },\n@@ -74,6 +76,8 @@\n   { \"info-file\",                octave_required_arg, nullptr, INFO_FILE_OPTION },\n   { \"info-program\",             octave_required_arg, nullptr, INFO_PROG_OPTION },\n   { \"interactive\",              octave_no_arg,       nullptr, 'i' },\n+  { \"json-sock\",                octave_required_arg, nullptr, JSON_SOCK_OPTION },\n+  { \"json-max-len\",             octave_required_arg, nullptr, JSON_MAX_LEN_OPTION },\n   { \"line-editing\",             octave_no_arg,       nullptr, LINE_EDITING_OPTION },\n   { \"no-gui\",                   octave_no_arg,       nullptr, NO_GUI_OPTION },\n   { \"no-gui-libs\",              octave_no_arg,       nullptr, NO_GUI_LIBS_OPTION },\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 libinterp/usage.h\n--- a/libinterp/usage.h\tThu Dec 29 16:10:07 2022 +0100\n+++ b/libinterp/usage.h\tFri Dec 30 00:39:40 2022 -0600\n@@ -37,11 +37,11 @@\n   \"octave [-HVWdfhiqvx] [--debug] [--doc-cache-file file] [--echo-commands]\\n\\\n        [--eval CODE] [--exec-path path] [--experimental-terminal-widget]\\n\\\n        [--gui] [--help] [--image-path path] [--info-file file]\\n\\\n-       [--info-program prog] [--interactive] [--line-editing] [--no-gui]\\n\\\n-       [--no-history] [--no-init-file] [--no-init-path] [--no-line-editing]\\n\\\n-       [--no-site-file] [--no-window-system] [--norc] [-p path]\\n\\\n-       [--path path] [--persist] [--server] [--silent] [--traditional]\\n\\\n-       [--verbose] [--version] [file]\";\n+       [--info-program prog] [--interactive] [--json-sock] [--json-max-len] \\n\\\n+       [--line-editing] [--no-gui] [--no-history] [--no-init-file] \\n\\\n+       [--no-init-path] [--no-line-editing] [--no-site-file] \\n\\\n+       [--no-window-system] [--norc] [-p path] [--path path] [--persist] \\n\\\n+       [--server] [--silent] [--traditional] [--verbose] [--version] [file]\";\n \n // Usage message with extra help.\n \n@@ -69,6 +69,8 @@\n   --info-file FILE        Use top-level info file FILE.\\n\\\n   --info-program PROGRAM  Use PROGRAM for reading info files.\\n\\\n   --interactive, -i       Force interactive behavior.\\n\\\n+  --json-sock PATH        Listen to and publish events on this UNIX socket.\\n\\\n+  --json-max-len LEN      Suppress JSON messages greater than LEN bytes.\\n\\\n   --line-editing          Force readline use for command-line editing.\\n\\\n   --no-gui                Disable the graphical user interface.\\n\\\n   --no-history, -H        Don't save commands to the history list\\n\\\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 liboctave/util/oct-mutex.cc\n--- a/liboctave/util/oct-mutex.cc\tThu Dec 29 16:10:07 2022 +0100\n+++ b/liboctave/util/oct-mutex.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -58,6 +58,18 @@\n   return false;\n }\n \n+  void\n+  base_mutex::cond_wait (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n+  void\n+  base_mutex::cond_signal (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n #if defined (OCTAVE_USE_WINDOWS_API)\n \n class\n@@ -68,11 +80,13 @@\n     : base_mutex ()\n   {\n     InitializeCriticalSection (&cs);\n+    InitializeConditionVariable (&cv);\n   }\n \n   ~w32_mutex (void)\n   {\n     DeleteCriticalSection (&cs);\n+    // no need to delete cv: http://stackoverflow.com/a/28981408/1407170\n   }\n \n   void lock (void)\n@@ -90,8 +104,19 @@\n     return (TryEnterCriticalSection (&cs) != 0);\n   }\n \n+  void cond_wait (void)\n+  {\n+    SleepConditionVariableCS (&cv, &cs, INFINITE);\n+  }\n+\n+  void cond_signal (void)\n+  {\n+    WakeConditionVariable (&cv);\n+  }\n+\n private:\n   CRITICAL_SECTION cs;\n+  CONDITION_VARIABLE cv;\n };\n \n static DWORD thread_id = 0;\n@@ -123,11 +148,18 @@\n     pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);\n     pthread_mutex_init (&m_pm, &attr);\n     pthread_mutexattr_destroy (&attr);\n+\n+    pthread_condattr_t condattr;\n+\n+    pthread_condattr_init (&condattr);\n+    pthread_cond_init (&condv, &condattr);\n+    pthread_condattr_destroy (&condattr);\n   }\n \n   ~pthread_mutex (void)\n   {\n     pthread_mutex_destroy (&m_pm);\n+    pthread_cond_destroy (&condv);\n   }\n \n   void lock (void)\n@@ -145,8 +177,19 @@\n     return (pthread_mutex_trylock (&m_pm) == 0);\n   }\n \n+  void cond_wait (void)\n+  {\n+    pthread_cond_wait (&condv, &m_pm);\n+  }\n+\n+  void cond_signal (void)\n+  {\n+    pthread_cond_signal (&condv);\n+  }\n+\n private:\n   pthread_mutex_t m_pm;\n+  pthread_cond_t condv;\n };\n \n static pthread_t thread_id = 0;\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 liboctave/util/oct-mutex.h\n--- a/liboctave/util/oct-mutex.h\tThu Dec 29 16:10:07 2022 +0100\n+++ b/liboctave/util/oct-mutex.h\tFri Dec 30 00:39:40 2022 -0600\n@@ -50,6 +50,10 @@\n   virtual void unlock (void);\n \n   virtual bool try_lock (void);\n+\n+  virtual void cond_wait (void);\n+\n+  virtual void cond_signal (void);\n };\n \n class\n@@ -80,6 +84,16 @@\n     return m_rep->try_lock ();\n   }\n \n+  void cond_wait (void)\n+  {\n+    m_rep->cond_wait ();\n+  }\n+\n+  void cond_signal (void)\n+  {\n+    m_rep->cond_signal ();\n+  }\n+\n protected:\n   std::shared_ptr<base_mutex> m_rep;\n };\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tThu Dec 29 16:10:07 2022 +0100\n+++ b/liboctave/util/url-transfer.cc\tFri Dec 30 00:39:40 2022 -0600\n@@ -31,6 +31,7 @@\n #include <iomanip>\n #include <iostream>\n \n+#include \"base64-wrappers.h\"\n #include \"dir-ops.h\"\n #include \"file-ops.h\"\n #include \"file-stat.h\"\n@@ -48,6 +49,10 @@\n \n OCTAVE_BEGIN_NAMESPACE(octave)\n \n+// Forward declaration for event_manager\n+extern bool __event_manager_request_input_enabled__();\n+extern std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n base_url_transfer::base_url_transfer (void)\n   : m_host_or_url (), m_valid (false), m_ftp (false),\n     m_ascii_mode (false), m_ok (true), m_errmsg (),\n@@ -228,6 +233,86 @@\n   return file_list;\n }\n \n+\n+class link_transfer : public base_url_transfer\n+{\n+public:\n+\n+  link_transfer (void)\n+      : base_url_transfer () {\n+    m_valid = true;\n+  }\n+\n+  link_transfer (const std::string& host, const std::string& user_arg,\n+                 const std::string& passwd, std::ostream& os)\n+      : base_url_transfer (host, user_arg, passwd, os) {\n+    m_valid = true;\n+    // url = \"ftp://\" + host;\n+  }\n+\n+  link_transfer (const std::string& url_str, std::ostream& os)\n+      : base_url_transfer (url_str, os) {\n+    m_valid = true;\n+  }\n+\n+  ~link_transfer (void) {}\n+\n+  void http_get (const Array<std::string>& param) {\n+    perform_action (param, \"get\");\n+  }\n+\n+  void http_post (const Array<std::string>& param) {\n+    perform_action (param, \"post\");\n+  }\n+\n+  void http_action (const Array<std::string>& param, const std::string& action) {\n+    perform_action (param, action);\n+  }\n+\n+private:\n+  void perform_action(const Array<std::string>& param, const std::string& action) {\n+    std::string url = m_host_or_url;\n+\n+    // Convert from Array to std::list\n+    std::list<std::string> paramList;\n+    for (int i = 0; i < param.numel(); i ++) {\n+      std::string value = param(i);\n+      paramList.push_back(value);\n+    }\n+\n+    if (__event_manager_request_input_enabled__()) {\n+      bool success;\n+      std::string result = __event_manager_request_url__(url, paramList, action, success);\n+      if (success) {\n+        process_success(result);\n+      } else {\n+        m_ok = false;\n+        m_errmsg = result;\n+      }\n+    } else {\n+      m_ok = false;\n+      m_errmsg = \"octave_link not connected for link_transfer\";\n+    }\n+  }\n+\n+  void process_success(const std::string& result) {\n+    // If success, the result is returned as a base64 string, and we need to decode it.\n+    // Use the base64 implementation from gnulib, which is already an Octave dependency.\n+    const char *inc = &(result[0]);\n+    char *out;\n+    std::ptrdiff_t outlen;\n+    bool b64_ok = octave_base64_decode_alloc_wrapper(inc, result.length(), &out, &outlen);\n+    if (!b64_ok) {\n+      m_ok = false;\n+      m_errmsg = \"failed decoding base64 from octave_link\";\n+    } else {\n+      m_curr_ostream->write(out, outlen);\n+      ::free(out);\n+    }\n+  }\n+};\n+\n+\n #if defined (HAVE_CURL)\n \n static int\n@@ -918,17 +1003,30 @@\n #  define REP_CLASS base_url_transfer\n #endif\n \n-url_transfer::url_transfer (void) : m_rep (new REP_CLASS ())\n-{ }\n+url_transfer::url_transfer (void) {\n+  if (__event_manager_request_input_enabled__()) {\n+    m_rep.reset(new link_transfer());\n+  } else {\n+    m_rep.reset(new REP_CLASS());\n+  }\n+}\n \n url_transfer::url_transfer (const std::string& host, const std::string& user,\n-                            const std::string& passwd, std::ostream& os)\n-  : m_rep (new REP_CLASS (host, user, passwd, os))\n-{ }\n+                            const std::string& passwd, std::ostream& os) {\n+  if (__event_manager_request_input_enabled__()) {\n+    m_rep.reset(new link_transfer(host, user, passwd, os));\n+  } else {\n+    m_rep.reset(new REP_CLASS(host, user, passwd, os));\n+  }\n+}\n \n-url_transfer::url_transfer (const std::string& url, std::ostream& os)\n-  : m_rep (new REP_CLASS (url, os))\n-{ }\n+url_transfer::url_transfer (const std::string& url, std::ostream& os) {\n+  if (__event_manager_request_input_enabled__()) {\n+    m_rep.reset(new link_transfer(url, os));\n+  } else {\n+    m_rep.reset(new REP_CLASS(url, os));\n+  }\n+}\n \n #undef REP_CLASS\n \ndiff -r 601e7a142a15 -r 4c3d80dd9e65 scripts/help/__unimplemented__.m\n--- a/scripts/help/__unimplemented__.m\tThu Dec 29 16:10:07 2022 +0100\n+++ b/scripts/help/__unimplemented__.m\tFri Dec 30 00:39:40 2022 -0600\n@@ -45,7 +45,30 @@\n \n   is_matlab_function = true;\n \n+  ## First look at the package metadata\n+  # To generate package_metadata.mat, run: packages={}; for p=pkg('list'); packages={packages{:} pkg('describe', '-verbose', p{1}.name){:}}; endfor; save('/usr/local/share/octave/site/m/package_metadata.mat', 'packages');\n+  found_in_package_metadata = false;\n+  try\n+    vars = load(\"/usr/local/share/octave/site/m/package_metadata.mat\");\n+    for lvl1 = vars.packages\n+      for lvl2 = lvl1{1}.provides\n+        for lvl3 = lvl2{1}.functions\n+          if strcmp(fcn, lvl3{1})\n+            txt = check_package(fcn, lvl1{1}.name);\n+            found_in_package_metadata = true;\n+            break;\n+          endif\n+        endfor\n+        if found_in_package_metadata, break; endif\n+      endfor\n+      if found_in_package_metadata, break; endif\n+    endfor\n+  catch err\n+    warning(err)\n+  end_try_catch\n+\n   ## Some smarter cases, add more as needed.\n+  if !found_in_package_metadata\n   switch (fcn)\n     case {\"avifile\", \"aviinfo\", \"aviread\"}\n       txt = [\"Basic video file support is provided in the video package.  \", ...\n@@ -524,6 +547,7 @@\n         txt = \"\";\n       endif\n   endswitch\n+  endif\n \n   if (is_matlab_function)\n     txt = [txt, \"\\n\\n@noindent\\nPlease read \", ...\n@@ -566,13 +590,13 @@\n       endfor\n       txt = sprintf (\"%s but has not yet been implemented.\", txt);\n     case \"not loaded\",\n-      txt = sprintf ([\"%s which you have installed but not loaded.  To \", ...\n-                      \"load the package, run 'pkg load %s' from the \", ...\n-                      \"Octave prompt.\"], txt, name);\n+      txt = sprintf ([\"%s, which you have installed but not loaded.\\n\\n\", ...\n+                      \"Run `pkg load %s' to use `%s'.\"], ...\n+                     txt, name, fcn);\n     otherwise\n       ## this includes \"not installed\" and anything else if pkg changes\n       ## the output of describe\n-      txt = sprintf (\"%s which seems to not be installed in your system.\", txt);\n+      txt = sprintf (\"%s, which seems to not be installed in your system.\", txt);\n   endswitch\n \n endfunction\ndiff -r 601e7a142a15 -r 4c3d80dd9e65 scripts/plot/util/__gnuplot_drawnow__.m\n--- a/scripts/plot/util/__gnuplot_drawnow__.m\tThu Dec 29 16:10:07 2022 +0100\n+++ b/scripts/plot/util/__gnuplot_drawnow__.m\tFri Dec 30 00:39:40 2022 -0600\n@@ -32,9 +32,84 @@\n \n   if (nargin < 1 || nargin == 2)\n     print_usage ();\n-  endif\n+\n+  elseif (nargin >= 3 && nargin <= 4)\n+    ## Write the plot to the given file (e.g., via the \"print\" command)\n+    if (nargin == 5)\n+      __gnuplot_draw_to_file__ (h, term, file, debug_file);\n+    else\n+      __gnuplot_draw_to_file__ (h, term, file);\n+    endif\n+\n+  else  # nargin == 1\n+    ##  Plot to terminal and/or static (e.g., via the \"plot\" command)\n+    plot_stream = get (h, \"__plot_stream__\");\n+    if (isempty (plot_stream))\n+      plot_stream = __gnuplot_open_stream__ (2, h);\n+      new_stream = true;\n+    else\n+      new_stream = false;\n+    endif\n+    term = gnuplot_default_term (plot_stream);\n+\n+    ## There are a few options for how we can proceed.\n+    ## In most cases, we will tell GNUPLOT to put the plot in its terminal.\n+    ## If we have no display, we want to use the \"dumb\" terminal.\n+    ## Octave Link may request that we send the plot as an event.\n+    ## The latter two cases require plotting to a temp file.\n+\n+    should_plot_to_terminal = (\n+      !strcmp (term, \"dumb\") && (\n+        __event_manager_plot_destination__ () == 0 ||\n+        __event_manager_plot_destination__ () == 2\n+      )\n+    );\n+\n+    if (should_plot_to_terminal)\n+      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n+      __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n+      fflush (plot_stream(1));\n+    endif\n \n-  if (nargin >= 3 && nargin <= 4)\n+    should_plot_to_temp_file = (\n+      strcmp (term, \"dumb\") ||\n+      __event_manager_plot_destination__ () == 1 ||\n+      __event_manager_plot_destination__ () == 2\n+    );\n+\n+    if (should_plot_to_temp_file)\n+      tmp_file = tempname ();\n+      __gnuplot_draw_to_file__ (h, term, tmp_file);\n+      fflush (plot_stream(1));\n+\n+      ## Read the temp file into memory and then delete it\n+      fid = fopen (tmp_file, 'r');\n+      while (fid < 0)\n+        fprintf (stderr, \"🛈 Waiting for plot to finish… ⏳\\n\");\n+        pause (0.5);\n+        fid = fopen (tmp_file, 'r');\n+      endwhile\n+      [a, count] = fscanf (fid, '%c', Inf);\n+      fclose (fid);\n+      unlink (tmp_file);\n+\n+      ## What to do with the plot data?\n+      if (count > 0)\n+        if (a(1) == 12)\n+          a = a(2:end);  # avoid ^L at the beginning\n+        endif\n+        if strcmp (term, \"dumb\")\n+          puts (a);\n+        else\n+          __event_manager_show_static_plot__ (term, a);\n+        endif\n+      endif\n+    endif\n+\n+  endif\n+endfunction\n+\n+function __gnuplot_draw_to_file__ (h, term, file, debug_file)\n     ## Produce various output formats, or redirect gnuplot stream to a\n     ## debug file.\n     plot_stream = [];\n@@ -70,44 +145,6 @@\n         fclose (fid);\n       endif\n     end_unwind_protect\n-  else  # nargin == 1\n-    ##  Graphics terminal for display.\n-    plot_stream = get (h, \"__plot_stream__\");\n-    if (isempty (plot_stream))\n-      plot_stream = __gnuplot_open_stream__ (2, h);\n-      new_stream = true;\n-    else\n-      new_stream = false;\n-    endif\n-    term = gnuplot_default_term (plot_stream);\n-    if (strcmp (term, \"dumb\"))\n-      ## popen2 eats stdout of gnuplot, use temporary file instead\n-      dumb_tmp_file = tempname ();\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h,\n-                                   term, dumb_tmp_file);\n-    else\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n-    endif\n-    __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n-    fflush (plot_stream(1));\n-    if (strcmp (term, \"dumb\"))\n-      fid = -1;\n-      while (fid < 0)\n-        pause (0.1);\n-        fid = fopen (dumb_tmp_file, 'r');\n-      endwhile\n-      ## reprint the plot on screen\n-      [a, count] = fscanf (fid, '%c', Inf);\n-      fclose (fid);\n-      if (count > 0)\n-        if (a(1) == 12)\n-          a = a(2:end);  # avoid ^L at the beginning\n-        endif\n-        puts (a);\n-      endif\n-      unlink (dumb_tmp_file);\n-    endif\n-  endif\n \n endfunction\n \n"
  },
  {
    "path": "back-octave/oo-changesets/421-de16dd99ab0e.hg.txt",
    "content": "# HG changeset patch\n# User Octave Online Team <webmaster@octave-online.net>\n# Date 1672385388 21600\n#      Fri Dec 30 01:29:48 2022 -0600\n# Branch oo-7.4\n# Node ID de16dd99ab0ec3baee489d7187cfd184df64aca6\n# Parent  41ff58a98cd0078da6f5de9cfec0b9064dedbafa\nMoving URL event manager to libinterp, and other minor changes for 7.4\n\ndiff -r 41ff58a98cd0 -r de16dd99ab0e libinterp/corefcn/event-manager-url.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/event-manager-url.cc\tFri Dec 30 01:29:48 2022 -0600\n@@ -0,0 +1,127 @@\n+#if defined (HAVE_CONFIG_H)\n+#  include \"config.h\"\n+#endif\n+\n+#include <iostream>\n+\n+#include \"event-manager.h\"\n+#include \"url-transfer.h\"\n+#include \"interpreter-private.h\"\n+#include \"base64-wrappers.h\"\n+\n+OCTAVE_BEGIN_NAMESPACE(octave)\n+\n+bool __event_manager_request_input_enabled__() {\n+  event_manager& evmgr = __get_event_manager__ ();\n+  return evmgr.request_input_enabled();\n+}\n+\n+std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\n+  event_manager& evmgr = __get_event_manager__ ();\n+  return evmgr.request_url(url, param, action, success);\n+}\n+\n+class link_transfer : public base_url_transfer\n+{\n+public:\n+\n+  link_transfer (void)\n+      : base_url_transfer () {\n+    m_valid = true;\n+  }\n+\n+  link_transfer (const std::string& host, const std::string& user_arg,\n+                 const std::string& passwd, std::ostream& os)\n+      : base_url_transfer (host, user_arg, passwd, os) {\n+    m_valid = true;\n+    // url = \"ftp://\" + host;\n+  }\n+\n+  link_transfer (const std::string& url_str, std::ostream& os)\n+      : base_url_transfer (url_str, os) {\n+    m_valid = true;\n+  }\n+\n+  ~link_transfer (void) {}\n+\n+  void http_get (const Array<std::string>& param) {\n+    perform_action (param, \"get\");\n+  }\n+\n+  void http_post (const Array<std::string>& param) {\n+    perform_action (param, \"post\");\n+  }\n+\n+  void http_action (const Array<std::string>& param, const std::string& action) {\n+    perform_action (param, action);\n+  }\n+\n+private:\n+  void perform_action(const Array<std::string>& param, const std::string& action) {\n+    std::string url = m_host_or_url;\n+\n+    // Convert from Array to std::list\n+    std::list<std::string> paramList;\n+    for (int i = 0; i < param.numel(); i ++) {\n+      std::string value = param(i);\n+      paramList.push_back(value);\n+    }\n+\n+    if (__event_manager_request_input_enabled__()) {\n+      bool success;\n+      std::string result = __event_manager_request_url__(url, paramList, action, success);\n+      if (success) {\n+        process_success(result);\n+      } else {\n+        m_ok = false;\n+        m_errmsg = result;\n+      }\n+    } else {\n+      m_ok = false;\n+      m_errmsg = \"octave_link not connected for link_transfer\";\n+    }\n+  }\n+\n+  void process_success(const std::string& result) {\n+    // If success, the result is returned as a base64 string, and we need to decode it.\n+    // Use the base64 implementation from gnulib, which is already an Octave dependency.\n+    const char *inc = &(result[0]);\n+    char *out;\n+    std::ptrdiff_t outlen;\n+    bool b64_ok = octave_base64_decode_alloc_wrapper(inc, result.length(), &out, &outlen);\n+    if (!b64_ok) {\n+      m_ok = false;\n+      m_errmsg = \"failed decoding base64 from octave_link\";\n+    } else {\n+      m_curr_ostream->write(out, outlen);\n+      ::free(out);\n+    }\n+  }\n+};\n+\n+url_transfer::url_transfer (void) {\n+  if (__event_manager_request_input_enabled__()) {\n+    m_rep.reset(new link_transfer());\n+  } else {\n+    m_rep.reset(new base_url_transfer());\n+  }\n+}\n+\n+url_transfer::url_transfer (const std::string& host, const std::string& user,\n+                            const std::string& passwd, std::ostream& os) {\n+  if (__event_manager_request_input_enabled__()) {\n+    m_rep.reset(new link_transfer(host, user, passwd, os));\n+  } else {\n+    m_rep.reset(new base_url_transfer(host, user, passwd, os));\n+  }\n+}\n+\n+url_transfer::url_transfer (const std::string& url, std::ostream& os) {\n+  if (__event_manager_request_input_enabled__()) {\n+    m_rep.reset(new link_transfer(url, os));\n+  } else {\n+    m_rep.reset(new base_url_transfer(url, os));\n+  }\n+}\n+\n+OCTAVE_END_NAMESPACE(octave)\ndiff -r 41ff58a98cd0 -r de16dd99ab0e libinterp/corefcn/event-manager.cc\n--- a/libinterp/corefcn/event-manager.cc\tFri Dec 30 00:39:40 2022 -0600\n+++ b/libinterp/corefcn/event-manager.cc\tFri Dec 30 01:29:48 2022 -0600\n@@ -46,16 +46,6 @@\n \n OCTAVE_BEGIN_NAMESPACE(octave)\n \n-bool __event_manager_request_input_enabled__() {\n-  event_manager& evmgr = __get_event_manager__ (\"request_input_enabled\");\n-  return evmgr.request_input_enabled();\n-}\n-\n-std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\n-  event_manager& evmgr = __get_event_manager__ (\"request_url\");\n-  return evmgr.request_url(url, param, action, success);\n-}\n-\n static int readline_event_hook (void)\n {\n   event_manager& evmgr = __get_event_manager__ ();\n@@ -889,8 +879,6 @@\n   return ovl ();\n }\n \n-OCTAVE_END_NAMESPACE(octave)\n-\n DEFMETHOD (__event_manager_plot_destination__, interp, , ,\n            doc: /* -*- texinfo -*-\n @deftypefn {} {} __event_manager_plot_destination__ ()\n@@ -914,3 +902,5 @@\n   std::string content = args(1).string_value();\n   return ovl (interp.get_event_manager().show_static_plot(term, content));\n }\n+\n+OCTAVE_END_NAMESPACE(octave)\ndiff -r 41ff58a98cd0 -r de16dd99ab0e libinterp/corefcn/input.cc\n--- a/libinterp/corefcn/input.cc\tFri Dec 30 00:39:40 2022 -0600\n+++ b/libinterp/corefcn/input.cc\tFri Dec 30 01:29:48 2022 -0600\n@@ -1683,8 +1683,6 @@\n   return input_sys.auto_repeat_debug_command (args, nargout);\n }\n \n-OCTAVE_END_NAMESPACE(octave)\n-\n DEFUN (current_command_number, args, ,\n        doc: /* -*- texinfo -*-\n @deftypefn  {} {@var{val} =} current_command_number ()\n@@ -1713,3 +1711,5 @@\n     return ovl(n);\n   }\n }\n+\n+OCTAVE_END_NAMESPACE(octave)\ndiff -r 41ff58a98cd0 -r de16dd99ab0e libinterp/corefcn/json-util.cc\n--- a/libinterp/corefcn/json-util.cc\tFri Dec 30 00:39:40 2022 -0600\n+++ b/libinterp/corefcn/json-util.cc\tFri Dec 30 01:29:48 2022 -0600\n@@ -245,7 +245,7 @@\n \t}\r\n \r\n \tjson_tokener_free(tok);\r\n-\tdelete buf;\r\n+\tdelete[] buf;\r\n }\r\n \r\n void json_util::process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\r\ndiff -r 41ff58a98cd0 -r de16dd99ab0e libinterp/corefcn/module.mk\n--- a/libinterp/corefcn/module.mk\tFri Dec 30 00:39:40 2022 -0600\n+++ b/libinterp/corefcn/module.mk\tFri Dec 30 01:29:48 2022 -0600\n@@ -166,6 +166,7 @@\n   %reldir%/error.cc \\\n   %reldir%/errwarn.cc \\\n   %reldir%/event-manager.cc \\\n+  %reldir%/event-manager-url.cc \\\n   %reldir%/event-queue.cc \\\n   %reldir%/fcn-info.cc \\\n   %reldir%/fft.cc \\\ndiff -r 41ff58a98cd0 -r de16dd99ab0e libinterp/corefcn/sysdep.cc\n--- a/libinterp/corefcn/sysdep.cc\tFri Dec 30 00:39:40 2022 -0600\n+++ b/libinterp/corefcn/sysdep.cc\tFri Dec 30 01:29:48 2022 -0600\n@@ -748,7 +748,7 @@\n   set_interrupt_handler (saved_interrupt_handler, false);\n \n   int c;\n-  event_manager& evmgr = __get_event_manager__ (\"kbhit\");\n+  event_manager& evmgr = __get_event_manager__ ();\n   if (evmgr.request_input_enabled ()) {\n     std::string line = evmgr.request_input (prompt);\n     if (line.length() >= 1) {\n@@ -822,7 +822,7 @@\n {\n   bool skip_redisplay = true;\n \n-  octave::event_manager& evmgr = octave::__get_event_manager__ (\"clc\");\n+  octave::event_manager& evmgr = octave::__get_event_manager__ ();\n   evmgr.clear_screen();\n \n   command_editor::clear_screen (skip_redisplay);\ndiff -r 41ff58a98cd0 -r de16dd99ab0e liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tFri Dec 30 00:39:40 2022 -0600\n+++ b/liboctave/util/url-transfer.cc\tFri Dec 30 01:29:48 2022 -0600\n@@ -49,10 +49,6 @@\n \n OCTAVE_BEGIN_NAMESPACE(octave)\n \n-// Forward declaration for event_manager\n-extern bool __event_manager_request_input_enabled__();\n-extern std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n-\n base_url_transfer::base_url_transfer (void)\n   : m_host_or_url (), m_valid (false), m_ftp (false),\n     m_ascii_mode (false), m_ok (true), m_errmsg (),\n@@ -234,84 +230,6 @@\n }\n \n \n-class link_transfer : public base_url_transfer\n-{\n-public:\n-\n-  link_transfer (void)\n-      : base_url_transfer () {\n-    m_valid = true;\n-  }\n-\n-  link_transfer (const std::string& host, const std::string& user_arg,\n-                 const std::string& passwd, std::ostream& os)\n-      : base_url_transfer (host, user_arg, passwd, os) {\n-    m_valid = true;\n-    // url = \"ftp://\" + host;\n-  }\n-\n-  link_transfer (const std::string& url_str, std::ostream& os)\n-      : base_url_transfer (url_str, os) {\n-    m_valid = true;\n-  }\n-\n-  ~link_transfer (void) {}\n-\n-  void http_get (const Array<std::string>& param) {\n-    perform_action (param, \"get\");\n-  }\n-\n-  void http_post (const Array<std::string>& param) {\n-    perform_action (param, \"post\");\n-  }\n-\n-  void http_action (const Array<std::string>& param, const std::string& action) {\n-    perform_action (param, action);\n-  }\n-\n-private:\n-  void perform_action(const Array<std::string>& param, const std::string& action) {\n-    std::string url = m_host_or_url;\n-\n-    // Convert from Array to std::list\n-    std::list<std::string> paramList;\n-    for (int i = 0; i < param.numel(); i ++) {\n-      std::string value = param(i);\n-      paramList.push_back(value);\n-    }\n-\n-    if (__event_manager_request_input_enabled__()) {\n-      bool success;\n-      std::string result = __event_manager_request_url__(url, paramList, action, success);\n-      if (success) {\n-        process_success(result);\n-      } else {\n-        m_ok = false;\n-        m_errmsg = result;\n-      }\n-    } else {\n-      m_ok = false;\n-      m_errmsg = \"octave_link not connected for link_transfer\";\n-    }\n-  }\n-\n-  void process_success(const std::string& result) {\n-    // If success, the result is returned as a base64 string, and we need to decode it.\n-    // Use the base64 implementation from gnulib, which is already an Octave dependency.\n-    const char *inc = &(result[0]);\n-    char *out;\n-    std::ptrdiff_t outlen;\n-    bool b64_ok = octave_base64_decode_alloc_wrapper(inc, result.length(), &out, &outlen);\n-    if (!b64_ok) {\n-      m_ok = false;\n-      m_errmsg = \"failed decoding base64 from octave_link\";\n-    } else {\n-      m_curr_ostream->write(out, outlen);\n-      ::free(out);\n-    }\n-  }\n-};\n-\n \n #if defined (HAVE_CURL)\n \n@@ -1003,31 +921,6 @@\n #  define REP_CLASS base_url_transfer\n #endif\n \n-url_transfer::url_transfer (void) {\n-  if (__event_manager_request_input_enabled__()) {\n-    m_rep.reset(new link_transfer());\n-  } else {\n-    m_rep.reset(new REP_CLASS());\n-  }\n-}\n-\n-url_transfer::url_transfer (const std::string& host, const std::string& user,\n-                            const std::string& passwd, std::ostream& os) {\n-  if (__event_manager_request_input_enabled__()) {\n-    m_rep.reset(new link_transfer(host, user, passwd, os));\n-  } else {\n-    m_rep.reset(new REP_CLASS(host, user, passwd, os));\n-  }\n-}\n-\n-url_transfer::url_transfer (const std::string& url, std::ostream& os) {\n-  if (__event_manager_request_input_enabled__()) {\n-    m_rep.reset(new link_transfer(url, os));\n-  } else {\n-    m_rep.reset(new REP_CLASS(url, os));\n-  }\n-}\n-\n #undef REP_CLASS\n \n OCTAVE_END_NAMESPACE(octave)\n"
  },
  {
    "path": "back-octave/oo-changesets/422-de16dd99ab0e.hg.txt",
    "content": "# HG changeset patch\n# User Octave Online Team <webmaster@octave-online.net>\n# Date 1673173830 21600\n#      Sun Jan 08 04:30:30 2023 -0600\n# Branch oo-7.4\n# Node ID a602982ec42d2e26dd5f1b127afb64011033348d\n# Parent  de16dd99ab0ec3baee489d7187cfd184df64aca6\nRedirect warnings to new display-warning message\n\ndiff -r de16dd99ab0e -r a602982ec42d libinterp/corefcn/error.cc\n--- a/libinterp/corefcn/error.cc\tFri Dec 30 01:29:48 2022 -0600\n+++ b/libinterp/corefcn/error.cc\tSun Jan 08 04:30:30 2023 -0600\n@@ -533,7 +533,9 @@\n   if (! quiet_warning ())\n     {\n       octave_diary << msg_string;\n-      std::cerr << msg_string;\n+      //std::cerr << msg_string;\n+      event_manager& evmgr = m_interpreter.get_event_manager ();\n+      evmgr.display_warning (id, name, base_msg, msg_string);\n \n       if (! fmt_suppresses_backtrace && in_user_code\n           && backtrace_on_warning ()\ndiff -r de16dd99ab0e -r a602982ec42d libinterp/corefcn/event-manager.cc\n--- a/libinterp/corefcn/event-manager.cc\tFri Dec 30 01:29:48 2022 -0600\n+++ b/libinterp/corefcn/event-manager.cc\tSun Jan 08 04:30:30 2023 -0600\n@@ -64,6 +64,11 @@\n   ee.display (std::cerr);\n }\n \n+void interpreter_events::display_warning (const std::string&, const std::string&, const std::string&, const std::string& formatted)\n+{\n+  std::cerr << formatted;\n+}\n+\n event_manager::event_manager (interpreter& interp)\n   : m_event_queue_mutex (new mutex ()), m_gui_event_queue (),\n     m_debugging (false), m_link_enabled (true),\ndiff -r de16dd99ab0e -r a602982ec42d libinterp/corefcn/event-manager.h\n--- a/libinterp/corefcn/event-manager.h\tFri Dec 30 01:29:48 2022 -0600\n+++ b/libinterp/corefcn/event-manager.h\tSun Jan 08 04:30:30 2023 -0600\n@@ -246,6 +246,8 @@\n \n   virtual void display_exception (const execution_exception& ee, bool beep);\n \n+  virtual void display_warning (const std::string& id, const std::string& name, const std::string& message, const std::string& formatted);\n+\n   virtual void gui_status_update (const std::string& /*feature*/,\n                                   const std::string& /*status*/) { }\n \n@@ -667,6 +669,17 @@\n       return false;\n   }\n \n+  bool display_warning (const std::string& id, const std::string& name, const std::string& message, const std::string& formatted)\n+  {\n+    if (enabled ())\n+      {\n+       \tm_instance->display_warning (id, name, message, formatted);\n+        return true;\n+      }\n+    else\n+      return false;\n+  }\n+\n   bool gui_status_update (const std::string& feature,\n                           const std::string& status)\n   {\ndiff -r de16dd99ab0e -r a602982ec42d libinterp/corefcn/octave-json-link.cc\n--- a/libinterp/corefcn/octave-json-link.cc\tFri Dec 30 01:29:48 2022 -0600\n+++ b/libinterp/corefcn/octave-json-link.cc\tSun Jan 08 04:30:30 2023 -0600\n@@ -221,6 +221,16 @@\n \t_publish_message(\"display-exception\", json_util::from_map(m));\r\n }\r\n \r\n+void octave_json_link::display_warning(const std::string& id, const std::string& name, const std::string& message, const std::string& formatted) {\r\n+       \t// Redirected warnings from std::cerr\r\n+       \tJSON_MAP_T m;\r\n+       \tJSON_MAP_SET(m, id, string);\r\n+       \tJSON_MAP_SET(m, name, string);\r\n+       \tJSON_MAP_SET(m, message, string);\r\n+       \tJSON_MAP_SET(m, formatted, string);\r\n+\t_publish_message(\"display-warning\", json_util::from_map(m));\r\n+}\r\n+\r\n void octave_json_link::gui_status_update(const std::string& feature, const std::string& status) {\r\n \t// Triggered in __profiler_enable__\r\n \tJSON_MAP_T m;\r\ndiff -r de16dd99ab0e -r a602982ec42d libinterp/corefcn/octave-json-link.h\n--- a/libinterp/corefcn/octave-json-link.h\tFri Dec 30 01:29:48 2022 -0600\n+++ b/libinterp/corefcn/octave-json-link.h\tSun Jan 08 04:30:30 2023 -0600\n@@ -157,6 +157,8 @@\n \r\n   void display_exception (const execution_exception& ee, bool beep) override;\r\n \r\n+  void display_warning (const std::string& id, const std::string& name, const std::string& message, const std::string& formatted) override;\r\n+\r\n   void gui_status_update (const std::string& feature, const std::string& status) override;\r\n \r\n   void update_gui_lexer (void) override;\r\n"
  },
  {
    "path": "back-octave/oo-changesets/430-d2250ae9bddd.hg.patch",
    "content": "# HG changeset patch\n# User username = Octave Online Team <webmaster@octave-online.net>\n# Date 1703965894 21600\n#      Sat Dec 30 13:51:34 2023 -0600\n# Branch oo-8.4\n# Node ID d2250ae9bddde54dfc2ec9c772ab997376b1c307\n# Parent  78c13a2594f359474f28a93e43b288c9b40afa2c\n# Parent  44753c71594bb69cf144106cd06aa4246c703e00\nMerge oo-7.4 into oo-8.4\n\ndiff -r 78c13a2594f3 -r d2250ae9bddd configure.ac\n--- a/configure.ac\tSun Nov 05 12:45:40 2023 -0500\n+++ b/configure.ac\tSat Dec 30 13:51:34 2023 -0600\n@@ -2983,7 +2983,7 @@\n AC_SUBST(LIBOCTAVE_LINK_DEPS)\n AC_SUBST(LIBOCTAVE_LINK_OPTS)\n \n-LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $SPARSE_XLIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $JAVA_LIBS $LAPACK_LIBS\"\n+LIBOCTINTERP_LINK_DEPS=\"$FT2_LIBS $HDF5_LIBS $MAGICK_LIBS $Z_LIBS $SPARSE_XLIBS $FFTW_XLIBS $OPENGL_LIBS $FONTCONFIG_LIBS $FREETYPE_LIBS $X11_LIBS $CARBON_LIBS $GL2PS_LIBS $JAVA_LIBS $LAPACK_LIBS -ljson-c\"\n \n LIBOCTINTERP_LINK_OPTS=\"$FT2_LDFLAGS $HDF5_LDFLAGS $MAGICK_LDFLAGS $Z_LDFLAGS $SPARSE_XLDFLAGS $FFTW_XLDFLAGS\"\n \ndiff -r 78c13a2594f3 -r d2250ae9bddd libgui/src/qt-interpreter-events.cc\n--- a/libgui/src/qt-interpreter-events.cc\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libgui/src/qt-interpreter-events.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -309,6 +309,21 @@\n   emit edit_variable_signal (QString::fromStdString (expr), val);\n }\n \n+void qt_interpreter_events::show_static_plot (const std::string&, const std::string&)\n+{\n+  return;\n+}\n+\n+std::string qt_interpreter_events::request_input (const std::string&)\n+{\n+  return {};\n+}\n+\n+std::string qt_interpreter_events::request_url (const std::string&, const std::list<std::string>&, const std::string&, bool&)\n+{\n+  return {};\n+}\n+\n bool qt_interpreter_events::confirm_shutdown (void)\n {\n   QMutexLocker autolock (&m_mutex);\n@@ -604,6 +619,9 @@\n   emit clear_history_signal ();\n }\n \n+void qt_interpreter_events::do_clear_screen (void)\n+{ }\n+\n void qt_interpreter_events::pre_input_event (void)\n { }\n \ndiff -r 78c13a2594f3 -r d2250ae9bddd libgui/src/qt-interpreter-events.h\n--- a/libgui/src/qt-interpreter-events.h\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libgui/src/qt-interpreter-events.h\tSat Dec 30 13:51:34 2023 -0600\n@@ -136,6 +136,12 @@\n \n   void edit_variable (const std::string& name, const octave_value& val);\n \n+  void show_static_plot (const std::string& term, const std::string& content);\n+\n+  std::string request_input (const std::string&);\n+\n+  std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success);\n+\n   bool confirm_shutdown (void);\n \n   bool prompt_new_edit_file (const std::string& file);\n@@ -190,6 +196,8 @@\n \n   void clear_history (void);\n \n+  void clear_screen (void);\n+\n   void pre_input_event (void);\n \n   void post_input_event (void);\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/error.cc\n--- a/libinterp/corefcn/error.cc\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/corefcn/error.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -533,7 +533,9 @@\n   if (! quiet_warning ())\n     {\n       octave_diary << msg_string;\n-      std::cerr << msg_string;\n+      //std::cerr << msg_string;\n+      event_manager& evmgr = m_interpreter.get_event_manager ();\n+      evmgr.display_warning (id, name, base_msg, msg_string);\n \n       if (! fmt_suppresses_backtrace && in_user_code\n           && backtrace_on_warning ()\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/event-manager-url.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/event-manager-url.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -0,0 +1,127 @@\n+#if defined (HAVE_CONFIG_H)\n+#  include \"config.h\"\n+#endif\n+\n+#include <iostream>\n+\n+#include \"event-manager.h\"\n+#include \"url-transfer.h\"\n+#include \"interpreter-private.h\"\n+#include \"base64-wrappers.h\"\n+\n+OCTAVE_BEGIN_NAMESPACE(octave)\n+\n+bool __event_manager_request_input_enabled__() {\n+  event_manager& evmgr = __get_event_manager__ ();\n+  return evmgr.request_input_enabled();\n+}\n+\n+std::string __event_manager_request_url__(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\n+  event_manager& evmgr = __get_event_manager__ ();\n+  return evmgr.request_url(url, param, action, success);\n+}\n+\n+class link_transfer : public base_url_transfer\n+{\n+public:\n+\n+  link_transfer (void)\n+      : base_url_transfer () {\n+    m_valid = true;\n+  }\n+\n+  link_transfer (const std::string& host, const std::string& user_arg,\n+                 const std::string& passwd, std::ostream& os)\n+      : base_url_transfer (host, user_arg, passwd, os) {\n+    m_valid = true;\n+    // url = \"ftp://\" + host;\n+  }\n+\n+  link_transfer (const std::string& url_str, std::ostream& os)\n+      : base_url_transfer (url_str, os) {\n+    m_valid = true;\n+  }\n+\n+  ~link_transfer (void) {}\n+\n+  void http_get (const Array<std::string>& param) {\n+    perform_action (param, \"get\");\n+  }\n+\n+  void http_post (const Array<std::string>& param) {\n+    perform_action (param, \"post\");\n+  }\n+\n+  void http_action (const Array<std::string>& param, const std::string& action) {\n+    perform_action (param, action);\n+  }\n+\n+private:\n+  void perform_action(const Array<std::string>& param, const std::string& action) {\n+    std::string url = m_host_or_url;\n+\n+    // Convert from Array to std::list\n+    std::list<std::string> paramList;\n+    for (int i = 0; i < param.numel(); i ++) {\n+      std::string value = param(i);\n+      paramList.push_back(value);\n+    }\n+\n+    if (__event_manager_request_input_enabled__()) {\n+      bool success;\n+      std::string result = __event_manager_request_url__(url, paramList, action, success);\n+      if (success) {\n+        process_success(result);\n+      } else {\n+        m_ok = false;\n+        m_errmsg = result;\n+      }\n+    } else {\n+      m_ok = false;\n+      m_errmsg = \"octave_link not connected for link_transfer\";\n+    }\n+  }\n+\n+  void process_success(const std::string& result) {\n+    // If success, the result is returned as a base64 string, and we need to decode it.\n+    // Use the base64 implementation from gnulib, which is already an Octave dependency.\n+    const char *inc = &(result[0]);\n+    char *out;\n+    std::ptrdiff_t outlen;\n+    bool b64_ok = octave_base64_decode_alloc_wrapper(inc, result.length(), &out, &outlen);\n+    if (!b64_ok) {\n+      m_ok = false;\n+      m_errmsg = \"failed decoding base64 from octave_link\";\n+    } else {\n+      m_curr_ostream->write(out, outlen);\n+      ::free(out);\n+    }\n+  }\n+};\n+\n+url_transfer::url_transfer (void) {\n+  if (__event_manager_request_input_enabled__()) {\n+    m_rep.reset(new link_transfer());\n+  } else {\n+    m_rep.reset(new base_url_transfer());\n+  }\n+}\n+\n+url_transfer::url_transfer (const std::string& host, const std::string& user,\n+                            const std::string& passwd, std::ostream& os) {\n+  if (__event_manager_request_input_enabled__()) {\n+    m_rep.reset(new link_transfer(host, user, passwd, os));\n+  } else {\n+    m_rep.reset(new base_url_transfer(host, user, passwd, os));\n+  }\n+}\n+\n+url_transfer::url_transfer (const std::string& url, std::ostream& os) {\n+  if (__event_manager_request_input_enabled__()) {\n+    m_rep.reset(new link_transfer(url, os));\n+  } else {\n+    m_rep.reset(new base_url_transfer(url, os));\n+  }\n+}\n+\n+OCTAVE_END_NAMESPACE(octave)\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/event-manager.cc\n--- a/libinterp/corefcn/event-manager.cc\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/corefcn/event-manager.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -64,6 +64,11 @@\n   ee.display (std::cerr);\n }\n \n+void interpreter_events::display_warning (const std::string&, const std::string&, const std::string&, const std::string& formatted)\n+{\n+  std::cerr << formatted;\n+}\n+\n event_manager::event_manager (interpreter& interp)\n   : m_event_queue_mutex (new mutex ()), m_gui_event_queue (),\n     m_debugging (false), m_link_enabled (true),\n@@ -879,4 +884,28 @@\n   return ovl ();\n }\n \n+DEFMETHOD (__event_manager_plot_destination__, interp, , ,\n+           doc: /* -*- texinfo -*-\n+@deftypefn {} {} __event_manager_plot_destination__ ()\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  return ovl (interp.get_event_manager().plot_destination());\n+}\n+\n+DEFMETHOD (__event_manager_show_static_plot__, interp, args, ,\n+           doc: /* -*- texinfo -*-\n+@deftypefn {} {} __event_manager_show_static_plot__ (@var{term}, @var{content})\n+Undocumented internal function.\n+@end deftypefn*/)\n+{\n+  if (args.length () != 2) {\n+    return ovl ();\n+  }\n+\n+  std::string term = args(0).string_value();\n+  std::string content = args(1).string_value();\n+  return ovl (interp.get_event_manager().show_static_plot(term, content));\n+}\n+\n OCTAVE_END_NAMESPACE(octave)\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/event-manager.h\n--- a/libinterp/corefcn/event-manager.h\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/corefcn/event-manager.h\tSat Dec 30 13:51:34 2023 -0600\n@@ -50,6 +50,12 @@\n class execution_exception;\n class symbol_info_list;\n \n+enum plot_destination_t {\n+  TERMINAL_ONLY = 0,\n+  STATIC_ONLY = 1,\n+  TERMINAL_AND_STATIC = 2\n+};\n+\n // The methods in this class provide a way to pass signals to the GUI\n // thread.  A GUI that wishes to act on these events should derive\n // from this class and perform actions in a thread-safe way.  In\n@@ -176,6 +182,18 @@\n   // confirmation before another action.  Could these be reformulated\n   // using the question_dialog action?\n \n+  bool _request_input_enabled;\n+  virtual std::string request_input (const std::string&)\n+  {\n+    return \"\";\n+  }\n+  virtual std::string request_url (const std::string& /*url*/, const std::list<std::string>& /*param*/, const std::string& /*action*/, bool& /*success*/) {\n+    return \"\";\n+  }\n+\n+  plot_destination_t _plot_destination;\n+  virtual void show_static_plot (const std::string& /*term*/, const std::string& /*content*/) { }\n+\n   virtual bool confirm_shutdown (void) { return true; }\n \n   virtual bool prompt_new_edit_file (const std::string& /*file*/)\n@@ -228,6 +246,8 @@\n \n   virtual void display_exception (const execution_exception& ee, bool beep);\n \n+  virtual void display_warning (const std::string& id, const std::string& name, const std::string& message, const std::string& formatted);\n+\n   virtual void gui_status_update (const std::string& /*feature*/,\n                                   const std::string& /*status*/) { }\n \n@@ -260,6 +280,8 @@\n \n   virtual void clear_history (void) { }\n \n+  virtual void clear_screen (void) { }\n+\n   virtual void pre_input_event (void) { }\n \n   virtual void post_input_event (void) { }\n@@ -448,6 +470,28 @@\n       m_instance->update_path_dialog ();\n   }\n \n+  bool request_input_enabled (void)\n+  {\n+    return enabled () ? m_instance->_request_input_enabled : false;\n+  }\n+\n+  plot_destination_t plot_destination (void)\n+  {\n+    return enabled () ? m_instance->_plot_destination : TERMINAL_ONLY;\n+  }\n+\n+  bool\n+  show_static_plot (const std::string& term, const std::string& content)\n+  {\n+    if (enabled ())\n+      {\n+        m_instance->show_static_plot (term, content);\n+        return true;\n+      }\n+    else\n+      return false;\n+  }\n+\n   bool show_preferences (void)\n   {\n     if (enabled ())\n@@ -625,6 +669,17 @@\n       return false;\n   }\n \n+  bool display_warning (const std::string& id, const std::string& name, const std::string& message, const std::string& formatted)\n+  {\n+    if (enabled ())\n+      {\n+       \tm_instance->display_warning (id, name, message, formatted);\n+        return true;\n+      }\n+    else\n+      return false;\n+  }\n+\n   bool gui_status_update (const std::string& feature,\n                           const std::string& status)\n   {\n@@ -709,6 +764,12 @@\n       m_instance->clear_history ();\n   }\n \n+  void clear_screen (void)\n+  {\n+    if (enabled ())\n+      m_instance->clear_screen ();\n+  }\n+\n   void pre_input_event (void)\n   {\n     if (enabled ())\n@@ -721,6 +782,20 @@\n       m_instance->post_input_event ();\n   }\n \n+  std::string request_input (const std::string& prompt)\n+  {\n+    return request_input_enabled ()\n+      ? m_instance->request_input (prompt)\n+      : std::string ();\n+  }\n+\n+  std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success)\n+  {\n+    return request_input_enabled ()\n+      ? m_instance->request_url (url, param, action, success)\n+      : std::string ();\n+  }\n+\n   void enter_debugger_event (const std::string& fcn_name,\n                              const std::string& fcn_file_name, int line)\n   {\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/input.cc\n--- a/libinterp/corefcn/input.cc\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/corefcn/input.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -786,7 +786,12 @@\n \n   eof = false;\n \n-  std::string retval = command_editor::readline (s, eof);\n+  std::string retval;\n+  event_manager& evmgr = m_interpreter.get_event_manager ();\n+  if (evmgr.request_input_enabled ())\n+    retval = evmgr.request_input (s);\n+  else\n+    retval = command_editor::readline (s, eof);\n \n   if (! eof && retval.empty ())\n     retval = \"\\n\";\n@@ -1678,4 +1683,33 @@\n   return input_sys.auto_repeat_debug_command (args, nargout);\n }\n \n+DEFUN (current_command_number, args, ,\n+       doc: /* -*- texinfo -*-\n+@deftypefn  {} {@var{val} =} current_command_number ()\n+@deftypefnx {} {@var{old_val} =} current_command_number (@var{new_val})\n+Sets the current command number, which appears in the prompt string.\n+For example, if the prompt says \"octave:1>\", then the current command\n+number is 1.\n+\n+This is a custom function in Octave Online.\n+\n+@example\n+current_command_number(1)\n+@end example\n+@end deftypefn */)\n+{\n+  int nargin = args.length ();\n+  if (nargin == 0) {\n+    int n = octave::command_editor::current_command_number();\n+    return ovl(n);\n+  } else if (nargin > 1) {\n+    print_usage ();\n+    return ovl();\n+  } else {\n+    int n = args(0).int_value ();\n+    octave::command_editor::reset_current_command_number(n);\n+    return ovl(n);\n+  }\n+}\n+\n OCTAVE_END_NAMESPACE(octave)\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/interpreter.cc\n--- a/libinterp/corefcn/interpreter.cc\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/corefcn/interpreter.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -61,6 +61,7 @@\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"interpreter.h\"\n+#include \"json-main.h\"\n #include \"load-path.h\"\n #include \"load-save.h\"\n #include \"octave.h\"\n@@ -624,6 +625,11 @@\n       std::string texi_macros_file = options.texi_macros_file ();\n       if (! texi_macros_file.empty ())\n         Ftexi_macros_file (*this, octave_value (texi_macros_file));\n+\n+      if (!options.json_sock_path().empty ()) {\n+        static json_main _json_main (*this, options.json_sock_path(), options.json_max_message_length());\n+        _json_main.run_loop_on_new_thread();\n+      }\n     }\n \n   // FIXME: we defer creation of the gh_manager object because it\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/json-main.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -0,0 +1,102 @@\n+#ifdef HAVE_CONFIG_H\n+#include <config.h>\n+#endif\n+\n+#include \"json-main.h\"\n+#include \"interpreter.h\"\n+\n+#include <iostream>\n+#include <sys/un.h>\n+#include <sys/socket.h>\n+#include <unistd.h>\n+\n+\n+// Analog of main-window.cc\n+// TODO: Think more about concurrency and null pointer exceptions\n+\n+namespace octave {\n+\n+void* run_loop_pthread(void* arg) {\n+  json_main* _json_main = static_cast<json_main*>(arg);\n+  _json_main->run_loop();\n+  return NULL;\n+}\n+\n+void json_object_cb(std::string name, JSON_OBJECT_T jobj, void* arg) {\n+  json_main* _json_main = static_cast<json_main*>(arg);\n+  _json_main->process_json_object(name, jobj);\n+}\n+\n+json_main::json_main(interpreter& interp, const std::string& json_sock_path, int max_message_length)\n+  : _json_sock_path (json_sock_path),\n+    _max_message_length (max_message_length),\n+    _loop_thread_active (false),\n+    _octave_json_link (new octave_json_link(this))\n+{\n+  // Enable the octave_json_link instance\n+  // Note: this passes ownership to octave_link\n+  event_manager& evmgr = interp.get_event_manager ();\n+  evmgr.connect_link (_octave_json_link);\n+  evmgr.install_qt_event_handlers (_octave_json_link);\n+  evmgr.enable ();\n+\n+  // Open UNIX socket file descriptor\n+  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+  struct sockaddr_un addr;\n+  memset(&addr, 0, sizeof(addr));\n+  addr.sun_family = AF_UNIX;\n+  memcpy(&addr.sun_path, _json_sock_path.c_str(), sizeof(addr.sun_path)-1);\n+  connect(\n+    sockfd,\n+    reinterpret_cast<struct sockaddr*>(&addr),\n+    sizeof(addr));\n+}\n+\n+json_main::~json_main(void) {\n+  close(sockfd);\n+\n+  // TODO: Stop the _loop_thread\n+}\n+\n+void json_main::publish_message(const std::string& name, JSON_OBJECT_T jobj) {\n+  std::string jstr = json_util::to_message(name, jobj);\n+\n+  // Do not send any messages over the socket that exceed the user-specified max length.  Instead, send an error message.  If max_length is 0 (default), do not suppress any messages.\n+  // In stress testing, Node may be able to handle as much as 10-20 MB, but I'd prefer to stay on the safe side.  Before this safeguard was implemented, fewer than 5% of plots exceeded 1 MB.\n+  int length = jstr.length();\n+  int max_length = _max_message_length;\n+  if (max_length > 0 && length > max_length) {\n+    JSON_MAP_T m;\n+    JSON_MAP_SET(m, name, string);\n+    JSON_MAP_SET(m, length, int);\n+    JSON_MAP_SET(m, max_length, int);\n+    jstr = json_util::to_message(\"message-too-long\", json_util::from_map(m));\n+  }\n+\n+  send(sockfd, jstr.c_str(), jstr.length(), 0);\n+}\n+\n+void json_main::run_loop_on_new_thread(void) {\n+  if (_loop_thread_active)\n+    perror(\"won't run JSON socket loop multiple times\");\n+  _loop_thread_active = true;\n+\n+  pthread_create(\n+    &_loop_thread,\n+    NULL,\n+    run_loop_pthread,\n+    static_cast<void*>(this));\n+}\n+\n+void json_main::run_loop(void) {\n+  json_util::read_stream(\n+    sockfd,\n+    json_object_cb,\n+    static_cast<void*>(this));\n+}\n+\n+void json_main::process_json_object(std::string name, JSON_OBJECT_T jobj) {\n+  _octave_json_link->receive_message(name, jobj);\n+}\n+\n+} // namespace octave\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/json-main.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-main.h\tSat Dec 30 13:51:34 2023 -0600\n@@ -0,0 +1,37 @@\n+#ifndef json_main_h\n+#define json_main_h\n+\n+#include <queue>\n+#include <pthread.h>\n+#include <stdio.h>\n+\n+#include \"octave-json-link.h\"\n+#include \"json-util.h\"\n+\n+namespace octave {\n+\n+class interpreter;\n+\n+class json_main {\n+public:\n+\tjson_main(interpreter& interp, const std::string& json_sock_path, int max_message_length);\n+\t~json_main(void);\n+\n+\tvoid publish_message(const std::string& name, JSON_OBJECT_T jobj);\n+\tvoid run_loop_on_new_thread(void);\n+\tvoid run_loop(void);\n+\tvoid process_json_object(std::string name, JSON_OBJECT_T jobj);\n+\n+private:\n+\tstd::string _json_sock_path;\n+\tint _max_message_length;\n+\tint sockfd;\n+\tbool _loop_thread_active;\n+\tpthread_t _loop_thread;\n+\n+\tstd::shared_ptr<octave_json_link> _octave_json_link;\n+};\n+\n+} // namespace octave\n+\n+#endif\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/json-util.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -0,0 +1,264 @@\n+#ifdef HAVE_CONFIG_H\n+#include <config.h>\n+#endif\n+\n+#include <cstdlib>\n+#include <assert.h>\n+#include <sys/un.h>\n+#include <sys/socket.h>\n+#include <stdio.h>\n+#include <json-c/arraylist.h>\n+#include <json-c/json_object.h>\n+\n+#include \"str-vec.h\"\n+\n+#include \"json-util.h\"\n+\n+namespace octave {\n+\n+JSON_OBJECT_T json_util::from_string(const std::string& str) {\n+\t// Note: the string is not necesarilly valid UTF-8. The consumers of this stream must be able to handle that situation and substitute replacement characters, etc., where necessary.\n+\treturn json_object_new_string_len(str.c_str(), str.length());\n+}\n+\n+JSON_OBJECT_T json_util::from_int(int i) {\n+\treturn json_object_new_int(i);\n+}\n+\n+JSON_OBJECT_T json_util::from_float(float flt) {\n+\treturn json_object_new_double(flt);\n+}\n+\n+JSON_OBJECT_T json_util::from_boolean(bool b) {\n+\treturn json_object_new_boolean(b);\n+}\n+\n+JSON_OBJECT_T json_util::empty() {\n+\treturn json_object_new_object();\n+}\n+\n+template<typename T>\n+JSON_OBJECT_T json_object_from_list(const std::list<T>& list, JSON_OBJECT_T (*convert)(T)) {\n+\tJSON_OBJECT_T jobj = json_object_new_array();\n+\tfor (\n+\t\tauto it = list.begin();\n+\t\tit != list.end();\n+\t\t++it\n+\t){\n+\t\tjson_object_array_add(jobj, convert(*it));\n+\t}\n+\treturn jobj;\n+}\n+\n+JSON_OBJECT_T json_util::from_string_list(const std::list<std::string>& list) {\n+\treturn json_object_from_list(list, json_util::from_value_string);\n+}\n+\n+JSON_OBJECT_T json_util::from_string_vector(const string_vector& vect) {\n+\t// TODO: Make sure this function does what it's supposed to do\n+\tstd::list<std::string> list;\n+\tfor (int i = 0; i < vect.numel(); ++i) {\n+\t\tlist.push_back(vect[i]);\n+\t}\n+\n+\treturn json_object_from_list(list, json_util::from_value_string);\n+}\n+\n+JSON_OBJECT_T json_util::from_int_list(const std::list<int>& list) {\n+\treturn json_object_from_list(list, json_util::from_int);\n+}\n+\n+JSON_OBJECT_T json_util::from_float_list(const std::list<float>& list) {\n+\treturn json_object_from_list(list, json_util::from_float);\n+}\n+\n+JSON_OBJECT_T json_util::from_symbol_info_list(const symbol_info_list& list) {\n+\tJSON_OBJECT_T jobj = json_object_new_array();\n+\tfor (\n+\t\tauto it = list.begin();\n+\t\tit != list.end();\n+\t\t++it\n+\t){\n+\t\tjson_object_array_add(jobj, json_util::from_symbol_info(*it));\n+\t}\n+\treturn jobj;\n+}\n+\n+JSON_OBJECT_T json_util::from_filter_list(const interpreter_events::filter_list& list) {\n+\treturn json_object_from_list(list, json_util::from_pair);\n+}\n+\n+JSON_OBJECT_T json_util::from_value_string(const std::string str) {\n+\treturn json_util::from_string(str);\n+}\n+\n+JSON_OBJECT_T json_util::from_symbol_info(const symbol_info element) {\n+\toctave_value val = element.value();\n+\n+\tstd::string dims_str = val.get_dims_str();\n+\n+\tstd::ostringstream display_str;\n+\tval.short_disp(display_str);\n+\n+\tJSON_MAP_T m;\n+\t// m[\"scope\"] = json_util::from_int(element.scope());\n+\tm[\"symbol\"] = json_util::from_string(element.name());\n+\tm[\"class_name\"] = json_util::from_string(val.class_name());\n+\tm[\"dimension\"] = json_util::from_string(dims_str);\n+\tm[\"value\"] = json_util::from_string(display_str.str());\n+\tm[\"complex_flag\"] = json_util::from_boolean(element.is_complex());\n+\treturn json_util::from_map(m);\n+}\n+\n+JSON_OBJECT_T json_util::from_pair(std::pair<std::string, std::string> pair) {\n+\tJSON_OBJECT_T jobj = json_object_new_array();\n+\tjson_object_array_add(jobj, json_util::from_string(pair.first.c_str()));\n+\tjson_object_array_add(jobj, json_util::from_string(pair.second.c_str()));\n+\treturn jobj;\n+}\n+\n+JSON_OBJECT_T json_util::from_map(JSON_MAP_T m) {\n+\tJSON_OBJECT_T jobj = json_object_new_object();\n+\tfor(\n+\t\tstd::map<std::string, JSON_OBJECT_T>::iterator it = m.begin();\n+\t\tit != m.end();\n+\t\t++it\n+\t){\n+\t\tjson_object_object_add(jobj, it->first.c_str(), it->second);\n+\t}\n+\treturn jobj;\n+}\n+\n+std::string json_util::to_message(const std::string& name, JSON_OBJECT_T jobj) {\n+  JSON_OBJECT_T jmsg = json_object_new_array();\n+  json_object_array_add(jmsg, json_util::from_string(name));\n+  json_object_array_add(jmsg, jobj);\n+  std::string str (json_object_to_json_string(jmsg));\n+  return str;\n+}\n+\n+std::string json_util::to_string(JSON_OBJECT_T jobj) {\n+  return std::string(json_object_get_string(jobj));\n+}\n+\n+template<typename T>\n+std::list<T> json_object_to_list(JSON_OBJECT_T jobj, T (*convert)(JSON_OBJECT_T)) {\n+\tstd::list<T> ret;\n+\n+\tstruct array_list* arr = json_object_get_array(jobj);\n+\tif (arr == NULL)\n+\t\treturn ret;\n+\n+\tfor (size_t i = 0; i < array_list_length(arr); ++i) {\n+\t\tJSON_OBJECT_T jsub = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, i));\n+\t\tret.push_back(convert(jsub));\n+\t}\n+\treturn ret;\n+}\n+\n+std::pair<std::list<int>, int> json_util::to_int_list_int_pair(JSON_OBJECT_T jobj) {\n+\tstd::pair<std::list<int>, int> ret;\n+\n+\tstruct array_list* arr = json_object_get_array(jobj);\n+\tif (arr == NULL)\n+\t\treturn ret;\n+\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\n+\n+\tret.first = json_object_to_list<int>(first, json_util::to_int);\n+\tret.second = json_object_get_int(second);\n+\n+\treturn ret;\n+}\n+\n+std::pair<bool, std::string> json_util::to_bool_string_pair(JSON_OBJECT_T jobj) {\n+\tstd::pair<bool, std::string> ret;\n+\n+\tstruct array_list* arr = json_object_get_array(jobj);\n+\tif (arr == NULL)\n+\t\treturn ret;\n+\n+\tJSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));\n+\tJSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));\n+\n+\tret.first = json_object_get_boolean(first);\n+\tret.second = json_object_get_string(second);\n+\n+\treturn ret;\n+}\n+\n+std::list<std::string> json_util::to_string_list(JSON_OBJECT_T jobj) {\n+\treturn json_object_to_list<std::string>(jobj, json_util::to_string);\n+}\n+\n+int json_util::to_int(JSON_OBJECT_T jobj) {\n+\treturn json_object_get_int(jobj);\n+}\n+\n+bool json_util::to_boolean(JSON_OBJECT_T jobj) {\n+\treturn json_object_get_boolean(jobj);\n+}\n+\n+void json_util::read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\n+\n+\t// Make some local variables\n+\tint BUF_LEN = 24;\n+\tchar* buf = new char[BUF_LEN];  // buffer for socket read\n+\tint buf_len;  // length of new bytes in the buffer\n+\tint buf_offset;  // offset of the JSON parser in the buffer\n+\tJSON_OBJECT_T jobj;  // pointer to parsed JSON object\n+\tjson_tokener* tok = json_tokener_new();  // JSON tokenizer instance\n+\tenum json_tokener_error jerr;  // status of JSON tokenizer\n+\n+\t// Start the blocking I/O loop\n+\twhile( (buf_len=recv(sockfd, buf, BUF_LEN, 0)) > 0) {\n+\t\tbuf_offset = 0;\n+\t\twhile(buf_offset < buf_len){\n+\t\t\tjobj = json_tokener_parse_ex(tok, buf + buf_offset, buf_len - buf_offset);\n+\t\t\tjerr = json_tokener_get_error(tok);\n+\t\t\tbuf_offset += tok->char_offset;\n+\n+\t\t\t// Do we need more material in order to make JSON?\n+\t\t\tif (jerr == json_tokener_continue) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\t// Make a new tokenizer\n+\t\t\tjson_tokener_free(tok);\n+\t\t\ttok = json_tokener_new();\n+\n+\t\t\t// Did we encounter a malformed JSON object?\n+\t\t\tif (jerr != json_tokener_success) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t\"JSON parse error: %s: '%.*s'\\n\",\n+\t\t\t\t\tjson_tokener_error_desc(jerr),\n+\t\t\t\t\t1,\n+\t\t\t\t\tbuf + buf_offset);\n+\t\t\t\tfflush(stderr);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\t// Our object is ready\n+\t\t\tprocess_message(jobj, cb, arg);\n+\t\t}\n+\t}\n+\n+\tjson_tokener_free(tok);\n+\tdelete[] buf;\n+}\n+\n+void json_util::process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {\n+  if (!json_object_is_type(jobj, json_type_array))\n+    return;\n+  if (json_object_array_length(jobj) != 2)\n+    return;\n+\n+  cb(\n+  \tjson_util::to_string(json_object_array_get_idx(jobj, 0)),\n+  \tjson_object_array_get_idx(jobj, 1),\n+  \targ\n+  );\n+}\n+\n+} // namespace octave\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/json-util.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/json-util.h\tSat Dec 30 13:51:34 2023 -0600\n@@ -0,0 +1,64 @@\n+#ifndef json_util_h\n+#define json_util_h\n+\n+#include <json-c/json.h>\n+#include <map>\n+#include <list>\n+\n+#include \"syminfo.h\"\n+#include \"event-manager.h\"\n+\n+class string_vector;\n+\n+// All of the code interacting with the external JSON library should be in\n+// the json-util.h and json-util.cc files.  This way, if we want to change\n+// the external JSON library, we can do it all in one place.\n+\n+#define JSON_OBJECT_T json_object*\n+#define JSON_MAP_T std::map<std::string, JSON_OBJECT_T>\n+\n+#define JSON_MAP_SET(M, FIELD, TYPE){ \\\n+\tm[#FIELD] = json_util::from_##TYPE (FIELD); \\\n+}\n+\n+namespace octave {\n+\n+class json_util {\n+public:\n+\tstatic JSON_OBJECT_T from_string(const std::string& str);\n+\tstatic JSON_OBJECT_T from_int(int i);\n+\tstatic JSON_OBJECT_T from_float(float flt);\n+\tstatic JSON_OBJECT_T from_boolean(bool b);\n+\tstatic JSON_OBJECT_T empty();\n+\n+\tstatic JSON_OBJECT_T from_string_list(const std::list<std::string>& list);\n+\tstatic JSON_OBJECT_T from_string_vector(const string_vector& list);\n+\tstatic JSON_OBJECT_T from_int_list(const std::list<int>& list);\n+\tstatic JSON_OBJECT_T from_float_list(const std::list<float>& list);\n+\tstatic JSON_OBJECT_T from_symbol_info_list(const symbol_info_list& list);\n+        static JSON_OBJECT_T from_filter_list(const interpreter_events::filter_list& list);\n+\n+\tstatic JSON_OBJECT_T from_value_string(const std::string str);\n+\tstatic JSON_OBJECT_T from_symbol_info(const symbol_info element);\n+\tstatic JSON_OBJECT_T from_pair(std::pair<std::string, std::string> pair);\n+\n+\tstatic JSON_OBJECT_T from_map(JSON_MAP_T m);\n+\n+\tstatic std::string to_message(const std::string& name, JSON_OBJECT_T jobj);\n+\n+\tstatic std::string to_string(JSON_OBJECT_T jobj);\n+\tstatic std::pair<std::list<int>, int> to_int_list_int_pair(JSON_OBJECT_T jobj);\n+\tstatic std::pair<bool, std::string> to_bool_string_pair(JSON_OBJECT_T jobj);\n+\tstatic std::list<std::string> to_string_list(JSON_OBJECT_T jobj);\n+\tstatic int to_int(JSON_OBJECT_T jobj);\n+\tstatic bool to_boolean(JSON_OBJECT_T jobj);\n+\n+\tstatic void read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\n+\n+private:\n+\tstatic void process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);\n+};\n+\n+} // namespace octave\n+\n+#endif\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/module.mk\n--- a/libinterp/corefcn/module.mk\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/corefcn/module.mk\tSat Dec 30 13:51:34 2023 -0600\n@@ -46,6 +46,8 @@\n   %reldir%/help.h \\\n   %reldir%/hook-fcn.h \\\n   %reldir%/input.h \\\n+  %reldir%/json-main.h \\\n+  %reldir%/json-util.h \\\n   %reldir%/interpreter.h \\\n   %reldir%/latex-text-renderer.h \\\n   %reldir%/load-path.h \\\n@@ -77,6 +79,7 @@\n   %reldir%/oct-strstrm.h \\\n   %reldir%/oct.h \\\n   %reldir%/octave-default-image.h \\\n+  %reldir%/octave-json-link.h \\\n   %reldir%/pager.h \\\n   %reldir%/pr-flt-fmt.h \\\n   %reldir%/pr-output.h \\\n@@ -163,6 +166,7 @@\n   %reldir%/error.cc \\\n   %reldir%/errwarn.cc \\\n   %reldir%/event-manager.cc \\\n+  %reldir%/event-manager-url.cc \\\n   %reldir%/event-queue.cc \\\n   %reldir%/fcn-info.cc \\\n   %reldir%/fft.cc \\\n@@ -189,6 +193,8 @@\n   %reldir%/hex2num.cc \\\n   %reldir%/hook-fcn.cc \\\n   %reldir%/input.cc \\\n+  %reldir%/json-main.cc \\\n+  %reldir%/json-util.cc \\\n   %reldir%/interpreter-private.cc \\\n   %reldir%/interpreter.cc \\\n   %reldir%/inv.cc \\\n@@ -228,6 +234,7 @@\n   %reldir%/oct-tex-lexer.ll \\\n   %reldir%/oct-tex-parser.h \\\n   %reldir%/oct-tex-parser.yy \\\n+  %reldir%/octave-json-link.cc \\\n   %reldir%/ordqz.cc \\\n   %reldir%/ordschur.cc \\\n   %reldir%/pager.cc \\\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/octave-json-link.cc\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -0,0 +1,463 @@\n+/*\n+\n+Copyright (C) 2015-2016 Shane Carr\n+\n+This file is part of Octave.\n+\n+Octave is free software; you can redistribute it and/or modify it\n+under the terms of the GNU General Public License as published by the\n+Free Software Foundation; either version 3 of the License, or (at your\n+option) any later version.\n+\n+Octave is distributed in the hope that it will be useful, but WITHOUT\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n+for more details.\n+\n+You should have received a copy of the GNU General Public License\n+along with Octave; see the file COPYING.  If not, see\n+<http://www.gnu.org/licenses/>.\n+\n+*/\n+\n+#ifdef HAVE_CONFIG_H\n+#include <config.h>\n+#endif\n+\n+#include <iostream>\n+#include \"octave-json-link.h\"\n+#include \"cmd-edit.h\"\n+#include \"json-main.h\"\n+#include \"json-util.h\"\n+\n+namespace octave {\n+\n+octave_json_link::octave_json_link(json_main* __json_main)\n+\t: interpreter_events (),\n+\t\t_json_main (__json_main)\n+{\n+\t_request_input_enabled = true;\n+\t_plot_destination = STATIC_ONLY;\n+}\n+\n+octave_json_link::~octave_json_link(void) { }\n+\n+std::string octave_json_link::request_input(const std::string& prompt) {\n+\t// Triggered whenever the console prompts for user input\n+\n+\tstd::string value;\n+\tif (!request_input_queue.dequeue_to(&value)) {\n+\t\t_publish_message(\"request-input\", json_util::from_string(prompt));\n+\t\tvalue = request_input_queue.dequeue();\n+\t}\n+\treturn value;\n+}\n+\n+std::string octave_json_link::request_url(const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) {\n+\t// Triggered on urlread/urlwrite\n+\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, url, string);\n+\tJSON_MAP_SET(m, param, string_list);\n+\tJSON_MAP_SET(m, action, string);\n+\n+\t_publish_message(\"request-url\", json_util::from_map(m));\n+\tstd::pair<bool, std::string> result = request_url_queue.dequeue();\n+\tsuccess = result.first;\n+\treturn result.second;\n+}\n+\n+bool octave_json_link::confirm_shutdown(void) {\n+\t// Triggered when the kernel tries to exit\n+\t_publish_message(\"confirm-shutdown\", json_util::empty());\n+\n+\treturn confirm_shutdown_queue.dequeue();\n+}\n+\n+// do_exit was removed in Octave 5\n+// bool octave_json_link::do_exit(int status) {\n+// \tJSON_MAP_T m;\n+// \tJSON_MAP_SET(m, status, int);\n+// \t_publish_message(\"exit\", json_util::from_map(m));\n+\n+// \t// It is our responsibility in octave_link to call exit. If we don't, then\n+// \t// the kernel waits for 24 hours expecting us to do something.\n+// \t::exit(status);\n+\n+// \treturn true;\n+// }\n+\n+bool octave_json_link::copy_image_to_clipboard(const std::string& file) {\n+\t// This endpoint might be unused?  (References appear only in libgui)\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, file, string);\n+\t_publish_message(\"copy-image-to-clipboard\", json_util::from_map(m));\n+\n+\treturn true;\n+}\n+\n+bool octave_json_link::edit_file(const std::string& file) {\n+\t// Triggered in \"edit\" for existing files\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, file, string);\n+\t_publish_message(\"edit-file\", json_util::from_map(m));\n+\n+\treturn true;\n+}\n+\n+bool octave_json_link::prompt_new_edit_file(const std::string& file) {\n+\t// Triggered in \"edit\" for new files\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, file, string);\n+\t_publish_message(\"prompt-new-edit-file\", json_util::from_map(m));\n+\n+\treturn prompt_new_edit_file_queue.dequeue();\n+}\n+\n+// int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {\n+// \t// Triggered in \"msgbox\", \"helpdlg\", and \"errordlg\", among others\n+// \tJSON_MAP_T m;\n+// \tJSON_MAP_SET(m, dlg, string); // i.e., m[\"dlg\"] = json_util::from_string(dlg);\n+// \tJSON_MAP_SET(m, msg, string);\n+// \tJSON_MAP_SET(m, title, string);\n+// \t_publish_message(\"message-dialog\", json_util::from_map(m));\n+\n+// \treturn message_dialog_queue.dequeue();\n+// }\n+\n+bool octave_json_link::have_dialogs() const {\n+\t// Triggered in \"inputdlg\" and similar functions to check for dialog support\n+\treturn true;\n+}\n+\n+std::string octave_json_link::question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {\n+\t// Triggered in \"questdlg\"\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, msg, string);\n+\tJSON_MAP_SET(m, title, string);\n+\tJSON_MAP_SET(m, btn1, string);\n+\tJSON_MAP_SET(m, btn2, string);\n+\tJSON_MAP_SET(m, btn3, string);\n+\tJSON_MAP_SET(m, btndef, string);\n+\t_publish_message(\"question-dialog\", json_util::from_map(m));\n+\n+\treturn question_dialog_queue.dequeue();\n+}\n+\n+std::pair<std::list<int>, int> octave_json_link::list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {\n+\t// Triggered in \"listdlg\"\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, list, string_list);\n+\tJSON_MAP_SET(m, mode, string);\n+\tJSON_MAP_SET(m, width, int);\n+\tJSON_MAP_SET(m, height, int);\n+\tJSON_MAP_SET(m, initial_value, int_list);\n+\tJSON_MAP_SET(m, name, string);\n+\tJSON_MAP_SET(m, prompt, string_list);\n+\tJSON_MAP_SET(m, ok_string, string);\n+\tJSON_MAP_SET(m, cancel_string, string);\n+\t_publish_message(\"list-dialog\", json_util::from_map(m));\n+\n+\treturn list_dialog_queue.dequeue();\n+}\n+\n+std::list<std::string> octave_json_link::input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {\n+\t// Triggered in \"inputdlg\"\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, prompt, string_list);\n+\tJSON_MAP_SET(m, title, string);\n+\tJSON_MAP_SET(m, nr, float_list);\n+\tJSON_MAP_SET(m, nc, float_list);\n+\tJSON_MAP_SET(m, defaults, string_list);\n+\t_publish_message(\"input-dialog\", json_util::from_map(m));\n+\n+\treturn input_dialog_queue.dequeue();\n+}\n+\n+std::list<std::string> octave_json_link::file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {\n+\t// Triggered in \"uiputfile\", \"uigetfile\", and \"uigetdir\"\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, filter, filter_list);\n+\tJSON_MAP_SET(m, title, string);\n+\tJSON_MAP_SET(m, filename, string);\n+\tJSON_MAP_SET(m, pathname, string);\n+\tJSON_MAP_SET(m, multimode, string);\n+\t_publish_message(\"file-dialog\", json_util::from_map(m));\n+\t\n+\treturn file_dialog_queue.dequeue();\n+}\n+\n+void octave_json_link::update_path_dialog(void) {\n+\t// Triggered in \"rehash\"\n+\t_publish_message(\"update-path-dialog\", json_util::empty());\n+}\n+\n+int octave_json_link::debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {\n+\t// This endpoint might be unused?  (No references)\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, file, string);\n+\tJSON_MAP_SET(m, dir, string);\n+\tJSON_MAP_SET(m, addpath_option, boolean);\n+\t_publish_message(\"debug-cd-or-addpath-error\", json_util::from_map(m));\n+\n+\treturn debug_cd_or_addpath_error_queue.dequeue();\n+}\n+\n+void octave_json_link::focus_window(const std::string win_name) {\n+\t// Triggered in \"commandhistory\", \"commandwindow\", \"filebrowser\", \"workspace\"\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, win_name, string);\n+\t_publish_message(\"focus-window\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::display_exception(const execution_exception& ee, bool beep) {\n+\t// Triggered in various places in libinterp\n+        std::ostringstream buf;\n+        ee.display (buf);\n+        std::string ee_str = buf.str();\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, ee_str, string);\n+\tJSON_MAP_SET(m, beep, boolean);\n+\t_publish_message(\"display-exception\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::display_warning(const std::string& id, const std::string& name, const std::string& message, const std::string& formatted) {\n+       \t// Redirected warnings from std::cerr\n+       \tJSON_MAP_T m;\n+       \tJSON_MAP_SET(m, id, string);\n+       \tJSON_MAP_SET(m, name, string);\n+       \tJSON_MAP_SET(m, message, string);\n+       \tJSON_MAP_SET(m, formatted, string);\n+\t_publish_message(\"display-warning\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::gui_status_update(const std::string& feature, const std::string& status) {\n+\t// Triggered in __profiler_enable__\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, feature, string);\n+\tJSON_MAP_SET(m, status, string);\n+\t_publish_message(\"gui-status-update\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::update_gui_lexer(void) {\n+\t// Triggered in \"load_packages_and_dependencies\"\n+\t_publish_message(\"update-gui-lexer\", json_util::empty());\n+}\n+\n+void octave_json_link::directory_changed(const std::string& dir) {\n+\t// This endpoint might be unused?  (References appear only in libgui)\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, dir, string);\n+\t_publish_message(\"change-directory\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::file_remove (const std::string& old_name, const std::string& new_name) {\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, old_name, string);\n+\tJSON_MAP_SET(m, new_name, string);\n+\t_publish_message(\"file-remove\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::file_renamed (bool status) {\n+\t// Called by \"unlink\", \"rmdir\", \"rename\"\n+\t_publish_message(\"file-renamed\", json_util::from_boolean(status));\n+}\n+\n+void octave_json_link::execute_command_in_terminal(const std::string& command) {\n+\t// This endpoint might be unused?  (References appear only in libgui)\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, command, string);\n+\t_publish_message(\"execute-command-in-terminal\", json_util::from_map(m));\n+}\n+\n+uint8NDArray octave_json_link::get_named_icon (const std::string& /* icon_name */) {\n+\t// Called from msgbox.m\n+\t// TODO: Implement request/response for this event\n+\tuint8NDArray retval;\n+\treturn retval;\n+}\n+\n+void octave_json_link::set_workspace(bool top_level, bool debug,\n+                         const symbol_info_list& ws,\n+                         bool update_variable_editor) {\n+\t// Triggered on every new line entry\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, top_level, boolean);\n+\tJSON_MAP_SET(m, debug, boolean);\n+\tJSON_MAP_SET(m, ws, symbol_info_list);\n+\tJSON_MAP_SET(m, update_variable_editor, boolean);\n+\t_publish_message(\"set-workspace\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::clear_workspace(void) {\n+\t// Triggered on \"clear\" command (but not \"clear all\" or \"clear foo\")\n+\t_publish_message(\"clear-workspace\", json_util::empty());\n+}\n+\n+void octave_json_link::set_history(const string_vector& hist) {\n+\t// Called at startup, possibly more?\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, hist, string_vector);\n+\t_publish_message(\"set-history\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::append_history(const std::string& hist_entry) {\n+\t// Appears to be tied to readline, if available\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, hist_entry, string);\n+\t_publish_message(\"append-history\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::clear_history(void) {\n+\t// Appears to be tied to readline, if available\n+\t_publish_message(\"clear-history\", json_util::empty());\n+}\n+\n+void octave_json_link::clear_screen(void) {\n+\t// Triggered by clc\n+\t_publish_message(\"clear-screen\", json_util::empty());\n+}\n+\n+void octave_json_link::pre_input_event(void) {\n+\t// noop\n+}\n+\n+void octave_json_link::post_input_event(void) {\n+\t// noop\n+}\n+\n+void octave_json_link::enter_debugger_event(const std::string& fcn_name, const std::string& fcn_file_name, int line) {\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, fcn_name, string);\n+\tJSON_MAP_SET(m, fcn_file_name, string);\n+\tJSON_MAP_SET(m, line, int);\n+\t_publish_message(\"enter-debugger-event\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::execute_in_debugger_event(const std::string& file, int line) {\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, file, string);\n+\tJSON_MAP_SET(m, line, int);\n+\t_publish_message(\"execute-in-debugger-event\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::exit_debugger_event(void) {\n+\t_publish_message(\"exit-debugger-event\", json_util::empty());\n+}\n+\n+void octave_json_link::update_breakpoint(bool insert, const std::string& file, int line, const std::string& cond) {\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, insert, boolean);\n+\tJSON_MAP_SET(m, file, string);\n+\tJSON_MAP_SET(m, line, int);\n+\tJSON_MAP_SET(m, cond, string);\n+\t_publish_message(\"update-breakpoint\", json_util::from_map(m));\n+}\n+\n+// void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {\n+// \t// Triggered upon interpreter startup\n+// \tJSON_MAP_T m;\n+// \tJSON_MAP_SET(m, ps1, string);\n+// \tJSON_MAP_SET(m, ps2, string);\n+// \tJSON_MAP_SET(m, ps4, string);\n+// \t_publish_message(\"set-default-prompts\", json_util::from_map(m));\n+// }\n+\n+void octave_json_link::show_preferences(void) {\n+\t// Triggered on \"preferences\" command\n+\t_publish_message(\"show-preferences\", json_util::empty());\n+}\n+\n+std::string octave_json_link::gui_preference (const std::string& /* key */, const std::string& /* value */) {\n+\t// Used by Octave GUI?\n+\t// TODO: Implement request/response for this event\n+\tstd::string retval;\n+\treturn retval;\n+}\n+\n+bool octave_json_link::show_documentation(const std::string& file) {\n+\t// Triggered on \"doc\" command\n+\t_publish_message(\"show-doc\", json_util::from_string(file));\n+\treturn true;\n+}\n+\n+void octave_json_link::register_documentation (const std::string& file) {\n+\t// Triggered by the GUI documentation viewer?\n+\t_publish_message(\"register-doc\", json_util::from_string(file));\n+}\n+\n+void octave_json_link::unregister_documentation (const std::string& file) {\n+\t// Triggered by the GUI documentation viewer?\n+\t_publish_message(\"unregister-doc\", json_util::from_string(file));\n+}\n+\n+void octave_json_link::edit_variable (const std::string& name, const octave_value& /* val */) {\n+\t// Triggered on \"openvar\" command\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, name, string);\n+\t// TODO: val\n+\t_publish_message(\"edit-variable\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::show_static_plot(const std::string& term, const std::string& content) {\n+\t// Triggered on all plot commands with setenv(\"GNUTERM\",\"svg\")\n+\tint command_number = command_editor::current_command_number();\n+\tJSON_MAP_T m;\n+\tJSON_MAP_SET(m, term, string);\n+\tJSON_MAP_SET(m, content, string);\n+\tJSON_MAP_SET(m, command_number, int);\n+\t_publish_message(\"show-static-plot\", json_util::from_map(m));\n+}\n+\n+void octave_json_link::receive_message (const std::string& name, JSON_OBJECT_T jobj) {\n+\tif (name == \"cmd\" || name == \"request-input-answer\") {\n+\t\tstd::string answer = json_util::to_string(jobj);\n+\t\trequest_input_queue.enqueue(answer);\n+\t}\n+\telse if (name == \"request-url-answer\") {\n+\t\tstd::pair<bool, std::string> answer = json_util::to_bool_string_pair(jobj);\n+\t\trequest_url_queue.enqueue(answer);\n+\t}\n+\telse if (name == \"confirm-shutdown-answer\"){\n+\t\tbool answer = json_util::to_boolean(jobj);\n+\t\tconfirm_shutdown_queue.enqueue(answer);\n+\t}\n+\telse if (name == \"prompt-new-edit-file-answer\"){\n+\t\tbool answer = json_util::to_boolean(jobj);\n+\t\tprompt_new_edit_file_queue.enqueue(answer);\n+\t}\n+\telse if (name == \"message-dialog-answer\"){\n+\t\tint answer = json_util::to_int(jobj);\n+\t\tmessage_dialog_queue.enqueue(answer);\n+\t}\n+\telse if (name == \"question-dialog-answer\") {\n+\t\tstd::string answer = json_util::to_string(jobj);\n+\t\tquestion_dialog_queue.enqueue(answer);\n+\t}\n+\telse if (name == \"list-dialog-answer\") {\n+\t\tstd::pair<std::list<int>, int> answer = json_util::to_int_list_int_pair(jobj);\n+\t\tlist_dialog_queue.enqueue(answer);\n+\t}\n+\telse if (name == \"input-dialog-answer\") {\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\n+\t\tinput_dialog_queue.enqueue(answer);\n+\t}\n+\telse if (name == \"file-dialog-answer\") {\n+\t\tstd::list<std::string> answer = json_util::to_string_list(jobj);\n+\t\tfile_dialog_queue.enqueue(answer);\n+\t}\n+\telse if (name == \"debug-cd-or-addpath-error-answer\") {\n+\t\tint answer = json_util::to_int(jobj);\n+\t\tdebug_cd_or_addpath_error_queue.enqueue(answer);\n+\t}\n+\telse {\n+\t\tstd::cerr << \"warning: received unknown message: \" << name << std::endl;\n+\t}\n+}\n+\n+void octave_json_link::_publish_message(const std::string& name, JSON_OBJECT_T jobj) {\n+\t_json_main->publish_message(name, jobj);\n+}\n+\n+} // namespace octave\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/octave-json-link.h\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/libinterp/corefcn/octave-json-link.h\tSat Dec 30 13:51:34 2023 -0600\n@@ -0,0 +1,269 @@\n+/*\n+\n+Copyright (C) 2015-2016 Shane Carr\n+\n+This file is part of Octave.\n+\n+Octave is free software; you can redistribute it and/or modify it\n+under the terms of the GNU General Public License as published by the\n+Free Software Foundation; either version 3 of the License, or (at your\n+option) any later version.\n+\n+Octave is distributed in the hope that it will be useful, but WITHOUT\n+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n+for more details.\n+\n+You should have received a copy of the GNU General Public License\n+along with Octave; see the file COPYING.  If not, see\n+<http://www.gnu.org/licenses/>.\n+\n+*/\n+\n+#ifndef octave_json_link_h\n+#define octave_json_link_h\n+\n+#include <list>\n+#include <string>\n+\n+#include \"event-manager.h\"\n+#include \"json-util.h\"\n+#include \"oct-mutex.h\"\n+\n+class string_vector;\n+\n+namespace octave {\n+\n+// Circular reference\n+class json_main;\n+\n+// Thread-safe queue\n+template<typename T> class json_queue {\n+public:\n+  json_queue();\n+  ~json_queue();\n+\n+  void enqueue(const T& value);\n+  T dequeue();\n+  bool dequeue_to(T* destination);\n+\n+private:\n+  std::queue<T> _queue;\n+  mutex _mutex;\n+};\n+\n+class octave_json_link : public interpreter_events\n+{\n+\n+public:\n+\n+  octave_json_link (json_main* __json_main);\n+\n+  ~octave_json_link (void);\n+\n+  // TODO\n+  // void start_gui (bool gui_app = false) override;\n+  // void close_gui (void) override;\n+\n+  bool have_dialogs (void) const override;\n+\n+  std::string\n+  question_dialog (const std::string& msg, const std::string& title,\n+                      const std::string& btn1, const std::string& btn2,\n+                      const std::string& btn3, const std::string& btndef) override;\n+\n+  std::pair<std::list<int>, int>\n+  list_dialog (const std::list<std::string>& list,\n+                  const std::string& mode,\n+                  int width, int height,\n+                  const std::list<int>& initial_value,\n+                  const std::string& name,\n+                  const std::list<std::string>& prompt,\n+                  const std::string& ok_string,\n+                  const std::string& cancel_string) override;\n+\n+  std::list<std::string>\n+  input_dialog (const std::list<std::string>& prompt,\n+                   const std::string& title,\n+                   const std::list<float>& nr,\n+                   const std::list<float>& nc,\n+                   const std::list<std::string>& defaults) override;\n+\n+  std::list<std::string>\n+  file_dialog (const filter_list& filter, const std::string& title,\n+                  const std::string &filename, const std::string &pathname,\n+                  const std::string& multimode) override;\n+\n+  void update_path_dialog (void) override;\n+\n+  void show_preferences (void) override;\n+\n+  // TODO:\n+  // void apply_preferences (void) override;\n+\n+  // TODO:\n+  // void show_terminal_window (void) override;\n+\n+  bool show_documentation (const std::string& file) override;\n+\n+  // TODO:\n+  // void show_file_browser (void) override;\n+\n+  // TODO:\n+  // void show_command_history (void) override;\n+\n+  // TODO:\n+  // void show_workspace (void) override;\n+\n+  // TODO:\n+  // void show_community_news (int serial) override;\n+  // void show_release_notes (void) override;\n+\n+  bool edit_file (const std::string& file) override;\n+\n+  void edit_variable (const std::string& name, const octave_value& val) override;\n+\n+  std::string request_input (const std::string& prompt) override;\n+\n+  std::string request_url (const std::string& url, const std::list<std::string>& param, const std::string& action, bool& success) override;\n+\n+  void show_static_plot (const std::string& term, const std::string& content) override;\n+\n+  bool confirm_shutdown (void) override;\n+\n+  bool prompt_new_edit_file (const std::string& file) override;\n+\n+  int\n+  debug_cd_or_addpath_error (const std::string& file,\n+                                const std::string& dir,\n+                                bool addpath_option) override;\n+\n+  uint8NDArray get_named_icon (const std::string& icon_name) override;\n+\n+  std::string gui_preference (const std::string& key, const std::string& value) override;\n+\n+  bool copy_image_to_clipboard (const std::string& file) override;\n+\n+  void focus_window (const std::string win_name) override;\n+\n+  void execute_command_in_terminal (const std::string& command) override;\n+\n+  void register_documentation (const std::string& file) override;\n+\n+  void unregister_documentation (const std::string& file) override;\n+\n+  // TODO:\n+  // void interpreter_output (const std::string& msg) override;\n+\n+  void display_exception (const execution_exception& ee, bool beep) override;\n+\n+  void display_warning (const std::string& id, const std::string& name, const std::string& message, const std::string& formatted) override;\n+\n+  void gui_status_update (const std::string& feature, const std::string& status) override;\n+\n+  void update_gui_lexer (void) override;\n+\n+  void directory_changed (const std::string& dir) override;\n+\n+  void file_remove (const std::string& old_name, const std::string& new_name) override;\n+\n+  void file_renamed (bool) override;\n+\n+  void set_workspace (bool top_level, bool debug,\n+                         const symbol_info_list& ws,\n+                         bool update_variable_editor) override;\n+\n+  void clear_workspace (void) override;\n+\n+  // TODO:\n+  // void update_prompt (const std::string& prompt) override;\n+\n+  void set_history (const string_vector& hist) override;\n+\n+  void append_history (const std::string& hist_entry) override;\n+\n+  void clear_history (void) override;\n+\n+  void clear_screen (void) override;\n+\n+  void pre_input_event (void) override;\n+\n+  void post_input_event (void) override;\n+\n+  void enter_debugger_event (const std::string& fcn_name, const std::string& fcn_file_name, int line) override;\n+\n+  void execute_in_debugger_event (const std::string& file, int line) override;\n+\n+  void exit_debugger_event (void) override;\n+\n+  void update_breakpoint (bool insert,\n+                             const std::string& file, int line,\n+                             const std::string& cond) override;\n+\n+  // TODO:\n+  // void interpreter_interrupted (void) override;\n+\n+  // Custom methods\n+  void receive_message (const std::string& name, JSON_OBJECT_T jobj);\n+\n+private:\n+  json_main* _json_main;\n+  void _publish_message (const std::string& name, JSON_OBJECT_T jobj);\n+\n+  // Queues\n+  json_queue<std::string> request_input_queue;\n+  json_queue<std::pair<bool, std::string> > request_url_queue;\n+  json_queue<bool> confirm_shutdown_queue;\n+  json_queue<bool> prompt_new_edit_file_queue;\n+  json_queue<int> message_dialog_queue;\n+  json_queue<std::string> question_dialog_queue;\n+  json_queue<std::pair<std::list<int>, int> > list_dialog_queue;\n+  json_queue<std::list<std::string> > input_dialog_queue;\n+  json_queue<std::list<std::string> > file_dialog_queue;\n+  json_queue<int> debug_cd_or_addpath_error_queue;\n+};\n+\n+// Template classes require definitions in the header file...\n+\n+template<typename T>\n+json_queue<T>::json_queue() { }\n+\n+template<typename T>\n+json_queue<T>::~json_queue() { }\n+\n+template<typename T>\n+void json_queue<T>::enqueue(const T& value) {\n+  _mutex.lock();\n+  _queue.push(value);\n+  _mutex.cond_signal();\n+  _mutex.unlock();\n+}\n+\n+template<typename T>\n+T json_queue<T>::dequeue() {\n+  _mutex.lock();\n+  while (_queue.empty()) {\n+    _mutex.cond_wait();\n+  }\n+  T value = _queue.front();\n+  _queue.pop();\n+  _mutex.unlock();\n+  return value;\n+}\n+\n+template<typename T>\n+bool json_queue<T>::dequeue_to(T* destination) {\n+  _mutex.lock();\n+  bool retval = false;\n+  if (!_queue.empty()) {\n+    retval = true;\n+    *destination = _queue.front();\n+    _queue.pop();\n+  }\n+  _mutex.unlock();\n+  return retval;\n+}\n+\n+} // namespace octave\n+  \n+#endif\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/syscalls.cc\n--- a/libinterp/corefcn/syscalls.cc\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/corefcn/syscalls.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -150,9 +150,8 @@\n @deftypefn {} {[@var{err}, @var{msg}] =} exec (@var{file}, @var{args})\n Replace current process with a new process.\n \n-Calling @code{exec} without first calling @code{fork} will terminate your\n-current Octave process and replace it with the program named by @var{file}.\n-For example,\n+Calling @code{exec} will terminate your current Octave process and replace\n+it with the program named by @var{file}. For example,\n \n @example\n exec (\"ls\", \"-l\")\n@@ -475,42 +474,6 @@\n   return retval;\n }\n \n-DEFMETHODX (\"fork\", Ffork, interp, args, ,\n-            doc: /* -*- texinfo -*-\n-@deftypefn {} {[@var{pid}, @var{msg}] =} fork ()\n-Create a copy of the current process.\n-\n-Fork can return one of the following values:\n-\n-@table @asis\n-@item > 0\n-You are in the parent process.  The value returned from @code{fork} is the\n-process id of the child process.  You should probably arrange to wait for\n-any child processes to exit.\n-\n-@item 0\n-You are in the child process.  You can call @code{exec} to start another\n-process.  If that fails, you should probably call @code{exit}.\n-\n-@item < 0\n-The call to @code{fork} failed for some reason.  You must take evasive\n-action.  A system dependent error message will be waiting in @var{msg}.\n-@end table\n-@end deftypefn */)\n-{\n-  if (args.length () != 0)\n-    print_usage ();\n-\n-  if (interp.at_top_level ())\n-    error (\"fork: cannot be called from command line\");\n-\n-  std::string msg;\n-\n-  pid_t pid = sys::fork (msg);\n-\n-  return ovl (pid, msg);\n-}\n-\n DEFUNX (\"getpgrp\", Fgetpgrp, args, ,\n         doc: /* -*- texinfo -*-\n @deftypefn {} {pgid =} getpgrp ()\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/sysdep.cc\n--- a/libinterp/corefcn/sysdep.cc\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/corefcn/sysdep.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -75,6 +75,7 @@\n #include \"defun.h\"\n #include \"error.h\"\n #include \"errwarn.h\"\n+#include \"event-manager.h\"\n #include \"input.h\"\n #include \"interpreter-private.h\"\n #include \"octave.h\"\n@@ -719,7 +720,7 @@\n \n // Read one character from the terminal.\n \n-int kbhit (bool wait)\n+int kbhit (const std::string& prompt, bool wait)\n {\n #if defined (HAVE__KBHIT) && defined (HAVE__GETCH)\n   // This essentially means we are on a Windows system.\n@@ -746,13 +747,24 @@\n \n   set_interrupt_handler (saved_interrupt_handler, false);\n \n-  int c = std::cin.get ();\n+  int c;\n+  event_manager& evmgr = __get_event_manager__ ();\n+  if (evmgr.request_input_enabled ()) {\n+    std::string line = evmgr.request_input (prompt);\n+    if (line.length() >= 1) {\n+      c = line.at(0);\n+    } else {\n+      c = '\\n';\n+    }\n+  } else {\n+    c = std::cin.get ();\n \n-  if (std::cin.fail () || std::cin.eof ())\n-    {\n-      std::cin.clear ();\n-      clearerr (stdin);\n-    }\n+    if (std::cin.fail () || std::cin.eof ())\n+      {\n+        std::cin.clear ();\n+        clearerr (stdin);\n+      }\n+  }\n \n   // Restore it, enabling system call restarts (if possible).\n   set_interrupt_handler (saved_interrupt_handler, true);\n@@ -810,6 +822,9 @@\n {\n   bool skip_redisplay = true;\n \n+  octave::event_manager& evmgr = octave::__get_event_manager__ ();\n+  evmgr.clear_screen();\n+\n   command_editor::clear_screen (skip_redisplay);\n \n   return ovl ();\n@@ -1244,7 +1259,7 @@\n \n   Fdrawnow (interp);\n \n-  int c = kbhit (args.length () == 0);\n+  int c = kbhit (\"kbhit>\", args.length () == 0);\n \n   if (c == -1)\n     c = 0;\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/sysdep.h\n--- a/libinterp/corefcn/sysdep.h\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/corefcn/sysdep.h\tSat Dec 30 13:51:34 2023 -0600\n@@ -49,7 +49,7 @@\n \n extern OCTINTERP_API int pclose (FILE *f);\n \n-extern OCTINTERP_API int kbhit (bool wait = true);\n+extern OCTINTERP_API int kbhit (const std::string& prompt, bool wait);\n \n extern OCTINTERP_API std::string get_P_tmpdir (void);\n \ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/corefcn/utils.cc\n--- a/libinterp/corefcn/utils.cc\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/corefcn/utils.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -1556,7 +1556,7 @@\n           if (do_graphics_events)\n             gh_mgr.process_events ();\n \n-          c = kbhit (false);\n+          c = kbhit (\"press enter to continue\", false);\n         }\n     }\n   else\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/dldfcn/__init_gnuplot__.cc\n--- a/libinterp/dldfcn/__init_gnuplot__.cc\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/dldfcn/__init_gnuplot__.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -65,25 +65,6 @@\n   gnuplot_graphics_toolkit (octave::interpreter& interp)\n     : octave::base_graphics_toolkit (\"gnuplot\"), m_interpreter (interp)\n   {\n-    static bool warned = false;\n-\n-    if (! warned)\n-      {\n-        warning_with_id\n-        (\"Octave:gnuplot-graphics\",\n-         \"using the gnuplot graphics toolkit is discouraged\\n\\\n-\\n\\\n-The gnuplot graphics toolkit is not actively maintained and has a number\\n\\\n-of limitations that are unlikely to be fixed.  Communication with gnuplot\\n\\\n-uses a one-directional pipe and limited information is passed back to the\\n\\\n-Octave interpreter so most changes made interactively in the plot window\\n\\\n-will not be reflected in the graphics properties managed by Octave.  For\\n\\\n-example, if the plot window is closed with a mouse click, Octave will not\\n\\\n-be notified and will not update its internal list of open figure windows.\\n\\\n-The qt toolkit is recommended instead.\\n\");\n-\n-        warned = true;\n-      }\n   }\n \n   ~gnuplot_graphics_toolkit (void) = default;\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/octave.cc\n--- a/libinterp/octave.cc\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/octave.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -186,6 +186,16 @@\n         case LINE_EDITING_OPTION:\n           m_forced_line_editing = m_line_editing = true;\n           break;\n+ \n+        case JSON_SOCK_OPTION:\n+          if (octave_optarg_wrapper ())\n+            m_json_sock_path = octave_optarg_wrapper ();\n+          break;\n+\n+        case JSON_MAX_LEN_OPTION:\n+          if (octave_optarg_wrapper ())\n+            m_json_max_message_length = strtol(octave_optarg_wrapper (), NULL, 10);\n+          break;\n \n         case NO_GUI_OPTION:\n           m_gui = false;\n@@ -420,6 +430,14 @@\n   sysdep_init ();\n }\n \n+bool application::link_enabled (void) const\n+{\n+  if (m_interpreter) {\n+    event_manager& evmgr = m_interpreter->get_event_manager ();\n+    return evmgr.enabled();\n+  } else return false;\n+}\n+\n int cli_application::execute (void)\n {\n   interpreter& interp = create_interpreter ();\n@@ -442,7 +460,7 @@\n   // FIXME: This isn't quite right, it just says that we intended to\n   // start the GUI, not that it is actually running.\n \n-  return ovl (application::is_gui_running ());\n+  return ovl (application::is_link_enabled ());\n }\n \n /*\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/octave.h\n--- a/libinterp/octave.h\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/octave.h\tSat Dec 30 13:51:34 2023 -0600\n@@ -85,6 +85,8 @@\n   std::string info_file (void) const { return m_info_file; }\n   std::string info_program (void) const { return m_info_program; }\n   std::string texi_macros_file (void) const {return m_texi_macros_file; }\n+  std::string json_sock_path (void) const { return m_json_sock_path; }\n+  int json_max_message_length (void) const { return m_json_max_message_length; }\n   string_vector all_args (void) const { return m_all_args; }\n   string_vector remaining_args (void) const { return m_remaining_args; }\n \n@@ -117,6 +119,8 @@\n   void info_file (const std::string& arg) { m_info_file = arg; }\n   void info_program (const std::string& arg) { m_info_program = arg; }\n   void texi_macros_file (const std::string& arg) { m_texi_macros_file = arg; }\n+  void json_sock_path (const std::string& arg) { m_json_sock_path = arg; }\n+  void json_max_message_length (int arg) { m_json_max_message_length = arg; }\n   void all_args (const string_vector& arg) { m_all_args = arg; }\n   void remaining_args (const string_vector& arg) { m_remaining_args = arg; }\n \n@@ -225,6 +229,14 @@\n   // (--texi-macros-file)\n   std::string m_texi_macros_file;\n \n+  // The value for \"JSON_SOCK\" specified on the command line.\n+  // (--json-sock)\n+  std::string m_json_sock_path;\n+\n+  // The maximum message length; valid only if \"JSON_SOCK\" is specified.\n+  // (--json-max-len)\n+  int m_json_max_message_length = 0;\n+\n   // All arguments passed to the argc, argv constructor.\n   string_vector m_all_args;\n \n@@ -238,6 +250,7 @@\n // both) of them...\n \n class interpreter;\n+class event_manager;\n \n // Base class for an Octave application.\n \n@@ -287,6 +300,8 @@\n   virtual bool gui_running (void) const { return false; }\n   virtual void gui_running (bool) { }\n \n+  bool link_enabled (void) const;\n+\n   void program_invocation_name (const std::string& nm)\n   { m_program_invocation_name = nm; }\n \n@@ -320,6 +335,11 @@\n     return s_instance ? s_instance->gui_running () : false;\n   }\n \n+  static bool is_link_enabled (void)\n+  {\n+    return s_instance ? s_instance->link_enabled () : false;\n+  }\n+\n   // Convenience functions.\n \n   static bool forced_interactive (void);\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/options.h\n--- a/libinterp/options.h\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/options.h\tSat Dec 30 13:51:34 2023 -0600\n@@ -46,17 +46,19 @@\n #define IMAGE_PATH_OPTION 7\n #define INFO_FILE_OPTION 8\n #define INFO_PROG_OPTION 9\n-#define LINE_EDITING_OPTION 10\n-#define NO_GUI_OPTION 11\n-#define NO_GUI_LIBS_OPTION 12\n-#define NO_INIT_FILE_OPTION 13\n-#define NO_INIT_PATH_OPTION 14\n-#define NO_LINE_EDITING_OPTION 15\n-#define NO_SITE_FILE_OPTION 16\n-#define PERSIST_OPTION 17\n-#define SERVER_OPTION 18\n-#define TEXI_MACROS_FILE_OPTION 19\n-#define TRADITIONAL_OPTION 20\n+#define JSON_SOCK_OPTION 10\n+#define JSON_MAX_LEN_OPTION 11\n+#define LINE_EDITING_OPTION 12\n+#define NO_GUI_OPTION 13\n+#define NO_GUI_LIBS_OPTION 14\n+#define NO_INIT_FILE_OPTION 15\n+#define NO_INIT_PATH_OPTION 16\n+#define NO_LINE_EDITING_OPTION 17\n+#define NO_SITE_FILE_OPTION 18\n+#define PERSIST_OPTION 19\n+#define SERVER_OPTION 20\n+#define TEXI_MACROS_FILE_OPTION 21\n+#define TRADITIONAL_OPTION 22\n struct octave_getopt_options long_opts[] =\n {\n   { \"braindead\",                octave_no_arg,       nullptr, TRADITIONAL_OPTION },\n@@ -74,6 +76,8 @@\n   { \"info-file\",                octave_required_arg, nullptr, INFO_FILE_OPTION },\n   { \"info-program\",             octave_required_arg, nullptr, INFO_PROG_OPTION },\n   { \"interactive\",              octave_no_arg,       nullptr, 'i' },\n+  { \"json-sock\",                octave_required_arg, nullptr, JSON_SOCK_OPTION },\n+  { \"json-max-len\",             octave_required_arg, nullptr, JSON_MAX_LEN_OPTION },\n   { \"line-editing\",             octave_no_arg,       nullptr, LINE_EDITING_OPTION },\n   { \"no-gui\",                   octave_no_arg,       nullptr, NO_GUI_OPTION },\n   { \"no-gui-libs\",              octave_no_arg,       nullptr, NO_GUI_LIBS_OPTION },\ndiff -r 78c13a2594f3 -r d2250ae9bddd libinterp/usage.h\n--- a/libinterp/usage.h\tSun Nov 05 12:45:40 2023 -0500\n+++ b/libinterp/usage.h\tSat Dec 30 13:51:34 2023 -0600\n@@ -37,11 +37,11 @@\n   \"octave [-HVWdfhiqvx] [--debug] [--doc-cache-file file] [--echo-commands]\\n\\\n        [--eval CODE] [--exec-path path] [--experimental-terminal-widget]\\n\\\n        [--gui] [--help] [--image-path path] [--info-file file]\\n\\\n-       [--info-program prog] [--interactive] [--line-editing] [--no-gui]\\n\\\n-       [--no-history] [--no-init-file] [--no-init-path] [--no-line-editing]\\n\\\n-       [--no-site-file] [--no-window-system] [--norc] [-p path]\\n\\\n-       [--path path] [--persist] [--server] [--silent] [--traditional]\\n\\\n-       [--verbose] [--version] [file]\";\n+       [--info-program prog] [--interactive] [--json-sock] [--json-max-len] \\n\\\n+       [--line-editing] [--no-gui] [--no-history] [--no-init-file] \\n\\\n+       [--no-init-path] [--no-line-editing] [--no-site-file] \\n\\\n+       [--no-window-system] [--norc] [-p path] [--path path] [--persist] \\n\\\n+       [--server] [--silent] [--traditional] [--verbose] [--version] [file]\";\n \n // Usage message with extra help.\n \n@@ -69,6 +69,8 @@\n   --info-file FILE        Use top-level info file FILE.\\n\\\n   --info-program PROGRAM  Use PROGRAM for reading info files.\\n\\\n   --interactive, -i       Force interactive behavior.\\n\\\n+  --json-sock PATH        Listen to and publish events on this UNIX socket.\\n\\\n+  --json-max-len LEN      Suppress JSON messages greater than LEN bytes.\\n\\\n   --line-editing          Force readline use for command-line editing.\\n\\\n   --no-gui                Disable the graphical user interface.\\n\\\n   --no-history, -H        Don't save commands to the history list\\n\\\ndiff -r 78c13a2594f3 -r d2250ae9bddd liboctave/util/oct-mutex.cc\n--- a/liboctave/util/oct-mutex.cc\tSun Nov 05 12:45:40 2023 -0500\n+++ b/liboctave/util/oct-mutex.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -58,6 +58,18 @@\n   return false;\n }\n \n+  void\n+  base_mutex::cond_wait (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n+  void\n+  base_mutex::cond_signal (void)\n+  {\n+    (*current_liboctave_error_handler) (\"mutex not supported on this platform\");\n+  }\n+\n #if defined (OCTAVE_USE_WINDOWS_API)\n \n class\n@@ -68,11 +80,13 @@\n     : base_mutex ()\n   {\n     InitializeCriticalSection (&cs);\n+    InitializeConditionVariable (&cv);\n   }\n \n   ~w32_mutex (void)\n   {\n     DeleteCriticalSection (&cs);\n+    // no need to delete cv: http://stackoverflow.com/a/28981408/1407170\n   }\n \n   void lock (void)\n@@ -90,8 +104,19 @@\n     return (TryEnterCriticalSection (&cs) != 0);\n   }\n \n+  void cond_wait (void)\n+  {\n+    SleepConditionVariableCS (&cv, &cs, INFINITE);\n+  }\n+\n+  void cond_signal (void)\n+  {\n+    WakeConditionVariable (&cv);\n+  }\n+\n private:\n   CRITICAL_SECTION cs;\n+  CONDITION_VARIABLE cv;\n };\n \n static DWORD thread_id = 0;\n@@ -123,11 +148,18 @@\n     pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);\n     pthread_mutex_init (&m_pm, &attr);\n     pthread_mutexattr_destroy (&attr);\n+\n+    pthread_condattr_t condattr;\n+\n+    pthread_condattr_init (&condattr);\n+    pthread_cond_init (&condv, &condattr);\n+    pthread_condattr_destroy (&condattr);\n   }\n \n   ~pthread_mutex (void)\n   {\n     pthread_mutex_destroy (&m_pm);\n+    pthread_cond_destroy (&condv);\n   }\n \n   void lock (void)\n@@ -145,8 +177,19 @@\n     return (pthread_mutex_trylock (&m_pm) == 0);\n   }\n \n+  void cond_wait (void)\n+  {\n+    pthread_cond_wait (&condv, &m_pm);\n+  }\n+\n+  void cond_signal (void)\n+  {\n+    pthread_cond_signal (&condv);\n+  }\n+\n private:\n   pthread_mutex_t m_pm;\n+  pthread_cond_t condv;\n };\n \n static pthread_t thread_id = 0;\ndiff -r 78c13a2594f3 -r d2250ae9bddd liboctave/util/oct-mutex.h\n--- a/liboctave/util/oct-mutex.h\tSun Nov 05 12:45:40 2023 -0500\n+++ b/liboctave/util/oct-mutex.h\tSat Dec 30 13:51:34 2023 -0600\n@@ -50,6 +50,10 @@\n   virtual void unlock (void);\n \n   virtual bool try_lock (void);\n+\n+  virtual void cond_wait (void);\n+\n+  virtual void cond_signal (void);\n };\n \n class\n@@ -80,6 +84,16 @@\n     return m_rep->try_lock ();\n   }\n \n+  void cond_wait (void)\n+  {\n+    m_rep->cond_wait ();\n+  }\n+\n+  void cond_signal (void)\n+  {\n+    m_rep->cond_signal ();\n+  }\n+\n protected:\n   std::shared_ptr<base_mutex> m_rep;\n };\ndiff -r 78c13a2594f3 -r d2250ae9bddd liboctave/util/url-transfer.cc\n--- a/liboctave/util/url-transfer.cc\tSun Nov 05 12:45:40 2023 -0500\n+++ b/liboctave/util/url-transfer.cc\tSat Dec 30 13:51:34 2023 -0600\n@@ -31,6 +31,7 @@\n #include <iomanip>\n #include <iostream>\n \n+#include \"base64-wrappers.h\"\n #include \"dir-ops.h\"\n #include \"file-ops.h\"\n #include \"file-stat.h\"\n@@ -228,6 +229,8 @@\n   return file_list;\n }\n \n+\n+\n #if defined (HAVE_CURL)\n \n static int\n@@ -924,18 +927,6 @@\n #  define REP_CLASS base_url_transfer\n #endif\n \n-url_transfer::url_transfer (void) : m_rep (new REP_CLASS ())\n-{ }\n-\n-url_transfer::url_transfer (const std::string& host, const std::string& user,\n-                            const std::string& passwd, std::ostream& os)\n-  : m_rep (new REP_CLASS (host, user, passwd, os))\n-{ }\n-\n-url_transfer::url_transfer (const std::string& url, std::ostream& os)\n-  : m_rep (new REP_CLASS (url, os))\n-{ }\n-\n #undef REP_CLASS\n \n OCTAVE_END_NAMESPACE(octave)\ndiff -r 78c13a2594f3 -r d2250ae9bddd scripts/help/__unimplemented__.m\n--- a/scripts/help/__unimplemented__.m\tSun Nov 05 12:45:40 2023 -0500\n+++ b/scripts/help/__unimplemented__.m\tSat Dec 30 13:51:34 2023 -0600\n@@ -45,7 +45,30 @@\n \n   is_matlab_function = true;\n \n+  ## First look at the package metadata\n+  # To generate package_metadata.mat, run: packages={}; for p=pkg('list'); packages={packages{:} pkg('describe', '-verbose', p{1}.name){:}}; endfor; save('/usr/local/share/octave/site/m/package_metadata.mat', 'packages');\n+  found_in_package_metadata = false;\n+  try\n+    vars = load(\"/usr/local/share/octave/site/m/package_metadata.mat\");\n+    for lvl1 = vars.packages\n+      for lvl2 = lvl1{1}.provides\n+        for lvl3 = lvl2{1}.functions\n+          if strcmp(fcn, lvl3{1})\n+            txt = check_package(fcn, lvl1{1}.name);\n+            found_in_package_metadata = true;\n+            break;\n+          endif\n+        endfor\n+        if found_in_package_metadata, break; endif\n+      endfor\n+      if found_in_package_metadata, break; endif\n+    endfor\n+  catch err\n+    warning(err)\n+  end_try_catch\n+\n   ## Some smarter cases, add more as needed.\n+  if !found_in_package_metadata\n   switch (fcn)\n     case {\"avifile\", \"aviinfo\", \"aviread\"}\n       txt = [\"Basic video file support is provided in the video package.  \", ...\n@@ -524,6 +547,7 @@\n         txt = \"\";\n       endif\n   endswitch\n+  endif\n \n   if (is_matlab_function)\n     txt = [txt, \"\\n\\n@noindent\\nPlease read \", ...\n@@ -566,13 +590,13 @@\n       endfor\n       txt = sprintf (\"%s but has not yet been implemented.\", txt);\n     case \"not loaded\",\n-      txt = sprintf ([\"%s which you have installed but not loaded.  To \", ...\n-                      \"load the package, run 'pkg load %s' from the \", ...\n-                      \"Octave prompt.\"], txt, name);\n+      txt = sprintf ([\"%s, which you have installed but not loaded.\\n\\n\", ...\n+                      \"Run `pkg load %s' to use `%s'.\"], ...\n+                     txt, name, fcn);\n     otherwise\n       ## this includes \"not installed\" and anything else if pkg changes\n       ## the output of describe\n-      txt = sprintf (\"%s which seems to not be installed in your system.\", txt);\n+      txt = sprintf (\"%s, which seems to not be installed in your system.\", txt);\n   endswitch\n \n endfunction\ndiff -r 78c13a2594f3 -r d2250ae9bddd scripts/plot/util/__gnuplot_drawnow__.m\n--- a/scripts/plot/util/__gnuplot_drawnow__.m\tSun Nov 05 12:45:40 2023 -0500\n+++ b/scripts/plot/util/__gnuplot_drawnow__.m\tSat Dec 30 13:51:34 2023 -0600\n@@ -32,9 +32,84 @@\n \n   if (nargin < 1 || nargin == 2)\n     print_usage ();\n-  endif\n+\n+  elseif (nargin >= 3 && nargin <= 4)\n+    ## Write the plot to the given file (e.g., via the \"print\" command)\n+    if (nargin == 5)\n+      __gnuplot_draw_to_file__ (h, term, file, debug_file);\n+    else\n+      __gnuplot_draw_to_file__ (h, term, file);\n+    endif\n+\n+  else  # nargin == 1\n+    ##  Plot to terminal and/or static (e.g., via the \"plot\" command)\n+    plot_stream = get (h, \"__plot_stream__\");\n+    if (isempty (plot_stream))\n+      plot_stream = __gnuplot_open_stream__ (2, h);\n+      new_stream = true;\n+    else\n+      new_stream = false;\n+    endif\n+    term = gnuplot_default_term (plot_stream);\n+\n+    ## There are a few options for how we can proceed.\n+    ## In most cases, we will tell GNUPLOT to put the plot in its terminal.\n+    ## If we have no display, we want to use the \"dumb\" terminal.\n+    ## Octave Link may request that we send the plot as an event.\n+    ## The latter two cases require plotting to a temp file.\n+\n+    should_plot_to_terminal = (\n+      !strcmp (term, \"dumb\") && (\n+        __event_manager_plot_destination__ () == 0 ||\n+        __event_manager_plot_destination__ () == 2\n+      )\n+    );\n+\n+    if (should_plot_to_terminal)\n+      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n+      __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n+      fflush (plot_stream(1));\n+    endif\n \n-  if (nargin >= 3 && nargin <= 4)\n+    should_plot_to_temp_file = (\n+      strcmp (term, \"dumb\") ||\n+      __event_manager_plot_destination__ () == 1 ||\n+      __event_manager_plot_destination__ () == 2\n+    );\n+\n+    if (should_plot_to_temp_file)\n+      tmp_file = tempname ();\n+      __gnuplot_draw_to_file__ (h, term, tmp_file);\n+      fflush (plot_stream(1));\n+\n+      ## Read the temp file into memory and then delete it\n+      fid = fopen (tmp_file, 'r');\n+      while (fid < 0)\n+        fprintf (stderr, \"🛈 Waiting for plot to finish… ⏳\\n\");\n+        pause (0.5);\n+        fid = fopen (tmp_file, 'r');\n+      endwhile\n+      [a, count] = fscanf (fid, '%c', Inf);\n+      fclose (fid);\n+      unlink (tmp_file);\n+\n+      ## What to do with the plot data?\n+      if (count > 0)\n+        if (a(1) == 12)\n+          a = a(2:end);  # avoid ^L at the beginning\n+        endif\n+        if strcmp (term, \"dumb\")\n+          puts (a);\n+        else\n+          __event_manager_show_static_plot__ (term, a);\n+        endif\n+      endif\n+    endif\n+\n+  endif\n+endfunction\n+\n+function __gnuplot_draw_to_file__ (h, term, file, debug_file)\n     ## Produce various output formats, or redirect gnuplot stream to a\n     ## debug file.\n     plot_stream = [];\n@@ -70,44 +145,6 @@\n         fclose (fid);\n       endif\n     end_unwind_protect\n-  else  # nargin == 1\n-    ##  Graphics terminal for display.\n-    plot_stream = get (h, \"__plot_stream__\");\n-    if (isempty (plot_stream))\n-      plot_stream = __gnuplot_open_stream__ (2, h);\n-      new_stream = true;\n-    else\n-      new_stream = false;\n-    endif\n-    term = gnuplot_default_term (plot_stream);\n-    if (strcmp (term, \"dumb\"))\n-      ## popen2 eats stdout of gnuplot, use temporary file instead\n-      dumb_tmp_file = tempname ();\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h,\n-                                   term, dumb_tmp_file);\n-    else\n-      enhanced = gnuplot_set_term (plot_stream(1), new_stream, h, term);\n-    endif\n-    __gnuplot_draw_figure__ (h, plot_stream(1), enhanced);\n-    fflush (plot_stream(1));\n-    if (strcmp (term, \"dumb\"))\n-      fid = -1;\n-      while (fid < 0)\n-        pause (0.1);\n-        fid = fopen (dumb_tmp_file, 'r');\n-      endwhile\n-      ## reprint the plot on screen\n-      [a, count] = fscanf (fid, '%c', Inf);\n-      fclose (fid);\n-      if (count > 0)\n-        if (a(1) == 12)\n-          a = a(2:end);  # avoid ^L at the beginning\n-        endif\n-        puts (a);\n-      endif\n-      unlink (dumb_tmp_file);\n-    endif\n-  endif\n \n endfunction\n \n"
  },
  {
    "path": "client/.bowerrc",
    "content": "{\n  \"directory\": \"app/vendor\"\n}"
  },
  {
    "path": "client/.gitignore",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\napp/vendor/\napp/css/\n.idea/\n.sass-cache/\n_old/\nconfig.json\n"
  },
  {
    "path": "client/.npmrc",
    "content": "# Please keep this in sync with all other .npmrc files\n# SEE: https://github.com/npm/feedback/discussions/864\ninstall-links=false\n"
  },
  {
    "path": "client/COPYING",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "client/Gruntfile.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst fs = require(\"fs\");\nconst config = require(\"@oo/shared\").config;\n\nfunction getCssTimestamp() {\n\treturn fs.statSync(\"dist/css/themes/fire.css\").mtime.valueOf();\n}\n\nfunction getJsTimestamp() {\n\treturn fs.statSync(\"dist/js/app.js\").mtime.valueOf();\n}\n\nfunction getFileUtf8(filepath) {\n\treturn function() {\n\t\treturn fs.readFileSync(filepath).toString(\"utf-8\");\n\t};\n}\n\nmodule.exports = function (grunt) {\n\tgrunt.loadNpmTasks(\"grunt-contrib-requirejs\");\n\tgrunt.loadNpmTasks(\"grunt-contrib-stylus\");\n\tgrunt.loadNpmTasks(\"grunt-contrib-watch\");\n\tgrunt.loadNpmTasks(\"grunt-contrib-copy\");\n\tgrunt.loadNpmTasks(\"grunt-regex-replace\");\n\tgrunt.loadNpmTasks(\"grunt-contrib-uglify\");\n\tgrunt.loadNpmTasks(\"grunt-sync\");\n\n\tgrunt.initConfig({\n\t\tpkg: grunt.file.readJSON(\"package.json\"),\n\t\trequirejs: {\n\t\t\tcompile: {\n\t\t\t\toptions: {\n\t\t\t\t\tbaseUrl: \"app\",\n\t\t\t\t\tmainConfigFile: \"app/main.js\",\n\t\t\t\t\tout: \"dist/js/app.js\",\n\t\t\t\t\tname: \"js/app\",\n\t\t\t\t\toptimize: \"uglify2\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tstylus: {\n\t\t\tdev: {\n\t\t\t\toptions: {\n\t\t\t\t\tcompress: false,\n\t\t\t\t},\n\t\t\t\tfiles: [\n\t\t\t\t\t{\n\t\t\t\t\t\texpand: true,\n\t\t\t\t\t\tcwd: \"app/styl/themes/\" + config.client.theme_collection,\n\t\t\t\t\t\tsrc: [\"*.styl\"],\n\t\t\t\t\t\tdest: \"app/css/themes\",\n\t\t\t\t\t\text: \".css\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t},\n\t\t\tdist: {\n\t\t\t\toptions: {\n\t\t\t\t\tcompress: true,\n\t\t\t\t},\n\t\t\t\tfiles: [\n\t\t\t\t\t{\n\t\t\t\t\t\texpand: true,\n\t\t\t\t\t\tcwd: \"app/styl/themes/\" + config.client.theme_collection,\n\t\t\t\t\t\tsrc: [\"*.styl\"],\n\t\t\t\t\t\tdest: \"dist/css/themes\",\n\t\t\t\t\t\text: \".css\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t},\n\t\tuglify: {\n\t\t\trequirejs: {\n\t\t\t\tfiles: {\n\t\t\t\t\t\"dist/js/require.js\": [\"app/vendor/requirejs/require.js\"],\n\t\t\t\t\t\"dist/js/runtime.js\": [\"app/js/runtime.js\"],\n\t\t\t\t\t\"dist/js/modernizr-201406b.js\": [\"app/js/modernizr-201406b.js\"]\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tsync: {\n\t\t\tdist: {\n\t\t\t\tfiles: [{\n\t\t\t\t\tcwd: \"app\",\n\t\t\t\t\tsrc: [\n\t\t\t\t\t\t\"index.html\",\n\t\t\t\t\t\t\"privacy.txt\",\n\t\t\t\t\t\t\"compatibility.html\",\n\t\t\t\t\t\t\"gdpr.html\",\n\t\t\t\t\t\t\"images/**\",\n\t\t\t\t\t\t\"!images/logo_collections/**\",\n\t\t\t\t\t\t\"!images/logos/**\",\n\t\t\t\t\t\t\"!images/flaticons/**\",\n\t\t\t\t\t\t\"!images/sanscons/**\",\n\t\t\t\t\t\t\"errors/**\",\n\t\t\t\t\t\t\"fonts/**\",\n\t\t\t\t\t\t\"js/gnuplot/**\"\n\t\t\t\t\t],\n\t\t\t\t\tdest: \"dist\"\n\t\t\t\t}, {\n\t\t\t\t\tcwd: \"app/images/logo_collections/\" + config.client.theme_collection,\n\t\t\t\t\tsrc: [\"**\"],\n\t\t\t\t\tdest: \"dist/images/logos\"\n\t\t\t\t}],\n\t\t\t\tverbose: true,\n\t\t\t\tcompareUsing: \"md5\"\n\t\t\t}\n\t\t},\n\t\t\"regex-replace\": {\n\t\t\tappcss: {\n\t\t\t\tsrc: [\"dist/js/app.js\", \"dist/js/runtime.js\"],\n\t\t\t\tactions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"css-timestamp\",\n\t\t\t\t\t\tsearch: \"\\\\{!css-timestamp!\\\\}\",\n\t\t\t\t\t\treplace: getCssTimestamp,\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"config-session-payloadMessageDelay\",\n\t\t\t\t\t\tsearch: \"parseInt\\\\(\\\"\\\\d+!config.session.payloadMessageDelay\\\"\\\\)\",\n\t\t\t\t\t\treplace: \"\" + config.session.payloadMessageDelay,\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"config-session-legalTime\",\n\t\t\t\t\t\tsearch: \"parseInt\\\\(\\\"\\\\d+!config.session.legalTime.guest\\\"\\\\)\",\n\t\t\t\t\t\treplace: \"\" + config.session.legalTime.guest,\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"config-session-countdownExtraTime\",\n\t\t\t\t\t\tsearch: \"parseInt\\\\(\\\"\\\\d+!config.session.countdownExtraTime\\\"\\\\)\",\n\t\t\t\t\t\treplace: \"\" + config.session.countdownExtraTime,\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"config-session-countdownRequestTime\",\n\t\t\t\t\t\tsearch: \"parseInt\\\\(\\\"\\\\d+!config.session.countdownRequestTime\\\"\\\\)\",\n\t\t\t\t\t\treplace: \"\" + config.session.countdownRequestTime,\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"config-client-welcome_back_ms\",\n\t\t\t\t\t\tsearch: \"parseInt\\\\(\\\"\\\\d+!config.client.welcome_back_ms\\\"\\\\)\",\n\t\t\t\t\t\treplace: \"\" + config.client.welcome_back_ms,\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"gacode\",\n\t\t\t\t\t\tsearch: \"\\\\{!gacode!\\\\}\",\n\t\t\t\t\t\treplace: config.client.gacode,\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"gtagid\",\n\t\t\t\t\t\tsearch: \"\\\\{!gtagid!\\\\}\",\n\t\t\t\t\t\treplace: config.client.gtagid,\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"uservoice\",\n\t\t\t\t\t\tsearch: \"\\\\{!uservoice!\\\\}\",\n\t\t\t\t\t\treplace: config.client.uservoice,\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"socket_io_path\",\n\t\t\t\t\t\tsearch: \"\\\\{!socket_io_path!\\\\}\",\n\t\t\t\t\t\treplace: config.front.socket_io_path,\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"file_history_url\",\n\t\t\t\t\t\tsearch: \"\\\\{!file_history_url!\\\\}\",\n\t\t\t\t\t\treplace: config.git.httpUrl,\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t},\n\t\t\thtml: {\n\t\t\t\tsrc: [\"dist/privacy.txt\"],\n\t\t\t\tactions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"privacy-txt\",\n\t\t\t\t\t\tsearch: \"<!-- Privacy TXT -->\",\n\t\t\t\t\t\treplace: getFileUtf8(\"app/privacy_standalone.txt\"),\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"eula-txt\",\n\t\t\t\t\t\tsearch: \"<!-- EULA TXT -->\",\n\t\t\t\t\t\treplace: getFileUtf8(\"app/eula.txt\"),\n\t\t\t\t\t\tflags: \"g\"\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tstylus: {\n\t\t\t\tfiles: \"app/styl/**/*.styl\",\n\t\t\t\ttasks: [\"stylus:dev\"]\n\t\t\t}\n\t\t}\n\t});\n\n\tgrunt.registerTask(\"build-data\", function() {\n\t\tgrunt.file.write(\"dist/build_data.json\", JSON.stringify({\n\t\t\tjsTimestamp: getJsTimestamp(),\n\t\t\tcssTimestamp: getCssTimestamp(),\n\t\t\tuseDistPaths: true,\n\t\t}));\n\t});\n\n\tgrunt.registerTask(\"default\", [\n\t\t\"requirejs\", // app.js\n\t\t\"uglify\", // runtime.js\n\t\t\"stylus:dist\",\n\t\t\"regex-replace:appcss\",\n\t\t\"sync\",\n\t\t\"regex-replace:html\",\n\t\t\"build-data\",\n\t]);\n\n\tgrunt.registerTask(\"index\", [\"sync\", \"regex-replace:html\"]);\n\n};\n"
  },
  {
    "path": "client/README.md",
    "content": "Octave Online Server: Client\n============================\n\nThis directory contains the web frontend code for Octave Online.\n\n## Installation\n\nBefore you can run any of the build scripts, you will need to install Node.JS.\n\nOnce Node.JS is installed, install the Octave Online Client dependencies like so:\n\n    npm install\n    npm run bower install\n\nNote:\n**npm** is used to manage the build system dependencies.\n**Bower** is a front-end dependency manager.\n\nFinally, you need to create a file *config.json* in this directory.  Use the same format as in the Octave Online Server: Back Server project.  If both projects are running on the same host, you can use a symlink.\n\n## Building\n\nTo build the distribution version of Octave Online Client, simply run Grunt:\n\n    npm run grunt\n\n**Grunt** is a build system, kind-of like **make** or **Ant** but for JavaScript.\n\nWhile you are developing, you can run `grunt watch` to automatically compile the TypeScript and SCSS when changes are made.\n"
  },
  {
    "path": "client/app/.eslintrc.yml",
    "content": "# Don't inherit ESLint configs:\n# The JavaScript here runs in the browser environment\n# and is designed to be ES5 compatible!\nroot: true\n\nenv:\n  browser: true\n  amd: true\nextends: 'eslint:recommended'\nparserOptions:\n  ecmaVersion: 5\nrules:\n  indent:\n    - error\n    - tab\n    - SwitchCase: 1\n  linebreak-style:\n    - error\n    - unix\n  quotes:\n    - error\n    - double\n  semi:\n    - error\n    - always\n  # Allow console.log\n  no-console:\n    - off\n  # Allow extra escapes in regular expressions\n  no-useless-escape:\n    - off\n"
  },
  {
    "path": "client/app/colab.html",
    "content": "<!DOCTYPE html>\n<!--\nCopyright © 2018, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or (at your\noption) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\nFITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License\nfor more details.\n\nYou should have received a copy of the GNU Affero General Public License along\nwith Octave Online Server.  If not, see <https://www.gnu.org/licenses/>.\n-->\n<html>\n<head>\n<title>Octave Online Colaboration Demo</title>\n\n<!-- Pre-Load Spinner Image -->\n<script>\nnew Image().src = \"images/spinner.svg\";\n</script>\n\n<style type=\"text/css\">\n#editor{\n\tdisplay: inline-block;\n\twidth: 300px;\n\theight: 100px;\n}\n</style>\n\n</head>\n<body>\n\n<div id=\"editor\"></div>\n\n<script type=\"text/javascript\" src=\"vendor/requirejs/require.js\"></script>\n<script type=\"text/javascript\" src=\"main.js\"></script>\n<script type=\"text/javascript\">\n\nvar wsId = \"test\";\nvar docId = \"prompt.test\";\n\nrequire([\"ace/ace\", \"js/ot-client\", \"socket.io\"], function(ace, Client, io){\n\tvar socket = io();\n\tsocket.emit(\"init\", {\n\t\taction: \"workspace\",\n\t\tinfo: wsId\n\t});\n\tsocket.on(\"init\", function(){\n\t\tsetTimeout(function(){\n\t\t\tsocket.emit(\"ot.subscribe\", {\n\t\t\t\tdocId: docId\n\t\t\t});\n\t\t}, 1000);\n\t});\n\tsocket.on(\"ot.doc\", function(obj){\n\t\tconsole.log(\"ot.doc\", obj);\n\n\t\tvar editor = ace.edit(\"editor\");\n\t\teditor.setValue(obj.content);\n\t\totClient = new Client(obj.rev);\n\t\totClient.attachEditor(editor);\n\t\totClient.addEventListener(\"send\", function(revision, operation){\n\t\t\tconsole.log(\"client send\", arguments);\n\t\t\tsocket.emit(\"ot.change\", {\n\t\t\t\tdocId: docId,\n\t\t\t\trev: revision,\n\t\t\t\top: operation\n\t\t\t});\n\t\t});\n\t\totClient.addEventListener(\"cursor\", function(cursor){\n\t\t\tconsole.log(\"client cursor\", arguments);\n\t\t\tsocket.emit(\"ot.cursor\", {\n\t\t\t\tdocId: docId,\n\t\t\t\tcursor: cursor\n\t\t\t});\n\t\t});\n\t\totClient.initWith(obj.rev, obj.content);\n\n\t\tsocket.on(\"ot.broadcast\", function(obj){\n\t\t\tvar op = ot.TextOperation.fromJSON(obj.ops);\n\t\t\totClient.applyServer(op);\n\t\t});\n\t\tsocket.on(\"ot.ack\", function(obj){\n\t\t\tconsole.log(\"ack\", obj);\n\t\t\totClient.serverAck();\n\t\t});\n\t\tsocket.on(\"ot.cursor\", function(obj){\n\t\t\totClient.setOtherCursor(obj.cursor, \"#F00\", \"foo\");\n\t\t});\n\n\t});\n});\n\n</script>\n\n</body>\n</html>\n"
  },
  {
    "path": "client/app/eula.txt",
    "content": "This copy of Octave Online (\"the Software Product\") and accompanying documentation is licensed and not sold. This Software Product is protected by copyright laws and treaties, as well as laws and treaties related to other forms of intellectual property. Octave Online LLC or its subsidiaries, affiliates, and suppliers (collectively \"Octave Online\") own intellectual property rights in the Software Product. The Licensee's (\"you\" or \"your\") license to download, use, copy, or change the Software Product is subject to these rights and to all the terms and conditions of this End User License Agreement (\"Agreement\"). \n  \nAcceptance \nYOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT BY USING, DOWNLOADING, COPYING, OR REPRODUCING THE SOFTWARE. IF YOU DO NOT AGREE TO ALL OF THE TERMS OF THIS AGREEMENT, YOU MUST IMMEDIATELY CEASE ALL USE OF THE SOFTWARE.\n  \nRestrictions on Alteration \nYou may not modify the Software Product or create any derivative work of the Software Product or its accompanying documentation without written consent of Octave Online. Derivative works include but are not limited to translations. You may not alter any files or libraries in any portion of the Software Product. You may not reproduce the database portion or create any tables or reports relating to the database portion. \n  \nRestrictions on Copying \nYou may not copy any part of the Software Product except to the extent that licensed use inherently demands the creation of a temporary copy stored in computer memory and not permanently affixed on storage medium. You may make one archival copy which must be stored on a medium other than a computer hard drive. \n  \nDisclaimer of Warranties and Limitation of Liability \nUNLESS OTHERWISE EXPLICITLY AGREED TO IN WRITING BY OCTAVE ONLINE, OCTAVE ONLINE MAKES NO OTHER WARRANTIES, EXPRESS OR IMPLIED, IN FACT OR IN LAW, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OTHER THAN AS SET FORTH IN THIS AGREEMENT OR IN THE LIMITED WARRANTY DOCUMENTS PROVIDED WITH THE SOFTWARE PRODUCT. \n  \nOctave Online makes no warranty that the Software Product will meet your requirements or operate under your specific conditions of use. Octave Online makes no warranty that operation of the Software Product will be secure, error free, or free from interruption. YOU MUST DETERMINE WHETHER THE SOFTWARE PRODUCT SUFFICIENTLY MEETS YOUR REQUIREMENTS FOR SECURITY AND UNINTERRUPTABILITY. YOU BEAR SOLE RESPONSIBILITY AND ALL LIABILITY FOR ANY LOSS INCURRED DUE TO FAILURE OF THE SOFTWARE PRODUCT TO MEET YOUR REQUIREMENTS. OCTAVE ONLINE WILL NOT, UNDER ANY CIRCUMSTANCES, BE RESPONSIBLE OR LIABLE FOR THE LOSS OF DATA ON ANY COMPUTER OR INFORMATION STORAGE DEVICE. \n  \nUNDER NO CIRCUMSTANCES SHALL OCTAVE ONLINE, ITS DIRECTORS, OFFICERS, EMPLOYEES OR AGENTS BE LIABLE TO YOU OR ANY OTHER PARTY FOR INDIRECT, CONSEQUENTIAL, SPECIAL, INCIDENTAL, PUNITIVE, OR EXEMPLARY DAMAGES OF ANY KIND (INCLUDING LOST REVENUES OR PROFITS OR LOSS OF BUSINESS) RESULTING FROM THIS AGREEMENT, OR FROM THE FURNISHING, PERFORMANCE, INSTALLATION, OR USE OF THE SOFTWARE PRODUCT, WHETHER DUE TO A BREACH OF CONTRACT, BREACH OF WARRANTY, OR THE NEGLIGENCE OF OCTAVE ONLINE OR ANY OTHER PARTY, EVEN IF OCTAVE ONLINE IS ADVISED BEFOREHAND OF THE POSSIBILITY OF SUCH DAMAGES. TO THE EXTENT THAT THE APPLICABLE JURISDICTION LIMITS OCTAVE ONLINE'S ABILITY TO DISCLAIM ANY IMPLIED WARRANTIES, THIS DISCLAIMER SHALL BE EFFECTIVE TO THE MAXIMUM EXTENT PERMITTED. \n  \nLimitation of Remedies and Damages \nYour remedy for a breach of this Agreement or of any warranty included in this Agreement is the correction or replacement of the Software Product. Selection of whether to correct or replace shall be solely at the discretion of Octave Online. Octave Online reserves the right to substitute a functionally equivalent copy of the Software Product as a replacement. If Octave Online is unable to provide a replacement or substitute Software Product or corrections to the Software Product, your sole alternate remedy shall be a refund of the purchase price for the Software Product exclusive of any costs for shipping and handling. \n  \nAny claim must be made within the applicable warranty period. All warranties cover only defects arising under normal use and do not include malfunctions or failure resulting from misuse, abuse, neglect, alteration, problems with electrical power, acts of nature, unusual temperatures or humidity, improper installation, or damage determined by Octave Online to have been caused by you. All limited warranties on the Software Product are granted only to you and are non-transferable. You agree to indemnify and hold Octave Online harmless from all claims, judgments, liabilities, expenses, or costs arising from your breach of this Agreement and/or acts or omissions. \n  \nGoverning Law, Jurisdiction and Costs \nThis Agreement is governed by the laws of Missouri, without regard to Missouri's conflict or choice of law provisions. \n  \nSeverability \nIf any provision of this Agreement shall be held to be invalid or unenforceable, the remainder of this Agreement shall remain in full force and effect. To the extent any express or implied restrictions are not permitted by applicable laws, these express or implied restrictions shall remain in force and effect to the maximum extent permitted by such applicable laws.\n"
  },
  {
    "path": "client/app/fonts/dejavusansmono_book/DejaVuSansMono-demo.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n\t\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n\t<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n\t<script src=\"specimen_files/easytabs.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n\t<link rel=\"stylesheet\" href=\"specimen_files/specimen_stylesheet.css\" type=\"text/css\" charset=\"utf-8\" />\n\t<link rel=\"stylesheet\" href=\"stylesheet.css\" type=\"text/css\" charset=\"utf-8\" />\n\n\t<style type=\"text/css\">\n\t\t\tbody{\n\t\t\tfont-family: 'dejavu_sans_monobook';\n\t\t\t\t\t}\n</style>\n\n<title>DejaVu Sans Mono Book Specimen</title>\n\t\n\t\n\t<script type=\"text/javascript\" charset=\"utf-8\">\n\t\t$(document).ready(function() {\n\t\t\t$('#container').easyTabs({defaultContent:1});\n\t\t});\n\t</script>\n</head>\n\n<body>\n<div id=\"container\">\n\t<div id=\"header\">\n\t\tDejaVu Sans Mono Book\t</div>\n\t<ul class=\"tabs\">\n\t\t<li><a href=\"#specimen\">Specimen</a></li>\n\t\t<li><a href=\"#layout\">Sample Layout</a></li>\n\t\t<li><a href=\"#installing\">Installing Webfonts</a></li>\n\t\t\n\t</ul>\n\t\n\t<div id=\"main_content\">\n\n\t\t\n\t\t\t<div id=\"specimen\">\n\t\t\n\t\t\t\t<div class=\"section\">\n\t\t\t\t\t<div class=\"grid12 firstcol\">\n\t\t\t\t\t\t<div class=\"huge\">AaBb</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\n\t\t\t\t<div class=\"section\">\n\t\t\t\t\t<div class=\"glyph_range\">A&#x200B;B&#x200b;C&#x200b;D&#x200b;E&#x200b;F&#x200b;G&#x200b;H&#x200b;I&#x200b;J&#x200b;K&#x200b;L&#x200b;M&#x200b;N&#x200b;O&#x200b;P&#x200b;Q&#x200b;R&#x200b;S&#x200b;T&#x200b;U&#x200b;V&#x200b;W&#x200b;X&#x200b;Y&#x200b;Z&#x200b;a&#x200b;b&#x200b;c&#x200b;d&#x200b;e&#x200b;f&#x200b;g&#x200b;h&#x200b;i&#x200b;j&#x200b;k&#x200b;l&#x200b;m&#x200b;n&#x200b;o&#x200b;p&#x200b;q&#x200b;r&#x200b;s&#x200b;t&#x200b;u&#x200b;v&#x200b;w&#x200b;x&#x200b;y&#x200b;z&#x200b;1&#x200b;2&#x200b;3&#x200b;4&#x200b;5&#x200b;6&#x200b;7&#x200b;8&#x200b;9&#x200b;0&#x200b;&amp;&#x200b;.&#x200b;,&#x200b;?&#x200b;!&#x200b;&#64;&#x200b;(&#x200b;)&#x200b;#&#x200b;$&#x200b;%&#x200b;*&#x200b;+&#x200b;-&#x200b;=&#x200b;:&#x200b;;</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"section\">\n\t\t\t\t\t<div class=\"grid12 firstcol\">\n\t\t\t\t\t\t<table class=\"sample_table\">\n\t\t\t\t\t\t\t<tr><td>10</td><td class=\"size10\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>11</td><td class=\"size11\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>12</td><td class=\"size12\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>13</td><td class=\"size13\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>14</td><td class=\"size14\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>16</td><td class=\"size16\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>18</td><td class=\"size18\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>20</td><td class=\"size20\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>24</td><td class=\"size24\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>30</td><td class=\"size30\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>36</td><td class=\"size36\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>48</td><td class=\"size48\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>60</td><td class=\"size60\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>72</td><td class=\"size72\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t\t<tr><td>90</td><td class=\"size90\">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>\n\t\t\t\t\t\t</table>\n\t\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\n\t\t\t\t</div>\n\t\t\n\t\t\n\t\t\n\t\t\t\t\t\t\t\t<div class=\"section\" id=\"bodycomparison\">\n\n\n\t\t\t\t\t\t\t\t\t\t<div id=\"xheight\">\n\t\t\t\t<div class=\"fontbody\">&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;body</div><div class=\"arialbody\">body</div><div class=\"verdanabody\">body</div><div class=\"georgiabody\">body</div></div>\n\t\t\t\t\t\t\t\t\t\t<div class=\"fontbody\" style=\"z-index:1\">\n\t\t\t\t\t\t\t\t\t\t\tbody<span>DejaVu Sans Mono Book</span>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t<div class=\"arialbody\" style=\"z-index:1\">\n\t\t\t\t\t\t\t\t\t\t\tbody<span>Arial</span>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t<div class=\"verdanabody\" style=\"z-index:1\">\n\t\t\t\t\t\t\t\t\t\t\tbody<span>Verdana</span>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t<div class=\"georgiabody\" style=\"z-index:1\">\n\t\t\t\t\t\t\t\t\t\t\tbody<span>Georgia</span>\n\t\t\t\t\t\t\t\t\t\t</div>\n\n\n\n\t\t\t\t\t\t\t\t</div>\n\t\t\n\t\t\n\t\t\t\t<div class=\"section psample psample_row1\" id=\"\">\n\t\t\t\t\t\n\t\t\t\t\t<div class=\"grid2 firstcol\">\n\t\t\t\t\t\t<p class=\"size10\"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"grid3\">\n\t\t\t\t\t\t<p class=\"size11\"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"grid3\">\n\t\t\t\t\t\t<p class=\"size12\"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"grid4\">\n\t\t\t\t\t\t<p class=\"size13\"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"white_blend\"></div>\n\t\t\t\t\t\n\t\t\t\t</div>\n\t\t\t\t<div class=\"section psample psample_row2\" id=\"\">\n\t\t\t\t\t<div class=\"grid3 firstcol\">\n\t\t\t\t\t\t<p class=\"size14\"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"grid4\">\n\t\t\t\t\t\t<p class=\"size16\"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"grid5\">\n\t\t\t\t\t\t<p class=\"size18\"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div class=\"white_blend\"></div>\n\n\t\t\t\t</div>\n\t\t\t\t\n\t\t\t\t<div class=\"section psample psample_row3\" id=\"\">\n\t\t\t\t\t<div class=\"grid5 firstcol\">\n\t\t\t\t\t\t<p class=\"size20\"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"grid7\">\n\t\t\t\t\t\t<p class=\"size24\"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t\n\t\t\t\t\t<div class=\"white_blend\"></div>\n\t\t\t\t\t\n\t\t\t\t</div>\n\t\t\t\t\n\t\t\t\t<div class=\"section psample psample_row4\" id=\"\">\n\t\t\t\t\t<div class=\"grid12 firstcol\">\n\t\t\t\t\t\t<p class=\"size30\"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"white_blend\"></div>\n\t\t\t\t\t\n\t\t\t\t</div>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t<div class=\"section psample psample_row1 fullreverse\">\n\t\t\t\t\t<div class=\"grid2 firstcol\">\n\t\t\t\t\t\t<p class=\"size10\"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"grid3\">\n\t\t\t\t\t\t<p class=\"size11\"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"grid3\">\n\t\t\t\t\t\t<p class=\"size12\"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"grid4\">\n\t\t\t\t\t\t<p class=\"size13\"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"black_blend\"></div>\n\t\t\t\t\t\n\t\t\t\t</div>\n\t\t\t\t\n\t\t\t\t<div class=\"section psample psample_row2 fullreverse\">\n\t\t\t\t\t<div class=\"grid3 firstcol\">\n\t\t\t\t\t\t<p class=\"size14\"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"grid4\">\n\t\t\t\t\t\t<p class=\"size16\"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"grid5\">\n\t\t\t\t\t\t<p class=\"size18\"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"black_blend\"></div>\n\n\t\t\t\t</div>\n\t\t\t\t\n\t\t\t\t<div class=\"section psample fullreverse psample_row3\" id=\"\">\n\t\t\t\t\t<div class=\"grid5 firstcol\">\n\t\t\t\t\t\t<p class=\"size20\"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"grid7\">\n\t\t\t\t\t\t<p class=\"size24\"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t\n\t\t\t\t\t<div class=\"black_blend\"></div>\n\t\t\t\t\t\n\t\t\t\t</div>\n\t\t\t\t\n\t\t\t\t<div class=\"section psample fullreverse psample_row4\" id=\"\" style=\"border-bottom: 20px #000 solid;\">\n\t\t\t\t\t<div class=\"grid12 firstcol\">\n\t\t\t\t\t\t<p class=\"size30\"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"black_blend\"></div>\n\t\t\t\t\t\n\t\t\t\t</div>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t</div>\n\t\t\t\n\t\t\t<div id=\"layout\">\n\t\t\t\t\n\t\t\t\t<div class=\"section\">\n\t\t\t\t\t\n\t\t\t\t\t<div class=\"grid12 firstcol\">\n\t\t\t\t\t\t<h1>Lorem Ipsum Dolor</h1>\n\t\t\t\t\t\t<h2>Etiam porta sem malesuada magna mollis euismod</h2>\n\t\t\t\t\t\t\n\t\t\t\t\t\t<p class=\"byline\">By <a href=\"#link\">Aenean Lacinia</a></p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"section\">\n\t\t\t\t\t<div class=\"grid8 firstcol\">\n\t\t\t\t\t\t<p class=\"large\">Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>\n\n\t\t\t\t\t\t\n\t\t\t\t\t\t<h3>Pellentesque ornare sem</h3>\n\n\t\t\t\t\t\t<p>Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit. </p>\n\n\t\t\t\t\t\t<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. </p>\n\n\t\t\t\t\t\t<p>Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur. </p>\n\n\t\t\t\t\t\t<p>Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla. </p>\n\n\t\t\t\t\t\t<h3>Cras mattis consectetur</h3>\n\n\t\t\t\t\t\t<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum. </p>\n\n\t\t\t\t\t\t<p>Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t\n\t\t\t\t\t<div class=\"grid4 sidebar\">\n\t\t\t\t\t\t\n\t\t\t\t\t\t<div class=\"box reverse\">\n\t\t\t\t\t\t\t<p class=\"last\">Nullam quis risus eget urna mollis ornare vel eu leo. Donec ullamcorper nulla non metus auctor fringilla. Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\n\t\t\t\t\t\t<p class=\"caption\">Maecenas sed diam eget risus varius.</p>\n\n\t\t\t\t\t\t<p>Vestibulum id ligula porta felis euismod semper. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>\n\n\t\t\t\t\t\n\n\t\t\t\t\t\t<p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Aenean lacinia bibendum nulla sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla sed consectetur. Nullam quis risus eget urna mollis ornare vel eu leo. </p>\n\n\t\t\t\t\t\t<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec ullamcorper nulla non metus auctor fringilla. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>\n\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t\n\t\t\t</div>\n\n\n\n\n\n\n\n\t\t\n\t\t\n\t\t<div id=\"specs\">\n\t\t\t\n\t\t</div>\n\t\n\t\t<div id=\"installing\">\n\t\t\t<div class=\"section\">\n\t\t\t\t<div class=\"grid7 firstcol\">\n\t\t\t\t\t<h1>Installing Webfonts</h1>\n\t\t\t\t\t\n\t\t\t\t\t<p>Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.</p>\n\t\t\t\t\t\n\t\t\t\t\t<h2>1. Upload your webfonts</h2>\n\t\t\t\t\t<p>You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.</p>\n\t\t\t\t\t\n\t\t\t\t\t<h2>2. Include the webfont stylesheet</h2>\n\t\t\t\t\t<p>A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the <a href=\"http://www.fontspring.com/blog/further-hardening-of-the-bulletproof-syntax\">Fontspring blog post</a> about it. The code for it is as follows:</p>\n\t\t\t\t\t\n\t\t\t\t\t\n<code>\n@font-face{ \n\tfont-family: 'MyWebFont';\n\tsrc: url('WebFont.eot');\n\tsrc: url('WebFont.eot?iefix') format('eot'),\n\t     url('WebFont.woff') format('woff'),\n\t     url('WebFont.ttf') format('truetype'),\n\t     url('WebFont.svg#webfont') format('svg');\n}\n</code>\n\n\t<p>We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:</p>\n\t<code>&lt;link rel=&quot;stylesheet&quot; href=&quot;stylesheet.css&quot; type=&quot;text/css&quot; charset=&quot;utf-8&quot; /&gt;</code>\n\n\t\t\t\t\t<h2>3. Modify your own stylesheet</h2>\n\t\t\t\t\t<p>To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called \"font-family.\" The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the \"font-family\" property, inside the selector you want to change. For example:</p>\n<code>p { font-family: 'MyWebFont', Arial, sans-serif; }</code>\n\n<h2>4. Test</h2>\n<p>Getting webfonts to work cross-browser <em>can</em> be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.</p>\n\t\t\t\t</div>\n\t\t\t\t\n\t\t\t\t<div class=\"grid5 sidebar\">\n\t\t\t\t\t<div class=\"box\">\n\t\t\t\t\t\t<h2>Troubleshooting<br />Font-Face Problems</h2>\n\t\t\t\t\t\t<p>Having trouble getting your webfonts to load in your new website? Here are some tips to sort out what might be the problem.</p>\n\n\t\t\t\t\t\t<h3>Fonts not showing in any browser</h3>\n\n\t\t\t\t\t\t<p>This sounds like you need to work on the plumbing. You either did not upload the fonts to the correct directory, or you did not link the fonts properly in the CSS. If you've confirmed that all this is correct and you still have a problem, take a look at your .htaccess file and see if requests are getting intercepted.</p>\n\n\t\t\t\t\t\t<h3>Fonts not loading in iPhone or iPad</h3>\n\n\t\t\t\t\t\t<p>The most common problem here is that you are serving the fonts from an IIS server. IIS refuses to serve files that have unknown MIME types. If that is the case, you must set the MIME type for SVG to \"image/svg+xml\" in the server settings. Follow these instructions from Microsoft if you need help.</p>\n\n\t\t\t\t\t\t<h3>Fonts not loading in Firefox</h3>\n\n\t\t\t\t\t\t<p>The primary reason for this failure? You are still using a version Firefox older than 3.5. So upgrade already! If that isn't it, then you are very likely serving fonts from a different domain. Firefox requires that all font assets be served from the same domain. Lastly it is possible that you need to add WOFF to your list of MIME types (if you are serving via IIS.)</p>\n\n\t\t\t\t\t\t<h3>Fonts not loading in IE</h3>\n\n\t\t\t\t\t\t<p>Are you looking at Internet Explorer on an actual Windows machine or are you cheating by using a service like Adobe BrowserLab? Many of these screenshot services do not render @font-face for IE. Best to test it on a real machine.</p>\n\n\t\t\t\t\t\t<h3>Fonts not loading in IE9</h3>\n\n\t\t\t\t\t\t<p>IE9, like Firefox, requires that fonts be served from the same domain as the website. Make sure that is the case.</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\n\t\t</div>\n\t\n\t</div>\n\t<div id=\"footer\">\n\t\t<p>&copy;2010-2011 Fontspring. All rights reserved.</p>\n\t</div>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "client/app/fonts/dejavusansmono_book/specimen_files/easytabs.js",
    "content": "(function($){$.fn.easyTabs=function(option){var param=jQuery.extend({fadeSpeed:\"fast\",defaultContent:1,activeClass:'active'},option);$(this).each(function(){var thisId=\"#\"+this.id;if(param.defaultContent==''){param.defaultContent=1;}\nif(typeof param.defaultContent==\"number\")\n{var defaultTab=$(thisId+\" .tabs li:eq(\"+(param.defaultContent-1)+\") a\").attr('href').substr(1);}else{var defaultTab=param.defaultContent;}\n$(thisId+\" .tabs li a\").each(function(){var tabToHide=$(this).attr('href').substr(1);$(\"#\"+tabToHide).addClass('easytabs-tab-content');});hideAll();changeContent(defaultTab);function hideAll(){$(thisId+\" .easytabs-tab-content\").hide();}\nfunction changeContent(tabId){hideAll();$(thisId+\" .tabs li\").removeClass(param.activeClass);$(thisId+\" .tabs li a[href=#\"+tabId+\"]\").closest('li').addClass(param.activeClass);if(param.fadeSpeed!=\"none\")\n{$(thisId+\" #\"+tabId).fadeIn(param.fadeSpeed);}else{$(thisId+\" #\"+tabId).show();}}\n$(thisId+\" .tabs li\").click(function(){var tabId=$(this).find('a').attr('href').substr(1);changeContent(tabId);return false;});});}})(jQuery);"
  },
  {
    "path": "client/app/fonts/dejavusansmono_book/specimen_files/grid_12-825-55-15.css",
    "content": "/*Notes about grid:\nColumns:      12\nGrid Width:   825px\nColumn Width: 55px\nGutter Width: 15px\n-------------------------------*/\n \n \n \n.section \t\t{margin-bottom: 18px;\n}\n.section:after\t{content: \".\";display: block;height: 0;clear: both;visibility: hidden;}\n.section \t\t{*zoom: 1;}\n \n.section .firstcolumn,\n.section .firstcol {margin-left: 0;}\n \n \n/* Border on left hand side of a column. */\n.border {\n  padding-left: 7px;\n  margin-left: 7px;\n  border-left: 1px solid #eee;\n}\n \n/* Border with more whitespace, spans one column. */\n.colborder {\n    padding-left: 42px;\n  margin-left: 42px;\n  border-left: 1px solid #eee;\n}\n \n\n \n/* The Grid Classes */\n.grid1, .grid1_2cols, .grid1_3cols, .grid1_4cols, .grid2, .grid2_3cols, .grid2_4cols, .grid3, .grid3_2cols, .grid3_4cols, .grid4, .grid4_3cols, .grid5, .grid5_2cols, .grid5_3cols, .grid5_4cols, .grid6, .grid6_4cols, .grid7, .grid7_2cols, .grid7_3cols, .grid7_4cols, .grid8, .grid8_3cols, .grid9, .grid9_2cols, .grid9_4cols, .grid10, .grid10_3cols, .grid10_4cols, .grid11, .grid11_2cols, .grid11_3cols, .grid11_4cols, .grid12\n{margin-left: 15px;float: left;display: inline; overflow: hidden;}\n \n \n.width1, .grid1, .span-1 {width: 55px;}\n.width1_2cols,.grid1_2cols {width: 20px;}\n.width1_3cols,.grid1_3cols  {width: 8px;}\n.width1_4cols,.grid1_4cols  {width: 2px;}\n.input_width1 {width: 49px;}\n \n.width2, .grid2, .span-2 {width: 125px;}\n.width2_3cols,.grid2_3cols  {width: 31px;}\n.width2_4cols,.grid2_4cols  {width: 20px;}\n.input_width2 {width: 119px;}\n \n.width3, .grid3, .span-3 {width: 195px;}\n.width3_2cols,.grid3_2cols {width: 90px;}\n.width3_4cols,.grid3_4cols  {width: 37px;}\n.input_width3 {width: 189px;}\n \n.width4, .grid4, .span-4 {width: 265px;}\n.width4_3cols,.grid4_3cols  {width: 78px;}\n.input_width4 {width: 259px;}\n \n.width5, .grid5, .span-5 {width: 335px;}\n.width5_2cols,.grid5_2cols {width: 160px;}\n.width5_3cols,.grid5_3cols  {width: 101px;}\n.width5_4cols,.grid5_4cols  {width: 72px;}\n.input_width5 {width: 329px;}\n \n.width6, .grid6, .span-6 {width: 405px;}\n.width6_4cols,.grid6_4cols  {width: 90px;}\n.input_width6 {width: 399px;}\n \n.width7, .grid7, .span-7 {width: 475px;}\n.width7_2cols,.grid7_2cols {width: 230px;}\n.width7_3cols,.grid7_3cols  {width: 148px;}\n.width7_4cols,.grid7_4cols  {width: 107px;}\n.input_width7 {width: 469px;}\n \n.width8, .grid8, .span-8 {width: 545px;}\n.width8_3cols,.grid8_3cols  {width: 171px;}\n.input_width8 {width: 539px;}\n \n.width9, .grid9, .span-9 {width: 615px;}\n.width9_2cols,.grid9_2cols {width: 300px;}\n.width9_4cols,.grid9_4cols  {width: 142px;}\n.input_width9 {width: 609px;}\n \n.width10, .grid10, .span-10 {width: 685px;}\n.width10_3cols,.grid10_3cols  {width: 218px;}\n.width10_4cols,.grid10_4cols  {width: 160px;}\n.input_width10 {width: 679px;}\n \n.width11, .grid11, .span-11 {width: 755px;}\n.width11_2cols,.grid11_2cols {width: 370px;}\n.width11_3cols,.grid11_3cols  {width: 241px;}\n.width11_4cols,.grid11_4cols  {width: 177px;}\n.input_width11 {width: 749px;}\n \n.width12, .grid12, .span-12 {width: 825px;}\n.input_width12 {width: 819px;}\n \n/* Subdivided grid spaces */\n.emptycols_left1, .prepend-1 {padding-left: 70px;}\n.emptycols_right1, .append-1 {padding-right: 70px;}\n.emptycols_left2, .prepend-2 {padding-left: 140px;}\n.emptycols_right2, .append-2 {padding-right: 140px;}\n.emptycols_left3, .prepend-3 {padding-left: 210px;}\n.emptycols_right3, .append-3 {padding-right: 210px;}\n.emptycols_left4, .prepend-4 {padding-left: 280px;}\n.emptycols_right4, .append-4 {padding-right: 280px;}\n.emptycols_left5, .prepend-5 {padding-left: 350px;}\n.emptycols_right5, .append-5 {padding-right: 350px;}\n.emptycols_left6, .prepend-6 {padding-left: 420px;}\n.emptycols_right6, .append-6 {padding-right: 420px;}\n.emptycols_left7, .prepend-7 {padding-left: 490px;}\n.emptycols_right7, .append-7 {padding-right: 490px;}\n.emptycols_left8, .prepend-8 {padding-left: 560px;}\n.emptycols_right8, .append-8 {padding-right: 560px;}\n.emptycols_left9, .prepend-9 {padding-left: 630px;}\n.emptycols_right9, .append-9 {padding-right: 630px;}\n.emptycols_left10, .prepend-10 {padding-left: 700px;}\n.emptycols_right10, .append-10 {padding-right: 700px;}\n.emptycols_left11, .prepend-11 {padding-left: 770px;}\n.emptycols_right11, .append-11 {padding-right: 770px;}\n.pull-1 {margin-left: -70px;}\n.push-1 {margin-right: -70px;margin-left: 18px;float: right;}\n.pull-2 {margin-left: -140px;}\n.push-2 {margin-right: -140px;margin-left: 18px;float: right;}\n.pull-3 {margin-left: -210px;}\n.push-3 {margin-right: -210px;margin-left: 18px;float: right;}\n.pull-4 {margin-left: -280px;}\n.push-4 {margin-right: -280px;margin-left: 18px;float: right;}"
  },
  {
    "path": "client/app/fonts/dejavusansmono_book/specimen_files/specimen_stylesheet.css",
    "content": "@import url('grid_12-825-55-15.css');\n\n/*  \n\tCSS Reset by Eric Meyer - Released under Public Domain\n    http://meyerweb.com/eric/tools/css/reset/\n*/\nhtml, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, big, cite, code,\ndel, dfn, em, font, img, ins, kbd, q, s, samp,\nsmall, strike, strong, sub, sup, tt, var,\nb, u, i, center, dl, dt, dd, ol, ul, li,\nfieldset, form, label, legend, table, \ncaption, tbody, tfoot, thead, tr, th, td \n                  {margin: 0;padding: 0;border: 0;outline: 0;\n                  font-size: 100%;vertical-align: baseline;\n                  background: transparent;}\nbody              {line-height: 1;}\nol, ul            {list-style: none;}\nblockquote, q     {quotes: none;}\nblockquote:before, blockquote:after,\nq:before, q:after {content: '';\tcontent: none;}\n:focus            {outline: 0;}\nins               {text-decoration: none;}\ndel               {text-decoration: line-through;}\ntable             {border-collapse: collapse;border-spacing: 0;}\n\n\n\n\nbody {\n\tcolor: #000;\n\tbackground-color: #dcdcdc;\n}\n\na {\n\ttext-decoration: none;\n\tcolor: #1883ba;\n}\n\nh1{\n\tfont-size: 32px;\n\tfont-weight: normal;\n\tfont-style: normal;\n\tmargin-bottom: 18px;\n}\n\nh2{\n\tfont-size: 18px;\n}\n\n#container {\n\twidth: 865px;\n\tmargin: 0px auto;\n}\n\n\n#header {\n\tpadding: 20px;\n\tfont-size: 36px;\n\tbackground-color: #000;\n\tcolor: #fff;\n}\n\n#header span {\n\tcolor: #666;\n}\n#main_content {\n\tbackground-color: #fff;\n\tpadding: 60px 20px 20px;\n}\n\n\n#footer p {\n\tmargin: 0;\n\tpadding-top: 10px;\n\tpadding-bottom: 50px;\n\tcolor: #333;\n\tfont: 10px Arial, sans-serif;\n}\n\n.tabs {\n\twidth: 100%;\n\theight: 31px;\n\tbackground-color: #444;\n}\n.tabs li {\n\tfloat:  left;\n\tmargin: 0;\n\toverflow: hidden;\n\tbackground-color: #444;\n}\n.tabs li a {\n\tdisplay: block;\n\tcolor: #fff;\n\ttext-decoration: none;\n\tfont: bold 11px/11px 'Arial';\n\ttext-transform: uppercase;\n\tpadding: 10px 15px;\n\tborder-right: 1px solid #fff;\n}\n\n.tabs li a:hover {\n\t\tbackground-color: #00b3ff;\n\n}\n\n.tabs li.active a {\n\tcolor:  #000;\n\tbackground-color: #fff;\n}\n\n\n\ndiv.huge {\n\t\n\tfont-size: 300px;\n\tline-height: 1em;\n\tpadding: 0;\n\tletter-spacing: -.02em;\n\toverflow: hidden;\n}\ndiv.glyph_range {\n\tfont-size: 72px;\n\tline-height: 1.1em;\n}\n\n.size10{ font-size: 10px; }\n.size11{ font-size: 11px; }\n.size12{ font-size: 12px; }\n.size13{ font-size: 13px; }\n.size14{ font-size: 14px; }\n.size16{ font-size: 16px; }\n.size18{ font-size: 18px; }\n.size20{ font-size: 20px; }\n.size24{ font-size: 24px; }\n.size30{ font-size: 30px; }\n.size36{ font-size: 36px; }\n.size48{ font-size: 48px; }\n.size60{ font-size: 60px; }\n.size72{ font-size: 72px; }\n.size90{ font-size: 90px; }\n\n\n.psample_row1 {\theight: 120px;}\n.psample_row1 {\theight: 120px;}\n.psample_row2 {\theight: 160px;}\n.psample_row3 {\theight: 160px;}\n.psample_row4 {\theight: 160px;}\n\n.psample {\n\toverflow: hidden;\n\tposition: relative;\n}\n.psample p {\n\tline-height: 1.3em;\n\tdisplay: block;\n\toverflow: hidden;\n\tmargin: 0;\n}\n\n.psample span {\n\tmargin-right: .5em;\n}\n\n.white_blend {\n\twidth: 100%;\n\theight: 61px;\n\tbackground-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAO1JREFUeNrs3TsKgFAMRUE/eer+NxztxMYuEWQG3ECKwwUF58ycAKixOAGAyAKILAAiCyCyACILgMgCiCyAyAIgsgAiCyCyAIgsgMgCiCwAIgsgsgAiC4DIAogsACIL0CWuZ3UGgLrIhjMA1EV2OAOAJQtgyQLwjOzmDAAiCyCyAIgsQFtkd2cAEFkAkQVAZAHaIns4A4AlC2DJAiCyACILILIAiCzAV5H1dQGAJQsgsgCILIDIAvwisl58AViyAJYsACILILIAIgvAe2T9EhxAZAFEFgCRBeiL7HAGgLrIhjMAWLIAliwAt1OAAQDwygTBulLIlQAAAABJRU5ErkJggg==);\n\tposition: absolute;\n\tbottom: 0;\n}\n.black_blend {\n\twidth: 100%;\n\theight: 61px;\n\tbackground-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPJJREFUeNrs3TEKhTAQRVGjibr/9QoxhY2N3Ywo50A28IrLwP9g6b1PAMSYTQAgsgAiC4DIAogsgMgCILIAIgsgsgCILIDIAogsACILILIAIguAyAKILIDIAiCyACILgMgCZCnjLWYAiFGvB0BQZJsZAFyyAC5ZAO6RXc0AILIAIguAyAKkRXYzA4DIAogsACILkBbZ3QwALlkAlywAIgsgsgAiC4DIArwVWf8uAHDJAogsACILILIAv4isH74AXLIALlkARBZAZAFEFoDnyPokOIDIAogsACILkBfZZgaAuMhWMwC4ZAE+p4x3mAEgxinAAJ+XBbPWGkwAAAAAAElFTkSuQmCC);\n\tposition: absolute;\n\tbottom: 0;\n}\n.fullreverse {\n\tbackground:  #000 !important;\n\tcolor:  #fff !important;\n\tmargin-left: -20px;\n\tpadding-left: 20px;\n\tmargin-right: -20px;\n\tpadding-right: 20px;\n\tpadding: 20px;\n\tmargin-bottom:0;\n}\n\n\n.sample_table td {\n\tpadding-top: 3px;\n\tpadding-bottom:5px;\n\tpadding-left: 5px;\n\tvertical-align: middle;\n\tline-height: 1.2em;\n}\n\n.sample_table td:first-child {\n\tbackground-color: #eee;\n\ttext-align: right;\n\tpadding-right: 5px;\n\tpadding-left: 0;\n\tpadding: 5px;\n\tfont: 11px/12px \"Courier New\", Courier, mono;\n}\n\ncode {\n\twhite-space: pre;\n\tbackground-color: #eee;\n\tdisplay: block;\n\tpadding: 10px;\n\tmargin-bottom: 18px;\n\toverflow: auto;\n}\n\n\n.bottom,.last \t{margin-bottom:0 !important; padding-bottom:0 !important;}\n\n.box  { \n  padding: 18px; \n  margin-bottom: 18px; \n  background: #eee; \n}\n\n.reverse,.reversed { background:  #000 !important;color:  #fff !important; border: none !important;}\n\n#bodycomparison {\n\tposition: relative;\n\toverflow: hidden;\n\tfont-size: 72px;\n\theight: 90px;\n\twhite-space: nowrap;\n}\n\n#bodycomparison div{\n\tfont-size: 72px;\n\tline-height: 90px;\n\tdisplay: inline;\n\tmargin: 0 15px 0 0;\n\tpadding: 0;\n}\n\n#bodycomparison div span{\n\tfont: 10px Arial;\n\tposition: absolute;\n\tleft: 0;\n}\n#xheight {\n\tfloat: none;\n\tposition: absolute;\n\tcolor: #d9f3ff;\n\tfont-size: 72px;\n\tline-height: 90px;\n}\n\n.fontbody {\n position: relative;\n}\n.arialbody{\n\tfont-family: Arial;\n\tposition: relative;\n}\n.verdanabody{\n\tfont-family: Verdana;\n\tposition: relative;\n}\n.georgiabody{\n\tfont-family: Georgia;\n\tposition: relative;\n}\n\n/* @group Layout page\n */\n\n#layout h1 {\n\tfont-size: 36px;\n\tline-height: 42px;\n\tfont-weight: normal;\n\tfont-style: normal;\n}\n\n#layout h2 {\n\tfont-size: 24px;\n\tline-height: 23px;\n\tfont-weight: normal;\n\tfont-style: normal;\n}\n\n#layout h3 {\n\tfont-size: 22px;\n\tline-height: 1.4em;\n\tmargin-top: 1em;\n\tfont-weight: normal;\n\tfont-style: normal;\n}\n\n\n#layout p.byline {\n\tfont-size: 12px;\n\tmargin-top: 18px;\n\tline-height: 12px;\n\tmargin-bottom: 0;\n}\n#layout p {\n\tfont-size: 14px;\n\tline-height: 21px;\n\tmargin-bottom: .5em;\n}\n\n#layout p.large{\n\tfont-size: 18px;\n\tline-height: 26px;\n}\n\n#layout .sidebar p{\n\tfont-size: 12px;\n\tline-height: 1.4em;\n}\n\n#layout p.caption {\n\tfont-size: 10px;\n\tmargin-top: -16px;\n\tmargin-bottom: 18px;\n}\n\n/* @end */\n\n/* @group Glyphs */\n\n#glyph_chart div{\n\tbackground-color: #d9f3ff;\n\tcolor: black;\n\tfloat: left;\n\tfont-size: 36px;\n\theight: 1.2em;\n\tline-height: 1.2em;\n\tmargin-bottom: 1px;\n\tmargin-right: 1px;\n\ttext-align: center;\n\twidth: 1.2em;\n\tposition: relative;\n\tpadding: .6em .2em .2em;\n}\n\n#glyph_chart div p {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\tdisplay: block;\n\ttext-align: center;\n\tfont: bold 9px Arial, sans-serif;\n\tbackground-color: #3a768f;\n\twidth: 100%;\n\tcolor: #fff;\n\tpadding: 2px 0;\n}\n\n\n#glyphs h1 {\n\tfont-family: Arial, sans-serif;\n}\n/* @end */\n\n/* @group Installing */\n\n#installing {\n\tfont: 13px Arial, sans-serif;\n}\n\n#installing p,\n#glyphs p{\n\tline-height: 1.2em;\n\tmargin-bottom: 18px;\n\tfont: 13px Arial, sans-serif;\n}\n\n\n\n#installing h3{\n\tfont-size: 15px;\n\tmargin-top: 18px;\n}\n\n/* @end */\n\n#rendering h1 {\n\tfont-family: Arial, sans-serif;\n}\n.render_table td {\n\tfont: 11px \"Courier New\", Courier, mono;\n\tvertical-align: middle;\n}\n\n\n"
  },
  {
    "path": "client/app/fonts/dejavusansmono_book/stylesheet.css",
    "content": "@font-face {\n    font-family: 'DejaVu Sans Mono';\n    src: url('DejaVuSansMono-webfont.eot');\n    src: url('DejaVuSansMono-webfont.eot?#iefix') format('embedded-opentype'),\n         url('DejaVuSansMono-webfont.woff') format('woff'),\n         url('DejaVuSansMono-webfont.ttf') format('truetype'),\n         url('DejaVuSansMono-webfont.svg#dejavu_sans_monobook') format('svg');\n    font-weight: normal;\n    font-style: normal;\n\n}\n\n"
  },
  {
    "path": "client/app/images/flaticons/download-svg.ai",
    "content": "%PDF-1.5\r%\r\n1 0 obj\r<</Metadata 2 0 R/OCProperties<</D<</ON[6 0 R]/Order 7 0 R/RBGroups[]>>/OCGs[6 0 R]>>/Pages 3 0 R/Type/Catalog>>\rendobj\r2 0 obj\r<</Length 23835/Subtype/XML/Type/Metadata>>stream\r\n<?xpacket begin=\"﻿\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"Adobe XMP Core 5.0-c060 61.134777, 2010/02/12-17:32:00        \">\n   <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n      <rdf:Description rdf:about=\"\"\n            xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"\n            xmlns:xmpGImg=\"http://ns.adobe.com/xap/1.0/g/img/\">\n         <xmp:CreatorTool>Adobe Illustrator CS5.1</xmp:CreatorTool>\n         <xmp:CreateDate>2015-07-05T03:33:12-07:00</xmp:CreateDate>\n         <xmp:MetadataDate>2015-07-05T03:33:12-07:00</xmp:MetadataDate>\n         <xmp:ModifyDate>2015-07-05T03:33:12-07:00</xmp:ModifyDate>\n         <xmp:Thumbnails>\n            <rdf:Alt>\n               <rdf:li rdf:parseType=\"Resource\">\n                  <xmpGImg:width>256</xmpGImg:width>\n                  <xmpGImg:height>240</xmpGImg:height>\n                  <xmpGImg:format>JPEG</xmpGImg:format>\n                  <xmpGImg:image>/9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA&#xA;AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK&#xA;DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f&#xA;Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgA8AEAAwER&#xA;AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA&#xA;AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB&#xA;UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE&#xA;1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ&#xA;qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy&#xA;obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp&#xA;0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo&#xA;+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7&#xA;FXYq7FXYq7FXYq7FX5V4q7FXYq7FX6laT/xyrL/jBF/xAYqisVdirsVdirsVdirsVdirsVdirsVd&#xA;irsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVflXirsVdirsVfqVpP/HKsv+MEX/EBiqKxV2Ku&#xA;xV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV+VeKuxV2KuxV+p&#xA;Wk/8cqy/4wRf8QGKorFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY&#xA;q7FXYq7FX5V4q7FXYq7FX6laT/xyrL/jBF/xAYqisVdirsVdirsVdirsVdirsVdirsVdirsVdirs&#xA;VdirsVdirsVdirsVedXX/OQ/5N2lzLa3XmSOC5gdo54JLe6R0dDRlZTECCCKEHFVL/oZH8kv+ppg&#xA;/wCRNz/1SxV3/QyP5Jf9TTB/yJuf+qWKu/6GR/JL/qaYP+RNz/1SxV3/AEMj+SX/AFNMH/Im5/6p&#xA;Yq7/AKGR/JL/AKmmD/kTc/8AVLFX564q7FXYq7FX6Bad/wA5GfkrFp9rFJ5ogWSOGNXX0bnYhQCP&#xA;7rFUR/0Mj+SX/U0wf8ibn/qlirv+hkfyS/6mmD/kTc/9UsVd/wBDI/kl/wBTTB/yJuf+qWKu/wCh&#xA;kfyS/wCppg/5E3P/AFSxVVtf+ch/ybu7mK1tfMkc9zO6xwQR2907u7miqqiIkkk0AGKvRQaiuKux&#xA;V2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV4X/zkP8A848W3ne2l8yeW4kg82wJWWIURL9EGyOd&#xA;gJgBRHPX7LbUKqviS6tbm0uZbW6ieC5gdo54JFKOjoaMrKaEEEUIOKqWKuxV2KuxV2KuxV2KuxV2&#xA;KuxV2KuxVVtbW5u7mK1tYnnuZ3WOCCNS7u7miqqipJJNABir7b/5x4/5x4tvJFtF5k8yRJP5tnSs&#xA;URo6WCON0Q7gzEGjuOn2V2qWVe6Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq8L/AOch&#xA;/wDnHi28720vmTy3EkHm2BKyxCiJfog2RzsBMAKI56/ZbahVV8SXVrc2lzLa3UTwXMDtHPBIpR0d&#xA;DRlZTQggihBxVSxV2KuxV2KuxV2KuxV2KuxV2Kqtra3N3cxWtrE89zO6xwQRqXd3c0VVUVJJJoAM&#xA;Vfbf/OPH/OPFt5ItovMnmSJJ/Ns6ViiNHSwRxuiHcGYg0dx0+yu1Syr3TFXYq7FXYq7FXYq7FXYq&#xA;7FXYq7FXYq7FXYq7FXYq7FXYq7FXhf8AzkP/AM48W3ne2l8yeW4kg82wJWWIURL9EGyOdgJgBRHP&#xA;X7LbUKqviS6tbm0uZbW6ieC5gdo54JFKOjoaMrKaEEEUIOKqWKuxV2KuxV2KuxV2KuxVVtbW5u7m&#xA;K1tYnnuZ3WOCCNS7u7miqqipJJNABir7b/5x4/5x4tvJFtF5k8yRJP5tnSsURo6WCON0Q7gzEGju&#xA;On2V2qWVe6Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq8L/wCch/8AnHi2&#xA;8720vmTy3EkHm2BKyxCiJfog2RzsBMAKI56/ZbahVV8SXVrc2lzLa3UTwXMDtHPBIpR0dDRlZTQg&#xA;gihBxVSxV2KuxV2KuxV2Kqtra3N3cxWtrE89zO6xwQRqXd3c0VVUVJJJoAMVfbf/ADjx/wA48W3k&#xA;i2i8yeZIkn82zpWKI0dLBHG6IdwZiDR3HT7K7VLKvdMVdirsVdirsVdirsVdirsVdirsVdirsVdi&#xA;rsVdirsVdirsVdirsVdirsVeF/8AOQ//ADjxbed7aXzJ5biSDzbAlZYhREv0QbI52AmAFEc9fstt&#xA;Qqq+JLq1ubS5ltbqJ4LmB2jngkUo6OhoyspoQQRQg4qpYq7FXYq7FVW1tbm7uYrW1iee5ndY4II1&#xA;Lu7uaKqqKkkk0AGKvtv/AJx4/wCceLbyRbReZPMkST+bZ0rFEaOlgjjdEO4MxBo7jp9ldqllXumK&#xA;uxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KvC/+ch/+ceLbzvbS&#xA;+ZPLcSQebYErLEKIl+iDZHOwEwAojnr9ltqFVXxJdWtzaXMtrdRPBcwO0c8EilHR0NGVlNCCCKEH&#xA;FVLFXYqq2trc3dzFa2sTz3M7rHBBGpd3dzRVVRUkkmgAxV9t/wDOPH/OPFt5ItovMnmSJJ/Ns6Vi&#xA;iNHSwRxuiHcGYg0dx0+yu1Syr3TFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq&#xA;7FXYq7FXYq7FXYq7FXhf/OQ//OPFt53tpfMnluJIPNsCVliFES/RBsjnYCYAURz1+y21Cqr4kurW&#xA;5tLmW1uonguYHaOeCRSjo6GjKymhBBFCDirrW1ubu5itbWJ57md1jggjUu7u5oqqoqSSTQAYq+2/&#xA;+ceP+ceLbyRbReZPMkST+bZ0rFEaOlgjjdEO4MxBo7jp9ldqllXumKuxV2KuxV2KuxV2KuxV2Kux&#xA;V2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVZNPDBE808ixQxgtJI5CqoHUknYDFWNzfmn+WMMjRTeb9F&#xA;jlXZkfUbRWB9wZMVWf8AK2Pys/6nLQ/+4lZ/9VMVTTR/N3lTW246NrVhqbUrSzuYbg0BIr+7ZvDF&#xA;U2xVB6vrejaNZm+1i/ttNslIVrq7lSCIM3QF5Cq1PzxVWs72zvbWK7sp47m1mXnDcQuskbqe6upK&#xA;kfLFXiP/ADkP/wA48W3ne2l8yeW4kg82wJWWIURL9EGyOdgJgBRHPX7LbUKqu/5x4/5x4tvJFtF5&#xA;k8yRJP5tnSsURo6WCON0Q7gzEGjuOn2V2qWVexTeZ/LUGsR6LNq1lFrMwBi0x7iJblwRUFYS3qHb&#xA;wGKplirsVSDU/wAwvIOlXTWmqeZdKsLtCQ9vdX1tDIpHUFHdWGKr9I8+eR9Zufquj+YtM1K6PSC0&#xA;vLeeT/gY3Y9sVTe7vLSztpLq8njtraEcpZ5mWONFHdmYgAfPFVHS9X0nVrNL3Sr2DULKT+7urWVJ&#xA;omp4PGWU/fiqLxV2KuxV2KuxV2KuxV2KuxV2KuxV2KpX5i80+XPLdg2oa/qVvplmtaS3Mix8iBXi&#xA;gJq7f5KgnFXj8X/OYH5ZT+brbRoY7kaRMxjl8wTKIoEc04H0m/e+kf2nYKV/lpuFXuUckcsayRsH&#xA;jcBkdSCrKRUEEdQcVXYq+Df+ckvzZ13zZ541PQo7p4vLei3L2dvYxsRHLLbsUknlA2di6nhXovTu&#xA;SqkVp/zjz+c91bRXMPlW6EUyh09RoYnodxySSRXU+xGKqv8A0Lh+dv8A1K1x/wAjbb/qrirG9f8A&#xA;J/n7yHqVrNq+n3mhXob1LK6NUPOM/aimjPGq/wCS2Kvtr/nG78wfMfnj8uV1HzBHW/srqSx+u8eI&#xA;ukiSNhNTpy/ecWoKVHzAVeSf85xXlz9a8pWXqEWvC8mMQPwmSsKhiO5A2HzPjirN/wDnDK9ubj8p&#xA;ruGZy8dnrFzBbKf2IzBBMVH/AD0lY/Tir3jFXYq/Nj8wtZ1I/mn5h1b12F/DrN1LFOD8SNDct6fH&#xA;w4cRT5Yq/SfFXyX/AM5A/wDOT+pvfX/k/wAkyNZwW0j22pa2ppNI6ErJHbEfYQEEGT7R/ZoNyq8O&#xA;8rflP+ZPm+zfUtA0K51Cz5lWu/hSNnB+IK8rIHIPWlcVW+afyv8AzH8lwwahr2i3WmQM49G8+FkW&#xA;QGqj1YmdUb+WpB8MVTXzL+eHnjzP+X9v5N1y5N7DbXUdwuoOx+sSRxo6rDOf92gM4YMd6jevUKvb&#xA;v+cGry5ez842bSE20MmnzRRdlklW4V2HuwiT7sVfUmKvJfzV/wCckfJn5e65baLPDLq2oMQ2ow2b&#xA;JW1iIqpfnRWkbYiOo23JG1VWQeR/zu/LPzoI49G1mJb+Tb9GXZ+r3XL+VY5Kep/zzLDFWdYq7FXY&#xA;q7FXYq7FXYqxHzz+bHkDyPCzeYtXht7njySwjPq3T+HGFKvvXq1B74q+bPzA/wCczvMN/wCrZ+St&#xA;PXSbY1VdSvAs10R/MkW8MZ+fPFXz9r3mPXvMGoPqOuahcalev1nuZGkYCteK8j8Kjso2GKpbir6H&#xA;/wCccv8AnI2TyxJb+UfN1wX8uORHp2oyElrJidkc94D/AMJ/q9FX2XHJHLGskbB43AZHUgqykVBB&#xA;HUHFX5+/85D/AJdav5Q/MfVbia3caPrN1NfaXd7tGyzuZHi5fzxM5Uqd6UPeuKs9/Lz/AJzJ8zaW&#xA;sNj5ysV1qzQBDqFvSK9AApV1P7qU/wDAHxOKvpXyJ+b/AOXvniMDy/q8Ut3Sr6dN+5ul8f3T0ZgP&#xA;FKj3xVkWu+XdB1+wOn65p9vqdkWD/V7qJJU5DowDg0I8Riqvpum6dpljDYadbRWdjbLwt7WBFjiR&#xA;R2VFAAGKvBP+cyPIk2seSrLzTaKWn8uyMt0gqa2t0URmp4pIqfQSe2KvO/8AnDb8whpfmq98m3kv&#xA;Gz1xfrFgGPwreQLVlH/GWEH6UUd8VfZGKsS/NbzzB5H8g6v5icj6xbQlLCNt+d1L8EK07jmQW9gc&#xA;VfBv5VeTr38wPzJ0zSJmeZLy4Nzq07GrfV0Pq3Dsx/aYVUE/tEYq/Ri/neCwuZ0pziid1r0qqkiu&#xA;KvzU8h6fba5+YPl3TtSBmttU1azt70E/E6XFyiSb+JDHFX6WWVlZ2NpDZ2UEdtaW6COC3hUJGiKK&#xA;KqKoAUAdhiqzU9M07VdPuNO1K2ju7G6QxXFtMoeN0bqGU4q+Iv8AnJD8iLH8u7m11rQ7gv5f1SZo&#xA;Us5SWltpgpfgHP20Kg8SdxShr1xVnn/ODH/Tbf8Abr/7HMVZt/zkP/zkPbeSLaXy35blSfzbOlJZ&#xA;RR0sEcbO43BmINUQ9PtNtQMq+JLq6ubu5lurqV57md2knnkYu7u5qzMxqSSTUk4qpgkGo2IxV6V5&#xA;G/5yI/NTyf6cNrqzalpyUA0/U63MQA2ARiRLGB4I4Htir6F8jf8AOZPkjVRHbearObQLtqBrmOt1&#xA;aE+JKD1Ur4cCB/Nir3TQ/MOg69Yrf6JqFvqVm/Se1lSVa+BKk0PiDviqYYq7FXlv5gf85Jflh5N9&#xA;W3a//TGrR1X9HaaVmKsNqSS1EUdD9oFuQ/lxV80fmB/zlh+ZPmb1LXSHXy1pj7COyYtdFf8ALuiF&#xA;Yf8APNUxV4xPPNPM808jSzSEtJI5LMzHqSTuTiqnirsVdirsVfUX/OJn5secWu4/JV5YXer6Au1p&#xA;qMUbSfo/vwmfoID2qaqem3RV9Sa1oWja5p0um6zYwahYTf3ltcxrIh8DRgaEdiNxir5//MD/AJwz&#xA;8sal6l35Mv20W6NSNPui09oT4K+80f8Aw/yxV82+dvyl/MbyFcCXW9LmtoI3Bh1S3Pq23IEFSs8e&#xA;yGvQNQ4qzT8uv+cqvzH8rSQ22rz/AOJNHWivDesfrKrXf07ndyf+MnIfLFX2d5G87aD518tWnmHQ&#xA;5TJZXQIKOAssUimjxSqC3F1PXf3FQRiqaatpdlq2l3el30Ymsr6GS3uYjuGjlUow39jir84fMWka&#xA;7+Wv5kXFmjmLU/L18stnOQRzWNhLBLTb4ZE4tTwOKv0O8learDzZ5T0vzHY/7zanbpMErUo52kjP&#xA;vG4ZT7jFXyv/AM5m/mCb/wAx2Hkm0kra6Qou9RCnY3c6/u1b/jHCa/7PFWb/APOG/wCXh0ryteec&#xA;r2Lje643oWHIbrZQtuw7j1ZQfoVTir6A1j/jk3v/ADDy/wDEDir84fyn/wDJp+Tf+25pv/UZHir9&#xA;KcVdir5t/wCc29QsR5R8v6eZ0+vPqDXC23IeoYkhdGk49ePJwK4q8U/J78xvNnkryd51l8saVcXd&#xA;/ffo+NtUjiMsFgiC6rLLQMA7c/3fL4agk9KFV5bdXVzd3Mt1dSvPczu0k88jF3d3NWZmNSSSaknF&#xA;VLFXYq7FXYqmOheYte0C+W/0TUbjTbxdhPaytExHWhKkVHsdsVe6eRv+cyvO2lCO281WUOv2q0Bu&#xA;o6Wt2B4koDE9P9RSe5xV9DeRv+ch/wAq/OHpw2mrLp+oyU/3HalS2lqf2VZiYpD7I5OKvz0xV2Ku&#xA;xV2KuxVnPkH8lfzG88sj6HpTjT2NDql1WC0ArQkSMPjp3EYY+2Kvpf8AL7/nDrybpHp3fm26fX75&#xA;aE2icoLJW8CAfVkp7sAe64q960vSdL0myjsNLs4bGyhFIra2jWKNR7IgAxViGl/nf+Vuqea28q2O&#xA;vwzayGMaR8ZBFJIvWOOdlETt7K2/auKs5xVbLFFNG0UqLJE4KujgMrA9QQdiMVfH/wDzl1+WfkLy&#xA;wmkazoFvFpep6nPLHdabb/DDJGq8jMkXSPi1FPEBTXpXFWR/84OXt6+k+bbJyfqUE9lNAO3qzJMs&#xA;tPfjDHir6fxV8t/85o/l56ltpvnyyj+OEjTtW4jqjEtbytT+VuSE+6jFUl/5xb/ObTvLPlTzPouu&#xA;S/6NpNvJrWmoTQuAAk1uhP7TuYyg8WY4q8W0bTtf/Mz8yorZn56r5kv2kuZqErGJWMk0lN/giTka&#xA;eApir9G9G0iw0bSLLSdPjENjYQx21tGO0cShV6d6DFUTPCk8EkEn93KrI9NjRhQ4q/NnzP5a80/l&#xA;r56+p3aNa6rpFyl1p91T4ZBFJzguIj0ZWKA+x2O4OKvqjyR/zmJ5Cv8ARY282LLo+sx0W4SGGSe3&#xA;kNPtxFA7KD/K3TxOKqXn7/nMXyVY6K48mJJq2tTArC1xDJBbQn+eTnwd/ZV69yMVfInmPzLrvmXW&#xA;LjWNcvJL/UrpuUs8p39lUCiqo7KoAHbFX0v/AM4Mf9Nt/wBuv/scxV7J54/5x9/KzzjzlvtHSx1B&#xA;6k6jp1LWYk/tOFHpyH3kRsVfPXnn/nDPzlppkufKV/DrlqN1s5qWt2B4AsfRenjzX5Yq8J1/yz5i&#xA;8u3xsdd0250y7G4huomiJHivIDkPcbYqlmKuxV2KuxV2KuxV2KtgEkACpOwA61xV65+X3/OMP5n+&#xA;bvTubi0/QGlPQ/XNSDRyMp7x2/8AettuOQVT/Nir6X/L7/nFz8svKYiuby2PmHVo6E3eoKGhDjvH&#xA;bbxj258yPHFXr6IiIqIoVFACqBQADYAAYq3iq2WNZYniavF1KtQkGhFDQjFXwH+a/wDzjx538i6p&#xA;LJZ2k+seXWcmz1S2jMjKpOy3CICY3HjTiex7BVhA86+fLP8A0Ya9qlv6e3oi7uE4/wCx5imKu/5W&#xA;D59/6mXVf+k24/5rxVE6T5S/MXzxqafUdP1HW7yei/WnWWVQDuDJPJ8Crv1ZgMVfcn5DflSfy48k&#xA;rpt1Ik2s30n1vVJY6lBIVCrEhPVY1FK9zU4q9IxVJ/OHljT/ADT5X1Py9qAraanbvA7d0YiqSL/l&#xA;I4DD3GKvzZ80+WNZ8ra/faDrEDW9/ZSNFKpBCsAfhdCQOSOPiVu4xV9Pf84b/lhc2kN7591W2MTX&#xA;SfVNDEgIJhJrPOAezkKiN7N2OKvqDFXYqw78z/yq8rfmLoR0zWouFxFVrDUogv1i3fxRiN1b9pDs&#xA;fnQhV8yX/wDzhN+YiXTrYa3pFxagn05Z3uYJCK7co0gnVf8Agziqy0/5wn/MhrmNbzWdGhtif3ss&#xA;Ml1LIq+KxtbxBj7FxiqP/wCch/yl8r/lx+U2hado6Ga7n1VW1DU5QPWuHFtLStPsov7KDYe5qSqm&#xA;3/ODH/Tbf9uv/scxV9VYq7FUDrOhaJrdi9hrNhb6jZSfat7qJJUPvxcEV98VeG+ef+cOfIer+pc+&#xA;WLqby9eNUiDe6tCev2Hb1Er7PQfy4q+e/PP/ADjf+a3lH1JptLOq6dHv9f0wm4Tj4tGAJkp3LJT3&#xA;xV5gQQSCKEbEHrXFWsVdirsVdirYJBBBoRuCOtcVel+Sf+ci/wA1/KRjit9XbU7BKf6BqdbqOgps&#xA;rsRMgp2VwMVe/wDkj/nM3yZqXp23mvT59DuTQNdw1urUnYFjxAmT5cG+eKvdPLvmzyz5lsvrugap&#xA;banbbcntpVk4k9nUHkh9mAOKprirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVfJX/OZH5j+X&#xA;NVGmeT9LuEvL3TblrrU5YiGjhcIY1h5A0L/Gxcfs7Drtiqbf84OadPHpXm7UmB9C5nsraM02526T&#xA;O+/yuFxV9P4q7FUq8xebPLPlqy+u6/qltpltvxe5lWPkR2RSeTn2UE4q8L87/wDOZvkzTfUtvKmn&#xA;z65cioW7mra2oO4DDkDM/wAuC/PFXgHnb/nIv81/NpkiuNXbTLB6/wCgaZW1joa7M6kzOKdmcjFX&#xA;mhJJJJqTuSetcVaxV2KuxV2KuxV2KuxVF6Xq+q6TepfaVeT2F7F/d3NtI8Mq/J0KsMVe1eSf+cvv&#xA;zJ0P07fXUg8yWK0BM49C6AHYTxjifm8bH3xV795I/wCcqPyq8y+nBeXj+X9QegMGpAJFy2rxuVLR&#xA;cd+rlT7Yq9dtrm3uoI7i2lSe3lUNFNGwdGU9CrLUEYqqYq7FXYq7FXYq7FXYq7FXYq7FXzR/zmBr&#xA;n5mabNow0C4v7Ly20MjXlzp7SRg3If7E8kVCAEAKhjQ7+Gyr5h/5WD59/wCpl1X/AKTbj/mvFWx5&#xA;58/XP7geYNVm9T4fS+uXLcq9uPM1xVmv5Vf848+efPeoxy3FrNo/l9WButVuo2QsvdbeN+LSsfH7&#xA;I7nsVX2/5U8reWPInlaHSNLVbHSLBC8k0zgEk7yTTSNQcmO5PTwoNsVefed/+cqPyq8tepBZ3j+Y&#xA;NQSoEGmgPFy3pyuWKxcduqFj7Yq8B87f85ffmTrnqW+hJB5bsWqAYB690QexnkHEfNI1PvirxXVN&#xA;X1XVr177Vbye/vZf7y5uZHmlb5u5ZjiqExV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVkflH8xfPHk+&#xA;f1vLes3OnfFyaCN+UDnbd4H5RP0/aXFXvXkn/nNbVYPTtvOejJexigbUNOPpTU8WgkJjc/6roPbF&#xA;Xv8A5J/Ov8s/OYSPRdbh+uv/ANK65P1e5r4COTjz/wBhyGKs4xV2KuxV2KuxV2KuxV2KuxV2KsH8&#xA;6/nX+Wfk31I9a1uD69HsdOtj9YueX8rRx8uFf8viMVeAedv+c1tVn9S28maMllGahdQ1E+rNTxWC&#xA;MiND/rO49sVeC+bfzE87+b5/V8x6zc6iAeSQSPxgQ+KQpxiXr2XFWOYq7FXYq7FXYq7FXYq7FXYq&#xA;7FXYq7FXYq7FXYq7FXYq7FXYq9H8k/8AOQX5q+UPTisdZe9sE6afqNbqGg/ZUsfVQe0brir3/wAk&#xA;/wDOaPlO+9O383aZNo85oGvbWt1be7MgAmT5Kr4q918s+cvKnmi0+t+XtWtdTgABc28iuyV7SJ9t&#xA;D7MAcVTnFXYq7FXYqk3mbzl5U8r2n1vzDq1rpkBBKG4kVGenaNPtufZQTirwrzt/zmj5TsfUt/KO&#xA;mTaxOKhb26ra23syoQZn+TKmKvAPO3/OQX5q+b/UivtZeysH66fp1bWGh/ZYqfVce0jtirzjFXYq&#xA;7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqidO1PUtMu473TbuayvIjW&#xA;K5t5GilU+KuhVh9+KvZvJX/OXH5n6D6cGsGHzHYpsVux6VyFHZbiMbn3kVzir3/yT/zlf+VfmL04&#xA;NRuJPLt+2xi1AD0Cf8m5SsYHvJwxV3nb/nK/8q/LvqQadcSeYr9dhFp4HoA/5Vy9IyPePnirwDzr&#xA;/wA5cfmfr3qQaOYfLli+wW0Hq3JU9muJBsfeNUOKvGdR1PUtTu5L3Urua9vJTWW5uJGllY+LO5Zj&#xA;9+KobFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F&#xA;XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX6Hedv8AnHz8qvN/qS32jJZX79dQ06lrNU/t&#xA;MFBic+8iNir5/wDO3/OF/myw9S48o6nDrMA3WyuqWtz7KrkmF/mWTFXhPmXyf5p8sXn1PzBpVzpk&#xA;5rwW5jZFenUo5+Fx7qTiqT4q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F&#xA;XYq7FXYq7FU48teT/NPme8+p+X9KudTnFOa20bOqV6F3HwoPdiMVe7eSf+cL/Nl/6dx5u1OHRoDu&#xA;1la0urn3VnBEKfMM+KvoDyT/AM4+flV5Q9OWx0ZL2/TpqGo0upqj9pQwESH3jRcVejYq7FUNqOma&#xA;bqdnJZalaQ31nLtLbXMaSxMP8pHDKfuxV4v52/5xF/LPXvUn0X1vLd824NqfWtiT3a3kP4I6DFXg&#xA;Hnb/AJxU/NXy56k9jaJ5hsF3E2nEtMB/lWzUlJ9o+eKvILq1urS4e2uoXt7iI8ZIZVKOp8GVgCMV&#xA;UsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdiqra2t1d3CW1rC9xcSnjHDEpd2P&#xA;gqqCTir1/wAk/wDOKn5q+Y/TnvrRPL1g25m1ElZiP8m2WsoPtJwxV7/5J/5xF/LPQfTn1r1vMl8u&#xA;5N0fRtgR3W3jP4O7jFXtGnaZpumWcdlptpDY2cW0VtbRpFEo/wAlECqPuxVE4q7FXYq7FXYq7FXY&#xA;qx7zZ+Xvknzdb+h5k0a11IAcUllSkyD/AIrmTjKn+xYYq8F87f8AOFOkXHqXHkzWXsZTuthqIM0N&#xA;fBZ4x6iD5o5xV8/+dvyT/MzyZ6kmtaJN9Rj3Oo2o+sW1PFpI+XD/AGfE4qwbFXYq7FXYq7FXYq7F&#xA;XYq7FXYq7FXYq7FXYq7FXYqznyT+Sf5mec/Tk0XRJvqMm41G6H1e2p4rJJx5/wCw5HFX0B5J/wCc&#xA;KdIt/TuPOesvfSjdrDTgYYa+DTyD1HHyRDir3ryn+Xvknyjb+h5b0a100EcXliSszj/iyZ+Ur/7J&#xA;jirIcVdirsVdirsVdirsVdirsVdirsVdirsVec+dv+cfPyq83+pLfaMllfv11DTqWs1T+0wUGJz7&#xA;yI2Kvn/zt/zhf5ssPUuPKOpw6zAN1srqlrc+yq5Jhf5lkxV4T5l8n+afLF59T8waVc6ZOa8FuY2R&#xA;Xp1KOfhce6k4qk+KuxV2KuxV2KuxV2KuxV2KuxVOPLXk/wA0+Z7z6n5f0q51OcU5rbRs6pXoXcfC&#xA;g92IxV7t5J/5wv8ANl/6dx5u1OHRoDu1la0urn3VnBEKfMM+KvoDyT/zj5+VXlD05bHRkvb9Omoa&#xA;jS6mqP2lDARIfeNFxV6NirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVQ2o6Zpup2cll&#xA;qVpDfWcu0ttcxpLEw/ykcMp+7FXi/nb/AJxF/LPXvUn0X1vLd824NqfWtiT3a3kP4I6DFXgHnb/n&#xA;FT81fLnqT2NonmGwXcTacS0wH+VbNSUn2j54q8gurW6tLh7a6he3uIjxkhlUo6nwZWAIxVSxV2Ku&#xA;xV2Kqtra3V3cJbWsL3FxKeMcMSl3Y+CqoJOKvX/JP/OKn5q+Y/TnvrRPL1g25m1ElZiP8m2WsoPt&#xA;JwxV7/5J/wCcRfyz0H059a9bzJfLuTdH0bYEd1t4z+Du4xV7Rp2mabplnHZabaQ2NnFtFbW0aRRK&#xA;P8lECqPuxVE4q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqx7zZ+Xvk&#xA;nzdb+h5k0a11IAcUllSkyD/iuZOMqf7FhirwXzt/zhTpFx6lx5M1l7GU7rYaiDNDXwWeMeog+aOc&#xA;VfP/AJ2/JP8AMzyZ6kmtaJN9Rj3Oo2o+sW1PFpI+XD/Z8TirvJP5J/mZ5z9OTRdEm+oybjUbofV7&#xA;anisknHn/sORxV9AeSf+cKdIt/TuPOesvfSjdrDTgYYa+DTyD1HHyRDir3ryn+Xvknyjb+h5b0a1&#xA;00EcXliSszj/AIsmflK/+yY4qyHFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX/2Q==</xmpGImg:image>\n               </rdf:li>\n            </rdf:Alt>\n         </xmp:Thumbnails>\n      </rdf:Description>\n      <rdf:Description rdf:about=\"\"\n            xmlns:xmpTPg=\"http://ns.adobe.com/xap/1.0/t/pg/\"\n            xmlns:stDim=\"http://ns.adobe.com/xap/1.0/sType/Dimensions#\"\n            xmlns:stFnt=\"http://ns.adobe.com/xap/1.0/sType/Font#\"\n            xmlns:xmpG=\"http://ns.adobe.com/xap/1.0/g/\">\n         <xmpTPg:NPages>1</xmpTPg:NPages>\n         <xmpTPg:HasVisibleTransparency>False</xmpTPg:HasVisibleTransparency>\n         <xmpTPg:HasVisibleOverprint>False</xmpTPg:HasVisibleOverprint>\n         <xmpTPg:MaxPageSize rdf:parseType=\"Resource\">\n            <stDim:w>40.000000</stDim:w>\n            <stDim:h>40.000000</stDim:h>\n            <stDim:unit>Points</stDim:unit>\n         </xmpTPg:MaxPageSize>\n         <xmpTPg:Fonts>\n            <rdf:Bag>\n               <rdf:li rdf:parseType=\"Resource\">\n                  <stFnt:fontName>OCRAStd</stFnt:fontName>\n                  <stFnt:fontFamily>OCR A Std</stFnt:fontFamily>\n                  <stFnt:fontFace>Regular</stFnt:fontFace>\n                  <stFnt:fontType>Open Type</stFnt:fontType>\n                  <stFnt:versionString>Version 2.036;PS 2.000;hotconv 1.0.57;makeotf.lib2.0.21895</stFnt:versionString>\n                  <stFnt:composite>False</stFnt:composite>\n                  <stFnt:fontFileName>OCRAStd.otf</stFnt:fontFileName>\n               </rdf:li>\n            </rdf:Bag>\n         </xmpTPg:Fonts>\n         <xmpTPg:PlateNames>\n            <rdf:Seq>\n               <rdf:li>Cyan</rdf:li>\n               <rdf:li>Magenta</rdf:li>\n               <rdf:li>Yellow</rdf:li>\n               <rdf:li>Black</rdf:li>\n            </rdf:Seq>\n         </xmpTPg:PlateNames>\n         <xmpTPg:SwatchGroups>\n            <rdf:Seq>\n               <rdf:li rdf:parseType=\"Resource\">\n                  <xmpG:groupName>Default Swatch Group</xmpG:groupName>\n                  <xmpG:groupType>0</xmpG:groupType>\n                  <xmpG:Colorants>\n                     <rdf:Seq>\n                        <rdf:li rdf:parseType=\"Resource\">\n                           <xmpG:swatchName>Black</xmpG:swatchName>\n                           <xmpG:mode>RGB</xmpG:mode>\n                           <xmpG:type>PROCESS</xmpG:type>\n                           <xmpG:red>0</xmpG:red>\n                           <xmpG:green>0</xmpG:green>\n                           <xmpG:blue>0</xmpG:blue>\n                        </rdf:li>\n                     </rdf:Seq>\n                  </xmpG:Colorants>\n               </rdf:li>\n            </rdf:Seq>\n         </xmpTPg:SwatchGroups>\n      </rdf:Description>\n      <rdf:Description rdf:about=\"\"\n            xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n         <dc:format>application/pdf</dc:format>\n         <dc:title>\n            <rdf:Alt>\n               <rdf:li xml:lang=\"x-default\">download-svg</rdf:li>\n            </rdf:Alt>\n         </dc:title>\n      </rdf:Description>\n      <rdf:Description rdf:about=\"\"\n            xmlns:xmpMM=\"http://ns.adobe.com/xap/1.0/mm/\"\n            xmlns:stRef=\"http://ns.adobe.com/xap/1.0/sType/ResourceRef#\"\n            xmlns:stEvt=\"http://ns.adobe.com/xap/1.0/sType/ResourceEvent#\">\n         <xmpMM:DocumentID>xmp.did:A60C28F253206811AFFDE64AE370C81B</xmpMM:DocumentID>\n         <xmpMM:InstanceID>uuid:4bcda3da-31ce-014d-aa74-633cb22b5a0a</xmpMM:InstanceID>\n         <xmpMM:OriginalDocumentID>xmp.did:A10C28F253206811AFFDE64AE370C81B</xmpMM:OriginalDocumentID>\n         <xmpMM:RenditionClass>proof:pdf</xmpMM:RenditionClass>\n         <xmpMM:DerivedFrom rdf:parseType=\"Resource\">\n            <stRef:instanceID>xmp.iid:A50C28F253206811AFFDE64AE370C81B</stRef:instanceID>\n            <stRef:documentID>xmp.did:A50C28F253206811AFFDE64AE370C81B</stRef:documentID>\n            <stRef:originalDocumentID>xmp.did:A10C28F253206811AFFDE64AE370C81B</stRef:originalDocumentID>\n         </xmpMM:DerivedFrom>\n         <xmpMM:History>\n            <rdf:Seq>\n               <rdf:li rdf:parseType=\"Resource\">\n                  <stEvt:action>saved</stEvt:action>\n                  <stEvt:instanceID>xmp.iid:A10C28F253206811AFFDE64AE370C81B</stEvt:instanceID>\n                  <stEvt:when>2015-07-05T03:31:01-07:00</stEvt:when>\n                  <stEvt:softwareAgent>Adobe Illustrator CS5.1</stEvt:softwareAgent>\n                  <stEvt:changed>/</stEvt:changed>\n               </rdf:li>\n               <rdf:li rdf:parseType=\"Resource\">\n                  <stEvt:action>saved</stEvt:action>\n                  <stEvt:instanceID>xmp.iid:A20C28F253206811AFFDE64AE370C81B</stEvt:instanceID>\n                  <stEvt:when>2015-07-05T03:31:06-07:00</stEvt:when>\n                  <stEvt:softwareAgent>Adobe Illustrator CS5.1</stEvt:softwareAgent>\n                  <stEvt:changed>/</stEvt:changed>\n               </rdf:li>\n               <rdf:li rdf:parseType=\"Resource\">\n                  <stEvt:action>saved</stEvt:action>\n                  <stEvt:instanceID>xmp.iid:A30C28F253206811AFFDE64AE370C81B</stEvt:instanceID>\n                  <stEvt:when>2015-07-05T03:31:36-07:00</stEvt:when>\n                  <stEvt:softwareAgent>Adobe Illustrator CS5.1</stEvt:softwareAgent>\n                  <stEvt:changed>/</stEvt:changed>\n               </rdf:li>\n               <rdf:li rdf:parseType=\"Resource\">\n                  <stEvt:action>saved</stEvt:action>\n                  <stEvt:instanceID>xmp.iid:A40C28F253206811AFFDE64AE370C81B</stEvt:instanceID>\n                  <stEvt:when>2015-07-05T03:31:53-07:00</stEvt:when>\n                  <stEvt:softwareAgent>Adobe Illustrator CS5.1</stEvt:softwareAgent>\n                  <stEvt:changed>/</stEvt:changed>\n               </rdf:li>\n               <rdf:li rdf:parseType=\"Resource\">\n                  <stEvt:action>saved</stEvt:action>\n                  <stEvt:instanceID>xmp.iid:A50C28F253206811AFFDE64AE370C81B</stEvt:instanceID>\n                  <stEvt:when>2015-07-05T03:32:34-07:00</stEvt:when>\n                  <stEvt:softwareAgent>Adobe Illustrator CS5.1</stEvt:softwareAgent>\n                  <stEvt:changed>/</stEvt:changed>\n               </rdf:li>\n               <rdf:li rdf:parseType=\"Resource\">\n                  <stEvt:action>saved</stEvt:action>\n                  <stEvt:instanceID>xmp.iid:A60C28F253206811AFFDE64AE370C81B</stEvt:instanceID>\n                  <stEvt:when>2015-07-05T03:33:09-07:00</stEvt:when>\n                  <stEvt:softwareAgent>Adobe Illustrator CS5.1</stEvt:softwareAgent>\n                  <stEvt:changed>/</stEvt:changed>\n               </rdf:li>\n            </rdf:Seq>\n         </xmpMM:History>\n      </rdf:Description>\n      <rdf:Description rdf:about=\"\"\n            xmlns:illustrator=\"http://ns.adobe.com/illustrator/1.0/\">\n         <illustrator:Type>Document</illustrator:Type>\n      </rdf:Description>\n      <rdf:Description rdf:about=\"\"\n            xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\">\n         <pdf:Producer>Adobe PDF library 9.90</pdf:Producer>\n      </rdf:Description>\n   </rdf:RDF>\n</x:xmpmeta>\n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                                                                                                    \n                           \n<?xpacket end=\"w\"?>\rendstream\rendobj\r3 0 obj\r<</Count 1/Kids[8 0 R]/Type/Pages>>\rendobj\r8 0 obj\r<</ArtBox[0.0 1.25 40.0 38.75]/BleedBox[0.0 0.0 40.0 40.0]/Contents 9 0 R/LastModified(D:20150705033312-07'00')/MediaBox[0.0 0.0 40.0 40.0]/Parent 3 0 R/PieceInfo<</Illustrator 10 0 R>>/Resources<</ExtGState<</GS0 11 0 R>>/Font<</T1_0 5 0 R>>/ProcSet[/PDF/Text]/Properties<</MC0 6 0 R>>>>/Thumb 12 0 R/TrimBox[0.0 0.0 40.0 40.0]/Type/Page>>\rendobj\r9 0 obj\r<</Filter/FlateDecode/Length 229>>stream\r\nHTQ=O\u00031\f+<\u0010o=*\n!3TU\u0015\u001dhI.w(Rg9ۈndܼ\b\\&\u0018^?\u0018+|̔$\n,\u001e]\u0014<gD\u00023\u001d[haY=\u0015\u001dARN5_)ԖI}Dc-]Q|\u0012G\u001aru+n*I$2ebGƭRw\\ûΰ\u0012)d}09\u0001aS`(\u0016V\u000e`$`cPlߦL>\u0006V\fO\\N-՟\u0015`\u0000\u0000[P\rendstream\rendobj\r12 0 obj\r<</BitsPerComponent 8/ColorSpace 13 0 R/Filter[/ASCII85Decode/FlateDecode]/Height 5/Length 29/Width 5>>stream\r\n8;Xq'^qapf3f%0/\"TSQ-!(?P`>l~>\rendstream\rendobj\r13 0 obj\r[/Indexed/DeviceRGB 255 14 0 R]\rendobj\r14 0 obj\r<</Filter[/ASCII85Decode/FlateDecode]/Length 428>>stream\r\n8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@\"pJ+EP(%0\nb]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\\Ulg9dhD*\"iC[;*=3`oP1[!S^)?1)IZ4dup`\nE1r!/,*0[*9.aFIR2&b-C#s<Xl5FH@[<=!#6V)uDBXnIr.F>oRZ7Dl%MLY\\.?d>Mn\n6%Q2oYfNRF$$+ON<+]RUJmC0I<jlL.oXisZ;SYU[/7#<&37rclQKqeJe#,UF7Rgb1\nVNWFKf>nDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j<etJICj7e7nPMb=O6S7UOH<\nPO7r\\I.Hu&e0d&E<.')fERr/l+*W,)q^D*ai5<uuLX.7g/>$XKrcYp0n+Xl_nU*O(\nl[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\\~>\rendstream\rendobj\r6 0 obj\r<</Intent 15 0 R/Name(Capa 1)/Type/OCG/Usage 16 0 R>>\rendobj\r15 0 obj\r[/View/Design]\rendobj\r16 0 obj\r<</CreatorInfo<</Creator(Adobe Illustrator 15.1)/Subtype/Artwork>>>>\rendobj\r5 0 obj\r<</BaseFont/FYQHYL+OCRAStd/Encoding/WinAnsiEncoding/FirstChar 103/FontDescriptor 17 0 R/LastChar 118/Subtype/Type1/Type/Font/Widths[720 0 0 0 0 0 0 0 0 0 0 0 720 0 0 720]>>\rendobj\r17 0 obj\r<</Ascent 823/CapHeight 778/CharSet(/g/s/v)/Descent -239/Flags 34/FontBBox[-720 -239 1440 823]/FontFamily(OCR A Std)/FontFile3 18 0 R/FontName/FYQHYL+OCRAStd/FontStretch/Normal/FontWeight 400/ItalicAngle 0/StemV 96/Type/FontDescriptor/XHeight 539>>\rendobj\r18 0 obj\r<</Filter/FlateDecode/Length 698/Subtype/Type1C>>stream\r\nH<ORq\u00189Ud5;lzaExccK+jP@C!^\u000e\u00108@i'1R\u0010\u0004-VZj.w\u000e;\u0002/z|{<H ȉ:n\u0015\u0016u\"y\u001cO\u0015bɁZpWX3'`mVIQ\u0011(\fFY;tB]hSuu\\m\u0018P\n\u001bmi}l4-\u001au\r%\u00052khy,oֶ(zlF\r%ԚAQD=ۇD\b$\u001c2\b,UbOy+7}Ѕ,!\u0011\u001ay\u0011\u0003\u001eͽ\u0014:X\\!\t\u0005\u000bTSY\u000e~\u0010X$W\fD=kN7icu:C7(\u0010`6&Q8)+.\\AG\u0017\u0004\t/[T\u001c\u0003_R^\u001el\u000fMz`cPlkPc=jgTw[\u0012}\u001c[\b\u0012;)\u000f\u0004}!;\u001az:鈐?;@c\u0018i3L3\u001e/!\u0000\u000esr\u0002oF'|~\u000fJ\u0001WQ,*-e9?`+[Nzxf|dB\u000b\b\u001aǭ!\"94껅\u000b2r{R&X\u0015XL8,REOK\b^?<N\\'\u000eh\u00193불4lg28Fyme{\u0014<\u0010\u0001NIL?\u0012>}@\u001f\r>Wj\u0019hs.\u0006ê5\u0013\u0016މ\u0006IX\twA`qn~I\u0015K2q;%`,o͢ňX\bw;͉y\u001dϪO\u0013`\u0000:A\rendstream\rendobj\r11 0 obj\r<</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask/None/Type/ExtGState/ca 1.0/op false>>\rendobj\r10 0 obj\r<</LastModified(D:20150705033312-07'00')/Private 19 0 R>>\rendobj\r19 0 obj\r<</AIMetaData 20 0 R/AIPrivateData1 21 0 R/AIPrivateData2 22 0 R/ContainerVersion 11/CreatorVersion 15/NumBlock 2/RoundtripStreamType 1/RoundtripVersion 15>>\rendobj\r20 0 obj\r<</Length 970>>stream\r\n%!PS-Adobe-3.0 \r%%Creator: Adobe Illustrator(R) 15.0\r%%AI8_CreatorVersion: 15.1.0\r%%For: (Shane Carr) ()\r%%Title: (download-svg.svg)\r%%CreationDate: 7/5/15 3:33 AM\r%%Canvassize: 16383\r%%BoundingBox: 285 377 326 416\r%%HiResBoundingBox: 285.5 377.75 325.5 415.25\r%%DocumentProcessColors: Cyan Magenta Yellow Black\r%AI5_FileFormat 11.0\r%AI12_BuildNumber: 39\r%AI3_ColorUsage: Color\r%AI7_ImageSettings: 0\r%%RGBProcessColor: 0 0 0 ([Registration])\r%AI3_Cropmarks: 285.5 376.5 325.5 416.5\r%AI3_TemplateBox: 305.5 396.5 305.5 396.5\r%AI3_TileBox: 17.5 40.5 593.5 774.5\r%AI3_DocumentPreview: None\r%AI5_ArtSize: 14400 14400\r%AI5_RulerUnits: 2\r%AI9_ColorModel: 1\r%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0\r%AI5_TargetResolution: 800\r%AI5_NumLayers: 1\r%AI9_OpenToView: 211.75 449.75 8 1631 842 26 0 0 43 134 0 0 0 1 1 0 1 1 0 1\r%AI5_OpenViewLayers: 7\r%%PageOrigin:0 0\r%AI7_GridSettings: 283.4646 10 283.4646 10 1 0 0.8 0.8 0.8 0.9 0.9 0.9\r%AI9_Flatten: 1\r%AI12_CMSettings: 00.MS\r%%EndComments\r\rendstream\rendobj\r21 0 obj\r<</Length 5951>>stream\r\n%%BoundingBox: 285 377 326 416\r%%HiResBoundingBox: 285.5 377.75 325.5 415.25\r%AI7_Thumbnail: 128 120 8\r%%BeginData: 5810 Hex Bytes\r%0000330000660000990000CC0033000033330033660033990033CC0033FF\r%0066000066330066660066990066CC0066FF009900009933009966009999\r%0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66\r%00FF9900FFCC3300003300333300663300993300CC3300FF333300333333\r%3333663333993333CC3333FF3366003366333366663366993366CC3366FF\r%3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99\r%33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033\r%6600666600996600CC6600FF6633006633336633666633996633CC6633FF\r%6666006666336666666666996666CC6666FF669900669933669966669999\r%6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33\r%66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF\r%9933009933339933669933999933CC9933FF996600996633996666996699\r%9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33\r%99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF\r%CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399\r%CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933\r%CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF\r%CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC\r%FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699\r%FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33\r%FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100\r%000011111111220000002200000022222222440000004400000044444444\r%550000005500000055555555770000007700000077777777880000008800\r%000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB\r%DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF\r%00FF0000FFFFFF0000FF00FFFFFF00FFFFFF\r%524C45FD38FF27FD0FF8FD70FFFD10F8A8FD6FFF27FD0FF8A8FD6FFFFD10\r%F8A8FD6FFF27FD0FF8FD70FFFD10F8A8FD6FFF27FD0FF8FD70FFFD10F8A8\r%FD6FFF27FD0FF8FD70FFFD10F8A8FD6FFF27FD0FF8A8FD6FFFFD10F8A8FD\r%6FFF27FD0FF8FD70FFFD10F8A8FD6FFF27FD0FF8FD70FFFD10F8A8FD6FFF\r%27FD0FF8FD70FFFD10F8A8FD6FFF27FD0FF8A8FD57FF52F8F827F827F827\r%F827F827F827F827F827F827F827F827FD11F827F827F827F827F827F827\r%F827F827F827F827F827F852FD41FF27FD3CF827FD43FF27FD3AF827FD45\r%FF27FD38F852FD47FF27FD36F827FD49FF27FD34F852FD4BFF27FD32F827\r%FD4DFF27FD30F827FD4FFF27FD2EF827FD51FF27FD2CF852FD53FF27FD2A\r%F827FD55FF27FD28F852FD57FF27FD26F827FD59FF27FD24F852FD5BFF27\r%FD22F827FD5DFF27FD20F827FD5FFF27FD1EF827FD61FF27FD1CF852FD63\r%FF27FD1AF827FD65FF27FD18F852FD67FF27FD16F827FD69FF27FD14F852\r%FD6BFF27FD12F827FD6DFF27FD10F827FD6FFF27FD0EF827FD71FF27FD0C\r%F852FD73FF27FD0AF827FD75FF27FD08F852FD77FF27FD06F827FD79FF27\r%FD04F852FD7BFF27F8F827FD7DFF2727FD62FFA87D7D527D527D527D527D\r%7DFD07FFA87D7DFD07FFA8FD04FF7D52FD09FFA87D527D527D7DFD04FFA8\r%52FD45FF52FD0CF827FD06FF27F8F8A8FD0AFFA8F8F827FD07FF7D27FD06\r%F827A8FFA8F8F827FD43FF27FD0FF8FD04FFA8F8F8F87DFD0AFF52F8F8F8\r%A8FD05FF52FD0AF87D27F8F8F8A8FD36FF5227A8FD08FFA8FD10F852FFFF\r%FFA8F8F8F852FD0AFF52F8F8F8FD05FF27FD10F8FD08FF5227A8FD29FF7D\r%52F8F8F827A8FD07FF52F8F8F87DFD08A87DF8F8F87DFFFFFFA8F8F8F87D\r%FD0AFF52F8F8F8A8FFFFFF52FD04F852FD04A827FD07F8A8FD06FF27F8F8\r%F82752A8FD24FFA827FD06F827FD07FF52F8F8F8FD0AFFA8527DFD04FFA8\r%F8F8F852FD0AFF52F8F8F8FD04FF27F8F8F8A8FD06FF7DFD06F8FD06FF52\r%FD07F8527DFD20FF5227FD09F827A8FD05FF52FD04F852A8FD0FFF27F8F8\r%F8A8FD08FFA8F8F8F852FFFFFFA8F8F8F852FD08FF7D27FD04F8A8FD04FF\r%27FD0BF852A8FD1AFFA852FD0DF827FD06FFFD06F852A8FD0DFFA8F8F8F8\r%52FD08FF27F8F8F8A8FFFFFFA8F8F8F87DFD09FFA827F8F8F8FD04FF52FD\r%0EF827A8FD16FFA827FD10F827A8FD04FFA8FD07F8277DFD0BFFA827F8F8\r%F8FD07FFA8F8F8F827FD04FFA8F8F8F87DFD0AFF27F8F8F8A8FFFF27FD11\r%F82752A8FD11FF7D27FD13F827A8FD05FF5227FD06F8277DA8FD09FF7DF8\r%F8F87DFD06FF52F8F8F8A8FD04FFA8F8F8F852FD0AFF52F8F8F8FFFF52FD\r%15F8527DFD0CFFA852FD17F827A8FD05FFA87DFD08F852FD09FFF8F8F827\r%FD05FFA8F8F8F827FD05FFA8F8F8F87DFD0AFF52F8F827A852FD19F8527D\r%FD07FFA852FD1AF827A8FD07FF7D52FD07F8A8FD07FF7DF8F8F87DFD04FF\r%52F8F8F87DFD05FFA8F8F8F852FD0AFF52F8F8F827FD1CF8277DA8FFFF7D\r%27FD1BF82752FD0AFFA827FD06F8A8FD06FFA8FD04F8FFFFFFA827F8F8F8\r%FD06FFA8F8F8F87DFD0AFFFD04F85227FD1EF827A8FD1BF8277DFD0EFFA8\r%7DFD04F852FD07FF52F8F8F87DFFFF7DF8F8F87DFD06FFA8F8F8F852FD08\r%FFA8FD05F8FFFFA85227FD36F8277DFD12FFA8F8F8F827FD07FFA8F8F8F8\r%52FFFFFD04F8A8FD07FFFD04F87DFD06FF7DFD06F8A8FFFFFFA87D27FD32\r%F852A8FD08FFA8FD0CFF52F8F8F8FD08FF52F8F8F8FFA8F8F8F852FD08FF\r%52FD04F852FFA8A8A852FD07F8FD07FFA852FD2EF852A8FD09FF52F8F8A8\r%FD0AFFF8F8F827A8FD07FF7DF8F8F82752F8F8F8A8FD09FF27FD10F8A8FD\r%09FF7D27FD28F8277DFD0BFFA8FD04F8FD0952FD04F827FD09FF27FD06F8\r%52FD0BFF52FD0AF82752F8F8F8FD0DFF5227FD23F8277DFD0EFF27FD10F8\r%A8FD09FFA8FD06F8A8FD0CFF52FD08F852FF52F8F8F8A8FD0EFFA827FD1F\r%F8527DFD11FF27FD0EF87DFD0BFF27FD04F827FD0EFFA87D7D527D527D7D\r%FFFF52F8F8F8FD11FFA87D27FD1AF852FD15FF5227FD09F82752A8FD0CFF\r%7D27F8F8F8FD19FF52F8F8F8A8FD13FFA852FD18F87DA8FD4CFF52F8F8F8\r%FD14FFA85227FD1AF852A8FD49FF27F8F827A8FD10FFA85227FD1FF8277D\r%A8FD45FFA827F8F827FD0FFF7D27FD25F8277DA8FD41FF7DFD04F852FD0C\r%FF7D52FD2AF82752A8FD35FFA8FD085227F8F827F8F8A8FD09FFA87DFD30\r%F82752FD32FFA8FD0DF827A8FD07FFA87D27FD35F8527DFD2FFF7DFD0CF8\r%27A8FD06FFA82727FD3AF8527DFD2DFF5227FD07F827277DFD05FFA85227\r%FD3FF8277DA8FD39FF7D27FD45F8277DA8FD33FF7D52FD4AF82752A8FD2E\r%FFA852FD50F82752A8FD28FFA87DFD56F8527DFD24FFA827FD5BF8527DFD\r%1EFFA852FD60F8277DA8FD19FF5227FD65F8277DA8FD13FF7D27FD6AF827\r%52A8FD0EFFA852FD70F82752A8FD08FFA87D27FD75F8527DFFFFFFA87D27\r%FD7BF85252FDFCF8FDFCF8FD47F8A85227FD7BF8527DFFFFA87D27FD76F8\r%277DFD08FFA87D27FD70F82752FD0EFF7D52FD6CF827A8FD13FF7D52FD66\r%F8277DA8FD17FFA85227FD61F87DA8FD1DFFA85227FD5BF8527DFD22FFA8\r%7D27FD56F8277DFD28FFA87D27FD50F82752FD2EFF7D52FD4BF82752A8FD\r%33FF7D52FD46F8277DA8FD37FFA85227FD41F852A8FD3DFFA85227FD3BF8\r%52A8FD42FFA87D27FD36F8277DFD48FFA87DFD31F82752A8FD4DFF7D52FD\r%2CF827A8A8FD52FF7D52FD26F8277DFD58FFA85227FD21F87DA8FD5DFFA8\r%5227FD1BF852A8FD62FFA87D27FD16F8277DFD68FFA87D27FD10F82752FD\r%6EFF7D52FD0CF827A8FD73FF7D52FD06F8277DFD78FFA852F8F87DA8FD3C\r%FFFF\r%%EndData\r\rendstream\rendobj\r22 0 obj\r<</Length 20538>>stream\r\n%AI12_CompressedDataxݽ~:0zn{\u0004\u0002bK*t\u0012һ\u0001'(&6wG\\dz}γO\u0016`yF\u001afF\u0019M(&*\u0003'2I\u000bj\"-\u0004GO&\u0010O?KQ4Ei$Q\n=l·#!75N\u0014H\u0014|;ZLx` 3\b !NGU\u0000J[&\u0005&ǤrL9W.cn|\u0001Ul0}Wt\u00114/\u0014tޟM膗mU\u0000I/Yt\u000e\u000b唟-:%&L\u0004Q*k\u0006O8\u0013?\b\u0013?^i\n\u0005hzUt\u0017\u0003%\u000fq$\u001cMx@)\u0010*'Gu9\f.\u001e\u000fȗ)_3\u001f'w\u0012\u0002\r\f.|L/]~\u0000\u0003!oZUG_\u001e\u0019\u0004}*`Ea>ı%ԩ\u0002>n|\u0002\u0004\u00110â%R\u0004cBR\u0005\b\u0005r\f[(d6:#R2I*+Oo6˲_rw\u0011b\u001aTr!\f\thߜp\u0016返Wnpˉ\u00020Y.\u0010\u0016U\f\u001f\u001eNxJFp5g=c\u001aL\u0011`l\u0004)BKٴ\u001f\u001ađS6CX\u0010\u0000\u0003fJ\u001c}fe8\u001a3.f|6OhD\"\n\u0005?S\u0002تv\t\u0002٠&LTHp%\u0001\u0006\u0001ޙ\b33z\u0002^_}/LY\n\u000b^\u0002&gE7OLe0EDb*\"x`4pL/\u0017<s\u00003Wj3\u001fsM8\u0004#\u0011pׄ6\u001c\u0002WM\u001f˯ثH\u0006?ی6#\f1V\nX[Ak+]YMrӥ,\u0003\u0017\u0001 3}@oFZ\u0004B\u000fr2\u0017̜\u0013!\u0005C\u00061f\u0004PIDk\u0017@\u001b0}a\u0007p<e>h3a1࿘J&4i?́\u0014fK\u001f~L}v꿂8\u0001ь_N\u0017\u0001\u0007~W,y\t\u000e\u0012|?#Q\u0017\u001eͬ\u0006RqO\u0005;\u00167\u0012@\"pҜLEl?\rF<2\u001b.1\u0014\u0018\u0019\u0018\u0001U07w\b2F4Oڧ/g\u0014\u0005SR~o\b\u001a\u0001+\u000103sG\u0018@\u0006As\u001e'Z\u0007\u0017t1\u0014\u0012`\u0011\u001fSX}Ȝ:אIs\"~Dnt%7㾒[-p0w\u0001)\u0019my\"\\_\u000e?jт}p9t-\u0017`\u001d\u00020f\u001cxw@4wq+\u0001-+\u0007\u0012SY_Pt_\u0018'F}$\u000bg\u0019\u0000ysg^RvV0\u0006ϒ/yf4\u0002k`\u0007[Ypܨ\u0001\u0010\u0001D_|b\n.0\u0017#n2\u0018}}1`\u0000S(\f}G\u0000\u0002\n2\u0000Xb7J9\u0006vXR^L\u0000$*\u001c\nVoޟβ\u0004Gϴ'L>\u0018_ESCɴ\u0000쀉jw9!3YG-QXOf_/\"\u001b!z\u0004\u0003\u001c-$7G\u001d\u0001\u0001M%~!x\u0019}Uu\u0001\u000bJ]1O9\u0006V\u0005\u0017\u0003'Aw&܌\u0013w\r\b\u000e\u0007h\u0000\n\u001cjL\b\u001c\u001a`(\u0010q!0R4WcuР\u0010Uq)\r0\u001a\u001fiЕѯ\u0003G\u00070\u0004bR\u001a1\u0001?\u0005&c\u0000\u000fk\u001cP\u0019\u0010<0ϼ0\u0016\u0012y#i\u0013K\u0007\u00016\t\u001a\u0006\u0003H\u0015sG0\u0017&j\u0000栂el\",|K\u0013)Z\u0001jǜ݄̀`p3\u0001䦼?c#Kէ&b6K}\u0001/SCf3hPbS<\u0006Jh'm\u0013\u0003\u000fOǅ\u000fϏ7#0?dy\u0003E\u00055u\u0005-'7b{\r\u0001lX|/f\u00076N˽ϫ\u001b\n}S^#\u0019:g\u001d\u000e`\u0001vzԽ5oѨ\u001fg٤w\u0014|\u0011@ouZ>zs\t|,A\rVa\u0014+?\u001f\u0004(?\u0003@\u001f\t^:|\u000fF\u000e\t~\u001b!#\u0013i?s\u0002\u001e*\u0014n?\u0017\u0005\bx8\u0014\u0006<j\u001e\u001e(\u0000v\u00176GʀO?3{sҠ\u001f/t\u0015Q>\u001a{0\u0017\u0000\fm\u000eVoz\u0013^\u000bއM*mCm\u0012XC`Q\u001eؐIr=\u001b]8(x_X!\u0019{\fߑʿ];\u001aٙI\u001f`=BSi`\u0010np\u0010\u0019qE.\b1?p\u00130\u001e۬GN\u0004\u0012\rml\u0011:HƇor_\u0004D2]\u0006&/\u001f\u001aHWX}\u001e\t'd򐇮b~fq\u001bss\f=N?b:,\u001f\u001bmxPL\u000f:no班A:\u00165\u0013\u0016`:\u0016!Z\ngT\u0001G+PF\u001fW0\u001b<qhhN\u0004Axe\u00141Μ\u0016Jr\u0019#\u0004Qm\\TL94SO\u000e-[\"OyHxG3B@\u0000Zk]um5+\u0016H}EMΦɽm\u000bY\u001bn\u0019b\u001a?\u0016EՀEiqR\u001b.x7x[0z\u0018VJ\f#f\u000bhX\u001awD\u0019OJ\u0001UPLRF \u0007;~F\u0010\fL\u0006]f)s9`Ah\u0005S(y\u0002s+1\u0018$hGҨ7\u0000>.\u0005\u0016)ﯡ@'Yޟ\u0006͑(J\u000b3e\u0014J9D\f梦\u000bGs\u0002L#[{\u0011\r̵\u000e8ٞ:\u0003\rr,y0/0HZp&}Vt\u0015\u0018O\u0018Ԛ\u0016m\u0019s\u000e\u001a?cY\t\u0007Ea\u001bdP[XiКzp$\u001a\bj\u0006H\u001cWN:\b\f\u0005xSo3@0FΊ&Y?\u0001-*U'}\u000e\u0006jv\u000f\u000eĭ)X%\u0006_X.`ȖKRZXZRh\u000b\u001f\tYb\t1\"r0-\u001b]\u0010=n/\u001c\u0012K\u000byq\u000eO\u0014$\u0011AɎbX\u001d\u0011^Z\u001am.'\u0013TI\to\u0006OJ;j_n\u0001%hl\r:ΩP@}/o\u00110\u0003\u0001;\u000brnM\u001a\t{\u0002'\u000e[\b{\u001fX.M;iz+D^˂4Ҥ_J]1z0itBܘy\"\u001dW\u001e4\u0004Ru>\\pXj*qL3lv\u0015\u0011\"\u0011\u0005䍊s\u0015$\u0004{Iɿ\u00104瀡\u00062/i-\u0001Jrԇj-j\u000e6Z.˅\u0006~=@Y\u001c\r\\\r?\u0002\u0006`\u000f\nrh<\t1X`ʛ\u000b~0ZN1oV\u001etrY5j\n\u0004\f棤ddH\\0K)')\u001cɫs\u0003+˅ѕ*\u001bH{N]Z}\u0018~\u000eeQU6-u-ڶ)\\:.\u000e:ڶgqqZaR]&A'v=0(\u00062\u0019IB)Q\u0011^Cƴф\u0001Z[>\fG\u001ei_\u0010\u0007y~RXxye\r=3`y\u0003+x\fd\u001c\u0015NTՙ\u0018b\b\u001dkٟ\u0019\u001b!$]?=M\u0000\u0002`t\u001f:QuZP\"*:vk\n^\u001bJ\u0014o\"\u000eD))a*=rFsmM\u0015\u001e4Rm\u0002Ɂr҄\u0017YwYT0\u001f,]\u001aHn\u0010\u0002y.@\u0003h97\u0010fjZ#.n\"\"Ud\u0012\b\u0017\nʰe!gl\bZ\u0019`.OF@\u0010\u0019aZt\r`$#I}Fv\u000bt ؆ Lf7#GJFPR)\u0003$tOȘf\u0004`d.~\tm\u001d.\u0015\u0012eAa)\u000eBS\u001a&?,Y㽦h.\u001aen^S5^/!D'D,\u000eѥ\u001cl^F/\rAi*m\u001cq\u0003\u000bnF8;\u001eX\u000f)77K\u001b\u0017Ńp\u00170Q\rYj8L\t\u0006P1!۩$Ny7e;\u0007jfD\u0011*yFgNAn3U^͸\tݵm;\u001d@\u001b\u0005MʆyCA\u001fuoBj'#3\u0002p\"&5\u001fF\u000f:Uu78.|]\u0018\u0018\u0015g\u0005@\u001d$yQQ4lF;\u000bUkSB1\u0000\u000exi=s|.&܉\u0011`ClȻt\u0019\u0018漫_@\r?\b\"`\u0014ri[\b\u001a`BA#JYƘ>J\u0015jȭ\n%b_qSf\u0018l\u001ed ;\u001cd*ha!-&J|@F\r)v\u0014A[0'ؙ:Kp!VCr{u\u001fe`8DNG6).N\u0000_Q\u001ba\u001c\fڣُki6SnB\t \u0003\u001d\bKD}E\u0000AX̍`N(\u000e{xR\u001a@ttXv`ac6NکЃKvS\u0006Zt?]g/?\u0001[\u001av[з-'\u0019\u0000\u001eoi\u000f\n~Πs3ZX=\u0003-\u0014\"JIS9wPz\u0016f\u001an}t\u0012d/u!B8\u0003\u000bѸɂD?ƥ\u0014\u000eXv\nw\u001d\u0004\\?\u0014h\u0007V\u000f\u0019Lt\u0003\u001a\u0007S\\AŋCWLG'\u00067Ҧ\u0005\u0013!w@㙴{ޑ9U|\u0017ot5N(gKئK:SM9yO\b\u001c\u001aBppRcK+'\u0016\u0019\u00045%o\u0004GP[#\rMF!6\u0002@k]7\u0001t\b\u0018exn-\u0002i\u0002dN)BS\u0017ə5JJO<52ˤ\u0005Khf:{\t\u0018cgw\f-87@\u000eތ\u001bM\u001c=;I(Fyx\u001c\n\\ɵ\u0006y\u0003¹xn=GK\u0000\u0000xP܊)<{|0\u000e@v\u0018|Š/\u00009qt\u0018\u0018Q\t{@R\u001d\u001fT5`Q\u001a5]\u000ba&0c\u001eg\u0004L^anf+Ֆzӹ\u0002\u0012?B.~<Ld[y}|x\f@ʗ\rUa9J_\u0014zB\u0013j%U\u000f\u001d>MYb\u001ebc\u0010\u001b:B\u0017#6~f@->\u0016ZGmn]C[Gw鍋cmCm֨\u0015M`Q#\u0000&vY'Xu\u0013G訒\u0004J\u0015<κ\u001dj7E\u0018ߤ\u00156H\u001d\u0018\n)/#CҢ\u0000,(L\u0014Y;bA6VACdh,7\u0003K6Oҽ&\u0006#WIR\u001dY\u001fX_(j2qOSWZ\u001d^6S\"cW(G:!Nn\u0005&\f\u0000H\u0013ΆW2O\u0011\u0015\u001d\u0002&[\u0016Cޯ8[D3g~\rGY$~N?+\f՛Z8W\u000fj\b\u00125\u0002#,s~\u001b^\u0011j\u001977A\u0017\u0004(\u0007ȴWg@A\u0017\u0002\u0004#\u0013\u000f\f\u001dd_Z{':r`dl3@%\u001fIl\fIRU_\u001c]\u0017\f]?\u0002b\rg-~z\u0012(kK[}C\u0012\n\u0005nc+I1WH!a\u0010;1,\u0013ǎ\f_3ڃk\u0013z9]T_ָ}sտاCi:vp\u001f\u0006{ Bhr\u0018\\\u0006Y)\u0002uVJK\u0017\u000fq0S-fy?\u0014\n\u000f\u0019v?e\u001f|M\u0014\u000fYkrWTB3xm=BL|Vijۣ1ؗۧS٩iH>\u0001`MOb\u001a\\5\u001b_p%QLK\\ \u001c9\rZ?`,\u0001\f$GD1\u0006_D)\u001d\u0005`T\u000b0|3\u001brs\u000b\u00066oWuMT\rv\u001aҹ2\u0007OYyUAĆ+\u000f([\u001f\u000b\u001by\u0013_l2s^\\\u000e[\u001d}\u0018IF*˧\u001eښ(u{ң\u0016_L+\u0001\r\u0017B\u0003ƞ[.x15&\u0002!}׉HsיHB),\u0000\u0016#Z\u0015i\u0012i[R!EBΖB\t)ĂoH0C*.?H/xjmG\u0004`q醙VXOlzm;ҭ\u001c\u001a]\"mm\u000b&\b6|z \u0011N|O!Ҩ[\u000fPn\u0011\u0006e\u0005˜`\u0015p4Bkv+\u0011v߻! -\u0006ߌTd=q{IDZ`a{(c=N\u001d`y\u001c*\u000fR\u0004a4\\[HOOl^H\u0001\u0016|V\u000bi\u000e)|{k\u0012CE\u0014\r\u0015\u001dVHvV\u0017#\u0005S\"bZ4GكW\u0019iu\u0004<}g\u000b\u0014DkA\u0005u\u0016摞<s\u0004m_0\u001e4uFubd|KE\u000bgt`^hn% }Iݏ=ҳ6\u001c!\u0005r\u001f}\u0013\u001d^+\u0006i?#=τ\u0007\b/d\u001et !b\u001fvH}!i~fK෫\t\u0011鯧ܠI@ZdߤI\u0002!<f\u001aT\"8ÏHP7\u0016C\u000bes6FS[\u0002\u0017/\u0013[D\u0003 -\u0017 eE\u0013}>v\u0011R_(\u001d>c=g?w\u00154n4\u001f~vR\u001f\u0000hQo΁tq\u0014ӑ\u0002\f\u001a\u0000>ЦO\u0013(a\u0000\"MZ5IB\u001dimaB\u001ako\u0011R8/I\u0014O4\u0013,ΌHf\u0010)k\u001eX\u0005O\u0003:R\u0005ܯrU\u001eQ:i\">.vޅ2ujzjd!-CKS z;`\u0006vb5\u0015/`0bC4.Ά[ħI2'{\u000f\u0013V;}(Qykjk\u0013\u0016;;I~\u0015)Y(v\u0017\u001bR槽\u001eSLSb\u000f\u001e\u0013 Z/\u001f\u0010~\u000bA_μR>]&>蠟\u0000^NjX4=|$ݟ礧;]2v\u001a\u001eO|4}\"=\u0004h4f\u001b\u0007\u001aʑ=t<%MOo?[\u0015nH5S\u0013+lFeױZb\u000e/5Ez}&֬ۚi;\"\bl\\\u000b$NKO\u0012Iߚx\u0006<\u0000QW\nj{rY(*7ŭtPA*$\u001av&v\u0007lJH,\f6K\u00015|\u0017\u0005m\u00017;\t\u0016\u0016S\u0004\u001ei陈\u0014_)6.DPaIH\u00078l7\u000bah.t#4c\"Op51 >]\u0012@4\u0011);\u0010$9HX\fc}#\"\u0005\u0004dp@D\u000b~\u00130\u0011)Gtp\u001bB0|IE>)\u0013\u0011=\u000f4ޗ\u0019\t-?+vw1V\u0014iiY)\r;l.EEtM+\u0019mydplL\\\u0018A \u000eK/ԋR\u000e\u0004\u0017׀3\u0006\u0011D\b)\u0017\u0000\u0019*\u0002\u0003\u0003\u0006w0\u001b\u0019\u001cHU!_\u000finp{\u0003\u0010K\u000b\u001f\u0000]\u001fviRG\u0015qc${\u0003\u001dfOs\u0010\u0003\u0019AY/\u000e\u001aNL»\f\u0003ֽG19\u0006\u0011Ɂ|[V܈\u000e(6\u000b\u0007\u0011?\u0019T篻>v\u0015\u001e\u001b!4}\u001cE?\u0002OGI,\u0007`&\u0011Ub%<qW\u0006,sV;m\u000e\fgצ|\\:uL,ZE{(z2Dsc뉈\u0013P벁;豧{\u00030\n\t7\u001fvJD(I\u0012yl}h\u0016C\u001c^u\u0003}CH=f\u001a\u0015ŰQ2\u00161\u0000X\u0005O\u00030wf\u0011\fq>=\t\u0014` W$\fKC!vr\u000e%d\u000f,E\u000b\u000e\u0014ص;IW\u001dЕq.ST\u001cyag\r\u0016\u000018N@1|B\u001f0\u0013\u001b`|\u0014յ%_h,m\u000e[=4c_1\u0016+:%\"fA\n\u0015=\u000b\u0011\u0015\u0013ͨ7\u0006˂|!/ۂ4i{e6ĺ\u0010tХ)QW^(1@\u0006:u\u0010\f]af\u001bfp\u0016r4,<_3Aɜ\u0006_`UR\u00013*\u0014!'MAV\u0014\u00107H1Dد\u000fY\u0010-\u001bšul6$VAÀ\u001c\flU8ت>\u0004m\\Lq˹N\u001b\u0013N\u001d͇f[s(ijՍ`o\u0018MBvOӡ3uDdkW\\\u0000UGLj4qYXG=ruaO.\u001fi.\u001b<$&C\u001b|:<\u0000`\u0015\u001f\u0005rlvB^:\u00000JK\u0016!gL+\u0000Nt\u0001@'/LAr\u001f\u001d\r?l뾾_'Ha\u0013LU_\u001aoRK\f.!\u0001\u000f3X9\u0011~\u0013=h\u0004q\u001b-qF&X\u001a3\u0005~\nb\\ⶻnw\u0012-:\u000f/5%kLO\u0016<aS\u0005C\u00162$0ֽ]9]\u001a\n\r\u0007?i4 0/6E?W&JBN^)<\u001a~Z\u00033\u0002\u0006R'Oɘ4x\u001a\u0006Sxi6\u0001\u001fL0!̦\u0017\u0018Oȹ?]!l\bl/I\u0011,K:<4w>?ӕ_VFz4 _ȁ\u0001\u0018*\u0002.9\u001da>\\>#ٲ:&E禇p[V\u001e\u000baU\u0018߀\u00020\u0003qa\\\u0003Izy[صvSQ#H\u0000o;\u000b\u0015\u0002K`Bk\u001fRYsVy>j8ƍ(g2uW\fEMPT\u0005wm\u0019N\b)Q`\u0018Y\u0017⬡ ׍0P/\f)\r\u0004Nz>#\u0018\u00110\u0012tY1h1DQD\u0003<\nf!\u0001\u001c+t^Xf`\u0012Gy\u0004.ѴUٺ\u00176 I4k\u001f.,\u000b\u0019\u0012\rBY\u0015q8\rW#)\u000e\u000bY\u0003\u0006%t/8C{\u001b%\u001b1[=7\u000e[\u0000V/_\u001f\u0018j`_-\u0016&f\u0001'!\u000b\u0011\u000f|҆*\u0006\n5SP\u0013I¸á3P\u001f\u0006Y?\u0004\u0002A!lM'pe\u0004\u0004l\u0013jY\u0017ŠuaԆ:=Ƥ֍֛.}샅ַ_tW-\u0006gͻK˪ u{\f\u000bWb\b}o\u0007E۽\u0012jCg]$Р\u000b?\b\u000bu#HPa)\u0010<a\u0010m\u000faJpR)N\u001e\u001fU\u0000غ\u001bvv\u0018$B\u0011LRs\u001a=๢\b3u\u0011(uy\u0015S-ZWR(tq@\f#G\u0011gi:RR,h[\u0000xvy{h\u0003\u0001Lp|\u0013\u001e\u0012@7\u0013\u0010\bg#I僕\u0011\u0016c\u0011>O)J,.E#KWgB\u0003n@bv\u0011߿7\u0002J\u001d52|Z̚Ag\u0018Z99\u000e\r\u0011\f:b\u001c:\f:<[p\u001c:\f:շF\u000es\u0006![p\u001c:\f:lAO9t\u0019t([p\u00039ts,\u000bאA,̡sΠC{\u000e! !ì\u0013nY\u0018xr&)؅K޺{.]TӴG^9\u0018n~m\u0003ޗQ{:쌯n\u000eXN{\u0006v5j:>B\fݜ(3>'`qXtC>9<WBOt{\b.+.٥xJ5\rS(\u001e-j)\u0014D\u001f\r\u00066&9\u001b-4k\u0012CQ&\u0019#!:\u0006\u0011'9_c,'m$d7\u0001\r`\u0010m\u001b0e3\u00182\u0019CR\u0013^-NfqMO^*I^\u001cB)\u0014]Pf\u001d\u0001β5{:ss\u001cs\u0004{g\u001b4Uhv\u00110Kɜ\u0007jvQ-b=_\rN+rj\u001a9\tٔ\u000fۤN\u001dۥh5\u0006:`2z0m6(811:\u001d\u0002X|2S\u0000sxz\u0019\u001dkR5M~D[bFo0\u0006:M6\\tkfiKo/g\u0002A\u000002jd\u001b\u0010\u0014鸺\u0014=mnSoH\u00001+p\u001a\u0005̳v\u0006]J3\u0017դ_\u00160stIì4&N9r>4g#O4 +O|핃\u001dY,\bxsldK%A\"u\u0019Q\u000b[m=vBN~xs\u0011D+ii\u0012d\u00161tɐ\f:pq1k1\u0007:ei\u001c\u0014%\u001dIWNIڄG\u0014yd֌R\u0003p#\u0004ɝu<2~t}\f\u0000\u0010q𐆶Gƒ-G\u0006<2T'\"=.9\u0007\u0005t{\bk4tc\u001c7Nc,gb\u00148?L$\u0003ux3N\u001d0r%&\rm\u0000v\\\u00042wC%t6[-sЬ5B\u0015Q9a\u000eN3KՋٖz\u001d]7JE漓\u00026\u000f\u0007l\"9\u001fcEAw·[#C>Sz\u0015H\u000fgs#(1k|8C4Nxp{G匸Mù\u0012|8É\u0015Ejp\u0018X\u0007fY\u000f\u0015b\"\u0001(cL$ɶ\u0017b\"־4άk\u0006P\bNm)\u0010\u0018L.R$]\fDud5\u0019}ǈϮ%92\u0011EH<ޯ\f#̫2\u0004\u001cce4]\u000e8-m6`\u0004!#\u00038\u001bI3tZ\u0015\u000eJ`mZ0k\u0019Ҧ\u001aq\u000f\u000b&9_亡2RS[\u0006`c&],RF*ADJmh\\\u0006.~\u001bH}LF*T/~F*T\b\u001ahӖ.\u000f-\u0010򥩞\teqehJcNC\u0011=H­4Rya_F*\u001c~\u0007\u0011J+p6^\ng\u001fܟ=9\u0019(OvJ|\u001a\u0012cUin7c˶6\u000bQy\u0014)\u001c\u0010\u0018f;6a\u000bX6\u0013\u0011@۩<\u001d{r9x\u001dY9Q\u001b_X5.nO\u0003,հ5>ۃeY\n\u001e7o\u000f;cf\u0011\u0006y{Tx?7n\rߞ֪~Ve`\u0011\\UGˊɘeu\u0006wIJ\u000b9x]eLv_]g\fin\u001b\u000f\b\fӣSۀ!\"EEMH\f;XV#!t@J0\\rL¼pd7XjZjhJ\u0001ۊ8%&z|{SR]~{'|%%8eM3[\"ҝs~8 !]?6._ZD\u0006g\u000f\u0012kc\u001dU`QO\n)ٵ\ns*hGn!z\u000eˉY:)Bn\u0016ll&pF\u00116\u001ejXPt\t;\u0017s[p[{d%9#6wPx*y½ԩC$]!ѝ\u001e܊9K\u00119꾹\u0015\u001b\u001d{\u0001j\u00191ݥF8Ʌ᮴\u0005=dӭMg\u000fPkqn.~5|~6].ݪl:;_}:t\u0006(k@\u0010B6݊\u001ac69+7MgK7MgKG\u0019!׮V`\\6\"OF\u001cNE7MgKGm~l:kdKil:;\u0017t6DCMg\u0006ZSxl:ml:zMg\u0002e>\u0013P6J\u0014Mcl:Mg\u0001\u0000X6MgwZb@6x0^7Mr2l:wl:;b26MgKGǷ\u00060o\u0000\r<]De-RiA\r\nZ6׫ti\u0001Oy6]l]i\u0011\u000b{N%C$$t5,(Y@z&u%ZQ@Q]x0R @<]v} \u0005Q8ɡ2AdB-f\u0011\u001dN%ӎ(\u0013,]\n;{jYN.ssEiRX]*W\u001d).*@9>Y/t\\4Uf\u001cTTe\\=0kSl\u0018Bwks\\\n\u001d\u001e6pU.<y\u0016g\u001b\u000bQ&ʹ<`h\u0007QZ$\u0002un\t\u0003OHG\u0013s0$\u0007qP\u0017\u0002,\u000ea\u001e u?#'U\u0002|\"xi*qvwC{Z銉]h(T.P\u0000k\u0018u4\u0014\u0000F\u0017\u001c،dF2\u001f?\n\u0015\u0005\u001d\u0003Wf\u0012\u000eUmW;(XdWw\u0013+\u0010Adf\n#`T\u001a\u0010̚X۝mV@\bl#\u0011\b3\\w\u0011IykGJzN1Ի#]gv܉byi\u0014c\u0015rS\u0015\f\u000ezy*TM:}6'd(NQ\u001el\u0000\u0019κժe(\"[\u001dE\"\u0013hf\u0019S$2Py׮pWjܭ\fm+V[S\u001aPe4F*\u0013U[7H2tpgK\u000fYSWX{;[a?CڽR'\u0002`fБkӭXk|̭?\u0017BpV;\" s^t{1W8\\XS\u0012\tT.K~]\u0012\u0013~*\n\u0005Dܲ\f\u0011ku(Әh\u0007(hҘt꠴y!1#T\u0019vmU#4&)|03\u001b߫+\u001f=%1\"`D\u0007M0|Trx׸d\u0018\\$׀Dk-IU\f_VT\u0011l=mj't\u001c'TmܣK<f\u001cWl}ja\u0018\u0014Y9KYN9\u0007\bNy\u001ejm}UӇ6G\u0019˃O\u000ek>6\u0007V]V<ȵO$ˣ\u001b\u001c!\u001f\u0017:X\\{y\"EIx2dS\u001a\u001eKo\u0007\u000b77R\u0012+\u0011)\u0018Vs!mm?\u0011OzoR\u001dT\tɥ14)\\\bfLY\u000e/\u0004\tl9gH̹8y;\u0004P`\u0019q\u001f\u000eH[<\u0019iz\u000e)Vl\u001bR+Ho\bݓ6\u001awMCd\u0017@\u001b'%\ts{\u0018g.ScJs\\\u0018+\u000b&fS;Q=FYs\u0013k2l\u0012\u00105\u0017EA*f\u0014\u0019>0'6zH)I*b ';UImrOl6U\u00059t]*zΡUĩTf\u0001\b^BМL7O\u0012)SGOC/Ӽ0sEB,QZBMtOQZS~bo]\u0002Ғ\u001c\u0012\u001bk\u0012W]uZ3e\u0004}Bq\u000f\u001b\u0012\u0001\u001b\u000e)\u001c˴<uns\u0012-Wƭfd^Ɗe-0\u000bpKh#拹Zw9lcƠ\u0011#V\u0001Y47Gf\t꩷pGZʽ|gf?\u000e#zU?\u0000qQn\u0010j?\u000f\u001eɇ\u0012,,()Ǫ\u0003KAwj\u001b+ت\n8\u000eRr\\9\u00024v?}rOԡf\u0006S]c*3\u001eU\tk\u001dz\u0014s\u001frMD+\u0001%]:T(b\u0016 m\u000e =$\u00041\u001c@t,@\u001c@?K\u0016 m\u000e ~á,@\u001c@^9\u000b*\u000e;Ls\u001c,@\u001c@Hճ\u0000uz:\rc,@\u001c@mR\u0016 K\u001c@RuT\u0017壯ȶNQ>l,(ūWUdLQ>9g@Y(/dc\u0015n(>(Gɢ|T+)ʇ{\u0015`N)$rK\r&W[nM\rEY׏naCP~V\\ol9{w3P+sMs\f<WksڐCױ/ɡK<4b~XP\u0001Ķ\"TWgt9=a3em^_ڙu\u001c\u0015]\ri\rsv\u000b{ʡV6\nz~vi[\u001e\u0006r=s\u0002|⣆N9#uպ~6&V\u000fQϴ\fYcU?Ý]SXkF9W8yͺ~\u0001XFZu-;^4sr]?g\u0002\"\u000bv\u0003u\u001c֚4l\u001e߮3\u0014z|.UOb|fs9\u001f\u0011\u0013myu-yx&t$q-\ns^=It[nuϛQ\u00043Rۼ[\\\u001e \rr=\r,\u001fhnojx+gH]m'g\f_OK%4~f\u000e<sMJ]5}}u(^7PϹz|+eᚤXU?\fZud\u001f3uûLZl~΁N&ul\u0005|/\u001c}]Fyy\"+ר\u000b.~+G\u000f9r\u0004V\u0001d~Ά{]sB׮g\u001c\u001eX׏l\u00046MVul3i=qȮ%O~1sc\tglóvug\u001faWj(ߨhҕWk9ޔh&ӛÃ9\u0007/\u0014T\u0011L\n:aXk4D%\u0001VN\u0003 <\u000br#O\u0015\b^\u001c$,_ٌ8<m\u001eNJw4\u001aTeegY\u0007^A\u0006|<\u000e\u0016\u0017*~<Ky+Y\u0019\u001d\r\u0017-_J4[u{s6.O\u001f\u0005(2(Y2py\u0014\u000ef\"|$\u001cndW}yxw۽\u000f$cǁ}l?\u0013>fճ*ꟲ΍(~\"T|8=){h\u0003&̜Դ_(\n\\i[έ\u0010C~Gҿ\rV\u0002\u0012֓\\~BS;b!b.vd\u0001XQFc\u000bLd;r{2\b\f`&驚\u001c\u001aXK\u0018v1C9J6\u0006\u0001w\u0004>7-\u001f0N\rKЀXa>T[B&\nhZ7`Y\u001e3`Y\u001ew\u001a\u000fǙw@akrZi\u0003=\u0000G\u0005\u001bA*Tl\\O{\u0001\u00112\u0015 'W\u001e\u001e>b6\u0004\u0006ܙ$an\\\u001fDsbE\u0004F#|,dIaS*|ǀ\"\u0019ZEY@׃80a^DC-dE\u0011mz<lF''xxv,?(-8A\fp\u001eBڣ\u0004k\u000f\u0018=Ha\u000fZ\u001f]pv\u0017h/@sD\rS1\u000bUB0N43A\nH\"aj\u001f8w\fR:.7\u0007\u000be\\\fB^T?q\u00184MN ٮ\u0015;\u0004\u0016&oCj`n1RKt4-FtxOƲ\u0007\u000fon:\u0004:Kj\u000255dpVvs\u000e\u001eU!^\u001fh\u0010E'ȷޞߕ28j<\u000f:,\u001cU\nqɟqm1)yzϨuņ^?*\t{H \u00025\u0001\u001f\u0018\u001f\u0000\u001bR܃b]\bsFA4yYG\u0013̨o?'`XHs\u0006=Gq\u001fXf+Y)FE6=\u001aR\u0014,l>j\fH[ROf'z\u0005\u0006<=D৔)߲6n~?b\tҸ/\u0016k\u0019ڗ)\u0012/н@t2\u001bH0{P X/+\u001ap|\u0004\u001f\u0006?+\u0000|d\u0006c;-b\u001a\u0015`Hf\u0007.:C\u00173ED]\u001a\n\u001fʒd\u0004K\u001cw%`2@d\u001e\u001a\\#n\u001f˹\u001dA+\u0015Z}\u0003ޕQAUx!ɨ\u0004ZH8\"գ6eQ\u0012HK\u001dfڵP\u0014,s$;Dʢ8,#\u001c\u0017e(>\u0013D$}O\u0005\u00164o&-\u0013,'֬J\u0004OЋɑT|+u\u001cw\u0016\u0017TK5^\b[\u0004@\u0014P\u0007|=g\\\fJKVs\u001dj釖V\u000e9\u001f\u0006\u001d\u0014\u001bUUX\bn\u001aږi\"P7_S$[\u001demgt\u001b2\\&h\u000fv,\u001bI9rm>\u0010|.\u0005b1S\rK+`r ~\u000euo\u0002\u001d\u000bY\u0010ߨ(\u0003g\u00182fbNIs\u0010^Lxmh̎ql0ᛧޯH%\njdxԌ\u001fpj3Ð\u00107nl\\b\u0005\b=\u001c\u000bm\u0011ooW+lu- [O\n 46Zzꈈ\u0019U&\u000f3.gj\u001e\u0018zpg\"Z\u0014\u000ew((\u0017I\\̠<H`UY>)0+ָdvbN?v^\u001fͧX:t\u0003]NLE\f=M\u0006xcaW\u0000A\u0018C+U!\u0017W|\u0017j1Soo\r~T\u001dG\"׍vg:0t$5TUY\b(Ǜ\u0005\\d]3mGmV@i:\\\u0004,=C_qse\u001a\u0019\r\u0018Џ\u000ewt\u0001T\tsoHb\u0014HK \roC\u000e9\u0005\bg7\u0010\u001e\u0003Y[\u0007\u0012{ :\u0000:~p]\n\u001a0Fg|_&1c=K\u00173Icq\u001bh|毆D̋}e\u000e\u0005km6\\`[\u000f\u00182\u001aO_\\ʾo\u001bUϕI\u0006>X]kt<Q/XV\u001es\u0014\nŤNo\u0018-\u0002N^hDh(]\u0007Ѫa;(`Sl1jkM\u001ctGVMC\u0012Ǖe~\u0014˝VB9pJ;yyu;koܤ\u001e/1#.tY\u001d6KAt>WY>\u001d>m\\S/\u000fc%\\IS\u0002[EּO#oKyX\u00108\u0006\u000f\f\u000b?\u0006ǅ\u0010Q\u001eVA:]}J\u0018\u001c'VfV $~^\u001c\u0000_(\u0012ݐf\u0017#\f؊!]O&\u001f<K\u001dohm\u0018hDlz\u0012p\\OA\tC\u0013Ђ]ޛb*Qts\u0006\u0007g\r\u000f\u001b0^%wN\u001c/tOpzLw]\u0017C\u00069fZEF\u0005\u000f '\u000bLtD/\u0004\u000evb%\u001e}\u0017o\u0012P\u001dcg\n%X\u0003\u0002;qz%l#A+eE\u0001[]xW&p,VZu\u001c֓DT\u0000j/\u001bl`\u0010ŀU\t3\u000fVd \u0013Dq\u0017c{gk:\u0018\u0003dBA`DmКAy\u0016kEn\u0003\u0012f݅M\u0019^׊KMJxpM\u0017>f\u0014BV\\\\:;\u0014xǫ\u0019]kMZ>\u0019\fĞ#ǁ$\f?\u0006k8\u0010\u001fz>Kxn\u0007{WW\u0011\u001e4`L qZzp'\u001b\u0019u\to;Ѡ~~٬\u001f $d7Gv\\[M/\u001eJ\u0016xaWr芙?V\u0007c`IEګfs\u0011^ohK,m-\u000bkU<\u001cjKvQQA%Rkd>l9=\u0015\u0017a፧m?1%f07}x؇8R+ݯw*\u0006}j~\u0004Pu!BNPn\u0004Vi~'ʄ!\u0018\u0004ĚRM\u000faV\u00114Ŋ\u0013ʿ<^m&\u001eM;/w-<eѴ\u0003\u001bNoL<v\u0014\u00057'^݉-XWLQ1O\u0015\u0011X\u0019^\u0003E\r/KƼy\u0017\u001b㴕X\u0018q\u0007Gyg\u000f&Mn{YB\u0005\u00192\u0003X:?hUVs\u0014\u001b\u0001-p4w*BhΟpgNNh\u0001dN^HW\u0006$>\u0001:9H\u0011NG\u0002pZ,䃨\u001b6ފߤ^N\u0010\u001f\u0018n\b\u0017\u0015\u001c5\r=~\u0010[\u0007I\u0018[\u0011P`yMot7ׇ\u001eM.aG'cy>/\u0001i\u0001bE[H/xⶲ-I40RR)\f@\u0001YeG]YTi[\r|8 \u001fTIe\u0001?c\brI\u001e\bu\u0011\u0015\u0013k{˗+\u0004Vqܫ@$B:\u0012Q\u001d]\u0019.\u001fEDF\u001aJ\u00020\u0016\u0013\u0011RF[r\u001d\u0005\u001fwL\\\u001ak\u0015\u0014N\u0005\u000f²b6\u0012\u0011\u0000&a87z\u0000\u0003K\u0017\u0000@k\u000e\u0004*'BZ\u0011A\u0005&^b\f\u0003gH+\u0000mU:@^6\f\u0004\u0000Mzu\u0001Õ\u0019\u0013SX\nr#\b\u001b\u0011Uﯾ#8W:\u0001 p\\\u000f:W\u0018@F\u0004J\"\u0000\u000fru93\b/ɳqT\u001e>\u00000\u0004,\"=\u000e;W̽ҁ\u0012N*OJ]z|O3c˨.\u0014mUӛ\u001d\rQa<fT(()\u001b%\u0018-Fu\u001eX<\u0016u}\u0014y\f\f\u00021a<F+u<\u0016%\u0018u\u001f4r \\V@w#]\\#{\u0003wu$R¹\u000fDg\u000e5W5.\r_:\t52rxFٚwl',b3\u0010[\u0000h=\u001b[klmCT*ZʎynCvuSq,*\u001e\u0004%u\u0011_\u0004\u001eYe|yAJjn\u0010Lʪ\u0004@=[{y)Pzojj\u001aj՝#St]N\nSO6\u001a&ͷ~\u001fnk`/*p\u0016;JG\u000bP_k)ɎsUo/\u0011RIl/\u001eA˻\u001d9!f\u0015;`R\u0004<aFOܙ~|E'Þ㠛pfNyн\"`^ңe\u001b\u0002+\u001b\u001fe78Vre\u001e/9bq\rvC\u000f\fj*\u001exF\u0016\u0011_R_T<r~}/\u0001tcN\u000eNK-B\u0005\u0002_I\u0004K\u0018aF=\fr$\\@\"\u0004hO\u0001uz%3\u0002;\u001eTZdA\u001f^V&XN\u0015 z7E]\u001c_ߪW(~\u000f6\u001c}\u001e\u0017\u0001\u0007<:?\u0006:@XQN\u001b;<)\u001989OL!a`6YDYah,z\u0006f%\u0001zdt+8.\"Rޱd.8}Qjg`\u0018O99\u001f\u00019f;&̾J\u000ek\"(a\u0012j7fHDyqk-)Xڋux\u0005\u0004W\u0014lDI46\u000fR8?@g\u001f\u0004G<{2\u001f1228qL\u001d\f,G̈́\b\u0010N\u001e\u0015\u000bWr\f\u0002|KC@ur\u0002S&qeuN\u0012ciDۂ_ϓ\u0011*\u0006ްЈH?\u0003Q\u0006\u000b\u0016,\u0007Q%\u0004$RKWI]\bA\u000f{67v\u0011[\u0007\b\u0014\u0004x\u001as`\u0011\n(ܱ\\}ֆy2{@\u00124]}}95Ҫo3\u0005\u0007\u0010>s18`l$\u001b\u0002\u0004x\r^Tmݣ8J\u0003?A9iO'e\u0012{9ϟP4.\t-\u0014\"S$\u0000JD\u0005k\u0007Jo\\ZM\u000f ;R\u001f|go\u0015׃ckUu3N{\u0016k7Lܒ$\u0010k\ns\u001f\u000b\u0003{D'#\u00154$iL{AO7i{R\nIG#i#g\u001c^'\u0007[Y`79ga\r2\u000b{\u001cl\u000b,\u000b\u001c\nb_\u0015~f9+q==\u001fS9If\u0003)-.S~L[;9)|_\u0018~t\u0019\\1\rCd(AԿJq{怫Of{6i\u001fLj\"x8ޗs\u0006\u0010\u0006\u0006%[z ^ᙈoyO\u0013Ma\u00071c/\u001a\u001d\u001e*YI:,\u001fM9{\u0010_syP9#1n\fDX[yk\r\u0018tzT.\u001e\u001e\u0003\u0004z\u0007;`;\u00124a\u0004\\?\u0014SR\u0004}_\bM˗׮i\u00015'T;(l86\u0017\nq5\u0000|!қ\u0002J2\u00066c\n\u0000\u0018،)\u0019\u0004c`3no\fl\u0014@1\u001e\u001dY}YYbە\u0010i<T/Nщ9f\u0016v&o\u0017gi7s)|:WQUDu\u0004e\u001d?c*\u0019%x^dF[\u001dyHRR.%3\u0011t6\u001dBΕcvס!\u001fxEQ\u000eK=h\u0016Y\nY튥\u001d`y8I٧vծb\\2\u0010HQ\u0013J8K\u001abUK*\u000b=yr5:Uw\u0019S\u001c|\u0005/\u00155/{.}90k`\u0001\u0007F%j\u000e,7Ʋ|>K\n{N;I6P΂@-/Y\u0017\u0011I\u0003ѱP\u001c\u001a5 Z\u000e\u0012\u00171Zt1\u0004\u001e\u0003j\u0010Pe.Ta\u001c\u0002Ō\u000e\u001aQ|VۓȌf@w\"\u001fp,ݝ\u000eԑ\"\u000f\u000f҄Tx֕:/+Eic\nPAt\\#;N\u0004U\u000eԟq\u0012\u0018\u0002Zs=\u0000Q\u0005\u0002\u0014l>BHhDP*7#)\b8X؅U\u0000\u000b6Y\u0018S\u0006Ym`x\u0014\u0017\u001f!\u0002@i P!\u0015X}E@;\u0007*U\"%=i\u000f\u000e<\u0003\u001ejc\u0001qWf\tTaޏY\u0004\u0000ʱ@-#\u0014\u0011C\b,ͫ\u001b(-\b{\u0000z\u0012\u0006Z\n1>8\u0017P<\u0016w\u0005\u0002.0M+/u\u0010Ix/0\u000f\u0015W@&\u001d\u001d|!j\b̭Voﾈ\u001f]\u0013U5tC'|kוǜFպ`Rc\u0001֡=\u00172i;kf\u0011o\u0015f\u0006QV%0cr5X;d\u001aF;\"\u0018o\u001fݤ] \u001e2)\r@;H\u001a9{\u000f3S\u001fz߱\u0018\u000fF&B\u00011Oyc*S\u001f\u0000~Xk\u0013\u0007IgRB(JR\u0007ms\bʼSV\u0015oy/zۢX^h\u0019\u000b}ѱnVEx[4\u0015oy/zۢ}!OEx[2/Ex[}+\u0016dW-Z9M/x[t\u00151\u001a2dN\u0015\"E;S䣍 $0`ԓ\nͻ\u0006\u0016D$dWn`n\u0010G,\r>?H\u001d'P\f܍<%w/'\u0005%\n1^lk큽ë\u0001\u0014a.\u0017 bRZB̽I|!Apl`:Џ\u0002сk1%\u001d W]q٤\"$\u00026k]\u001d\t}WT\b\u0003<\u0018Jз\u000bl\u0002B!K_,Af\u001f^,\u000f-tџ\u001cԸ9OE3_胩\u0018\t3N/ß\u001e/N\u001f5\u0000M\u0011\u0017\u00034\u0006O\u0003+%_\u0007߮|lcӠ\u0007|/f\u00076N\u000f%6Ϣ\u001e84\u0007|9\u0005\u001f~X\u000f \u001b_%slʟ)ˤ\u001f,\u001c(&Y\u001f@K\f\u0007t\u0002Ԁ\u0005\u0010\u0017\u0012[\u0004\u0015,\u0000;M\u0016%\u000eπ\u000ft*Y\u001624 \n̟)T>\r\u0015KI6*M`\u00072,E\u0003\u0006\u000fs:6\u001aSRF|-.34m|6k\u001d\f\u001c=k\u0014lt[\u001f!7oy\u001dd\u0004\f|y$|O!/\u0003X\u001bqSd6w\u0017G3l1&#N\u001a;Y\u0019yU^\u0013`\u0015\u0000&[\u001bM\n߼̈́\u0005[%KC\u001f\u000bxEm\u000eU\u0002\u000bq\rendstream\rendobj\r7 0 obj\r[6 0 R]\rendobj\r23 0 obj\r<</CreationDate(D:20150705033312-07'00')/Creator(Adobe Illustrator CS5.1)/ModDate(D:20150705033312-07'00')/Producer(Adobe PDF library 9.90)/Title(download-svg)>>\rendobj\rxref\r0 24\r0000000000 65535 f\r\n0000000016 00000 n\r\n0000000144 00000 n\r\n0000024056 00000 n\r\n0000000000 00000 f\r\n0000025673 00000 n\r\n0000025488 00000 n\r\n0000054881 00000 n\r\n0000024107 00000 n\r\n0000024463 00000 n\r\n0000027021 00000 n\r\n0000026908 00000 n\r\n0000024760 00000 n\r\n0000024927 00000 n\r\n0000024975 00000 n\r\n0000025557 00000 n\r\n0000025588 00000 n\r\n0000025861 00000 n\r\n0000026126 00000 n\r\n0000027095 00000 n\r\n0000027269 00000 n\r\n0000028289 00000 n\r\n0000034291 00000 n\r\n0000054904 00000 n\r\ntrailer\r<</Size 24/Root 1 0 R/Info 23 0 R/ID[<84A2C404F3994D5B908030A2B45A1BFB><4F0049F23B184AF8970FCB3F165C9FAF>]>>\rstartxref\r55082\r%%EOF\r"
  },
  {
    "path": "client/app/images/logo_collections/official/favicon_package/README.md",
    "content": "# Your Favicon Package\n\nThis package was generated with [RealFaviconGenerator](https://realfavicongenerator.net/) [v0.16](https://realfavicongenerator.net/change_log#v0.16)\n\n## Install instructions\n\nTo install this package:\n\nExtract this package in <code>&lt;web site&gt;<?php echo /images/logos/official/ ?></code>. If your site is <code>http://www.example.com</code>, you should be able to access a file named <code>http://www.example.com<?php echo /images/logos/official/ ?>favicon.ico</code>.\n\nInsert the following code in the `head` section of your pages:\n\n    <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"images/logos/official/apple-touch-icon.png\">\n    <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"images/logos/official/favicon-32x32.png\">\n    <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"images/logos/official/favicon-16x16.png\">\n    <link rel=\"manifest\" href=\"images/logos/official/site.webmanifest\">\n    <link rel=\"mask-icon\" href=\"images/logos/official/safari-pinned-tab.svg\" color=\"#ff4b33\">\n    <link rel=\"shortcut icon\" href=\"images/logos/official/favicon.ico\">\n    <meta name=\"apple-mobile-web-app-title\" content=\"Octave Online\">\n    <meta name=\"application-name\" content=\"Octave Online\">\n    <meta name=\"msapplication-TileColor\" content=\"#da532c\">\n    <meta name=\"msapplication-config\" content=\"images/logos/official/browserconfig.xml\">\n    <meta name=\"theme-color\" content=\"#ff4b33\">\n\n*Optional* - Check your favicon with the [favicon checker](https://realfavicongenerator.net/favicon_checker)"
  },
  {
    "path": "client/app/images/logo_collections/official/favicon_package/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo src=\"images/logos/official/mstile-150x150.png\"/>\n            <TileColor>#da532c</TileColor>\n        </tile>\n    </msapplication>\n</browserconfig>\n"
  },
  {
    "path": "client/app/images/logo_collections/official/favicon_package/site.webmanifest",
    "content": "{\n    \"name\": \"Octave Online\",\n    \"short_name\": \"Octave Online\",\n    \"icons\": [\n        {\n            \"src\": \"android-chrome-192x192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"android-chrome-512x512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        }\n    ],\n    \"theme_color\": \"#ff4b33\",\n    \"background_color\": \"#ff4b33\",\n    \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "client/app/images/logo_collections/server/favicon_package/README.md",
    "content": "# Your Favicon Package\n\nThis package was generated with [RealFaviconGenerator](https://realfavicongenerator.net/) [v0.16](https://realfavicongenerator.net/change_log#v0.16)\n\n## Install instructions\n\nTo install this package:\n\nExtract this package in <code>&lt;web site&gt;<?php echo /images/logos/server/ ?></code>. If your site is <code>http://www.example.com</code>, you should be able to access a file named <code>http://www.example.com<?php echo /images/logos/server/ ?>favicon.ico</code>.\n\nInsert the following code in the `head` section of your pages:\n\n    <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"images/logos/server/apple-touch-icon.png\">\n    <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"images/logos/server/favicon-32x32.png\">\n    <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"images/logos/server/favicon-16x16.png\">\n    <link rel=\"manifest\" href=\"images/logos/server/site.webmanifest\">\n    <link rel=\"mask-icon\" href=\"images/logos/server/safari-pinned-tab.svg\" color=\"#ad928e\">\n    <link rel=\"shortcut icon\" href=\"images/logos/server/favicon.ico\">\n    <meta name=\"apple-mobile-web-app-title\" content=\"Octave Online Server\">\n    <meta name=\"application-name\" content=\"Octave Online Server\">\n    <meta name=\"msapplication-TileColor\" content=\"#da532c\">\n    <meta name=\"msapplication-config\" content=\"images/logos/server/browserconfig.xml\">\n    <meta name=\"theme-color\" content=\"#ad928e\">\n\n*Optional* - Check your favicon with the [favicon checker](https://realfavicongenerator.net/favicon_checker)"
  },
  {
    "path": "client/app/images/logo_collections/server/favicon_package/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo src=\"images/logos/server/mstile-150x150.png\"/>\n            <TileColor>#da532c</TileColor>\n        </tile>\n    </msapplication>\n</browserconfig>\n"
  },
  {
    "path": "client/app/images/logo_collections/server/favicon_package/site.webmanifest",
    "content": "{\n    \"name\": \"Octave Online Server\",\n    \"short_name\": \"Octave Online Server\",\n    \"icons\": [\n        {\n            \"src\": \"android-chrome-192x192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"android-chrome-512x512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        }\n    ],\n    \"theme_color\": \"#ad928e\",\n    \"background_color\": \"#ad928e\",\n    \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "client/app/images/sanscons/license.txt",
    "content": "SANSCONS ICONS\n\nSource: http://somerandomdude.com/work/sanscons/\nCopyright: P.J. Onori\nLicense: http://creativecommons.org/licenses/by-sa/3.0/us/\n"
  },
  {
    "path": "client/app/js/ace-adapter.js",
    "content": "/* eslint-disable */\n/*\nFrom https://github.com/firebase/firepad/blob/master/lib/ace-adapter.coffee\n\nCopyright (c) 2015 Firebase, https://www.firebase.com/\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n*/\n\ndefine([\"ace/ace\", \"ot\"], function(ace, ot){\n\tfunction bind(fn, me){\n\t\treturn function(){\n\t\t\treturn fn.apply(me, arguments);\n\t\t};\n\t}\n\tvar slice = [].slice;\n\n\tfunction ACEAdapter(aceInstance) {\n\t\tthis.onCursorActivity = bind(this.onCursorActivity, this);\n\t\tthis.onFocus = bind(this.onFocus, this);\n\t\tthis.onBlur = bind(this.onBlur, this);\n\t\tthis.onChange = bind(this.onChange, this);\n\t\tvar ref;\n\t\tthis.ace = aceInstance;\n\t\tthis.aceSession = this.ace.getSession();\n\t\tthis.aceDoc = this.aceSession.getDocument();\n\t\tthis.aceDoc.setNewLineMode('unix');\n\t\tthis.grabDocumentState();\n\t\tthis.ace.on('change', this.onChange);\n\t\tthis.ace.on('blur', this.onBlur);\n\t\tthis.ace.on('focus', this.onFocus);\n\t\tthis.aceSession.selection.on('changeCursor', this.onCursorActivity);\n\t\tthis.aceSession.selection.on('changeSelection', this.onCursorActivity);\n\t\tif (this.aceRange == null) {\n\t\t\tthis.aceRange = ((ref = ace.require) != null ? ref : require)(\"ace/range\").Range;\n\t\t}\n\t}\n\n\tACEAdapter.prototype.ignoreChanges = false;\n\n\tACEAdapter.prototype.grabDocumentState = function() {\n\t\tthis.lastDocLines = this.aceDoc.getAllLines();\n\t\treturn this.lastCursorRange = this.aceSession.selection.getRange();\n\t};\n\n\tACEAdapter.prototype.detach = function() {\n\t\tthis.ace.removeListener('change', this.onChange);\n\t\tthis.ace.removeListener('blur', this.onBlur);\n\t\tthis.ace.removeListener('focus', this.onCursorActivity);\n\t\tthis.aceSession.selection.removeListener('changeCursor', this.onCursorActivity);\n\t\tthis.callbacks = {};\n\t};\n\n\tACEAdapter.prototype.onChange = function(change) {\n\t\tvar pair;\n\t\tif (!this.ignoreChanges) {\n\t\t\tpair = this.operationFromACEChange(change);\n\t\t\tthis.trigger.apply(this, ['change'].concat(slice.call(pair)));\n\t\t\treturn this.grabDocumentState();\n\t\t}\n\t};\n\n\tACEAdapter.prototype.onBlur = function() {\n\t\tif (this.ace.selection.isEmpty()) {\n\t\t\treturn this.trigger('blur');\n\t\t}\n\t};\n\n\tACEAdapter.prototype.onFocus = function() {\n\t\treturn this.trigger('focus');\n\t};\n\n\tACEAdapter.prototype.onCursorActivity = function() {\n\t\treturn setTimeout(((function(_this) {\n\t\t\treturn function() {\n\t\t\t\treturn _this.trigger('cursorActivity');\n\t\t\t};\n\t\t})(this)), 0);\n\t};\n\n\tACEAdapter.prototype.operationFromACEChange = function(change) {\n\t\tvar action, delete_op, delta, insert_op, ref, restLength, start, text;\n\t\tif (change.data) {\n\t\t\tdelta = change.data;\n\t\t\tif ((ref = delta.action) === 'insertLines' || ref === 'removeLines') {\n\t\t\t\ttext = delta.lines.join('\\n') + '\\n';\n\t\t\t\taction = delta.action.replace('Lines', '');\n\t\t\t} else {\n\t\t\t\ttext = delta.text.replace(this.aceDoc.getNewLineCharacter(), '\\n');\n\t\t\t\taction = delta.action.replace('Text', '');\n\t\t\t}\n\t\t\tstart = this.indexFromPos(delta.range.start);\n\t\t} else {\n\t\t\ttext = change.lines.join('\\n');\n\t\t\tstart = this.indexFromPos(change.start);\n\t\t}\n\t\trestLength = this.lastDocLines.join('\\n').length - start;\n\t\tif (change.action === 'remove') {\n\t\t\trestLength -= text.length;\n\t\t}\n\t\tinsert_op = new ot.TextOperation().retain(start).insert(text).retain(restLength);\n\t\tdelete_op = new ot.TextOperation().retain(start)[\"delete\"](text).retain(restLength);\n\t\tif (change.action === 'remove') {\n\t\t\treturn [delete_op, insert_op];\n\t\t} else {\n\t\t\treturn [insert_op, delete_op];\n\t\t}\n\t};\n\n\tACEAdapter.prototype.applyOperationToACE = function(operation) {\n\t\tvar from, index, j, len, op, range, ref, to;\n\t\tindex = 0;\n\t\tref = operation.ops;\n\t\tfor (j = 0, len = ref.length; j < len; j++) {\n\t\t\top = ref[j];\n\t\t\tif (ot.TextOperation.isRetain(op)) {\n\t\t\t\tindex += op;\n\t\t\t} else if (ot.TextOperation.isInsert(op)) {\n\t\t\t\tthis.aceDoc.insert(this.posFromIndex(index), op);\n\t\t\t\tindex += op.length;\n\t\t\t} else if (ot.TextOperation.isDelete(op)) {\n\t\t\t\tfrom = this.posFromIndex(index);\n\t\t\t\tto = this.posFromIndex(index - op);\n\t\t\t\trange = this.aceRange.fromPoints(from, to);\n\t\t\t\tthis.aceDoc.remove(range);\n\t\t\t}\n\t\t}\n\t\treturn this.grabDocumentState();\n\t};\n\n\tACEAdapter.prototype.posFromIndex = function(index) {\n\t\tvar j, len, line, ref, row;\n\t\tref = this.aceDoc.$lines;\n\t\tfor (row = j = 0, len = ref.length; j < len; row = ++j) {\n\t\t\tline = ref[row];\n\t\t\tif (index <= line.length) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tindex -= line.length + 1;\n\t\t}\n\t\treturn {\n\t\t\trow: row,\n\t\t\tcolumn: index\n\t\t};\n\t};\n\n\tACEAdapter.prototype.indexFromPos = function(pos, lines) {\n\t\tvar i, index, j, ref;\n\t\tif (lines == null) {\n\t\t\tlines = this.lastDocLines;\n\t\t}\n\t\tindex = 0;\n\t\tfor (i = j = 0, ref = pos.row; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {\n\t\t\tindex += this.lastDocLines[i].length + 1;\n\t\t}\n\t\treturn index += pos.column;\n\t};\n\n\tACEAdapter.prototype.getValue = function() {\n\t\treturn this.aceDoc.getValue();\n\t};\n\n\tACEAdapter.prototype.getCursor = function() {\n\t\tvar e, e2, end, ref, ref1, start;\n\t\ttry {\n\t\t\tstart = this.indexFromPos(this.aceSession.selection.getRange().start, this.aceDoc.$lines);\n\t\t\tend = this.indexFromPos(this.aceSession.selection.getRange().end, this.aceDoc.$lines);\n\t\t} catch (_error) {\n\t\t\te = _error;\n\t\t\ttry {\n\t\t\t\tstart = this.indexFromPos(this.lastCursorRange.start);\n\t\t\t\tend = this.indexFromPos(this.lastCursorRange.end);\n\t\t\t} catch (_error) {\n\t\t\t\te2 = _error;\n\t\t\t\tconsole.log(\"Couldn't figure out the cursor range:\", e2, \"-- setting it to 0:0.\");\n\t\t\t\tref = [0, 0], start = ref[0], end = ref[1];\n\t\t\t}\n\t\t}\n\t\tif (start > end) {\n\t\t\tref1 = [end, start], start = ref1[0], end = ref1[1];\n\t\t}\n\t\treturn {\n\t\t\tposition: start,\n\t\t\tselectionEnd: end\n\t\t};\n\t};\n\n\tACEAdapter.prototype.setCursor = function(cursor) {\n\t\tvar end, ref, start;\n\t\tstart = this.posFromIndex(cursor.position);\n\t\tend = this.posFromIndex(cursor.selectionEnd);\n\t\tif (cursor.position > cursor.selectionEnd) {\n\t\t\tref = [end, start], start = ref[0], end = ref[1];\n\t\t}\n\t\treturn this.aceSession.selection.setSelectionRange(new this.aceRange(start.row, start.column, end.row, end.column));\n\t};\n\n\tACEAdapter.prototype.setOtherCursor = function(cursor, color, clientId) {\n\t\tvar clazz, css, cursorRange, end, justCursor, ref, self, start;\n\t\tif (this.otherCursors == null) {\n\t\t\tthis.otherCursors = {};\n\t\t}\n\t\tcursorRange = this.otherCursors[clientId];\n\t\tif (cursorRange) {\n\t\t\tcursorRange.start.detach();\n\t\t\tcursorRange.end.detach();\n\t\t\tthis.aceSession.removeMarker(cursorRange.id);\n\t\t}\n\t\tstart = this.posFromIndex(cursor.position);\n\t\tend = this.posFromIndex(cursor.selectionEnd);\n\t\tif (cursor.selectionEnd < cursor.position) {\n\t\t\tref = [end, start], start = ref[0], end = ref[1];\n\t\t}\n\t\tclazz = \"other-client-selection-\" + (color.replace('#', ''));\n\t\tjustCursor = cursor.position === cursor.selectionEnd;\n\t\tif (justCursor) {\n\t\t\tclazz = clazz.replace('selection', 'cursor');\n\t\t}\n\t\tcss = \".\" + clazz + \" {\\n  position: absolute;\\n  background-color: \" + (justCursor ? 'transparent' : color) + \";\\n  border-left: 2px solid \" + color + \";\\n}\";\n\t\tthis.addStyleRule(css);\n\t\tthis.otherCursors[clientId] = cursorRange = new this.aceRange(start.row, start.column, end.row, end.column);\n\t\tself = this;\n\t\tcursorRange.clipRows = function() {\n\t\t\tvar range;\n\t\t\trange = self.aceRange.prototype.clipRows.apply(this, arguments);\n\t\t\trange.isEmpty = function() {\n\t\t\t\treturn false;\n\t\t\t};\n\t\t\treturn range;\n\t\t};\n\t\tcursorRange.start = this.aceDoc.createAnchor(cursorRange.start);\n\t\tcursorRange.end = this.aceDoc.createAnchor(cursorRange.end);\n\t\tcursorRange.id = this.aceSession.addMarker(cursorRange, clazz, \"text\");\n\t\treturn {\n\t\t\tclear: (function(_this) {\n\t\t\t\treturn function() {\n\t\t\t\t\tcursorRange.start.detach();\n\t\t\t\t\tcursorRange.end.detach();\n\t\t\t\t\treturn _this.aceSession.removeMarker(cursorRange.id);\n\t\t\t\t};\n\t\t\t})(this)\n\t\t};\n\t};\n\n\tACEAdapter.prototype.addStyleRule = function(css) {\n\t\tvar styleElement;\n\t\tif (typeof document === \"undefined\" || document === null) {\n\t\t\treturn;\n\t\t}\n\t\tif (!this.addedStyleRules) {\n\t\t\tthis.addedStyleRules = {};\n\t\t\tstyleElement = document.createElement('style');\n\t\t\tdocument.documentElement.getElementsByTagName('head')[0].appendChild(styleElement);\n\t\t\tthis.addedStyleSheet = styleElement.sheet;\n\t\t}\n\t\tif (this.addedStyleRules[css]) {\n\t\t\treturn;\n\t\t}\n\t\tthis.addedStyleRules[css] = true;\n\t\treturn this.addedStyleSheet.insertRule(css, 0);\n\t};\n\n\tACEAdapter.prototype.addEventListener = function(event, cb) {\n\t\tif(!this.callbacks) this.callbacks = {};\n\t\tif(!this.callbacks[event]) this.callbacks[event] = [];\n\t\tthis.callbacks[event].push(cb);\n\t};\n\n\tACEAdapter.prototype.trigger = function(event) {\n\t\tvar args = (2 <= arguments.length) ? slice.call(arguments, 1) : [];\n\t\tif(this.callbacks && this.callbacks[event]){\n\t\t\tfor (var i = this.callbacks[event].length - 1; i >= 0; i--) {\n\t\t\t\tthis.callbacks[event][i].apply(this, args);\n\t\t\t}\n\t\t\treturn true;\n\t\t} else return false;\n\t};\n\n\tACEAdapter.prototype.applyOperation = function(operation) {\n\t\tif (!operation.isNoop()) {\n\t\t\tthis.ignoreChanges = true;\n\t\t}\n\t\tthis.applyOperationToACE(operation);\n\t\treturn this.ignoreChanges = false;\n\t};\n\n\tACEAdapter.prototype.registerUndo = function(undoFn) {\n\t\treturn this.ace.undo = undoFn;\n\t};\n\n\tACEAdapter.prototype.registerRedo = function(redoFn) {\n\t\treturn this.ace.redo = redoFn;\n\t};\n\n\tACEAdapter.prototype.invertOperation = function(operation) {\n\t\treturn operation.invert(this.getValue());\n\t};\n\n\treturn ACEAdapter;\n\n});"
  },
  {
    "path": "client/app/js/ace-extras.js",
    "content": "/* eslint-disable */\n// Additional Ace classes\ndefine(\"ace/ext/language_tools\",[\"require\",\"exports\",\"module\",\"ace/snippets\",\"ace/autocomplete\",\"ace/config\",\"ace/autocomplete/text_completer\",\"ace/editor\"],function(e,t,n){var r=e(\"../snippets\").snippetManager,i=e(\"../autocomplete\").Autocomplete,s=e(\"../config\"),o=e(\"../autocomplete/text_completer\"),u={getCompletions:function(e,t,n,r,i){var s=e.session.getState(n.row),o=t.$mode.getCompletions(s,t,n,r);i(null,o)}},a={getCompletions:function(e,t,n,i,s){var o=r.snippetMap,u=[];r.getActiveScopes(e).forEach(function(e){var t=o[e]||[];for(var n=t.length;n--;){var r=t[n],i=r.name||r.tabTrigger;if(!i)continue;u.push({caption:i,snippet:r.content,meta:r.tabTrigger&&!r.name?r.tabTrigger+\"â‡¥ \":\"snippet\"})}},this),s(null,u)}},f=[a,o,u];t.addCompleter=function(e){f.push(e)};var l={name:\"expandSnippet\",exec:function(e){var t=r.expandWithTab(e);t||e.execCommand(\"indent\")},bindKey:\"tab\"},c=function(e,t){h(t.session.$mode)},h=function(e){var t=e.$id;r.files||(r.files={}),p(t),e.modes&&e.modes.forEach(h)},p=function(e){if(!e||r.files[e])return;var t=e.replace(\"mode\",\"snippets\");r.files[e]={},s.loadModule(t,function(t){t&&(r.files[e]=t,t.snippets=r.parseSnippetFile(t.snippetText),r.register(t.snippets,t.scope),t.includeScopes&&(r.snippetMap[t.scope].includeScopes=t.includeScopes,t.includeScopes.forEach(function(e){p(\"ace/mode/\"+e)})))})},d=e(\"../editor\").Editor;e(\"../config\").defineOptions(d.prototype,\"editor\",{enableBasicAutocompletion:{set:function(e){e?(this.completers=f,this.commands.addCommand(i.startCommand)):this.commands.removeCommand(i.startCommand)},value:!1},enableSnippets:{set:function(e){e?(this.commands.addCommand(l),this.on(\"changeMode\",c),c(null,this)):(this.commands.removeCommand(l),this.off(\"changeMode\",c))},value:!1}})}),define(\"ace/snippets\",[\"require\",\"exports\",\"module\",\"ace/lib/lang\",\"ace/range\",\"ace/keyboard/hash_handler\",\"ace/tokenizer\",\"ace/lib/dom\"],function(e,t,n){var r=e(\"./lib/lang\"),i=e(\"./range\").Range,s=e(\"./keyboard/hash_handler\").HashHandler,o=e(\"./tokenizer\").Tokenizer,u=i.comparePoints,a=function(){this.snippetMap={},this.snippetNameMap={}};(function(){this.getTokenizer=function(){function e(e,t,n){return e=e.substr(1),/^\\d+$/.test(e)&&!n.inFormatString?[{tabstopId:parseInt(e,10)}]:[{text:e}]}function t(e){return\"(?:[^\\\\\\\\\"+e+\"]|\\\\\\\\.)\"}return a.$tokenizer=new o({start:[{regex:/:/,onMatch:function(e,t,n){return n.length&&n[0].expectIf?(n[0].expectIf=!1,n[0].elseBranch=n[0],[n[0]]):\":\"}},{regex:/\\\\./,onMatch:function(e,t,n){var r=e[1];return r==\"}\"&&n.length?e=r:\"`$\\\\\".indexOf(r)!=-1?e=r:n.inFormatString&&(r==\"n\"?e=\"\\n\":r==\"t\"?e=\"\\n\":\"ulULE\".indexOf(r)!=-1&&(e={changeCase:r,local:r>\"a\"})),[e]}},{regex:/}/,onMatch:function(e,t,n){return[n.length?n.shift():e]}},{regex:/\\$(?:\\d+|\\w+)/,onMatch:e},{regex:/\\$\\{[\\dA-Z_a-z]+/,onMatch:function(t,n,r){var i=e(t.substr(1),n,r);return r.unshift(i[0]),i},next:\"snippetVar\"},{regex:/\\n/,token:\"newline\",merge:!1}],snippetVar:[{regex:\"\\\\|\"+t(\"\\\\|\")+\"*\\\\|\",onMatch:function(e,t,n){n[0].choices=e.slice(1,-1).split(\",\")},next:\"start\"},{regex:\"/(\"+t(\"/\")+\"+)/(?:(\"+t(\"/\")+\"*)/)(\\\\w*):?\",onMatch:function(e,t,n){var r=n[0];return r.fmtString=e,e=this.splitRegex.exec(e),r.guard=e[1],r.fmt=e[2],r.flag=e[3],\"\"},next:\"start\"},{regex:\"`\"+t(\"`\")+\"*`\",onMatch:function(e,t,n){return n[0].code=e.splice(1,-1),\"\"},next:\"start\"},{regex:\"\\\\?\",onMatch:function(e,t,n){n[0]&&(n[0].expectIf=!0)},next:\"start\"},{regex:\"([^:}\\\\\\\\]|\\\\\\\\.)*:?\",token:\"\",next:\"start\"}],formatString:[{regex:\"/(\"+t(\"/\")+\"+)/\",token:\"regex\"},{regex:\"\",onMatch:function(e,t,n){n.inFormatString=!0},next:\"start\"}]}),a.prototype.getTokenizer=function(){return a.$tokenizer},a.$tokenizer},this.tokenizeTmSnippet=function(e,t){return this.getTokenizer().getLineTokens(e,t).tokens.map(function(e){return e.value||e})},this.$getDefaultValue=function(e,t){if(/^[A-Z]\\d+$/.test(t)){var n=t.substr(1);return(this.variables[t[0]+\"__\"]||{})[n]}if(/^\\d+$/.test(t))return(this.variables.__||{})[t];t=t.replace(/^TM_/,\"\");if(!e)return;var r=e.session;switch(t){case\"CURRENT_WORD\":var i=r.getWordRange();case\"SELECTION\":case\"SELECTED_TEXT\":return r.getTextRange(i);case\"CURRENT_LINE\":return r.getLine(e.getCursorPosition().row);case\"PREV_LINE\":return r.getLine(e.getCursorPosition().row-1);case\"LINE_INDEX\":return e.getCursorPosition().column;case\"LINE_NUMBER\":return e.getCursorPosition().row+1;case\"SOFT_TABS\":return r.getUseSoftTabs()?\"YES\":\"NO\";case\"TAB_SIZE\":return r.getTabSize();case\"FILENAME\":case\"FILEPATH\":return\"ace.ajax.org\";case\"FULLNAME\":return\"Ace\"}},this.variables={},this.getVariableValue=function(e,t){return this.variables.hasOwnProperty(t)?this.variables[t](e,t)||\"\":this.$getDefaultValue(e,t)||\"\"},this.tmStrFormat=function(e,t,n){var r=t.flag||\"\",i=t.guard;i=new RegExp(i,r.replace(/[^gi]/,\"\"));var s=this.tokenizeTmSnippet(t.fmt,\"formatString\"),o=this,u=e.replace(i,function(){o.variables.__=arguments;var e=o.resolveVariables(s,n),t=\"E\";for(var r=0;r<e.length;r++){var i=e[r];if(typeof i==\"object\"){e[r]=\"\";if(i.changeCase&&i.local){var u=e[r+1];u&&typeof u==\"string\"&&(i.changeCase==\"u\"?e[r]=u[0].toUpperCase():e[r]=u[0].toLowerCase(),e[r+1]=u.substr(1))}else i.changeCase&&(t=i.changeCase)}else t==\"U\"?e[r]=i.toUpperCase():t==\"L\"&&(e[r]=i.toLowerCase())}return e.join(\"\")});return this.variables.__=null,u},this.resolveVariables=function(e,t){function o(t){var n=e.indexOf(t,r+1);n!=-1&&(r=n)}var n=[];for(var r=0;r<e.length;r++){var i=e[r];if(typeof i==\"string\")n.push(i);else{if(typeof i!=\"object\")continue;if(i.skip)o(i);else{if(i.processed<r)continue;if(i.text){var s=this.getVariableValue(t,i.text);s&&i.fmtString&&(s=this.tmStrFormat(s,i)),i.processed=r,i.expectIf==null?s&&(n.push(s),o(i)):s?i.skip=i.elseBranch:o(i)}else i.tabstopId!=null?n.push(i):i.changeCase!=null&&n.push(i)}}}return n},this.insertSnippet=function(e,t){function l(e){var t=[];for(var n=0;n<e.length;n++){var r=e[n];if(typeof r==\"object\"){if(a[r.tabstopId])continue;var i=e.lastIndexOf(r,n-1);r=t[i]||{tabstopId:r.tabstopId}}t[n]=r}return t}var n=e.getCursorPosition(),r=e.session.getLine(n.row),i=r.match(/^\\s*/)[0],s=e.session.getTabString(),o=this.tokenizeTmSnippet(t);o=this.resolveVariables(o,e),o=o.map(function(e){return e==\"\\n\"?e+i:typeof e==\"string\"?e.replace(/\\t/g,s):e});var u=[];o.forEach(function(e,t){if(typeof e!=\"object\")return;var n=e.tabstopId,r=u[n];r||(r=u[n]=[],r.index=n,r.value=\"\");if(r.indexOf(e)!==-1)return;r.push(e);var i=o.indexOf(e,t+1);if(i===-1)return;var s=o.slice(t+1,i),a=s.some(function(e){return typeof e==\"object\"});a&&!r.value?r.value=s:s.length&&(!r.value||typeof r.value!=\"string\")&&(r.value=s.join(\"\"))}),u.forEach(function(e){e.length=0});var a={};for(var c=0;c<o.length;c++){var h=o[c];if(typeof h!=\"object\")continue;var p=h.tabstopId,d=o.indexOf(h,c+1);if(a[p]==h){a[p]=null;continue}var v=u[p],m=typeof v.value==\"string\"?[v.value]:l(v.value);m.unshift(c+1,Math.max(0,d-c)),m.push(h),a[p]=h,o.splice.apply(o,m),v.indexOf(h)===-1&&v.push(h)}var g=0,y=0,b=\"\";o.forEach(function(e){typeof e==\"string\"?(e[0]===\"\\n\"?(y=e.length-1,g++):y+=e.length,b+=e):e.start?e.end={row:g,column:y}:e.start={row:g,column:y}});var w=e.getSelectionRange(),E=e.session.replace(w,b),S=new f(e);S.addTabstops(u,w.start,E),S.tabNext()},this.$getScope=function(e){var t=e.session.$mode.$id||\"\";t=t.split(\"/\").pop();if(t===\"html\"||t===\"php\"){t===\"php\"&&(t=\"html\");var n=e.getCursorPosition(),r=e.session.getState(n.row);typeof r==\"object\"&&(r=r[0]),r.substring&&(r.substring(0,3)==\"js-\"?t=\"javascript\":r.substring(0,4)==\"css-\"?t=\"css\":r.substring(0,4)==\"php-\"&&(t=\"php\"))}return t},this.getActiveScopes=function(e){var t=this.$getScope(e),n=[t],r=this.snippetMap;return r[t]&&r[t].includeScopes&&n.push.apply(n,r[t].includeScopes),n.push(\"_\"),n},this.expandWithTab=function(e){var t=e.getCursorPosition(),n=e.session.getLine(t.row),r=n.substring(0,t.column),i=n.substr(t.column),s=this.snippetMap,o;return this.getActiveScopes(e).some(function(e){var t=s[e];return t&&(o=this.findMatchingSnippet(t,r,i)),!!o},this),o?(e.session.doc.removeInLine(t.row,t.column-o.replaceBefore.length,t.column+o.replaceAfter.length),this.variables.M__=o.matchBefore,this.variables.T__=o.matchAfter,this.insertSnippet(e,o.content),this.variables.M__=this.variables.T__=null,!0):!1},this.findMatchingSnippet=function(e,t,n){for(var r=e.length;r--;){var i=e[r];if(i.startRe&&!i.startRe.test(t))continue;if(i.endRe&&!i.endRe.test(n))continue;if(!i.startRe&&!i.endRe)continue;return i.matchBefore=i.startRe?i.startRe.exec(t):[\"\"],i.matchAfter=i.endRe?i.endRe.exec(n):[\"\"],i.replaceBefore=i.triggerRe?i.triggerRe.exec(t)[0]:\"\",i.replaceAfter=i.endTriggerRe?i.endTriggerRe.exec(n)[0]:\"\",i}},this.snippetMap={},this.snippetNameMap={},this.register=function(e,t){function o(e){return e&&!/^\\^?\\(.*\\)\\$?$|^\\\\b$/.test(e)&&(e=\"(?:\"+e+\")\"),e||\"\"}function u(e,t,n){return e=o(e),t=o(t),n?(e=t+e,e&&e[e.length-1]!=\"$\"&&(e+=\"$\")):(e+=t,e&&e[0]!=\"^\"&&(e=\"^\"+e)),new RegExp(e)}function a(e){e.scope||(e.scope=t||\"_\"),t=e.scope,n[t]||(n[t]=[],i[t]={});var o=i[t];if(e.name){var a=o[e.name];a&&s.unregister(a),o[e.name]=e}n[t].push(e),e.tabTrigger&&!e.trigger&&(!e.guard&&/^\\w/.test(e.tabTrigger)&&(e.guard=\"\\\\b\"),e.trigger=r.escapeRegExp(e.tabTrigger)),e.startRe=u(e.trigger,e.guard,!0),e.triggerRe=new RegExp(e.trigger,\"\",!0),e.endRe=u(e.endTrigger,e.endGuard,!0),e.endTriggerRe=new RegExp(e.endTrigger,\"\",!0)}var n=this.snippetMap,i=this.snippetNameMap,s=this;e.content?a(e):Array.isArray(e)&&e.forEach(a)},this.unregister=function(e,t){function i(e){var i=r[e.scope||t];if(i&&i[e.name]){delete i[e.name];var s=n[e.scope||t],o=s&&s.indexOf(e);o>=0&&s.splice(o,1)}}var n=this.snippetMap,r=this.snippetNameMap;e.content?i(e):Array.isArray(e)&&e.forEach(i)},this.parseSnippetFile=function(e){e=e.replace(/\\r/g,\"\");var t=[],n={},r=/^#.*|^({[\\s\\S]*})\\s*$|^(\\S+) (.*)$|^((?:\\n*\\t.*)+)/gm,i;while(i=r.exec(e)){if(i[1])try{n=JSON.parse(i[1]),t.push(n)}catch(s){}if(i[4])n.content=i[4].replace(/^\\t/gm,\"\"),t.push(n),n={};else{var o=i[2],u=i[3];if(o==\"regex\"){var a=/\\/((?:[^\\/\\\\]|\\\\.)*)|$/g;n.guard=a.exec(u)[1],n.trigger=a.exec(u)[1],n.endTrigger=a.exec(u)[1],n.endGuard=a.exec(u)[1]}else o==\"snippet\"?(n.tabTrigger=u.match(/^\\S*/)[0],n.name||(n.name=u)):n[o]=u}}return t},this.getSnippetByName=function(e,t){var n=this.snippetNameMap,r;return this.getActiveScopes(t).some(function(t){var i=n[t];return i&&(r=i[e]),!!r},this),r}}).call(a.prototype);var f=function(e){if(e.tabstopManager)return e.tabstopManager;e.tabstopManager=this,this.$onChange=this.onChange.bind(this),this.$onChangeSelection=r.delayedCall(this.onChangeSelection.bind(this)).schedule,this.$onChangeSession=this.onChangeSession.bind(this),this.$onAfterExec=this.onAfterExec.bind(this),this.attach(e)};(function(){this.attach=function(e){this.index=-1,this.ranges=[],this.tabstops=[],this.selectedTabstop=null,this.editor=e,this.editor.on(\"change\",this.$onChange),this.editor.on(\"changeSelection\",this.$onChangeSelection),this.editor.on(\"changeSession\",this.$onChangeSession),this.editor.commands.on(\"afterExec\",this.$onAfterExec),this.editor.keyBinding.addKeyboardHandler(this.keyboardHandler)},this.detach=function(){this.tabstops.forEach(this.removeTabstopMarkers,this),this.ranges=null,this.tabstops=null,this.selectedTabstop=null,this.editor.removeListener(\"change\",this.$onChange),this.editor.removeListener(\"changeSelection\",this.$onChangeSelection),this.editor.removeListener(\"changeSession\",this.$onChangeSession),this.editor.commands.removeListener(\"afterExec\",this.$onAfterExec),this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler),this.editor.tabstopManager=null,this.editor=null},this.onChange=function(e){var t=e.data.range,n=e.data.action[0]==\"r\",r=t.start,i=t.end,s=r.row,o=i.row,a=o-s,f=i.column-r.column;n&&(a=-a,f=-f);if(!this.$inChange&&n){var l=this.selectedTabstop,c=!l.some(function(e){return u(e.start,r)<=0&&u(e.end,i)>=0});if(c)return this.detach()}var h=this.ranges;for(var p=0;p<h.length;p++){var d=h[p];if(d.end.row<r.row)continue;if(u(r,d.start)<0&&u(i,d.end)>0){this.removeRange(d),p--;continue}d.start.row==s&&d.start.column>r.column&&(d.start.column+=f),d.end.row==s&&d.end.column>=r.column&&(d.end.column+=f),d.start.row>=s&&(d.start.row+=a),d.end.row>=s&&(d.end.row+=a),u(d.start,d.end)>0&&this.removeRange(d)}h.length||this.detach()},this.updateLinkedFields=function(){var e=this.selectedTabstop;if(!e.hasLinkedRanges)return;this.$inChange=!0;var n=this.editor.session,r=n.getTextRange(e.firstNonLinked);for(var i=e.length;i--;){var s=e[i];if(!s.linked)continue;var o=t.snippetManager.tmStrFormat(r,s.original);n.replace(s,o)}this.$inChange=!1},this.onAfterExec=function(e){e.command&&!e.command.readOnly&&this.updateLinkedFields()},this.onChangeSelection=function(){if(!this.editor)return;var e=this.editor.selection.lead,t=this.editor.selection.anchor,n=this.editor.selection.isEmpty();for(var r=this.ranges.length;r--;){if(this.ranges[r].linked)continue;var i=this.ranges[r].contains(e.row,e.column),s=n||this.ranges[r].contains(t.row,t.column);if(i&&s)return}this.detach()},this.onChangeSession=function(){this.detach()},this.tabNext=function(e){var t=this.tabstops.length-1,n=this.index+(e||1);n=Math.min(Math.max(n,0),t),this.selectTabstop(n),n==t&&this.detach()},this.selectTabstop=function(e){var t=this.tabstops[this.index];t&&this.addTabstopMarkers(t),this.index=e,t=this.tabstops[this.index];if(!t||!t.length)return;this.selectedTabstop=t;if(!this.editor.inVirtualSelectionMode){var n=this.editor.multiSelect;n.toSingleRange(t.firstNonLinked.clone());for(var r=t.length;r--;){if(t.hasLinkedRanges&&t[r].linked)continue;n.addRange(t[r].clone(),!0)}}else this.editor.selection.setRange(t.firstNonLinked);this.editor.keyBinding.addKeyboardHandler(this.keyboardHandler)},this.addTabstops=function(e,t,n){if(!e[0]){var r=i.fromPoints(n,n);c(r.start,t),c(r.end,t),e[0]=[r],e[0].index=0}var s=this.index,o=[s,0],u=this.ranges,a=this.editor;e.forEach(function(e){for(var n=e.length;n--;){var r=e[n],s=i.fromPoints(r.start,r.end||r.start);l(s.start,t),l(s.end,t),s.original=r,s.tabstop=e,u.push(s),e[n]=s,r.fmtString?(s.linked=!0,e.hasLinkedRanges=!0):e.firstNonLinked||(e.firstNonLinked=s)}e.firstNonLinked||(e.hasLinkedRanges=!1),o.push(e),this.addTabstopMarkers(e)},this),o.push(o.splice(2,1)[0]),this.tabstops.splice.apply(this.tabstops,o)},this.addTabstopMarkers=function(e){var t=this.editor.session;e.forEach(function(e){e.markerId||(e.markerId=t.addMarker(e,\"ace_snippet-marker\",\"text\"))})},this.removeTabstopMarkers=function(e){var t=this.editor.session;e.forEach(function(e){t.removeMarker(e.markerId),e.markerId=null})},this.removeRange=function(e){var t=e.tabstop.indexOf(e);e.tabstop.splice(t,1),t=this.ranges.indexOf(e),this.ranges.splice(t,1),this.editor.session.removeMarker(e.markerId)},this.keyboardHandler=new s,this.keyboardHandler.bindKeys({Tab:function(e){if(t.snippetManager&&t.snippetManager.expandWithTab(e))return;e.tabstopManager.tabNext(1)},\"Shift-Tab\":function(e){e.tabstopManager.tabNext(-1)},Esc:function(e){e.tabstopManager.detach()},Return:function(e){return!1}})}).call(f.prototype);var l=function(e,t){e.row==0&&(e.column+=t.column),e.row+=t.row},c=function(e,t){e.row==t.row&&(e.column-=t.column),e.row-=t.row};e(\"./lib/dom\").importCssString(\".ace_snippet-marker {    -moz-box-sizing: border-box;    box-sizing: border-box;    background: rgba(194, 193, 208, 0.09);    border: 1px dotted rgba(211, 208, 235, 0.62);    position: absolute;}\"),t.snippetManager=new a}),define(\"ace/autocomplete\",[\"require\",\"exports\",\"module\",\"ace/keyboard/hash_handler\",\"ace/autocomplete/popup\",\"ace/autocomplete/util\",\"ace/lib/event\",\"ace/lib/lang\",\"ace/snippets\"],function(e,t,n){var r=e(\"./keyboard/hash_handler\").HashHandler,i=e(\"./autocomplete/popup\").AcePopup,s=e(\"./autocomplete/util\"),o=e(\"./lib/event\"),u=e(\"./lib/lang\"),a=e(\"./snippets\").snippetManager,f=function(){this.autoInsert=!0,this.keyboardHandler=new r,this.keyboardHandler.bindKeys(this.commands),this.blurListener=this.blurListener.bind(this),this.changeListener=this.changeListener.bind(this),this.mousedownListener=this.mousedownListener.bind(this),this.mousewheelListener=this.mousewheelListener.bind(this),this.changeTimer=u.delayedCall(function(){this.updateCompletions(!0)}.bind(this))};(function(){this.$init=function(){this.popup=new i(document.body||document.documentElement),this.popup.on(\"click\",function(e){this.insertMatch(),e.stop()}.bind(this))},this.openPopup=function(e,t,n){this.popup||this.$init(),this.popup.setData(this.completions.filtered);var r=e.renderer;if(!n){this.popup.setRow(0),this.popup.setFontSize(e.getFontSize());var i=r.layerConfig.lineHeight,s=r.$cursorLayer.getPixelPosition(this.base,!0);s.left-=this.popup.getTextLeftOffset();var o=e.container.getBoundingClientRect();s.top+=o.top-r.layerConfig.offset,s.left+=o.left-e.renderer.scrollLeft,s.left+=r.$gutterLayer.gutterWidth,this.popup.show(s,i)}},this.detach=function(){this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler),this.editor.off(\"changeSelection\",this.changeListener),this.editor.off(\"blur\",this.changeListener),this.editor.off(\"mousedown\",this.mousedownListener),this.editor.off(\"mousewheel\",this.mousewheelListener),this.changeTimer.cancel(),this.popup&&this.popup.hide(),this.activated=!1,this.completions=this.base=null},this.changeListener=function(e){var t=this.editor.selection.lead;(t.row!=this.base.row||t.column<this.base.column)&&this.detach(),this.activated?this.changeTimer.schedule():this.detach()},this.blurListener=function(){document.activeElement!=this.editor.textInput.getElement()&&this.detach()},this.mousedownListener=function(e){this.detach()},this.mousewheelListener=function(e){this.detach()},this.goTo=function(e){var t=this.popup.getRow(),n=this.popup.session.getLength()-1;switch(e){case\"up\":t=t<0?n:t-1;break;case\"down\":t=t>=n?-1:t+1;break;case\"start\":t=0;break;case\"end\":t=n}this.popup.setRow(t)},this.insertMatch=function(e){e||(e=this.popup.getData(this.popup.getRow()));if(!e)return!1;if(e.completer&&e.completer.insertMatch)e.completer.insertMatch(this.editor);else{if(this.completions.filterText){var t=this.editor.selection.getAllRanges();for(var n=0,r;r=t[n];n++)r.start.column-=this.completions.filterText.length,this.editor.session.remove(r)}e.snippet?a.insertSnippet(this.editor,e.snippet):this.editor.execCommand(\"insertstring\",e.value||e)}this.detach()},this.commands={Up:function(e){e.completer.goTo(\"up\")},Down:function(e){e.completer.goTo(\"down\")},\"Ctrl-Up|Ctrl-Home\":function(e){e.completer.goTo(\"start\")},\"Ctrl-Down|Ctrl-End\":function(e){e.completer.goTo(\"end\")},Esc:function(e){e.completer.detach()},Space:function(e){e.completer.detach(),e.insert(\" \")},Return:function(e){e.completer.insertMatch()},\"Shift-Return\":function(e){e.completer.insertMatch(!0)},Tab:function(e){e.completer.insertMatch()},PageUp:function(e){e.completer.popup.gotoPageUp()},PageDown:function(e){e.completer.popup.gotoPageDown()}},this.gatherCompletions=function(e,t){var n=e.getSession(),r=e.getCursorPosition(),i=n.getLine(r.row),o=s.retrievePrecedingIdentifier(i,r.column);this.base=e.getCursorPosition(),this.base.column-=o.length;var u=[];return s.parForEach(e.completers,function(t,i){t.getCompletions(e,n,r,o,function(e,t){e||(u=u.concat(t)),i()})},function(){t(null,{prefix:o,matches:u})}),!0},this.showPopup=function(e){this.editor&&this.detach(),this.activated=!0,this.editor=e,e.completer!=this&&(e.completer&&e.completer.detach(),e.completer=this),e.keyBinding.addKeyboardHandler(this.keyboardHandler),e.on(\"changeSelection\",this.changeListener),e.on(\"blur\",this.blurListener),e.on(\"mousedown\",this.mousedownListener),e.on(\"mousewheel\",this.mousewheelListener),this.updateCompletions()},this.updateCompletions=function(e){if(e&&this.base&&this.completions){var t=this.editor.getCursorPosition(),n=this.editor.session.getTextRange({start:this.base,end:t});if(n==this.completions.filterText)return;this.completions.setFilter(n);if(!this.completions.filtered.length)return this.detach();this.openPopup(this.editor,n,e);return}this.gatherCompletions(this.editor,function(t,n){var r=n&&n.matches;if(!r||!r.length)return this.detach();this.completions=new l(r),this.completions.setFilter(n.prefix);var i=this.completions.filtered;if(!i.length)return this.detach();if(this.autoInsert&&i.length==1)return this.insertMatch(i[0]);this.openPopup(this.editor,n.prefix,e)}.bind(this))},this.cancelContextMenu=function(){var e=function(t){this.editor.off(\"nativecontextmenu\",e),t&&t.domEvent&&o.stopEvent(t.domEvent)}.bind(this);setTimeout(e,10),this.editor.on(\"nativecontextmenu\",e)}}).call(f.prototype),f.startCommand={name:\"startAutocomplete\",exec:function(e){e.completer||(e.completer=new f),e.completer.showPopup(e),e.completer.cancelContextMenu()},bindKey:\"Ctrl-Space|Ctrl-Shift-Space|Alt-Space\"};var l=function(e,t,n){this.all=e,this.filtered=e,this.filterText=t||\"\"};(function(){this.setFilter=function(e){if(e.length>this.filterText&&e.lastIndexOf(this.filterText,0)===0)var t=this.filtered;else var t=this.all;this.filterText=e,t=this.filterCompletions(t,this.filterText),t=t.sort(function(e,t){return t.exactMatch-e.exactMatch||t.score-e.score});var n=null;t=t.filter(function(e){var t=e.value||e.caption||e.snippet;return t===n?!1:(n=t,!0)}),this.filtered=t},this.filterCompletions=function(e,t){var n=[],r=t.toUpperCase(),i=t.toLowerCase();e:for(var s=0,o;o=e[s];s++){var u=o.value||o.caption||o.snippet;if(!u)continue;var a=-1,f=0,l=0,c,h;for(var p=0;p<t.length;p++){var d=u.indexOf(i[p],a+1),v=u.indexOf(r[p],a+1);c=d>=0?v<0||d<v?d:v:v;if(c<0)continue e;h=c-a-1,h>0&&(a===-1&&(l+=10),l+=h),f|=1<<c,a=c}o.matchMask=f,o.exactMatch=l?0:1,o.score=(o.score||0)-l,n.push(o)}return n}}).call(l.prototype),t.Autocomplete=f,t.FilteredList=l}),define(\"ace/autocomplete/popup\",[\"require\",\"exports\",\"module\",\"ace/edit_session\",\"ace/virtual_renderer\",\"ace/editor\",\"ace/range\",\"ace/lib/event\",\"ace/lib/lang\",\"ace/lib/dom\"],function(e,t,n){var r=e(\"../edit_session\").EditSession,i=e(\"../virtual_renderer\").VirtualRenderer,s=e(\"../editor\").Editor,o=e(\"../range\").Range,u=e(\"../lib/event\"),a=e(\"../lib/lang\"),f=e(\"../lib/dom\"),l=function(e){var t=new i(e);t.$maxLines=4;var n=new s(t);return n.setHighlightActiveLine(!1),n.setShowPrintMargin(!1),n.renderer.setShowGutter(!1),n.renderer.setHighlightGutterLine(!1),n.$mouseHandler.$focusWaitTimout=0,n},c=function(e){var t=f.createElement(\"div\"),n=new l(t);e&&e.appendChild(t),t.style.display=\"none\",n.renderer.content.style.cursor=\"default\",n.renderer.setStyle(\"ace_autocomplete\"),n.setOption(\"displayIndentGuides\",!1);var r=function(){};n.focus=r,n.$isFocused=!0,n.renderer.$cursorLayer.restartTimer=r,n.renderer.$cursorLayer.element.style.opacity=0,n.renderer.$maxLines=8,n.renderer.$keepTextAreaAtCursor=!1,n.setHighlightActiveLine(!1),n.session.highlight(\"\"),n.session.$searchHighlight.clazz=\"ace_highlight-marker\",n.on(\"mousedown\",function(e){var t=e.getDocumentPosition();n.moveCursorToPosition(t),n.selection.clearSelection(),c.start.row=c.end.row=t.row,e.stop()});var i,s=new o(-1,0,-1,Infinity),c=new o(-1,0,-1,Infinity);c.id=n.session.addMarker(c,\"ace_active-line\",\"fullLine\"),n.setSelectOnHover=function(e){e?s.id&&(n.session.removeMarker(s.id),s.id=null):s.id=n.session.addMarker(s,\"ace_line-hover\",\"fullLine\")},n.setSelectOnHover(!1),n.on(\"mousemove\",function(e){if(!i){i=e;return}if(i.x==e.x&&i.y==e.y)return;i=e,i.scrollTop=n.renderer.scrollTop;var t=i.getDocumentPosition().row;s.start.row!=t&&(s.id||n.setRow(t),p(t))}),n.renderer.on(\"beforeRender\",function(){if(i&&s.start.row!=-1){i.$pos=null;var e=i.getDocumentPosition().row;s.id||n.setRow(e),p(e,!0)}}),n.renderer.on(\"afterRender\",function(){var e=n.getRow(),t=n.renderer.$textLayer,r=t.element.childNodes[e-t.config.firstRow];if(r==t.selectedNode)return;t.selectedNode&&f.removeCssClass(t.selectedNode,\"ace_selected\"),t.selectedNode=r,r&&f.addCssClass(r,\"ace_selected\")});var h=function(){p(-1)},p=function(e,t){e!==s.start.row&&(s.start.row=s.end.row=e,t||n.session._emit(\"changeBackMarker\"),n._emit(\"changeHoverMarker\"))};n.getHoveredRow=function(){return s.start.row},u.addListener(n.container,\"mouseout\",h),n.on(\"hide\",h),n.on(\"changeSelection\",h),n.session.doc.getLength=function(){return n.data.length},n.session.doc.getLine=function(e){var t=n.data[e];return typeof t==\"string\"?t:t&&t.value||\"\"};var d=n.session.bgTokenizer;return d.$tokenizeRow=function(e){var t=n.data[e],r=[];if(!t)return r;typeof t==\"string\"&&(t={value:t}),t.caption||(t.caption=t.value);var i=-1,s,o;for(var e=0;e<t.caption.length;e++)o=t.caption[e],s=t.matchMask&1<<e?1:0,i!==s?(r.push({type:t.className||\"\"+(s?\"completion-highlight\":\"\"),value:o}),i=s):r[r.length-1].value+=o;if(t.meta){var u=n.renderer.$size.scrollerWidth/n.renderer.layerConfig.characterWidth;t.meta.length+t.caption.length<u-2&&r.push({type:\"rightAlignedText\",value:t.meta})}return r},d.$updateOnChange=r,d.start=r,n.session.$computeWidth=function(){return this.screenWidth=0},n.isOpen=!1,n.isTopdown=!1,n.data=[],n.setData=function(e){n.data=e||[],n.setValue(a.stringRepeat(\"\\n\",e.length),-1),n.setRow(0)},n.getData=function(e){return n.data[e]},n.getRow=function(){return c.start.row},n.setRow=function(e){e=Math.max(-1,Math.min(this.data.length,e)),c.start.row!=e&&(n.selection.clearSelection(),c.start.row=c.end.row=e||0,n.session._emit(\"changeBackMarker\"),n.moveCursorTo(e||0,0),n.isOpen&&n._signal(\"select\"))},n.on(\"changeSelection\",function(){n.isOpen&&n.setRow(n.selection.lead.row)}),n.hide=function(){this.container.style.display=\"none\",this._signal(\"hide\"),n.isOpen=!1},n.show=function(e,t,r){var s=this.container,o=window.innerHeight,u=window.innerWidth,a=this.renderer,f=a.$maxLines*t*1.4,l=e.top+this.$borderSize;l+f>o-t&&!r?(s.style.top=\"\",s.style.bottom=o-l+\"px\",n.isTopdown=!1):(l+=t,s.style.top=l+\"px\",s.style.bottom=\"\",n.isTopdown=!0),s.style.display=\"\",this.renderer.$textLayer.checkForSizeChanges();var c=e.left;c+s.offsetWidth>u&&(c=u-s.offsetWidth),s.style.left=c+\"px\",this._signal(\"show\"),i=null,n.isOpen=!0},n.getTextLeftOffset=function(){return this.$borderSize+this.renderer.$padding+this.$imageSize},n.$imageSize=0,n.$borderSize=1,n};f.importCssString(\".ace_autocomplete.ace-tm .ace_marker-layer .ace_active-line {    background-color: #CAD6FA;    z-index: 1;}.ace_autocomplete.ace-tm .ace_line-hover {    border: 1px solid #abbffe;    margin-top: -1px;    background: rgba(233,233,253,0.4);}.ace_autocomplete .ace_line-hover {    position: absolute;    z-index: 2;}.ace_rightAlignedText {    color: gray;    display: inline-block;    position: absolute;    right: 4px;    text-align: right;    z-index: -1;}.ace_autocomplete .ace_completion-highlight{    color: #000;    text-shadow: 0 0 0.01em;}.ace_autocomplete {    width: 280px;    z-index: 200000;    background: #fbfbfb;    color: #444;    border: 1px lightgray solid;    position: fixed;    box-shadow: 2px 3px 5px rgba(0,0,0,.2);    line-height: 1.4;}\"),t.AcePopup=c}),define(\"ace/autocomplete/util\",[\"require\",\"exports\",\"module\"],function(e,t,n){t.parForEach=function(e,t,n){var r=0,i=e.length;i===0&&n();for(var s=0;s<i;s++)t(e[s],function(e,t){r++,r===i&&n(e,t)})};var r=/[a-zA-Z_0-9\\$-]/;t.retrievePrecedingIdentifier=function(e,t,n){n=n||r;var i=[];for(var s=t-1;s>=0;s--){if(!n.test(e[s]))break;i.push(e[s])}return i.reverse().join(\"\")},t.retrieveFollowingIdentifier=function(e,t,n){n=n||r;var i=[];for(var s=t;s<e.length;s++){if(!n.test(e[s]))break;i.push(e[s])}return i}}),define(\"ace/autocomplete/text_completer\",[\"require\",\"exports\",\"module\",\"ace/range\"],function(e,t,n){function s(e,t){var n=e.getTextRange(r.fromPoints({row:0,column:0},t));return n.split(i).length-1}function o(e,t){var n=s(e,t),r=e.getValue().split(i),o=Object.create(null),u=r[n];return r.forEach(function(e,t){if(!e||e===u)return;var i=Math.abs(n-t),s=r.length-i;o[e]?o[e]=Math.max(s,o[e]):o[e]=s}),o}var r=e(\"ace/range\").Range,i=/[^a-zA-Z_0-9\\$\\-]+/;t.getCompletions=function(e,t,n,r,i){var s=o(t,n,r),u=Object.keys(s);i(null,u.map(function(e){return{name:e,value:e,score:s[e],meta:\"local\"}}))}}),\ndefine(\"ace/mode/octave\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text\",\"ace/tokenizer\",\"ace/mode/matching_brace_outdent\",\"ace/range\",\"ace/mode/octave_highlight_rules\"],function(e,t,n){var r=e(\"../lib/oop\"),i=e(\"./text\").Mode,s=e(\"../tokenizer\").Tokenizer,o=e(\"./matching_brace_outdent\").MatchingBraceOutdent,u=e(\"../range\").Range,a=e(\"./octave_highlight_rules\").OctaveHighlightRules,f=function(){this.HighlightRules=a,this.$outdent=new o};r.inherits(f,i),function(){var e=\"if|elseif|else|while|for|do\",t=\"end|else\",n=new RegExp(\"^\\\\s+(?:\"+t+\")$\");this.getNextLineIndent=function(t,n,r){var i=this.$getIndent(n),s=this.$tokenizer.getLineTokens(n,t),o=s.tokens;if(o.length&&o[o.length-1].type==\"comment\")return i;if(t==\"start\"){var u=n.match(\"(?:;|^)\\\\s*(?:\"+e+\")[^;]*$\"),a=n.match(/^\\s*function.+?\\=\\s*(\\w+)\\(.*\\)\\s*$/);u?i+=r:a&&(i+=\"% \"+a[1]+\": \")}return i},this.checkOutdent=function(e,t,r){return n.test(t+r)},this.autoOutdent=function(e,t,n){var r=this.$getIndent(t.getLine(n)),i=t.getTabString();r.slice(-i.length)==i&&t.remove(new u(n,r.length-i.length,n,r.length))}}.call(f.prototype),t.Mode=f}),define(\"ace/mode/matching_brace_outdent\",[\"require\",\"exports\",\"module\",\"ace/range\"],function(e,t,n){var r=e(\"../range\").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\\s+$/.test(e)?/^\\s*\\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\\s*\\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),define(\"ace/mode/octave_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules\"],function(e,t,n){var r=e(\"../lib/oop\"),i=e(\"./text_highlight_rules\").TextHighlightRules,s=function(){var e=\"argv|e|eps|F_DUPFD|F_GETFD|F_GETFL|filesep|F_SETFD|F_SETFL|inf|Inf|NA|nan|NaN|O_APPEND|O_ASYNC|O_CREAT|OCTAVE_HOME|OCTAVE_VERSION|O_EXCL|O_NONBLOCK|O_RDONLY|O_RDWR|O_SYNC|O_TRUNC|O_WRONLY|pi|program_invocation_name|program_name|P_tmpdir|realmax|realmin|SEEK_CUR|SEEK_END|SEEK_SET|SIG|stderr|stdin|stdout|ans|automatic_replot|beep_on_error|completion_append_char|crash_dumps_octave_core|current_script_file_name|debug_on_error|debug_on_interrupt|debug_on_warning|debug_symtab_lookups|DEFAULT_EXEC_PATH|DEFAULT_LOADPATH|default_save_format|echo_executing_commands|EDITOR|EXEC_PATH|FFTW_WISDOM_PROGRAM|fixed_point_format|gnuplot_binary|gnuplot_command_axes|gnuplot_command_end|gnuplot_command_plot|gnuplot_command_replot|gnuplot_command_splot|gnuplot_command_title|gnuplot_command_using|gnuplot_command_with|gnuplot_has_frames|history_file|history_size|ignore_function_time_stamp|IMAGEPATH|INFO_FILE|INFO_PROGRAM|__kluge_procbuf_delay__|LOADPATH|MAKEINFO_PROGRAM|max_recursion_depth|octave_core_file_format|octave_core_file_limit|octave_core_file_name|output_max_field_width|output_precision|page_output_immediately|PAGER|page_screen_output|print_answer_id_name|print_empty_dimensions|print_rhs_assign_val|PS1|PS2|PS4|save_header_format_string|save_precision|saving_history|sighup_dumps_octave_core|sigterm_dumps_octave_core|silent_functions|split_long_rows|string_fill_char|struct_levels_to_print|suppress_verbose_help_message|variables_can_hide_functions|warn_assign_as_truth_value|warn_divide_by_zero|warn_empty_list_elements|warn_fortran_indexing|warn_function_name_clash|warn_future_time_stamp|warn_imag_to_real|warn_matlab_incompatible|warn_missing_semicolon|warn_neg_dim_as_zero|warn_num_to_str|warn_precedence_change|warn_reload_forces_clear|warn_resize_on_range_error|warn_separator_insert|warn_single_quote_string|warn_str_to_num|warn_undefined_return_values|warn_variable_switch_label|whos_line_format\",t=\"all_va_args|break|case|catch|continue|else|end|for|elseif|end_unwind_protect|global|gplot|gsplot|if|otherwise|persistent|replot|return|static|start|startat|stop|switch|try|until|unwind_protect|unwind_protect_cleanup|varargin|varargout|wait|while\",n=\"abs|acos|acosh|all|angle|any|append|arg|argnames|asin|asinh|assignin|atan|atan2|atanh|atexit|bitand|bitmax|bitor|bitshift|bitxor|casesen|cat|ceil|cell|cell2struct|cellstr|char|class|clc|clear|clearplot|clg|closeplot|completion_matches|conj|conv|convmtx|cos|cosh|cumprod|cumsum|dbclear|dbstatus|dbstop|dbtype|dbwhere|deconv|det|dftmtx|diag|diary|disp|document|do_string_escapes|double|dup2|echo|edit_history|__end__|erf|erfc|ERRNO|error|__error_text__|error_text|eval|evalin|exec|exist|exit|exp|eye|fclose|fcntl|fdisp|feof|ferror|feval|fflush|fft|fgetl|fgets|fieldnames|file_in_loadpath|file_in_path|filter|find|find_first_of_in_loadpath|finite|fix|floor|fmod|fnmatch|fopen|fork|format|formula|fprintf|fputs|fread|freport|frewind|fscanf|fseek|ftell|func2str|functions|fwrite|gamma|gammaln|getegid|getenv|geteuid|getgid|getpgrp|getpid|getppid|getuid|glob|graw|gset|gshow|help|history|hold|home|horzcat|ifft|imag|inline|input|input_event_hook|int16|int32|int64|int8|intmax|intmin|inv|inverse|ipermute|isalnum|isalpha|isascii|isbool|iscell|iscellstr|ischar|iscntrl|iscomplex|isdigit|isempty|isfield|isfinite|isglobal|isgraph|ishold|isieee|isinf|iskeyword|islist|islogical|islower|ismatrix|isna|isnan|is_nan_or_na|isnumeric|isprint|ispunct|isreal|isspace|isstream|isstreamoff|isstruct|isupper|isvarname|isxdigit|kbhit|keyboard|kill|lasterr|lastwarn|length|lgamma|link|linspace|list|load|log|log10|lstat|lu|mark_as_command|mislocked|mkfifo|mkstemp|mlock|more|munlock|nargin|nargout|native_float_format|ndims|nth|numel|octave_config_info|octave_tmp_file_name|ones|pause|pclose|permute|pipe|popen|printf|__print_symbol_info__|__print_symtab_info__|prod|purge_tmp_files|putenv|puts|pwd|quit|rank|readdir|readlink|read_readline_init_file|real|rehash|rename|reshape|reverse|rmfield|roots|round|run_history|save|scanf|set|shell_cmd|show|sign|sin|sinh|size|sizeof|sleep|sort|source|splice|sprintf|sqrt|squeeze|sscanf|stat|str2func|streamoff|struct|struct2cell|sum|sumsq|symlink|system|tan|tanh|tilde_expand|tmpfile|tmpnam|toascii|__token_count__|tolower|toupper|type|typeinfo|uint16|uint32|uint64|uint8|umask|undo_string_escapes|unlink|unmark_command|usage|usleep|va_arg|va_start|vectorize|vertcat|vr_val|waitpid|warning|warranty|which|who|whos|zeros|airy|balance|besselh|besseli|besselj|besselk|bessely|betainc|chol|colloc|daspk|daspk_options|dasrt|dasrt_options|dassl|dassl_options|det|eig|endgrent|endpwent|expm|fft|fft2|fftn|fftw_wisdom|filter|find|fsolve|fsolve_options|gammainc|gcd|getgrent|getgrgid|getgrnam|getpwent|getpwnam|getpwuid|getrusage|givens|gmtime|hess|ifft|ifft2|ifftn|inv|inverse|kron|localtime|lpsolve|lpsolve_options|lsode|lsode_options|lu|max|min|minmax|mktime|odessa|odessa_options|pinv|qr|quad|quad_options|qz|rand|randn|schur|setgrent|setpwent|sort|sqrtm|strftime|strptime|svd|syl|time|abcddim|__abcddims__|acot|acoth|acsc|acsch|analdemo|anova|arch_fit|arch_rnd|arch_test|are|arma_rnd|asctime|asec|asech|autocor|autocov|autoreg_matrix|axis|axis2dlim|__axis_label__|bar|bartlett|bartlett_test|base2dec|bddemo|beep|bessel|beta|beta_cdf|betai|beta_inv|beta_pdf|beta_rnd|bin2dec|bincoeff|binomial_cdf|binomial_inv|binomial_pdf|binomial_rnd|bitcmp|bitget|bitset|blackman|blanks|bode|bode_bounds|__bodquist__|bottom_title|bug_report|buildssic|c2d|cart2pol|cart2sph|cauchy_cdf|cauchy_inv|cauchy_pdf|cauchy_rnd|cellidx|center|chisquare_cdf|chisquare_inv|chisquare_pdf|chisquare_rnd|chisquare_test_homogeneity|chisquare_test_independence|circshift|clock|cloglog|close|colormap|columns|com2str|comma|common_size|commutation_matrix|compan|complement|computer|cond|contour|controldemo|conv|cor|corrcoef|cor_test|cot|coth|cov|cputime|create_set|cross|csc|csch|ctime|ctrb|cut|d2c|damp|dare|date|dcgain|deal|deblank|dec2base|dec2bin|dec2hex|deconv|delete|DEMOcontrol|demoquat|detrend|dezero|dgkfdemo|dgram|dhinfdemo|diff|diffpara|dir|discrete_cdf|discrete_inv|discrete_pdf|discrete_rnd|dkalman|dlqe|dlqg|dlqr|dlyap|dmr2d|dmult|dot|dre|dump_prefs|duplication_matrix|durbinlevinson|empirical_cdf|empirical_inv|empirical_pdf|empirical_rnd|erfinv|__errcomm__|errorbar|__errplot__|etime|exponential_cdf|exponential_inv|exponential_pdf|exponential_rnd|f_cdf|fftconv|fftfilt|fftshift|figure|fileparts|findstr|f_inv|fir2sys|flipdim|fliplr|flipud|flops|f_pdf|fractdiff|frdemo|freqchkw|__freqresp__|freqz|freqz_plot|f_rnd|f_test_regression|fullfile|fv|fvl|gamma_cdf|gammai|gamma_inv|gamma_pdf|gamma_rnd|geometric_cdf|geometric_inv|geometric_pdf|geometric_rnd|gls|gram|gray|gray2ind|grid|h2norm|h2syn|hamming|hankel|hanning|hex2dec|hilb|hinf_ctr|hinfdemo|hinfnorm|hinfsyn|hinfsyn_chk|hinfsyn_ric|hist|hotelling_test|hotelling_test_2|housh|hsv2rgb|hurst|hypergeometric_cdf|hypergeometric_inv|hypergeometric_pdf|hypergeometric_rnd|image|imagesc|impulse|imshow|ind2gray|ind2rgb|ind2sub|index|int2str|intersection|invhilb|iqr|irr|isa|is_abcd|is_bool|is_complex|is_controllable|isdefinite|is_detectable|is_dgkf|is_digital|is_duplicate_entry|is_global|is_leap_year|isletter|is_list|is_matrix|is_observable|ispc|is_sample|is_scalar|isscalar|is_signal_list|is_siso|is_square|issquare|is_stabilizable|is_stable|isstr|is_stream|is_struct|is_symmetric|issymmetric|isunix|is_vector|isvector|jet707|kendall|kolmogorov_smirnov_cdf|kolmogorov_smirnov_test|kolmogorov_smirnov_test_2|kruskal_wallis_test|krylov|krylovb|kurtosis|laplace_cdf|laplace_inv|laplace_pdf|laplace_rnd|lcm|lin2mu|listidx|list_primes|loadaudio|loadimage|log2|logical|logistic_cdf|logistic_inv|logistic_pdf|logistic_regression|logistic_regression_derivatives|logistic_regression_likelihood|logistic_rnd|logit|loglog|loglogerr|logm|lognormal_cdf|lognormal_inv|lognormal_pdf|lognormal_rnd|logspace|lower|lqe|lqg|lqr|lsim|ltifr|lyap|mahalanobis|manova|mcnemar_test|mean|meansq|median|menu|mesh|meshdom|meshgrid|minfo|mod|moddemo|moment|mplot|mu2lin|multiplot|nargchk|nextpow2|nichols|norm|normal_cdf|normal_inv|normal_pdf|normal_rnd|not|nper|npv|ntsc2rgb|null|num2str|nyquist|obsv|ocean|ols|oneplot|ord2|orth|__outlist__|pack|packedform|packsys|parallel|paren|pascal_cdf|pascal_inv|pascal_pdf|pascal_rnd|path|periodogram|perror|place|playaudio|plot|plot_border|__plr__|__plr1__|__plr2__|__plt__|__plt1__|__plt2__|__plt2mm__|__plt2mv__|__plt2ss__|__plt2vm__|__plt2vv__|__pltopt__|__pltopt1__|pmt|poisson_cdf|poisson_inv|poisson_pdf|poisson_rnd|pol2cart|polar|poly|polyder|polyderiv|polyfit|polyinteg|polyout|polyreduce|polyval|polyvalm|popen2|postpad|pow2|ppplot|prepad|probit|prompt|prop_test_2|pv|pvl|pzmap|qconj|qcoordinate_plot|qderiv|qderivmat|qinv|qmult|qqplot|qtrans|qtransv|qtransvmat|quaternion|qzhess|qzval|randperm|range|rank|ranks|rate|record|rectangle_lw|rectangle_sw|rem|repmat|residue|rgb2hsv|rgb2ind|rgb2ntsc|rindex|rldemo|rlocus|roots|rot90|rotdim|rotg|rows|run_cmd|run_count|run_test|saveaudio|saveimage|sec|sech|semicolon|semilogx|semilogxerr|semilogy|semilogyerr|series|setaudio|setstr|shg|shift|shiftdim|sign_test|sinc|sinetone|sinewave|skewness|sombrero|sortcom|spearman|spectral_adf|spectral_xdf|spencer|sph2cart|split|ss|ss2sys|ss2tf|ss2zp|stairs|starp|statistics|std|stdnormal_cdf|stdnormal_inv|stdnormal_pdf|stdnormal_rnd|step|__stepimp__|stft|str2mat|str2num|strappend|strcat|strcmp|strerror|strjust|strrep|struct_contains|struct_elements|studentize|sub2ind|subplot|substr|subwindow|swap|swapcols|swaprows|sylvester_matrix|synthesis|sys2fir|sys2ss|sys2tf|sys2zp|sysadd|sysappend|syschnames|__syschnamesl__|syschtsam|__sysconcat__|sysconnect|syscont|__syscont_disc__|__sysdefioname__|__sysdefstname__|sysdimensions|sysdisc|sysdup|sysgetsignals|sysgettsam|sysgettype|sysgroup|__sysgroupn__|sysidx|sysmin|sysmult|sysout|sysprune|sysreorder|sysrepdemo|sysscale|syssetsignals|syssub|sysupdate|table|t_cdf|tempdir|tempname|texas_lotto|tf|tf2ss|tf2sys|__tf2sysl__|tf2zp|__tfl__|tfout|tic|t_inv|title|toc|toeplitz|top_title|t_pdf|trace|triangle_lw|triangle_sw|tril|triu|t_rnd|t_test|t_test_2|t_test_regression|tzero|tzero2|ugain|uniform_cdf|uniform_inv|uniform_pdf|uniform_rnd|union|unix|unpacksys|unwrap|upper|u_test|values|vander|var|var_test|vec|vech|version|vol|weibull_cdf|weibull_inv|weibull_pdf|weibull_rnd|welch_test|wgt1o|wiener_rnd|wilcoxon_test|xlabel|xor|ylabel|yulewalker|zgfmul|zgfslv|zginit|__zgpbal__|zgreduce|zgrownorm|zgscal|zgsgiv|zgshsr|zlabel|zp|zp2ss|__zp2ssg2__|zp2sys|zp2tf|zpout|z_test|z_test_2\",r=\"airy_Ai|airy_Ai_deriv|airy_Ai_deriv_scaled|airy_Ai_scaled|airy_Bi|airy_Bi_deriv|airy_Bi_deriv_scaled|airy_Bi_scaled|airy_zero_Ai|airy_zero_Ai_deriv|airy_zero_Bi|airy_zero_Bi_deriv|atanint|bchdeco|bchenco|bessel_il_scaled|bessel_In|bessel_In_scaled|bessel_Inu|bessel_Inu_scaled|bessel_jl|bessel_Jn|bessel_Jnu|bessel_kl_scaled|bessel_Kn|bessel_Kn_scaled|bessel_Knu|bessel_Knu_scaled|bessel_lnKnu|bessel_yl|bessel_Yn|bessel_Ynu|bessel_zero_J0|bessel_zero_J1|beta_gsl|bfgsmin|bisectionstep|builtin|bwfill|bwlabel|cell2csv|celleval|Chi|chol|Ci|clausen|conicalP_0|conicalP_1|conicalP_half|conicalP_mhalf|conv2|cordflt2|coupling_3j|coupling_6j|coupling_9j|csv2cell|csvconcat|csvexplode|cyclgen|cyclpoly|dawson|debye_1|debye_2|debye_3|debye_4|deref|dispatch|dispatch_help|display_fixed_operations|dlmread|ellint_Ecomp|ellint_Kcomp|ellipj|erfc_gsl|erf_gsl|erf_Q|erf_Z|_errcore|eta|eta_int|expint_3|expint_E1|expint_E2|expint_Ei|expm1|exp_mult|exprel|exprel_2|exprel_n|fabs|fangle|farg|fatan2|fceil|fconj|fcos|fcosh|fcumprod|fcumsum|fdiag|fermi_dirac_3half|fermi_dirac_half|fermi_dirac_inc_0|fermi_dirac_int|fermi_dirac_mhalf|fexp|ffloor|fimag|finitedifference|fixed|flog|flog10|fprod|freal|freshape|fround|fsin|fsinh|fsqrt|fsum|fsumsq|ftan|ftanh|full|gamma_gsl|gamma_inc|gamma_inc_P|gamma_inc_Q|gammainv_gsl|gammastar|gdet|gdiag|gexp|gf|gfilter|_gfweight|ginv|ginverse|glog|glu|gpick|gprod|grab|grank|graycomatrix|__grcla__|__grclf__|__grcmd__|greshape|__grexit__|__grfigure__|__grgetstat__|__grhold__|__grinit__|__grishold__|__grnewset__|__grsetgraph__|gsl_sf|gsqrt|gsum|gsumsq|gtext|gzoom|hazard|houghtf|hyperg_0F1|hzeta|is_complex_sparse|isfixed|isgalois|isprimitive|is_real_sparse|is_sparse|jpgread|jpgwrite|lambert_W0|lambert_Wm1|legendre_Pl|legendre_Plm|legendre_Ql|legendre_sphPlm|legendre_sphPlm_array|leval|listen|lnbeta|lncosh|lngamma_gsl|lnpoch|lnsinh|log_1plusx|log_1plusx_mx|log_erfc|lp|make_sparse|mark_for_deletion|medfilt1|newtonstep|nnz|numgradient|numhessian|pchip_deriv|pngread|pngwrite|poch|pochrel|pretty|primpoly|psi|psi_1_int|psi_1piy|psi_n|rand|rande|randn|randp|regexp|remez|reset_fixed_operations|rotate_scale|rsdec|rsenc|samin|SBBacksub|SBEig|SBFactor|SBProd|SBSolve|Shi|Si|sinc_gsl|spabs|sparse|spfind|spimag|spinv|splu|spreal|SymBand|synchrotron_1|synchrotron_2|syndtable|taylorcoeff|transport_2|transport_3|transport_4|transport_5|trisolve|waitbar|xmlread|zeta|zeta_int|aar|aarmam|ac2poly|ac2rc|acorf|acovf|addpath|ademodce|adim|adsmax|amodce|anderson_darling_cdf|anderson_darling_test|anovan|apkconst|append_save|applylut|ar2poly|ar2rc|arburg|arcext|arfit2|ar_spa|aryule|assert|au|aucapture|auload|auplot|aurecord|ausave|autumn|average_moments|awgn|azimuth|BandToFull|BandToSparse|base64encode|battery|bchpoly|bestblk|best_dir|best_dir_cov|betaln|bfgs|bfgsmin_example|bi2de|biacovf|bilinear|bisdemo|bispec|biterr|blkdiag|blkproc|bmpwrite|bone|bound_convex|boxcar|boxplot|brighten|bs_gradient|butter|buttord|bwborder|bweuler|bwlabel|bwmorph|bwselect|calendar|cceps|cdiff|cellstr|char|cheb|cheb1ord|cheb2ord|chebwin|cheby1|cheby2|chirp|clf|clip|cmpermute|cmunique|cohere|col2im|colfilt|colorgradient|comms|compand|complex|concat|conndef|content|contents|Contents|contourf|convhull|convmtx|cool|copper|corr2|cosets|count|covm|cplxpair|cquadnd|create_lookup_table|crule|crule2d|crule2dgen|csape|csapi|csd|csvread|csvwrite|ctranspose|cumtrapz|czt|d2_min|datenum|datestr|datevec|dct|dct2|dctmtx|de2bi|deal|decimate|decode|deg2rad|del2|delaunay|delaunay3|delta_method|demo|demodmap|deriv|detrend|dfdp|dftmtx|dhbar|dilate|dispatch|distance|dlmread|dlmwrite|dos|double|drawnow|durlev|dxfwrite|edge|edit|ellip|ellipdemo|ellipj|ellipke|ellipord|__ellip_ws|__ellip_ws_min|encode|eomday|erode|example|ExampleEigenValues|ExampleGenEigenValues|expdemo|expfit|eyediagram|factor|factorial|fail|fcnchk|feedback|fem_test|ff2n|fftconv2|fieldnames|fill|fill3|filter2|filtfilt|filtic|findsym|fir1|fir2|fixedpoint|flag|flag_implicit_samplerate|flattopwin|flix|float|fmin|fminbnd|fmins|fminunc|fnder|fnplt|fnval|fplot|freqs|freqs_plot|fsort|fullfact|FullToBand|funm|fzero|gammaln|gapTest|gaussian|gausswin|gconv|gconvmtx|gdeconv|gdftmtx|gen2par|geomean|getfield|getfields|gfft|gftable|gfweight|gget|gifft|ginput|gmm_estimate|gmm_example|gmm_obj|gmm_results|gmm_variance|gmm_variance_inefficient|gquad|gquad2d|gquad2d6|gquad2dgen|gquad6|gquadnd|grace_octave_path|gradient|grayslice|grep|grid|griddata|groots|grpdelay|grule|grule2d|grule2dgen|hadamard|hammgen|hankel|hann|harmmean|hilbert|histeq|histfit|histo|histo2|histo3|histo4|hot|hsv|hup|idct|idct2|idplot|idsim|ifftshift|im2bw|im2col|imadjust|imginfo|imhist|imnoise|impad|impz|imread|imrotate|imshear|imtranslate|imwrite|innerfun|inputname|interp|interp1|interp2|interpft|intersect|invest0|invest1|invfdemo|invfreq|invfreqs|invfreqz|inz|irsa_act|irsa_actcore|irsa_check|irsa_dft|irsa_dftfp|irsa_genreal|irsa_idft|irsa_isregular|irsa_jitsp|irsa_mdsp|irsa_normalize|irsa_plotdft|irsa_resample|irsa_rgenreal|isa|isbw|isdir|isequal|isfield|isgray|isind|ismember|isprime|isrgb|issparse|isunix|jet|kaiser|kaiserord|lambertw|lattice|lauchli|leasqr|leasqrdemo|legend|legendre|levinson|lin2mu|line_min|lloyds|lookup|lookup_table|lpc|lp_test|mad|magic|makelut|MakeShears|map|mat2gray|mat2str|mdsmax|mean2|medfilt2|meshc|minimize|minpol|mkpp|mktheta|mle_estimate|mle_example|mle_obj|mle_results|mle_variance|modmap|mu2lin|mvaar|mvar|mvfilter|mvfreqz|myfeval|nanmax|nanmean|nanmedian|nanmin|nanstd|nansum|ncauer|nchoosek|ncrule|ndims|nelder_mead_min|newmark|nlfilter|nlnewmark|__nlnewmark_fcn__|nmsmax|nonzeros|normplot|now|nrm|nthroot|nze|OCTAVE_FORGE_VERSION|ode23|ode45|ode78|optimset|ordfilt2|orient|pacf|padarray|parameterize|parcor|pareto|pascal|patch|pburg|pcg|pchip|pcolor|pcr|peaks|penddot|pendulum|perms|pie|pink|plot3|__plt3__|poly2ac|poly2ar|poly_2_ex|poly2mask|poly2rc|poly2sym|poly2th|polyarea|polyconf|polyder|polyderiv|polygcd|polystab|__power|ppval|prctile|prettyprint|prettyprint_c|primes|princomp|print|prism|proplan|pulstran|pwelch|pyulear|qaskdeco|qaskenco|qtdecomp|qtgetblk|qtsetblk|quad2dc|quad2dcgen|quad2dg|quad2dggen|quadc|quadg|quadl|quadndg|quantiz|quiver|rad2deg|rainbow|randerr|randint|randsrc|rat|rats|rc2ac|rc2ar|rc2poly|rceps|read_options|read_pdb|rectpuls|resample|rgb2gray|rk2fixed|rk4fixed|rk8fixed|rmfield|rmle|rmpath|roicolor|rosser|rotparams|rotv|rref|rsdecof|rsencof|rsgenpoly|samin_example|save_vrml|sbispec|scale_data|scatter|scatterplot|select_3D_points|selmo|setdiff|setfield|setfields|setxor|sftrans|sgolay|sgolayfilt|sinvest1|slurp_file|sortrows|sound|soundsc|spdiags|specgram|speed|speye|spfun|sphcat|spline|splot|spones|sprand|sprandn|spring|spstats|spsum|sp_test|sptest|spvcat|spy|std2|stem|str2double|strcmpi|stretchlim|strfind|strmatch|strncmp|strncmpi|strsort|strtok|strtoz|struct|strvcat|summer|sumskipnan|surf|surfc|sym2poly|symerr|symfsolve|tabulate|tar|temp_name|test|test_d2_min_1|test_d2_min_2|test_d2_min_3|test_ellipj|test_fminunc_1|testimio|test_inline_1|test_min_1|test_min_2|test_min_3|test_min_4|test_minimize_1|test_nelder_mead_min_1|test_nelder_mead_min_2|test_sncndn|test_struct|test_vmesh|test_vrml_faces|test_wpolyfit|text|textread|tf2zp|tfe|thfm|tics|toeplitz|toggle_grace_use|transpose|trapz|triang|tril|trimmean|tripuls|trisolve|triu|tsademo|tsearchdemo|ucp|uintlut|unique|unix|unmkpp|unscale_parameters|vec2mat|view|vmesh|voronoi|voronoin|vrml_arrow|vrml_Background|vrml_browse|vrml_cyl|vrml_demo_tutorial_1|vrml_demo_tutorial_2|vrml_demo_tutorial_3|vrml_demo_tutorial_4|vrml_ellipsoid|vrml_faces|vrml_flatten|vrml_frame|vrml_group|vrml_kill|vrml_lines|vrml_material|vrml_parallelogram|vrml_PointLight|vrml_points|vrml_select_points|vrml_surf|vrml_text|vrml_thick_surf|vrml_transfo|wavread|wavwrite|weekday|wgn|white|wilkinson|winter|wpolyfit|wpolyfitdemo|write_pdb|wsolve|xcorr|xcorr2|xcov|xlsread|xmlwrite|y2res|zero_count|zoom|zp2tf|zplane|zscore\",i=\"\\\\s*(?:@|'|\\\\+\\\\+|--|\\\\+=|-=|\\\\*=|\\\\/=|\\\\^=|\\\\.\\\\*=|\\\\.\\\\/=|\\\\.\\\\^=|==|~=|!=|>|>=|<|<=|&|&&|:|\\\\||\\\\|\\\\||\\\\+|-|\\\\*|\\\\.\\\\*|/|\\\\./|\\\\\\\\|\\\\.\\\\\\\\|\\\\^|\\\\.\\\\^|!)\\\\s*\",s=this.createKeywordMapper({\"keyword.control\":t,\"constant.language\":e,\"constant.language.boolean\":\"true|false\"},\"variable\"),o=this.createKeywordMapper({\"support.function\":n+\"|\"+r,\"keyword.control\":t},\"identifier\"),u=\"(?:(?:\\\\\\\\.)|(?:[^'\\\\\\\\]))*?\",a='(?:(?:\\\\\\\\.)|(?:[^\"\\\\\\\\]))*?',f=\"(?:[+-]\\\\s*)?(?:(?:\\\\.\\\\d+)|(?:\\\\d+(?:\\\\.\\\\d*)?))(?:[eE][+-]?\\\\d+)?\",l=f+\"\\\\s*[+-]\\\\s*\"+f+\"i\",c=\"[a-zA-Z]\\\\w*\",h=\"(?:\\\\s*\"+c+\"\\\\s*,?\\\\s*)\";this.$rules={start:[{token:\"comment\",regex:\"^\\\\s*%\\\\{\\\\s*$\",next:\"percent_block_comment\"},{token:\"comment\",regex:\"^\\\\s*#\\\\{\\\\s*$\",next:\"hash_block_comment\"},{token:\"comment\",regex:\"[#%].*$\"},{token:\"string.quoted.single\",regex:\"'\"+u+\"'\"},{token:\"string.quoted.double\",regex:'\"'+a+'\"'},{token:\"string.quoted.single\",regex:\"'\"+u+\"\\\\\\\\$\",next:\"qstring\"},{token:\"string.quoted.double\",regex:'\"'+a+\"\\\\\\\\$\",next:\"qqstring\"},{token:[\"keyword\",\"text\",\"identifier\",\"text\",\"keyword.operator\",\"text\",\"entity.name.function\",\"text\",\"lparen.function\"],regex:\"(function)(\\\\s*)((?:\\\\[\"+h+\"*\\\\])|(?:\"+h+\"*))?(\\\\s*)(=)(\\\\s*)(\"+c+\")(\\\\s*)(\\\\()\",next:\"function_parameters\"},{token:[\"text\",\"entity.name\",\"text\",\"keyword.operator\"],regex:\"((?:^|;)\\\\s*)(\"+c+\")(\\\\s*)(=)\"},{token:\"constant.numeric\",regex:l+\"\\\\b\"},{token:\"constant.numeric\",regex:f+\"\\\\b\"},{token:\"keyword.control\",regex:\"end\\\\w*\\\\b\"},{token:s,regex:\"\\\\w+(?!\\\\s*\\\\()\\\\b\"},{token:o,regex:\"\\\\w+(?=\\\\s*\\\\()\\\\b\"},{token:\"keyword.operator\",regex:i},{token:\"lparen\",regex:\"[[({]\"},{token:\"rparen\",regex:\"[\\\\])}]\"}],percent_block_comment:[{token:\"comment\",regex:\"^\\\\s*%\\\\}\\\\s*$\",next:\"start\"},{defaultToken:\"comment\"}],hash_block_comment:[{token:\"comment\",regex:\"^\\\\s*#\\\\}\\\\s*$\",next:\"start\"},{defaultToken:\"comment\"}],qstring:[{token:\"string.quoted.single\",regex:u+\"'\",next:\"start\"},{token:\"string.quoted.single\",regex:u+\"\\\\\\\\$\"}],qqstring:[{token:\"string.quoted.double\",regex:a+'\"',next:\"start\"},{token:\"string.quoted.double\",regex:a+\"\\\\\\\\$\"}],function_parameters:[{token:[\"text\",\"entity.name\",\"text\",\"keyword.operator\",\"text\"],regex:\"((?:^|,|\\\\b)\\\\s*)(\"+c+\")(\\\\s*)(=)(\\\\s*)\",next:\"function_parameter_variable\"},{token:[\"text\",\"entity.name\"],regex:\"((?:^|,|\\\\b)\\\\s*)(\"+c+\")\"},{token:\"rparen.function\",regex:\"\\\\)\",next:\"after_function_declaration\"},{token:\"invalid.illegal\",regex:\"[^\\\\s,]+\",next:\"start\"}],function_parameter_variable:[{token:\"constant.numeric\",regex:l+\"\\\\b\",next:\"function_parameters\"},{token:\"constant.numeric\",regex:f+\"\\\\b\",next:\"function_parameters\"},{token:\"string.quoted.single\",regex:\"'\"+u+\"'\",next:\"function_parameters\"},{token:\"string.quoted.double\",regex:'\"'+a+'\"',next:\"function_parameters\"},{token:s,regex:\"\\\\w+(?!\\\\s*\\\\()\\\\b\",next:\"function_parameters\"},{token:\"invalid.illegal\",regex:\".*\",next:\"function_parameters\"}],after_function_declaration:[{token:\"comment.doc\",regex:\"^\\\\s*[#%].*$\"},{regex:\"(?=^)\",next:\"start\"}]}};r.inherits(s,i),t.OctaveHighlightRules=s}),\ndefine(\"ace/theme/crimson_editor\",[\"require\",\"exports\",\"module\",\"ace/lib/dom\"],function(e,t,n){t.isDark=!1,t.cssText='.ace-crimson-editor .ace_gutter {background: #ebebeb;color: #333;overflow : hidden;}.ace-crimson-editor .ace_gutter-layer {width: 100%;text-align: right;}.ace-crimson-editor .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-crimson-editor {background-color: #FFFFFF;color: rgb(64, 64, 64);}.ace-crimson-editor .ace_cursor {color: black;}.ace-crimson-editor .ace_invisible {color: rgb(191, 191, 191);}.ace-crimson-editor .ace_identifier {color: black;}.ace-crimson-editor .ace_keyword {color: blue;}.ace-crimson-editor .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-crimson-editor .ace_constant.ace_language {color: rgb(255, 156, 0);}.ace-crimson-editor .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-crimson-editor .ace_invalid {text-decoration: line-through;color: rgb(224, 0, 0);}.ace-crimson-editor .ace_fold {}.ace-crimson-editor .ace_support.ace_function {color: rgb(192, 0, 0);}.ace-crimson-editor .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-crimson-editor .ace_support.ace_type,.ace-crimson-editor .ace_support.ace_class {color: rgb(109, 121, 222);}.ace-crimson-editor .ace_keyword.ace_operator {color: rgb(49, 132, 149);}.ace-crimson-editor .ace_string {color: rgb(128, 0, 128);}.ace-crimson-editor .ace_comment {color: rgb(76, 136, 107);}.ace-crimson-editor .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-crimson-editor .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-crimson-editor .ace_constant.ace_numeric {color: rgb(0, 0, 64);}.ace-crimson-editor .ace_variable {color: rgb(0, 64, 128);}.ace-crimson-editor .ace_xml-pe {color: rgb(104, 104, 91);}.ace-crimson-editor .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-crimson-editor .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-crimson-editor .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-crimson-editor .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-crimson-editor .ace_marker-layer .ace_active-line {background: rgb(232, 242, 254);}.ace-crimson-editor .ace_gutter-active-line {background-color : #dcdcdc;}.ace-crimson-editor .ace_meta.ace_tag {color:rgb(28, 2, 255);}.ace-crimson-editor .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-crimson-editor .ace_string.ace_regex {color: rgb(192, 0, 192);}.ace-crimson-editor .ace_indent-guide {background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==\") right repeat-y;}',t.cssClass=\"ace-crimson-editor\";var r=e(\"../lib/dom\");r.importCssString(t.cssText,t.cssClass)}),\ndefine(\"ace/theme/merbivore_soft\",[\"require\",\"exports\",\"module\",\"ace/lib/dom\"],function(e,t,n){t.isDark=!0,t.cssClass=\"ace-merbivore-soft\",t.cssText=\".ace-merbivore-soft .ace_gutter {background: #262424;color: #E6E1DC}.ace-merbivore-soft .ace_print-margin {width: 1px;background: #262424}.ace-merbivore-soft {background-color: #1C1C1C;color: #E6E1DC !important}.ace-merbivore-soft .ace_cursor {color: #FFFFFF}.ace-merbivore-soft .ace_marker-layer .ace_selection {background: #494949}.ace-merbivore-soft.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #1C1C1C;border-radius: 2px}.ace-merbivore-soft .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-merbivore-soft .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #404040}.ace-merbivore-soft .ace_marker-layer .ace_active-line {background: #333435}.ace-merbivore-soft .ace_gutter-active-line {background-color: #333435}.ace-merbivore-soft .ace_marker-layer .ace_selected-word {border: 1px solid #494949}.ace-merbivore-soft .ace_invisible {color: #404040}.ace-merbivore-soft .ace_entity.ace_name.ace_tag,.ace-merbivore-soft .ace_keyword,.ace-merbivore-soft .ace_meta,.ace-merbivore-soft .ace_meta.ace_tag,.ace-merbivore-soft .ace_storage {color: #FC803A}.ace-merbivore-soft .ace_constant,.ace-merbivore-soft .ace_constant.ace_character,.ace-merbivore-soft .ace_constant.ace_character.ace_escape,.ace-merbivore-soft .ace_constant.ace_other,.ace-merbivore-soft .ace_support.ace_type {color: #68C1D8}.ace-merbivore-soft .ace_constant.ace_character.ace_escape {color: #B3E5B4}.ace-merbivore-soft .ace_constant.ace_language {color: #E1C582}.ace-merbivore-soft .ace_constant.ace_library,.ace-merbivore-soft .ace_string,.ace-merbivore-soft .ace_support.ace_constant {color: #8EC65F}.ace-merbivore-soft .ace_constant.ace_numeric {color: #7FC578}.ace-merbivore-soft .ace_invalid,.ace-merbivore-soft .ace_invalid.ace_deprecated {color: #FFFFFF;background-color: #FE3838}.ace-merbivore-soft .ace_fold {background-color: #FC803A;border-color: #E6E1DC}.ace-merbivore-soft .ace_comment,.ace-merbivore-soft .ace_meta {font-style: italic;color: #AC4BB8}.ace-merbivore-soft .ace_entity.ace_other.ace_attribute-name {color: #EAF1A3}.ace-merbivore-soft .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWOQkpLyZfD09PwPAAfYAnaStpHRAAAAAElFTkSuQmCC) right repeat-y}\";var r=e(\"../lib/dom\");r.importCssString(t.cssText,t.cssClass)});\ndefine(\"ace/ext/static_highlight\",[\"require\",\"exports\",\"module\",\"ace/edit_session\",\"ace/layer/text\",\"ace/config\",\"ace/lib/dom\"],function(e,t,n){\"use strict\";var r=e(\"../edit_session\").EditSession,i=e(\"../layer/text\").Text,s=\".ace_static_highlight {font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'Droid Sans Mono', monospace;font-size: 12px;}.ace_static_highlight .ace_gutter {width: 25px !important;float: left;text-align: right;padding: 0 3px 0 0;margin-right: 3px;position: static !important;}.ace_static_highlight .ace_line { clear: both; }.ace_static_highlight .ace_gutter-cell {-moz-user-select: -moz-none;-khtml-user-select: none;-webkit-user-select: none;user-select: none;}.ace_static_highlight .ace_gutter-cell:before {content: counter(ace_line, decimal);counter-increment: ace_line;}.ace_static_highlight {counter-reset: ace_line;}\",o=e(\"../config\"),u=e(\"../lib/dom\"),a=function(e,t,n){var r=e.className.match(/lang-(\\w+)/),i=t.mode||r&&\"ace/mode/\"+r[1];if(!i)return!1;var s=t.theme||\"ace/theme/textmate\",o=\"\",f=[];if(e.firstElementChild){var l=0;for(var c=0;c<e.childNodes.length;c++){var h=e.childNodes[c];h.nodeType==3?(l+=h.data.length,o+=h.data):f.push(l,h)}}else o=u.getInnerText(e),t.trim&&(o=o.trim());a.render(o,i,s,t.firstLineNumber,!t.showGutter,function(t){u.importCssString(t.css,\"ace_highlight\"),e.innerHTML=t.html;var r=e.firstChild.firstChild;for(var i=0;i<f.length;i+=2){var s=t.session.doc.indexToPosition(f[i]),o=f[i+1],a=r.children[s.row];a&&a.appendChild(o)}n&&n()})};a.render=function(e,t,n,i,s,u){function h(){var r=a.renderSync(e,t,n,i,s);return u?u(r):r}var f=1,l=r.prototype.$modes;typeof n==\"string\"&&(f++,o.loadModule([\"theme\",n],function(e){n=e,--f||h()}));var c;return t&&typeof t==\"object\"&&!t.getTokenizer&&(c=t,t=c.path),typeof t==\"string\"&&(f++,o.loadModule([\"mode\",t],function(e){if(!l[t]||c)l[t]=new e.Mode(c);t=l[t],--f||h()})),--f||h()},a.renderSync=function(e,t,n,o,u){o=parseInt(o||1,10);var a=new r(\"\");a.setUseWorker(!1),a.setMode(t);var f=new i(document.createElement(\"div\"));f.setSession(a),f.config={characterWidth:10,lineHeight:20},a.setValue(e);var l=[],c=a.getLength();for(var h=0;h<c;h++)l.push(\"<div class='ace_line'>\"),u||l.push(\"<span class='ace_gutter ace_gutter-cell' unselectable='on'></span>\"),f.$renderLine(l,h,!0,!1),l.push(\"\\n</div>\");var p=\"<div class='\"+n.cssClass+\"'>\"+\"<div class='ace_static_highlight' style='counter-reset:ace_line \"+(o-1)+\"'>\"+l.join(\"\")+\"</div>\"+\"</div>\";return f.destroy(),{css:s+n.cssText,html:p,session:a}},n.exports=a,n.exports.highlight=a});(function() {window.require([\"ace/ext/static_highlight\"], function() {});})();\n"
  },
  {
    "path": "client/app/js/anal.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine(function(){\n\n\twindow.dataLayer = window.dataLayer || [];\n\tvar dataLayer;\n\tfunction gtag(){ dataLayer.push(arguments); }\n\n\t// Set up Analytics\n\tif (window.oo_configGtag) {\n\t\tvar cache = [];\n\t\tdataLayer = cache;\n\t\twindow.oo_configGtag(function(config) {\n\t\t\tdataLayer = window.dataLayer;\n\t\t\tgtag(\"js\", new Date());\n\t\t\tgtag(\"config\", \"{!gtagid!}\", config);\n\t\t\tArray.prototype.push.apply(dataLayer, cache);\n\t\t});\n\t} else {\n\t\tdataLayer = window.dataLayer;\n\t\tgtag(\"js\", new Date());\n\t\tgtag(\"config\", \"{!gtagid!}\");\n\t}\n\n\tfunction sendEvent(event_category, event_action, event_label, value) {\n\t\t// analytics.js:\n\t\t// garef(\"send\", \"event\", event_category, event_action, event_label, value);\n\t\t// gtag.js:\n\t\tgtag(\"event\", event_action, {\n\t\t\tevent_category: event_category,\n\t\t\tevent_label: event_label,\n\t\t\tvalue: value,\n\t\t});\n\t}\n\n\t// Wait to load Google Analytics to not slow down the module\n\trequire([\"js/runtime\"], function(){ require([\"gtag\"], function(){\n\t\t// Record browser window size\n\t\tvar width = window.innerWidth || document.body.clientWidth;\n\t\tvar height = window.innerHeight || document.body.clientHeight;\n\t\twidth = Math.round(width/50)*50;\n\t\theight = Math.round(height/50)*50;\n\t\tsendEvent(\"browser-size\", \"width\", width);\n\t\tsendEvent(\"browser-size\", \"height\", height);\n\t\tsendEvent(\"browser-size\", \"combined\", width+\"x\"+height);\n\t}); });\n\n\tvar numExtraTime = 0;\n\n\t// Return methods to register certain events\n\treturn {\n\t\tpageview: function(){\n\t\t\t// Obsolete in gtag.js\n\t\t\t// _ga(\"send\", \"pageview\");\n\t\t},\n\t\tsignedin: function(){\n\t\t\tsendEvent(\"accounts\", \"signed-in\");\n\t\t},\n\t\twelcomeback: function() {\n\t\t\tsendEvent(\"accounts\", \"welcome-back\");\n\t\t},\n\t\tsitecontrol: function(which){\n\t\t\tsendEvent(\"site-control\", which);\n\t\t},\n\t\tcommand: function(cmd){\n\t\t\tsendEvent(\"command\", \"user-cmd\", cmd.substr(0,5), cmd.length);\n\t\t},\n\t\trunfile: function(){\n\t\t\tsendEvent(\"command\", \"run-file\");\n\t\t},\n\t\tsigint: function(){\n\t\t\tsendEvent(\"signal\", \"user-interrupt\");\n\t\t},\n\t\tpatience: function(){\n\t\t\tsendEvent(\"loading\", \"patience-message\");\n\t\t},\n\t\tdismiss: function(what){\n\t\t\tsendEvent(\"dismiss\", \"promo\", what);\n\t\t},\n\t\tduration: function(duration){\n\t\t\tsendEvent(\"command\", \"duration\", \"millis\", duration);\n\t\t},\n\t\textraTime: function(){\n\t\t\tsendEvent(\"extra-time\", \"from-prompt\", numExtraTime++);\n\t\t},\n\t\tacknowledgePayload: function(){\n\t\t\tsendEvent(\"extra-time\", \"acknowledge-payload\");\n\t\t},\n\t\talert: function(message){\n\t\t\tsendEvent(\"alert\", \"alert\", message.substr(0,20), message.length);\n\t\t}\n\t};\n});\n"
  },
  {
    "path": "client/app/js/app.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine(\n\t[\"knockout\", \"socket.io\", \"js/client\", \"ace/ace\", \"jquery\", \"ismobile\", \"splittr\", \"SocketIOFileUpload\", \"js/anal\", \"js/onboarding\", \"js/ot-handler\", \"js/ws-shared\", \"js/utils\", \"ko-flash\", \"ace/mode/octave\", \"ace/ext/language_tools\", \"js/ko-ace\", \"js/flex-resize\"],\n\tfunction (ko, io, OctMethods, ace, $, isMobile, splittr, SocketIOFileUpload, anal, onboarding, OtHandler, WsShared) {\n\n\t\t// Initial GUI setup\n\t\tsplittr.init();\n\n\t\t// Make conveinence variable references\n\t\tvar viewModel = OctMethods.ko.viewModel;\n\t\tvar allOctFiles = OctMethods.ko.allOctFiles;\n\t\tvar vars = viewModel.vars;\n\n\t\t// Run Knockout\n\t\tko.applyBindings(viewModel);\n\n\t\t// Make Socket Connection and Add Listeners:\n\t\t// The first two lines are a hack to make injected configs work\n\t\tvar ioPath = \"{!socket_io_path!}\";\n\t\tvar socket = (ioPath[0] === \"{\") ? io() : io({ path: ioPath });\n\t\tOctMethods.socketListeners.subscribe(socket);\n\t\tOtHandler.listeners.subscribe(socket);\n\t\tWsShared.listeners.subscribe(socket);\n\t\tOctMethods.socket.instance = socket;\n\n\t\t// Autocompletion with filenames:\n\t\tace.require(\"ace/ext/language_tools\").addCompleter({\n\t\t\tgetCompletions: function (editor, session, pos, prefix, callback) {\n\t\t\t\tcallback(null, $.map(allOctFiles(), function (file) {\n\t\t\t\t\tvar filenameNoExtension = file.filename().replace(/\\.[^/.]+$/, \"\");\n\t\t\t\t\treturn {\n\t\t\t\t\t\tvalue: filenameNoExtension,\n\t\t\t\t\t\tmeta: \"file\"\n\t\t\t\t\t};\n\t\t\t\t}));\n\t\t\t}\n\t\t});\n\n\t\t// Autocompletion with variables:\n\t\tace.require(\"ace/ext/language_tools\").addCompleter({\n\t\t\tgetCompletions: function (editor, session, pos, prefix, callback) {\n\t\t\t\tcallback(null, ko.utils.arrayMap(vars(), function(v){\n\t\t\t\t\treturn {\n\t\t\t\t\t\tvalue: v.symbol(),\n\t\t\t\t\t\tmeta: \"var\"\n\t\t\t\t\t};\n\t\t\t\t}));\n\t\t\t}\n\t\t});\n\n\t\t// Make Prompt:\n\t\tvar prompt = ace.edit(\"prompt\");\n\t\tprompt.setTheme(viewModel.selectedSkin().aceTheme);\n\t\tprompt.getSession().setMode(\"ace/mode/octave\");\n\t\tprompt.renderer.setShowGutter(false);\n\t\tprompt.setHighlightActiveLine(false);\n\t\tprompt.setShowPrintMargin(false);\n\t\tprompt.setOptions({\n\t\t\tenableBasicAutocompletion: true,\n\t\t\tmaxLines: 6\n\t\t});\n\t\tprompt.setBehavioursEnabled(false);  // disables quto-quote\n\t\tprompt.renderer.setScrollMargin(5, 5);\n\t\tprompt.getSession().setUseWrapMode(true);\n\t\tprompt.commands.addCommand({\n\t\t\tname: \"nullifyLineNumber\",\n\t\t\tbindKey: {mac: \"Command-L\", win: \"Ctrl-L\"},\n\t\t\texec: function () {\n\t\t\t},\n\t\t\treadOnly: true\n\t\t});\n\t\tprompt.commands.addCommand({\n\t\t\tname: \"previousCommand\",\n\t\t\tbindKey: \"Up\",\n\t\t\texec: OctMethods.promptListeners.historyUp,\n\t\t\treadOnly: false\n\t\t});\n\t\tprompt.commands.addCommand({\n\t\t\tname: \"nextCommand\",\n\t\t\tbindKey: \"Down\",\n\t\t\texec: OctMethods.promptListeners.historyDown,\n\t\t\treadOnly: false\n\t\t});\n\t\tprompt.commands.addCommand({\n\t\t\tname: \"startAutocompleteOnTab\",\n\t\t\tbindKey: \"Tab\",\n\t\t\texec: ace.require(\"ace/autocomplete\").Autocomplete.startCommand.exec,\n\t\t\treadOnly: false\n\t\t});\n\t\tprompt.commands.addCommand({\n\t\t\tname: \"submitPrompt\",\n\t\t\tbindKey: {win: \"Enter\", mac: \"Enter\"},\n\t\t\texec: OctMethods.promptListeners.command,\n\t\t\treadOnly: false\n\t\t});\n\t\tOctMethods.prompt.instance = prompt;\n\n\t\t// Initialize the console screen\n\t\tOctMethods.console.clear();\n\n\t\t// Add Prompt/Console/Plot Listeners:\n\t\t$(\"#signal\").click(OctMethods.promptListeners.signal);\n\t\t$(\"#console\").on(\"click\", \".prompt_command\", OctMethods.promptListeners.permalink);\n\n\t\t// Add listeners to the file list toolbar\n\t\t$(\"#files_toolbar_create\").click(OctMethods.editorListeners.newCB);\n\t\t$(\"#files_toolbar_refresh\").click(OctMethods.editorListeners.refresh);\n\t\t$(\"#files_toolbar_info\").click(OctMethods.editorListeners.info);\n\n\t\t// Set up the file uploader:\n\t\ttry {\n\t\t\tvar siofu = new SocketIOFileUpload(socket);\n\t\t\tsiofu.useBuffer = false;\n\t\t\tvar _addDragClass = function () {\n\t\t\t\t$(this).addClass(\"drag-over\");\n\t\t\t};\n\t\t\tvar _removeDragClass = function () {\n\t\t\t\t$(this).removeClass(\"drag-over\");\n\t\t\t};\n\t\t\t$(\"#files_list_container\").on(\"dragover\", _addDragClass);\n\t\t\t$(\"#files_list_container\").on(\"dragenter\", _addDragClass);\n\t\t\t$(\"#files_list_container\").on(\"dragleave\", _removeDragClass);\n\t\t\t$(\"#files_list_container\").on(\"drop\", _removeDragClass);\n\t\t\tsiofu.listenOnDrop($(\"#files_list_container\")[0]);\n\t\t\t$(\"#files_toolbar_upload\").click(siofu.prompt);\n\t\t} catch (e) {\n\t\t\t// SIOFU not supported in current browser\n\t\t\tconsole.error(e);\n\t\t}\n\n\t\tvar currentUrl = new URL(window.location.href);\n\n\t\t// Shared workspace setup\n\t\tvar wsId = currentUrl.searchParams.get(\"w\");\n\t\tif (wsId) {\n\t\t\tOctMethods.vars.wsId = wsId;\n\t\t\tviewModel.purpose(\"shared\");\n\t\t\tviewModel.selectedSkin(OctMethods.ko.availableSkins[2]);\n\t\t}\n\n\t\t// Student workspace setup\n\t\tvar studentId = currentUrl.searchParams.get(\"s\");\n\t\tvar match;\n\t\tif (!studentId) {\n\t\t\tmatch = currentUrl.pathname.match(/^\\/workspace~(\\w+)$/);\n\t\t\tif (match) studentId = match[1];\n\t\t}\n\t\tif (studentId) {\n\t\t\tOctMethods.vars.studentId = studentId;\n\t\t\tviewModel.purpose(\"student\");\n\t\t\tviewModel.selectedSkin(OctMethods.ko.availableSkins[2]);\n\t\t}\n\n\t\t// Bucket setup\n\t\tvar bucketId = currentUrl.searchParams.get(\"b\");\n\t\tif (!bucketId) {\n\t\t\tmatch = currentUrl.pathname.match(/^\\/bucket~(\\w+)$/);\n\t\t\tif (match) bucketId = match[1];\n\t\t}\n\t\tif (bucketId) {\n\t\t\tOctMethods.vars.bucketId = bucketId;\n\t\t\tviewModel.purpose(\"bucket\");\n\t\t\tviewModel.selectedSkin(OctMethods.ko.availableSkins[3]);\n\t\t\tonboarding.showBucketPromo();\n\t\t} else {\n\t\t\tmatch = currentUrl.pathname.match(/^\\/project~(\\w+)$/);\n\t\t\tif (match) {\n\t\t\t\tOctMethods.vars.bucketId = match[1];\n\t\t\t\tviewModel.purpose(\"project\");\n\t\t\t}\n\t\t}\n\n\t\t// Global key bindings:\n\t\t$(window).keydown(function (e) {\n\t\t\tif (e.keyCode == 82) { // \"R\" key\n\t\t\t\tif (e.metaKey || e.ctrlKey) {\n\t\t\t\t\tOctMethods.editorListeners.keyRun(e);\n\t\t\t\t}\n\t\t\t} else if (e.keyCode == 69) { // \"E\" key\n\t\t\t\tif (e.metaKey || e.ctrlKey) {\n\t\t\t\t\tOctMethods.promptListeners.keyFocus(e);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t/* * * * END EDITOR/CONSOLE/PROMPT, START GUI * * * */\n\n\t\t// Privacy Policy\n\t\t$(\"#privacy\").find(\"[data-purpose='close']\").click(function () {\n\t\t\tanal.sitecontrol(\"privacy-close\");\n\t\t});\n\t\t$(\"#showprivacy\").click(function () {\n\t\t\t$(\"#privacy\").showSafe();\n\t\t\tanal.sitecontrol(\"privacy\");\n\t\t});\n\n\t\t// Mobile GUI\n\t\tif (isMobile) {\n\t\t\twindow.matchMedia(\"(orientation:portrait)\").addListener(function () {\n\t\t\t\tOctMethods.console.scroll();\n\t\t\t});\n\t\t}\n\n\t\t// Sign-In and Password\n\t\t$(\"#hamburger, #sign_in_shortcut\").click(function () {\n\t\t\tvar opened = $(\"#main_menu\").toggleSafe();\n\t\t\t$(\"#hamburger\").toggleClass(\"is-active\", opened);\n\t\t\tonboarding.hideScriptPromo();\n\t\t\tonboarding.hideBucketPromo();\n\t\t\tanal.sitecontrol(\"hamburger\");\n\t\t});\n\t\t$(\"#sign_in_with_google\").click(function () {\n\t\t\twindow.location.href = \"/auth/google\";\n\t\t});\n\t\t$(\"#sign_in_with_password\").click(function () {\n\t\t\t$(\"#email_password\").showSafe();\n\t\t\t$(\"#emailField2\").focus();\n\t\t});\n\t\t$(\"#sign_in_with_email\").click(function () {\n\t\t\t$(\"#email_token\").showSafe();\n\t\t\t$(\"#emailField1\").focus();\n\t\t\t$(\"#passwordField1\").hideSafe(); // !! HONEYPOT !!\n\t\t});\n\t\t// Callback for #create-password-btn is in Knockout setup\n\t\t$(\"#save-password-btn\").click(function() {\n\t\t\tvar password = $(\"#new_pwd\").val();\n\t\t\t$(\"#new_pwd\").val(\"\");\n\t\t\t$(\"#change_password\").hideSafe();\n\n\t\t\tOctMethods.socket.setPassword(password);\n\t\t});\n\n\t\t// Theme bindings\n\t\tfunction updateTheme(newValue) {\n\t\t\t$(\"#theme\").attr(\"href\", newValue.cssURL);\n\t\t\t$(\"body\").attr(\"data-sanscons-color\", newValue.iconColor);\n\t\t\t$(\"[data-theme]\").hideSafe();\n\t\t\t$(\"[data-theme='\"+newValue.name+\"']\").showSafe();\n\t\t\tOctMethods.prompt.instance.setTheme(newValue.aceTheme);\n\t\t}\n\t\tupdateTheme(viewModel.selectedSkin());\n\t\tviewModel.selectedSkin.subscribe(updateTheme);\n\t\tfunction toggleTheme(dark) {\n\t\t\tvar newSkin;\n\t\t\tif (dark) {\n\t\t\t\tnewSkin = OctMethods.ko.availableSkins[1];\n\t\t\t} else switch(viewModel.purpose()) {\n\t\t\t\tcase \"student\":\n\t\t\t\t\tnewSkin = OctMethods.ko.availableSkins[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"bucket\":\n\t\t\t\t\tnewSkin = OctMethods.ko.availableSkins[3];\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"project\":\n\t\t\t\t\tif (viewModel.currentBucket() && viewModel.currentBucket().butype() === \"collab\") {\n\t\t\t\t\t\tnewSkin = OctMethods.ko.availableSkins[2];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnewSkin = OctMethods.ko.availableSkins[3];\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"default\":\n\t\t\t\tdefault:\n\t\t\t\t\tnewSkin = OctMethods.ko.availableSkins[0];\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tviewModel.selectedSkin(newSkin);\n\t\t\tviewModel.prefersDarkMode(dark);\n\t\t\tOctMethods.prompt.focus();\n\t\t\tanal.sitecontrol(\"theme\");\n\t\t}\n\t\t$(\"#change-skin\").click(function () {\n\t\t\ttoggleTheme(!viewModel.prefersDarkMode());\n\t\t});\n\t\twindow.matchMedia(\"(prefers-color-scheme: dark)\").addListener(function(updated) {\n\t\t\ttoggleTheme(updated.matches);\n\t\t});\n\t\tviewModel.currentBucket.subscribe(function() {\n\t\t\ttoggleTheme(viewModel.prefersDarkMode());\n\t\t});\n\n\t\t// Callouts positioned relative to non-top-level elements\n\t\twindow.addEventListener(\"resize\", function() {\n\t\t\tonboarding.reposition();\n\t\t}, false);\n\n\t\t// Other GUI Initialization\n\t\tOctMethods.prompt.disable();\n\t\t$(\"#twitter-follow-holder\").click(function () {\n\t\t\tanal.sitecontrol(\"twitter\");\n\t\t});\n\t\t$(\"#feedback-btn\").click(function () {\n\t\t\tanal.sitecontrol(\"feedback\");\n\t\t});\n\t\t$(\"#reset-layout\").click(function () {\n\t\t\tviewModel.flex.sizes([100, 400, 75, 325]);\n\t\t\tviewModel.flex.shown(true);\n\t\t});\n\t\t$(\"[data-purpose='close']\").click(function () {\n\t\t\t// Clicking on a close button in a popover box\n\t\t\t$(this).closest(\"[data-purpose='popover']\").fadeOutSafe(250);\n\t\t\tOctMethods.prompt.focus();\n\t\t});\n\t\tOctMethods.load.startPatience();\n\n\t\t/* * * * END GUI * * * */\n\n\t}\n); // AMD Define\n"
  },
  {
    "path": "client/app/js/base64-toBlob.js",
    "content": "/* eslint-disable */\n/*\n * Copyright © 2013 Jeremy (@jeremy.ca)\n *\n * This work is licensed under the Creative Commons Attribution-ShareAlike 3.0\n * Unported License. To view a copy of this license, visit\n * http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative\n * Commons, PO Box 1866, Mountain View, CA 94042, USA.\n *\n * This code is from Stack Overflow, retrieved from\n * <http://stackoverflow.com/a/16245768/1407170>.\n *\n * More details: <https://stackoverflow.com/help/licensing>.\n */\n\ndefine(\"base64-toblob\", [\"base64\", \"blob\"], function(Base64){\n    return function(b64Data, contentType, sliceSize) {\n        contentType = contentType || '';\n        sliceSize = sliceSize || 512;\n\n        var byteCharacters = atob(b64Data, true);\n        var byteArrays = [];\n\n        for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {\n            var slice = byteCharacters.slice(offset, offset + sliceSize);\n\n            var byteNumbers = new Array(slice.length);\n            for (var i = 0; i < slice.length; i++) {\n                byteNumbers[i] = slice.charCodeAt(i);\n            }\n\n            var byteArray = new Uint8Array(byteNumbers);\n\n            byteArrays.push(byteArray);\n        }\n\n        return new Blob(byteArrays, {type: contentType});\n    };\n});\n"
  },
  {
    "path": "client/app/js/base64v1.module.js",
    "content": "﻿/* eslint-disable */\r\n/*\r\nCopyright Vassilis Petroulias [DRDigit]\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n\t   http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n\r\nWeb Site - http://jsbase64.codeplex.com/\r\n*/\r\n\r\ndefine(\"base64\", function(){\r\n\tvar B64 = {\r\n\t\talphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',\r\n\t\tlookup: null,\r\n\t\tie: /MSIE /.test(navigator.userAgent),\r\n\t\tieo: /MSIE [67]/.test(navigator.userAgent),\r\n\t\tencode: function (s) {\r\n\t\t\tvar buffer = B64.toUtf8(s),\r\n\t\t\tposition = -1,\r\n\t\t\tlen = buffer.length,\r\n\t\t\tnan0, nan1, nan2, enc = [, , , ];\r\n\t\t\tif (B64.ie) {\r\n\t\t\t\tvar result = [];\r\n\t\t\t\twhile (++position < len) {\r\n\t\t\t\t\tnan0 = buffer[position];\r\n\t\t\t\t\tnan1 = buffer[++position];\r\n\t\t\t\t\tenc[0] = nan0 >> 2;\r\n\t\t\t\t\tenc[1] = ((nan0 & 3) << 4) | (nan1 >> 4);\r\n\t\t\t\t\tif (isNaN(nan1))\r\n\t\t\t\t\t\tenc[2] = enc[3] = 64;\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tnan2 = buffer[++position];\r\n\t\t\t\t\t\tenc[2] = ((nan1 & 15) << 2) | (nan2 >> 6);\r\n\t\t\t\t\t\tenc[3] = (isNaN(nan2)) ? 64 : nan2 & 63;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tresult.push(B64.alphabet.charAt(enc[0]), B64.alphabet.charAt(enc[1]), B64.alphabet.charAt(enc[2]), B64.alphabet.charAt(enc[3]));\r\n\t\t\t\t}\r\n\t\t\t\treturn result.join('');\r\n\t\t\t} else {\r\n\t\t\t\tvar result = '';\r\n\t\t\t\twhile (++position < len) {\r\n\t\t\t\t\tnan0 = buffer[position];\r\n\t\t\t\t\tnan1 = buffer[++position];\r\n\t\t\t\t\tenc[0] = nan0 >> 2;\r\n\t\t\t\t\tenc[1] = ((nan0 & 3) << 4) | (nan1 >> 4);\r\n\t\t\t\t\tif (isNaN(nan1))\r\n\t\t\t\t\t\tenc[2] = enc[3] = 64;\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tnan2 = buffer[++position];\r\n\t\t\t\t\t\tenc[2] = ((nan1 & 15) << 2) | (nan2 >> 6);\r\n\t\t\t\t\t\tenc[3] = (isNaN(nan2)) ? 64 : nan2 & 63;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tresult += B64.alphabet[enc[0]] + B64.alphabet[enc[1]] + B64.alphabet[enc[2]] + B64.alphabet[enc[3]];\r\n\t\t\t\t}\r\n\t\t\t\treturn result;\r\n\t\t\t}\r\n\t\t},\r\n\t\tdecode: function (s) {\r\n\t\t\tif (s.length % 4)\r\n\t\t\t\tthrow new Error(\"InvalidCharacterError: 'B64.decode' failed: The string to be decoded is not correctly encoded.\");\r\n\t\t\tvar buffer = B64.fromUtf8(s),\r\n\t\t\tposition = 0,\r\n\t\t\tlen = buffer.length;\r\n\t\t\tif (B64.ieo) {\r\n\t\t\t\tvar result = [];\r\n\t\t\t\twhile (position < len) {\r\n\t\t\t\t\tif (buffer[position] < 128) \r\n\t\t\t\t\t\tresult.push(String.fromCharCode(buffer[position++]));\r\n\t\t\t\t\telse if (buffer[position] > 191 && buffer[position] < 224) \r\n\t\t\t\t\t\tresult.push(String.fromCharCode(((buffer[position++] & 31) << 6) | (buffer[position++] & 63)));\r\n\t\t\t\t\telse \r\n\t\t\t\t\t\tresult.push(String.fromCharCode(((buffer[position++] & 15) << 12) | ((buffer[position++] & 63) << 6) | (buffer[position++] & 63)));\r\n\t\t\t\t}\r\n\t\t\t\treturn result.join('');\r\n\t\t\t} else {\r\n\t\t\t\tvar result = '';\r\n\t\t\t\twhile (position < len) {\r\n\t\t\t\t\tif (buffer[position] < 128) \r\n\t\t\t\t\t\tresult += String.fromCharCode(buffer[position++]);\r\n\t\t\t\t\telse if (buffer[position] > 191 && buffer[position] < 224) \r\n\t\t\t\t\t\tresult += String.fromCharCode(((buffer[position++] & 31) << 6) | (buffer[position++] & 63));\r\n\t\t\t\t\telse \r\n\t\t\t\t\t\tresult += String.fromCharCode(((buffer[position++] & 15) << 12) | ((buffer[position++] & 63) << 6) | (buffer[position++] & 63));\r\n\t\t\t\t}\r\n\t\t\t\treturn result;\r\n\t\t\t}\r\n\t\t},\r\n\t\ttoUtf8: function (s) {\r\n\t\t\tvar position = -1,\r\n\t\t\tlen = s.length,\r\n\t\t\tchr, buffer = [];\r\n\t\t\tif (/^[\\x00-\\x7f]*$/.test(s)) while (++position < len)\r\n\t\t\t\tbuffer.push(s.charCodeAt(position));\r\n\t\t\telse while (++position < len) {\r\n\t\t\t\tchr = s.charCodeAt(position);\r\n\t\t\t\tif (chr < 128) \r\n\t\t\t\t\tbuffer.push(chr);\r\n\t\t\t\telse if (chr < 2048) \r\n\t\t\t\t\tbuffer.push((chr >> 6) | 192, (chr & 63) | 128);\r\n\t\t\t\telse \r\n\t\t\t\t\tbuffer.push((chr >> 12) | 224, ((chr >> 6) & 63) | 128, (chr & 63) | 128);\r\n\t\t\t}\r\n\t\t\treturn buffer;\r\n\t\t},\r\n\t\tfromUtf8: function (s) {\r\n\t\t\tvar position = -1,\r\n\t\t\tlen, buffer = [],\r\n\t\t\tenc = [, , , ];\r\n\t\t\tif (!B64.lookup) {\r\n\t\t\t\tlen = B64.alphabet.length;\r\n\t\t\t\tB64.lookup = {};\r\n\t\t\t\twhile (++position < len)\r\n\t\t\t\t\tB64.lookup[B64.alphabet.charAt(position)] = position;\r\n\t\t\t\tposition = -1;\r\n\t\t\t}\r\n\t\t\tlen = s.length;\r\n\t\t\twhile (++position < len) {\r\n\t\t\t\tenc[0] = B64.lookup[s.charAt(position)];\r\n\t\t\t\tenc[1] = B64.lookup[s.charAt(++position)];\r\n\t\t\t\tbuffer.push((enc[0] << 2) | (enc[1] >> 4));\r\n\t\t\t\tenc[2] = B64.lookup[s.charAt(++position)];\r\n\t\t\t\tif (enc[2] == 64) \r\n\t\t\t\t\tbreak;\r\n\t\t\t\tbuffer.push(((enc[1] & 15) << 4) | (enc[2] >> 2));\r\n\t\t\t\tenc[3] = B64.lookup[s.charAt(++position)];\r\n\t\t\t\tif (enc[3] == 64) \r\n\t\t\t\t\tbreak;\r\n\t\t\t\tbuffer.push(((enc[2] & 3) << 6) | enc[3]);\r\n\t\t\t}\r\n\t\t\treturn buffer;\r\n\t\t}\r\n\t};\r\n\r\n\t// Also expose as window.btoa and window.atob\r\n\tif (!window.btoa) window.btoa = B64.encode;\r\n\tif (!window.atob) window.atob = B64.decode;\r\n\r\n\treturn B64;\r\n});"
  },
  {
    "path": "client/app/js/bucket.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine([\"knockout\", \"require\", \"js/octfile\", \"js/utils\"], function(ko, require, OctFile, utils){\n\n\tvar OctMethods = require(\"js/client\");\n\n\t// Bucket MVVM class\n\tfunction Bucket(){\n\t\t// the \"self\" variable enables us to refer to the Bucket context even when\n\t\t// we are programming within callback function contexts\n\t\tvar self = this;\n\n\t\t// Main Bindings\n\t\tself.files = ko.observableArray();\n\t\tself.main = ko.observable(null);\n\t\tself.id = ko.observable(null);\n\t\tself.createdTime = ko.observable(new Date());\n\t\tself.mainFilename = ko.pureComputed({\n\t\t\tread: function() {\n\t\t\t\treturn self.main() ? self.main().filename() : null;\n\t\t\t},\n\t\t\twrite: function(filename) {\n\t\t\t\tif (filename) {\n\t\t\t\t\tself.main(new OctFile(filename, \"\", false));\n\t\t\t\t} else {\n\t\t\t\t\tself.main(null);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tself.butype = ko.observable(\"readonly\");\n\t\tself.base_bucket_id = ko.observable(null);\n\t\tself.baseModel = ko.observable(null);\n\t\tself.shortlink = ko.observable(null);\n\t\tself.url = ko.computed(function() {\n\t\t\tvar prefix = (self.butype() === \"readonly\") ? \"bucket\" : \"project\";\n\t\t\treturn window.location.origin + \"/\" + prefix + \"~\" + self.id();\n\t\t});\n\t\tself.shortUrl = ko.computed(function() {\n\t\t\treturn oo_translations[\"constants.shortlink_prefix\"] + self.shortlink();\n\t\t});\n\t\tself.createdTimeString = ko.computed(function() {\n\t\t\treturn self.createdTime().toLocaleString();\n\t\t});\n\t\tself.isOwnedByCurrentUser = ko.computed(function() {\n\t\t\treturn !!ko.utils.arrayFirst(OctMethods.ko.viewModel.allBuckets(), function(bucket) {\n\t\t\t\treturn bucket.id() === self.id();\n\t\t\t}, null);\n\t\t});\n\n\t\t// Bindings used during bucket creation\n\t\tself.selectedLeft = ko.observableArray();\n\t\tself.selectedRight = ko.observableArray();\n\t\tself.showCreateButton = ko.observable(true);\n\n\t\tself.filesNotIncluded = ko.pureComputed(function() {\n\t\t\treturn utils.sortedFilter(OctMethods.ko.allOctFiles(), self.files(), function(octfile) {\n\t\t\t\treturn octfile.filename();\n\t\t\t});\n\t\t});\n\t\tself.moveLeftToRight = function() {\n\t\t\tself.files.push.apply(self.files, self.selectedLeft());\n\t\t\tself.selectedLeft.removeAll();\n\t\t\tself.files.sort(OctFile.sorter);\n\t\t};\n\t\tself.moveRightToLeft = function() {\n\t\t\tself.files.removeAll(self.selectedRight());\n\t\t\tself.selectedRight.removeAll();\n\t\t};\n\n\t\tself.textFiles = ko.pureComputed(function() {\n\t\t\treturn ko.utils.arrayFilter(self.files(), function(octfile) {\n\t\t\t\treturn octfile.editable;\n\t\t\t});\n\t\t});\n\n\t\tself.setAutoShortlink = function() {\n\t\t\tvar shortlink = \"\";\n\t\t\t// 5 lowercase letters\n\t\t\tfor (var i = 0; i < 5; i++) {\n\t\t\t\tshortlink += String.fromCharCode(97 + Math.floor(Math.random() * 26));\n\t\t\t}\n\t\t\t// 3 numbers\n\t\t\tfor (var i = 0; i < 3; i++) {\n\t\t\t\tshortlink += Math.floor(Math.random() * 10);\n\t\t\t}\n\t\t\tself.shortlink(shortlink);\n\t\t};\n\t\tself.editShortlink = function() {\n\t\t\tvar result = window.prompt(oo_translations[\"buckets.label1\"] + \"\\n\\n\" + oo_translations[\"constants.shortlink_prefix\"] + \"…\", self.shortlink());\n\t\t\tif (result) {\n\t\t\t\tOctMethods.socket.changeBucketShortlink(self, result);\n\t\t\t}\n\t\t}\n\n\t\tself.createOnServer = function() {\n\t\t\tOctMethods.socket.createBucket(self);\n\t\t\tself.showCreateButton(false);\n\t\t};\n\t\tself.deleteit = function() {\n\t\t\tif (confirm(\"Are you sure you want to delete this bucket?\\n\\n\" + self.shortUrl())) {\n\t\t\t\tOctMethods.socket.deleteBucket(self);\n\t\t\t}\n\t\t};\n\t}\n\n\tBucket.fromBucketInfo = function(info) {\n\t\tvar bucket = new Bucket();\n\t\tbucket.id(info.bucket_id);\n\t\tbucket.mainFilename(info.main);\n\t\tbucket.createdTime(new Date(info.createdTime));\n\t\tbucket.butype(info.butype);\n\t\tbucket.base_bucket_id(info.base_bucket_id);\n\t\tbucket.shortlink(info.shortlink);\n\t\tif (info.baseModel) {\n\t\t\tbucket.baseModel(Bucket.fromBucketInfo(info.baseModel));\n\t\t}\n\t\treturn bucket;\n\t};\n\n\t// Expose interface\n\treturn Bucket;\n\n});\n"
  },
  {
    "path": "client/app/js/client.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Client-Side JavaScript for Octave Online\n\ndefine([\"jquery\", \"knockout\", \"canvg\", \"base64\", \"js/download\", \"ace/ext/static_highlight\", \"js/anal\", \"base64-toblob\", \"ismobile\", \"exports\", \"js/octfile\", \"js/bucket\", \"js/vars\", \"ko-takeArray\", \"require\", \"js/onboarding\", \"js/ws-shared\", \"js/utils\", \"blob\", \"jquery.md5\", \"ace/theme/crimson_editor\", \"ace/theme/merbivore_soft\", \"js/ko-ace\"], function($, ko, canvg, Base64, download, aceStaticHighlight, anal, b64ToBlob, isMobile, exports, OctFile, Bucket, Var, koTakeArray, require, onboarding, WsShared, utils){\n\n\tif (!window.oo_translations) {\n\t\tconsole.error(\"WARNING: Translations not found. UI text will be unavailable.\");\n\t}\n\tvar oo_translations = window.oo_translations || {};\n\tvar oo_currentLanguage = window.oo_currentLanguage || \"und\";\n\tvar oo_availableLanguages = window.oo_availableLanguages || [\"und\"];\n\n\t/* * * * START KNOCKOUT SETUP * * * */\n\n\t// Skin MVVM class\n\tfunction Skin(name, aceTheme, iconColor){\n\t\tvar self = this;\n\n\t\t// Main Bindings\n\t\tself.name = name;\n\t\tself.displayName = name.charAt(0).toUpperCase() + name.slice(1);\n\t\tself.iconColor = iconColor;\n\t\tself.rawAceTheme = aceTheme;\n\t\tself.aceTheme = \"ace/theme/\"+aceTheme;\n\t\tself.cssURL = \"css/themes/\"+self.name+\".css?{!css-timestamp!}\";\n\t}\n\tvar availableSkins = [\n\t\tnew Skin(\"fire\", \"crimson_editor\", \"black\"),\n\t\tnew Skin(\"lava\", \"merbivore_soft\", \"white\"),\n\t\tnew Skin(\"ice\", \"crimson_editor\", \"black\"),\n\t\tnew Skin(\"sun\", \"crimson_editor\", \"black\"),\n\t];\n\n\t// Initialization for skin and dark mode.\n\t// April 2019: This has to be done this way for backwards compatibility. Eventually, oldSelectedSkin can be deleted.\n\tvar oldSelectedSkin = ko.observable();\n\toldSelectedSkin.extend({ localStorage: \"selected-skin\" });\n\tvar prefersDarkMode = ko.observable(false);\n\tif (oldSelectedSkin() && oldSelectedSkin().name === \"lava\") {\n\t\tprefersDarkMode(true);\n\t} else if (window.matchMedia(\"(prefers-color-scheme: dark)\").matches) {\n\t\tprefersDarkMode(true);\n\t}\n\tprefersDarkMode.extend({ localStorage: \"prefers-dark-mode\" });\n\tvar defaultSkin;\n\tif (prefersDarkMode()) {\n\t\tdefaultSkin = availableSkins[1];\n\t} else {\n\t\tdefaultSkin = availableSkins[0];\n\t}\n\n\t// Plot MVVM class\n\tfunction PlotObject(id){\n\t\tvar self = this;\n\n\t\t// Main Bindings\n\t\tself.id = id;\n\t\tself.lineNumber = ko.observable(null);\n\t\tself.data = \"\"; // not an observable for performance reasons\n\t\tself.complete = ko.observable(false);\n\n\t\t// Functions\n\t\tself.addData = function(data){\n\t\t\tself.data += data;\n\t\t};\n\t\tself.setCurrent = function(){\n\t\t\tvar arr = plotHistory();\n\t\t\tfor (var i = arr.length - 1; i >= 0; i--) {\n\t\t\t\tif (arr[i].id === id) {\n\t\t\t\t\tcurrentPlotIdx(i);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tself.downloadPng = function(){\n\t\t\tvar plotCanvas = document.getElementById(\"plot_canvas\");\n\t\t\tvar filename = \"octave-online-line-\" + self.lineNumber() + \".png\";\n\n\t\t\tvar renderCallback = function(){\n\t\t\t\tplotCanvas.toBlob(function(blob){\n\t\t\t\t\tdownload(blob, filename);\n\t\t\t\t}, \"image/png\");\n\t\t\t};\n\n\t\t\tcanvg(plotCanvas, self.data, {\n\t\t\t\trenderCallback: renderCallback,\n\t\t\t\tignoreMouse: true\n\t\t\t});\n\t\t};\n\t\tself.downloadSvg = function(){\n\t\t\tvar blob = new Blob([self.data], { type: \"image/svg+xml\" });\n\t\t\tvar filename = \"octave-online-line-\" + self.lineNumber() + \".svg\";\n\n\t\t\tdownload(blob, filename);\n\t\t};\n\t\tself.completeData = ko.computed(function(){\n\t\t\tif (self.complete()) {\n\t\t\t\treturn self.data;\n\t\t\t} else {\n\t\t\t\treturn \"\";\n\t\t\t}\n\t\t});\n\t\tself.md5 = ko.computed(function(){\n\t\t\treturn $.md5(self.completeData());\n\t\t});\n\t\tself.bindElement = function($el){\n\t\t\tself.complete.subscribe(function(){\n\t\t\t\t$el.append(self.completeData());\n\t\t\t\t$el.find(\".inline-plot-loading\").fadeOut(500);\n\t\t\t});\n\t\t};\n\t}\n\n\t// Initialize MVVM variables\n\tvar allOctFiles = ko.observableArray([]);\n\tvar selectedSkin = ko.observable(defaultSkin);\n\tvar purpose = ko.observable(\"default\");\n\tvar vars = ko.observableArray([]);\n\tvar plotHistory = ko.observableArray([]);\n\tvar currentPlotIdx = ko.observable(-1);\n\tvar authUser = ko.observable(); // user who is currently logged in\n\tvar currentUser = ko.observable(); // user who owns the workspace (may or may not be the same as authUser)\n\tvar currentBucket = ko.observable();\n\tvar viewModel = window.viewModel = {\n\t\tfiles: allOctFiles,\n\t\topenFile: ko.observable(),\n\t\tclose: function(){\n\t\t\tOctMethods.editor.close();\n\t\t},\n\t\tselectedSkin: selectedSkin,\n\t\tprefersDarkMode: prefersDarkMode,\n\t\tpurpose: purpose,\n\t\tvars: vars,\n\t\tplots: plotHistory,\n\t\tcurrentPlotIdx: currentPlotIdx,\n\t\tinlinePlots: ko.observable(true),\n\t\tconsoleWhiteSpaceWrap: ko.observable(true),\n\t\tinstructorPrograms: ko.observableArray(),\n\t\tallBuckets: ko.observableArray(),\n\t\tnewBucket: ko.observable(),\n\t\tcountdownExtraTimeSeconds: ko.observable(),\n\t\tcurrentLanguage: ko.observable(oo_currentLanguage),\n\t\tavailableLanguages: ko.observableArray(oo_availableLanguages),\n\n\t\t// More for UI\n\t\tlogoSrc: ko.computed(function() {\n\t\t\tvar color = selectedSkin().iconColor;\n\t\t\tif (purpose() === \"bucket\") {\n\t\t\t\treturn \"images/logos/banner-\" + color + \"-bucket.svg\";\n\t\t\t} else {\n\t\t\t\treturn \"images/logos/banner-\" + color + \".svg\";\n\t\t\t}\n\t\t}),\n\t\tpatreonValue: ko.computed(function() {\n\t\t\tvar user = authUser();\n\t\t\treturn user && user.patreon && user.patreon.currently_entitled_amount_cents;\n\t\t}),\n\t\topenUserVoice: function() {\n\t\t\trequire([\"uservoice\"], function() {\n\t\t\t\twindow.UserVoice.push([\"showLightbox\", \"classic_widget\", {\n\t\t\t\t\tmode: \"full\",\n\t\t\t\t\tprimary_color: \"#cc6d00\",\n\t\t\t\t\tlink_color: \"#007dbf\",\n\t\t\t\t\tdefault_mode: \"support\",\n\t\t\t\t\tforum_id: 211888,\n\t\t\t\t}]);\n\t\t\t});\n\t\t},\n\t\t/*\n\t\topenUserVoiceSupport: function() {\n\t\t\trequire([\"uservoice\"], function() {\n\t\t\t\twindow.UserVoice.push([\"showLightbox\", \"classic_widget\", {\n\t\t\t\t\tmode: \"support\",\n\t\t\t\t\tprimary_color: \"#cc6d00\",\n\t\t\t\t\tlink_color: \"#007dbf\"\n\t\t\t\t}]);\n\t\t\t});\n\t\t},\n\t\t*/\n\n\t\t// More for plots\n\t\tcurrentPlot: ko.computed(function(){\n\t\t\tif (currentPlotIdx()<0) return null;\n\t\t\treturn plotHistory()[currentPlotIdx()];\n\t\t}),\n\t\tshowPlot: ko.computed(function(){\n\t\t\treturn currentPlotIdx() >= 0;\n\t\t}),\n\t\ttogglePlot: function(){\n\t\t\tvar idx = currentPlotIdx();\n\t\t\tvar len = plotHistory().length;\n\t\t\tif (len === 0) {\n\t\t\t\tutils.alert(oo_translations[\"console.plotwindow#alert\"]);\n\t\t\t} else if (idx < 0) {\n\t\t\t\tcurrentPlotIdx(len-1);\n\t\t\t} else {\n\t\t\t\tcurrentPlotIdx(-1);\n\t\t\t}\n\t\t\tOctMethods.prompt.focus();\n\t\t},\n\t\tfirstPlotShown: ko.computed(function(){\n\t\t\treturn currentPlotIdx() === 0;\n\t\t}),\n\t\tlastPlotShown: ko.computed(function(){\n\t\t\treturn currentPlotIdx()+1 === plotHistory().length;\n\t\t}),\n\t\tshowPrevPlot: function(){\n\t\t\tvar idx = currentPlotIdx();\n\t\t\tif (idx <= 0) return null;\n\t\t\tcurrentPlotIdx(idx - 1);\n\t\t},\n\t\tshowNextPlot: function(){\n\t\t\tvar idx = currentPlotIdx();\n\t\t\tvar len = plotHistory().length;\n\t\t\tif (idx+1 >= len) return null;\n\t\t\tcurrentPlotIdx(idx + 1);\n\t\t},\n\t\tplotZoomed: ko.observable(false),\n\t\tzoomPlot: function(){\n\t\t\tviewModel.plotZoomed(!viewModel.plotZoomed());\n\t\t},\n\n\t\t// Sign In / Sign Out\n\t\tauthUser: authUser,\n\t\tcurrentUser: currentUser,\n\t\tdoLogout: function(){\n\t\t\tonboarding.reset();\n\t\t\twindow.location.href = \"/logout\";\n\t\t},\n\t\tshowChangePassword: function() {\n\t\t\tanal.sitecontrol(\"changepwdbtn\");\n\t\t\t$(\"#change_password\").showSafe();\n\t\t\t$(\"#new_pwd\").focus();\n\t\t},\n\n\t\tunenrollStudent: function(user) {\n\t\t\tif (confirm(oo_translations[\"students.unenroll.p1\"] + \"\\n\\n\" + oo_translations[\"students.name#label\"] + \" \" + user.displayName + \"\\n\" + oo_translations[\"students.course#label\"] + \" \" + user.program)) {\n\t\t\t\tOctMethods.socket.unenrollStudent(user);\n\t\t\t}\n\t\t},\n\t\treenrollStudent: function(user) {\n\t\t\tvar newProgram = prompt(oo_translations[\"students.reenroll.p1\"], \"\");\n\t\t\tvar programs = viewModel.instructorPrograms();\n\t\t\tfor (var i=0; i<programs.length; i++) {\n\t\t\t\tif (programs[i].program === newProgram) {\n\t\t\t\t\tOctMethods.socket.reenrollStudent(user, newProgram);\n\t\t\t\t\tutils.alert(oo_translations[\"students.reenroll.p2\"] + \"\\n\\n\" + oo_translations[\"students.name#label\"] + \" \" + user.displayName + \"\\n\" + oo_translations[\"students.course#label\"] + \" \" + newProgram);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tutils.alert(oo_translations[\"students.reenroll.p3\"] + \" \" + newProgram);\n\t\t},\n\n\t\tcurrentBucket: currentBucket,\n\t\tsharingEnabled: ko.computed(function() {\n\t\t\tvar bucket = currentBucket();\n\t\t\tif (bucket) {\n\t\t\t\treturn bucket.butype() === \"collab\";\n\t\t\t}\n\t\t\tvar user = currentUser();\n\t\t\tif (user) {\n\t\t\t\treturn !!user.share_key;\n\t\t\t}\n\t\t\treturn false;\n\t\t}),\n\t\ttoggleSharing: function(){\n\t\t\tOctMethods.socket.toggleSharing(!viewModel.sharingEnabled());\n\t\t},\n\n\t\tshowUpgradeTier: function() {\n\t\t\tanal.sitecontrol(\"upgradetierbtn\");\n\t\t\t$(\"#upgrade_to_tier\").showSafe();\n\t\t},\n\t\tstartNewBucket: function(octfile){\n\t\t\tanal.sitecontrol(\"startnewbucketbtn\");\n\t\t\tvar bucket = new Bucket();\n\t\t\tbucket.files.push(octfile);\n\t\t\tbucket.main(octfile);\n\t\t\tif (currentBucket()) {\n\t\t\t\tbucket.base_bucket_id(currentBucket().id());\n\t\t\t}\n\t\t\tbucket.setAutoShortlink();\n\t\t\tviewModel.newBucket(bucket);\n\t\t\t$(\"#create_bucket\").showSafe();\n\t\t},\n\t\tshowCreateNewProject: function() {\n\t\t\tanal.sitecontrol(\"createnewprojectbtn\");\n\t\t\tvar bucket = new Bucket();\n\t\t\tbucket.butype(\"editable\");\n\t\t\tif (currentBucket()) {\n\t\t\t\tbucket.base_bucket_id(currentBucket().id());\n\t\t\t}\n\t\t\tbucket.setAutoShortlink();\n\t\t\tviewModel.newBucket(bucket);\n\t\t\t$(\"#create_bucket\").showSafe();\n\t\t},\n\t\tshowCloneAsProject: function() {\n\t\t\tanal.sitecontrol(\"cloneasprojectbtn\");\n\t\t\tif (!currentUser()) {\n\t\t\t\talert(oo_translations[\"common.loginrequired\"]);\n\t\t\t\tconsole.error(\"Auth user required to create bucket\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!currentBucket()) {\n\t\t\t\tconsole.error(\"Cannot clone non-bucket\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar bucket = new Bucket();\n\t\t\tbucket.butype(\"editable\");\n\t\t\tbucket.files(allOctFiles());\n\t\t\tbucket.base_bucket_id(currentBucket().id());\n\t\t\tbucket.setAutoShortlink();\n\t\t\tviewModel.newBucket(bucket);\n\t\t\t$(\"#bucket_info\").hideSafe();\n\t\t\t$(\"#create_bucket\").showSafe();\n\t\t},\n\t\topenGit: function() {\n\t\t\tanal.sitecontrol(\"opengit\");\n\t\t\tvar currentUser = window.viewModel.currentUser();\n\t\t\tvar parametrized = currentUser ? currentUser.parametrized : \"unknown\";\n\t\t\tvar email = currentUser ? currentUser.email : \"\";\n\t\t\twindow.open(\"{!file_history_url!}?next=\" + parametrized + \".git&user=\" + email);\n\t\t},\n\t\tgenerateZip: function() {\n\t\t\tanal.sitecontrol(\"generatezip\");\n\t\t\tOctMethods.socket.generateZip();\n\t\t\t$(\"#file_history_box\").hideSafe();\n\t\t},\n\n\t\tgetOctFileFromName: function(filename){\n\t\t\t// Since allOctFiles is always sorted, we can do binary search.\n\t\t\treturn utils.binarySearch(allOctFiles(), filename, function(octfile) {\n\t\t\t\treturn octfile.filename();\n\t\t\t});\n\t\t},\n\t\tfileNameExists: function(filename){\n\t\t\t// return false for filenames like .plot\n\t\t\tif (filename[0] === \".\") return false;\n\t\t\t// also return false for the Octave namespace files\n\t\t\tif (filename.substr(0,7) === \"octave-\") return false;\n\n\t\t\treturn !!viewModel.getOctFileFromName(filename);\n\t\t},\n\n\t\tcwd: ko.observable(\"\"), // current working directory\n\n\t\taddTime: function() {\n\t\t\tOctMethods.prompt.addTime();\n\t\t},\n\t\tacknowledgePayload: function() {\n\t\t\tOctMethods.prompt.acknowledgePayload();\n\t\t},\n\n\t\tclearBucket: function() {\n\t\t\tviewModel.newBucket(null);\n\t\t},\n\n\t\tflex: {\n\t\t\tsizes: ko.observableArray([100, 400, 75, 325]),\n\t\t\tshown: ko.observable(false)\n\t\t}\n\t};\n\tviewModel.isCollabProject = ko.computed(function(){\n\t\treturn viewModel.currentBucket() && viewModel.currentBucket().butype() === \"collab\";\n\t});\n\tviewModel.extraHeaderText = ko.computed(function(){\n\t\tif (viewModel.purpose() === \"student\") {\n\t\t\treturn currentUser() && currentUser().name;\n\t\t} else if (viewModel.currentBucket()) {\n\t\t\tif (viewModel.purpose() === \"project\") {\n\t\t\t\treturn oo_translations[\"common.project\"] + \" \" + viewModel.currentBucket().shortlink();\n\t\t\t} else {\n\t\t\t\treturn viewModel.currentBucket().shortlink();\n\t\t\t}\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t});\n\tviewModel.extraHeaderTextClick = function() {\n\t\tif (viewModel.currentBucket()) {\n\t\t\t$(\"#bucket_info\").showSafe();\n\t\t}\n\t};\n\tviewModel.shareLink = ko.computed(function(){\n\t\tif (!viewModel.currentUser()) return \"\";\n\t\treturn window.location.origin + \"/workspace~\" + viewModel.currentUser().share_key;\n\t});\n\tviewModel.flex.outputCss = ko.computed(function(){\n\t\treturn \"flex-basis:\" + (viewModel.flex.sizes()[2] + viewModel.flex.sizes()[3]) + \"px\";\n\t});\n\tviewModel.flex.sizes.extend({ localStorage: \"flex:h\" });\n\tviewModel.inlinePlots.extend({ localStorage: \"inline-plots\" });\n\tviewModel.consoleWhiteSpaceWrap.extend({ localStorage: \"console-white-space-wrap\" });\n\t// Keep the console output visible when the plot window opens\n\tviewModel.showPlot.subscribe(function(){\n\t\tsetTimeout(OctMethods.console.scroll, 0);\n\t});\n\n\t// Listener for showing and hiding the create-bucket promo\n\tviewModel.openFile.subscribe(function(octfile) {\n\t\tonboarding.toggleCreateBucketPromo(octfile && octfile.editable && viewModel.purpose() === \"default\");\n\t});\n\n\t// Set the lng query parameter when the language changes\n\tviewModel.currentLanguage.subscribe(function(lng) {\n\t\tif (URL) {\n\t\t\t// Correct solution for new browsers\n\t\t\tvar url = new URL(window.location.href);\n\t\t\turl.searchParams.set(\"lng\", lng);\n\t\t\twindow.location.href = url.toString();\n\t\t} else {\n\t\t\t// Partial solution for old browsers\n\t\t\twindow.location.href = \"/?lng=\" + lng;\n\t\t}\n\t});\n\n\t/* * * * END KNOCKOUT, START EDITOR/CONSOLE/PROMPT * * * */\n\n\tfunction getOrMakePlotById(id){\n\t\tvar arr = plotHistory();\n\t\tfor (var i = arr.length - 1; i >= 0; i--) {\n\t\t\tif (arr[i].id === id) return arr[i];\n\t\t}\n\n\t\t// Make a new plot object\n\t\tvar obj = new PlotObject(id);\n\t\tplotHistory.push(obj);\n\n\t\t// Display it, either inline or in the plot window\n\t\tif (viewModel.inlinePlots()) {\n\t\t\tobj.bindElement(OctMethods.console.writePlot());\n\t\t} else {\n\t\t\tobj.setCurrent();\n\t\t}\n\n\t\treturn obj;\n\t}\n\n\t// Define a massive singleton object to contain all methods and listeners\n\tvar OctMethods = {\n\n\t\t// Console Methods\n\t\tconsole: {\n\t\t\tcurrentInContentAdTimestamp: 0,\n\t\t\twrite: function(content){\n\t\t\t\t$(\"#console\").append(document.createTextNode(content));\n\t\t\t\tOctMethods.console.scroll();\n\t\t\t},\n\t\t\twriteError: function(content){\n\t\t\t\tvar span = $(\"<span class=\\\"prompt_error\\\"></span>\");\n\t\t\t\tspan.append(document.createTextNode(content));\n\t\t\t\t$(\"#console\").append(span);\n\t\t\t\tOctMethods.console.scroll();\n\t\t\t},\n\t\t\twriteRow: function(rowString){\n\t\t\t\tvar rowSpan = $(\"<span class=\\\"prompt_row\\\"></span>\");\n\t\t\t\trowSpan.append(document.createTextNode(rowString));\n\t\t\t\t$(\"#console\").append(rowSpan);\n\t\t\t},\n\t\t\twriteCommand: function(lineNumber, cmd){\n\t\t\t\tvar rowString;\n\t\t\t\tif(lineNumber >= 0){\n\t\t\t\t\trowString = \"octave:\" + lineNumber + \"> \";\n\t\t\t\t}else if(lineNumber === -1){\n\t\t\t\t\trowString = \"> \";\n\t\t\t\t}else{\n\t\t\t\t\trowString = \"\";\n\t\t\t\t}\n\t\t\t\tif(rowString) OctMethods.console.writeRow(rowString);\n\n\t\t\t\tvar commandSpan = $(\"<span class=\\\"prompt_command\\\"></span>\");\n\t\t\t\tcommandSpan.append(document.createTextNode(cmd));\n\t\t\t\t$(\"#console\").append(commandSpan);\n\n\t\t\t\t$(\"#console\").append(document.createTextNode(\"\\n\"));\n\n\t\t\t\tOctMethods.console.scroll();\n\t\t\t},\n\t\t\twriteRestartBtn: function(){\n\t\t\t\tvar options = $(\"<span></span>\");\n\n\t\t\t\t// Construct the normal restart button\n\t\t\t\tvar btn1 = $(\"<a class=\\\"clickable\\\"></a>\");\n\t\t\t\tbtn1.click(function(){\n\t\t\t\t\tOctMethods.socket.reconnect();\n\t\t\t\t\toptions.remove();\n\t\t\t\t});\n\t\t\t\tbtn1.append(document.createTextNode(oo_translations[\"console.reconnect#btn\"]));\n\t\t\t\toptions.append(btn1);\n\n\t\t\t\t// Append to the console\n\t\t\t\t$(\"#console\").append(options);\n\t\t\t\t$(\"#console\").append(document.createTextNode(\"\\n\"));\n\t\t\t\tOctMethods.console.scroll();\n\t\t\t},\n\t\t\twriteUrl: function(url, linkText){\n\t\t\t\tif (!linkText) linkText = url;\n\t\t\t\tvar el = $(\"<a></a>\");\n\t\t\t\tel.attr(\"href\", url);\n\t\t\t\tel.attr(\"target\", \"_blank\");\n\t\t\t\tel.append(document.createTextNode(linkText));\n\t\t\t\t$(\"#console\").append(document.createTextNode(oo_translations[\"console.seeurl#label\"]));\n\t\t\t\t$(\"#console\").append(document.createTextNode(\" \"));\n\t\t\t\t$(\"#console\").append(el);\n\t\t\t\t$(\"#console\").append(document.createTextNode(\"\\n\"));\n\t\t\t\tOctMethods.console.scroll();\n\t\t\t},\n\t\t\twritePlot: function(){\n\t\t\t\tvar el = $(\"<div></div>\");\n\t\t\t\tel.attr(\"class\", \"inline-plot\");\n\t\t\t\tvar loading = $(\"<div></div>\");\n\t\t\t\tloading.attr(\"class\", \"inline-plot-loading\");\n\t\t\t\tel.append(loading);\n\t\t\t\t$(\"#console\").append(el);\n\t\t\t\tOctMethods.console.scroll();\n\t\t\t\treturn el;\n\t\t\t},\n\t\t\tscroll: function(){\n\t\t\t\t$(\"#console\").scrollTop($(\"#console\")[0].scrollHeight);\n\t\t\t\t$(\"#type_here\").hideSafe();\n\t\t\t\t$(\"#agpl_icon\").hideSafe();\n\t\t\t\t$(\"#tier_background\").hideSafe();\n\t\t\t\t$(\"#plot_opener\").showSafe();\n\t\t\t},\n\t\t\tclear: function(){\n\t\t\t\t$(\"#console\").empty();\n\t\t\t},\n\t\t\tcommand: function(cmd, skipsend){\n\t\t\t\tif(!OctMethods.prompt.enabled) return;\n\n\t\t\t\tvar currentLine = OctMethods.prompt.currentLine;\n\n\t\t\t\t// In-content ad opportunity\n\t\t\t\tif (window.oo_inConsoleAd && currentLine >= 3) {\n\t\t\t\t\twindow.oo_inConsoleAd();\n\t\t\t\t}\n\n\t\t\t\t// Show the command on screen\n\t\t\t\tOctMethods.console.writeCommand(currentLine, cmd);\n\n\t\t\t\t// Add command to history\n\t\t\t\tvar history = OctMethods.prompt.history;\n\t\t\t\tif (cmd !== \"\" && history[history.length-2] !== cmd) {\n\t\t\t\t\thistory[history.length-1] = cmd;\n\t\t\t\t\thistory.push(\"\");\n\t\t\t\t}\n\t\t\t\tOctMethods.prompt.index = history.length - 1;\n\n\t\t\t\t// Start countdown\n\t\t\t\tOctMethods.prompt.startCountdown();\n\t\t\t\tOctMethods.prompt.disable();\n\n\t\t\t\t// Send to server\n\t\t\t\tif (!skipsend) {\n\t\t\t\t\tOctMethods.socket.command(cmd);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t// Prompt Methods\n\t\tprompt: {\n\t\t\tinstance: null,\n\t\t\tcurrentLine: 0,\n\t\t\thistory: [\"\"],\n\t\t\tindex: 0,\n\t\t\tlegalTime: parseInt(\"5000!config.session.legalTime.guest\"),\n\t\t\textraTime: 0,\n\t\t\tcountdownExtraTime: parseInt(\"15000!config.session.countdownExtraTime\"),\n\t\t\tcountdownRequestTime: parseInt(\"3000!config.session.countdownRequestTime\"),\n\t\t\tcountdownInterval: null,\n\t\t\tpayloadTimerInterval: null,\n\t\t\tpayloadDelay: -1,\n\t\t\tcountdownTime: 0,\n\t\t\tcountdownDelay: 20,\n\t\t\tenabled: true,\n\t\t\tenable: function(){\n\t\t\t\t$(\"#runtime_controls_container\").hideSafe();\n\t\t\t\t$(\"#prompt\").showSafe();\n\t\t\t\t$(\"#prompt_sign\").showSafe();\n\t\t\t\tOctMethods.prompt.enabled = true;\n\t\t\t\tOctMethods.prompt.endCountdown();\n\n\t\t\t\t// There is a bug/feature in ACE that disables rendering when the element is hidden with display: none.  This hack forces a re-render now.\n\t\t\t\tOctMethods.prompt.instance.resize(true);\n\t\t\t},\n\t\t\tdisable: function(){\n\t\t\t\t$(\"#prompt\").hideSafe();\n\t\t\t\t$(\"#prompt_sign\").hideSafe();\n\t\t\t\tOctMethods.prompt.enabled = false;\n\t\t\t},\n\t\t\tclear: function(){\n\t\t\t\tOctMethods.prompt.instance.setValue(\"\");\n\t\t\t},\n\t\t\tfocus: function(){\n\t\t\t\tOctMethods.prompt.instance.focus();\n\t\t\t},\n\t\t\tstartCountdown: function(){\n\t\t\t\t$(\"#add_time_container\").hideSafe();\n\t\t\t\t$(\"#payload_acknowledge_container\").hideSafe();\n\t\t\t\t$(\"#runtime_controls_container\").showSafe();\n\t\t\t\tOctMethods.prompt.countdownTime = new Date().valueOf();\n\t\t\t\tOctMethods.prompt.extraTime = 0;\n\n\t\t\t\tOctMethods.prompt.countdownTick();\n\t\t\t\tclearInterval(OctMethods.prompt.countdownInterval);\n\t\t\t\tOctMethods.prompt.countdownInterval = setInterval(\n\t\t\t\t\tOctMethods.prompt.countdownTick, OctMethods.prompt.countdownDelay\n\t\t\t\t);\n\t\t\t},\n\t\t\tcountdownTick: function(){\n\t\t\t\tvar elapsed = new Date().valueOf() - OctMethods.prompt.countdownTime;\n\t\t\t\tvar remaining = (OctMethods.prompt.legalTime + OctMethods.prompt.extraTime - elapsed);\n\t\t\t\tif(remaining<=0) {\n\t\t\t\t\tclearInterval(OctMethods.prompt.countdownInterval);\n\t\t\t\t\t$(\"#seconds_remaining\").text(\"---\");\n\t\t\t\t}else{\n\t\t\t\t\t$(\"#seconds_remaining\").text((remaining/1000).toFixed(2));\n\t\t\t\t}\n\t\t\t\tif (remaining <= OctMethods.prompt.countdownRequestTime) {\n\t\t\t\t\t$(\"#add_time_container\").showSafe();\n\t\t\t\t} else {\n\t\t\t\t\t$(\"#add_time_container\").hideSafe();\n\t\t\t\t}\n\t\t\t},\n\t\t\tendCountdown: function(){\n\t\t\t\tclearInterval(OctMethods.prompt.countdownInterval);\n\t\t\t\tclearInterval(OctMethods.prompt.payloadTimerInterval);\n\t\t\t\t$(\"#runtime_controls_container\").hideSafe();\n\t\t\t\t$(\"#seconds_remaining\").text(\"0\");\n\n\t\t\t\tif (OctMethods.prompt.countdownTime > 0)\n\t\t\t\t\tanal.duration(new Date().valueOf() - OctMethods.prompt.countdownTime);\n\t\t\t},\n\t\t\tstartPayloadTimer: function(payloadDelay){\n\t\t\t\t// Similar, but not identical, to startCountdown()\n\t\t\t\t$(\"#add_time_container\").hideSafe();\n\t\t\t\t$(\"#payload_acknowledge_container\").showSafe();\n\t\t\t\t$(\"#runtime_controls_container\").showSafe();\n\t\t\t\tOctMethods.prompt.countdownTime = new Date().valueOf(); // no need to create another countdownTime variable; can use the same one as regular countdown\n\t\t\t\tOctMethods.prompt.payloadDelay = payloadDelay;\n\n\t\t\t\tOctMethods.prompt.payloadTimerTick();\n\t\t\t\tclearInterval(OctMethods.prompt.payloadTimerInterval);\n\t\t\t\tOctMethods.prompt.payloadTimerInterval = setInterval(\n\t\t\t\t\tOctMethods.prompt.payloadTimerTick, OctMethods.prompt.countdownDelay\n\t\t\t\t);\n\t\t\t},\n\t\t\tpayloadTimerTick: function(){\n\t\t\t\t// Similar, but not identical, to countdownTick()\n\t\t\t\tvar elapsed = new Date().valueOf() - OctMethods.prompt.countdownTime;\n\t\t\t\tvar remaining = (OctMethods.prompt.payloadDelay - elapsed);\n\t\t\t\tif(remaining<=0) {\n\t\t\t\t\tclearInterval(OctMethods.prompt.countdownInterval);\n\t\t\t\t\t$(\"#seconds_remaining\").text(\"---\");\n\t\t\t\t}else{\n\t\t\t\t\t$(\"#seconds_remaining\").text((remaining/1000).toFixed(2));\n\t\t\t\t}\n\t\t\t},\n\t\t\taskForEnroll: function(program){\n\t\t\t\tif(!viewModel.authUser()){\n\t\t\t\t\tutils.alert(oo_translations[\"students.enroll.p1\"]);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif(confirm(\n\t\t\t\t\too_translations[\"students.enroll.p2\"] + \"\\n\\nenroll('default')\\n\\n\" + oo_translations[\"students.enroll.p3\"])){\n\t\t\t\t\tOctMethods.socket.enroll(program);\n\t\t\t\t\tviewModel.authUser().program = program; // note: this is not observable\n\t\t\t\t}\n\t\t\t},\n\t\t\taddTime: function() {\n\t\t\t\tOctMethods.prompt.extraTime += OctMethods.prompt.countdownExtraTime;\n\t\t\t\tOctMethods.socket.addTime();\n\t\t\t\tanal.extraTime();\n\t\t\t},\n\t\t\tacknowledgePayload: function() {\n\t\t\t\t// Acknowledging the payload resets the countdown on the server.\n\t\t\t\tOctMethods.prompt.endCountdown();\n\t\t\t\tOctMethods.prompt.startCountdown();\n\t\t\t\tOctMethods.socket.acknowledgePayload();\n\t\t\t\tanal.acknowledgePayload();\n\t\t\t}\n\t\t},\n\n\t\t// Prompt Callback Funcions\n\t\tpromptListeners: {\n\t\t\tcommand: function(){\n\t\t\t\tvar cmd = OctMethods.prompt.instance.getValue();\n\n\t\t\t\t// Check if this command is a front-end command\n\t\t\t\tvar enrollRegex = /^enroll\\s*\\(['\"“‘”’]([^'\"“‘”’]+)['\"“‘”’]\\).*$/;\n\t\t\t\tvar updateStudentsRegex = /^update_students\\s*\\(['\"“‘”’]([^'\"“‘”’]+)['\"“‘”’]\\).*$/;\n\t\t\t\tvar pingRegex = /^ping$/;\n\n\t\t\t\tvar program;\n\t\t\t\tif(enrollRegex.test(cmd)){\n\t\t\t\t\tprogram = cmd.match(enrollRegex)[1];\n\t\t\t\t\tOctMethods.prompt.askForEnroll(program);\n\t\t\t\t\tOctMethods.prompt.clear();\n\t\t\t\t}else if(updateStudentsRegex.test(cmd)){\n\t\t\t\t\tprogram = cmd.match(updateStudentsRegex)[1];\n\t\t\t\t\tOctMethods.socket.updateStudents(program);\n\t\t\t\t\tOctMethods.prompt.clear();\n\t\t\t\t}else if(pingRegex.test(cmd)) {\n\t\t\t\t\tOctMethods.console.command(cmd, true);\n\t\t\t\t\tOctMethods.socket.ping();\n\t\t\t\t\tOctMethods.prompt.clear();\n\t\t\t\t}else{\n\t\t\t\t\tOctMethods.console.command(cmd);\n\t\t\t\t\tOctMethods.prompt.clear();\n\t\t\t\t}\n\n\t\t\t\tanal.command(cmd);\n\t\t\t},\n\t\t\tsignal: function(){\n\t\t\t\t// Trigger both a signal and an empty command upstream.  The empty command will sometimes help if, for any reason, the \"prompt\" message was lost in transit.\n\t\t\t\t// This could be slightly improved by adding the empty command elsewhere in the stack, to reduce the number of packets that need to be sent.\n\t\t\t\tOctMethods.socket.signal();\n\t\t\t\tOctMethods.socket.command(\"\");\n\t\t\t\tanal.sigint();\n\t\t\t},\n\t\t\thistoryUp: function(prompt){\n\t\t\t\tvar history = OctMethods.prompt.history;\n\t\t\t\tif (OctMethods.prompt.index == history.length-1){\n\t\t\t\t\thistory[history.length-1] = prompt.getValue();\n\t\t\t\t}\n\t\t\t\tif (OctMethods.prompt.index > 0){\n\t\t\t\t\tOctMethods.prompt.index -= 1;\n\t\t\t\t\tprompt.setValue(history[OctMethods.prompt.index]);\n\t\t\t\t\tprompt.getSelection().clearSelection();\n\t\t\t\t}\n\t\t\t},\n\t\t\thistoryDown: function(prompt){\n\t\t\t\tvar history = OctMethods.prompt.history;\n\t\t\t\tif (OctMethods.prompt.index < history.length-1){\n\t\t\t\t\tOctMethods.prompt.index += 1;\n\t\t\t\t\tprompt.setValue(history[OctMethods.prompt.index]);\n\t\t\t\t\tprompt.getSelection().clearSelection();\n\t\t\t\t}\n\t\t\t},\n\t\t\tkeyFocus: function(e){\n\t\t\t\te.preventDefault();\n\t\t\t\tOctMethods.prompt.focus();\n\t\t\t},\n\t\t\tpermalink: function(){\n\t\t\t\tvar cmd = $(this).text();\n\t\t\t\twindow.location.hash = \"cmd=\" + encodeURIComponent(cmd);\n\t\t\t}\n\t\t},\n\n\t\t// Socket Methods\n\t\tsocket: {\n\t\t\tinstance: null,\n\t\t\tsessCode: null,\n\t\t\tisExited: false,\n\t\t\tsignal: function(){\n\t\t\t\treturn OctMethods.socket.emit(\"signal\", {});\n\t\t\t},\n\t\t\tcommand: function(cmd){\n\t\t\t\treturn OctMethods.socket.emit(\"data\", {\n\t\t\t\t\tdata: cmd\n\t\t\t\t});\n\t\t\t},\n\t\t\tsave: function(octfile){\n\t\t\t\treturn OctMethods.socket.emit(\"save\", {\n\t\t\t\t\tfilename: octfile.filename(),\n\t\t\t\t\tcontent: octfile.content()\n\t\t\t\t});\n\t\t\t},\n\t\t\trename: function(octfile, newName){\n\t\t\t\treturn OctMethods.socket.emit(\"rename\", {\n\t\t\t\t\tfilename: octfile.filename(),\n\t\t\t\t\tnewname: newName\n\t\t\t\t});\n\t\t\t},\n\t\t\tdeleteit: function(octfile){\n\t\t\t\treturn OctMethods.socket.emit(\"delete\", {\n\t\t\t\t\tfilename: octfile.filename()\n\t\t\t\t});\n\t\t\t},\n\t\t\tbinary: function(octfile){\n\t\t\t\treturn OctMethods.socket.emit(\"binary\", {\n\t\t\t\t\tfilename: octfile.filename()\n\t\t\t\t});\n\t\t\t},\n\t\t\tenroll: function(program){\n\t\t\t\treturn OctMethods.socket.emit(\"enroll\", {\n\t\t\t\t\tprogram: program\n\t\t\t\t});\n\t\t\t},\n\t\t\tupdateStudents: function(program){\n\t\t\t\treturn OctMethods.socket.emit(\"update_students\", {\n\t\t\t\t\tprogram: program\n\t\t\t\t});\n\t\t\t},\n\t\t\tunenrollStudent: function(user){\n\t\t\t\treturn OctMethods.socket.emit(\"oo.unenroll_student\", {\n\t\t\t\t\tuserId: user._id\n\t\t\t\t});\n\t\t\t},\n\t\t\treenrollStudent: function(user, newProgram){\n\t\t\t\treturn OctMethods.socket.emit(\"oo.reenroll_student\", {\n\t\t\t\t\tuserId: user._id,\n\t\t\t\t\tprogram: newProgram\n\t\t\t\t});\n\t\t\t},\n\t\t\tping: function() {\n\t\t\t\treturn OctMethods.socket.emit(\"oo.ping\", {\n\t\t\t\t\tstartTime: new Date().valueOf()\n\t\t\t\t});\n\t\t\t},\n\t\t\trefresh: function(){\n\t\t\t\treturn OctMethods.socket.emit(\"refresh\", {});\n\t\t\t},\n\t\t\ttoggleSharing: function(enabled){\n\t\t\t\treturn OctMethods.socket.emit(\"oo.toggle_sharing\", {\n\t\t\t\t\tenabled: enabled\n\t\t\t\t});\n\t\t\t},\n\t\t\taddTime: function() {\n\t\t\t\treturn OctMethods.socket.emit(\"oo.add_time\", {});\n\t\t\t},\n\t\t\tacknowledgePayload: function() {\n\t\t\t\treturn OctMethods.socket.emit(\"oo.acknowledge_payload\", {});\n\t\t\t},\n\t\t\tsetPassword: function(password) {\n\t\t\t\treturn OctMethods.socket.emit(\"oo.set_password\", {\n\t\t\t\t\tnew_pwd: password\n\t\t\t\t});\n\t\t\t},\n\t\t\tcreateBucket: function(bucket) {\n\t\t\t\treturn OctMethods.socket.emit(\"oo.create_bucket\", {\n\t\t\t\t\tfilenames: ko.utils.arrayMap(bucket.files(), function(octfile){\n\t\t\t\t\t\treturn octfile.filename();\n\t\t\t\t\t}),\n\t\t\t\t\tmain: bucket.mainFilename(),\n\t\t\t\t\tbutype: bucket.butype(),\n\t\t\t\t\tbase_bucket_id: bucket.base_bucket_id(),\n\t\t\t\t\tshortlink: bucket.shortlink(),\n\t\t\t\t});\n\t\t\t},\n\t\t\tdeleteBucket: function(bucket) {\n\t\t\t\treturn OctMethods.socket.emit(\"oo.delete_bucket\", {\n\t\t\t\t\tbucket_id: bucket.id()\n\t\t\t\t});\n\t\t\t},\n\t\t\tchangeBucketShortlink: function(bucket, newShortlink) {\n\t\t\t\treturn OctMethods.socket.emit(\"oo.change_bucket_shortlink\", {\n\t\t\t\t\told_shortlink: bucket.shortlink(),\n\t\t\t\t\tnew_shortlink: newShortlink,\n\t\t\t\t});\n\t\t\t},\n\t\t\tgenerateZip: function() {\n\t\t\t\treturn OctMethods.socket.emit(\"oo.generate_zip\", {});\n\t\t\t},\n\t\t\temit: function(message, data){\n\t\t\t\tif (!OctMethods.socket.instance\n\t\t\t\t\t|| !OctMethods.socket.instance.connected) {\n\t\t\t\t\tconsole.log(\"Socket Closed\", message, data);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tOctMethods.socket.instance.emit(message, data);\n\t\t\t\treturn true;\n\t\t\t},\n\t\t\treconnect: function(){\n\t\t\t\tOctMethods.load.showLoader();\n\t\t\t\tOctMethods.load.startPatience();\n\t\t\t\tOctMethods.socket.isExited = false;\n\n\t\t\t\treturn OctMethods.socket.emit(\"oo.reconnect\", {});\n\t\t\t}\n\t\t},\n\n\t\t// Socket Callback Functions\n\t\tsocketListeners: {\n\t\t\tsubscribe: function(socket) {\n\t\t\t\tsocket.on(\"data\", OctMethods.socketListeners.data);\n\t\t\t\tsocket.on(\"alert\", OctMethods.socketListeners.alert);\n\t\t\t\tsocket.on(\"prompt\", OctMethods.socketListeners.prompt);\n\t\t\t\tsocket.on(\"saved\", OctMethods.socketListeners.saved);\n\t\t\t\tsocket.on(\"renamed\", OctMethods.socketListeners.renamed);\n\t\t\t\tsocket.on(\"deleted\", OctMethods.socketListeners.deleted);\n\t\t\t\t// TODO: Stop this event from operating on everyone in a shared workspace\n\t\t\t\tsocket.on(\"binary\", OctMethods.socketListeners.binary);\n\t\t\t\tsocket.on(\"oo.authuser\", OctMethods.socketListeners.authuser);\n\t\t\t\tsocket.on(\"oo.wsuser\", OctMethods.socketListeners.wsuser);\n\t\t\t\t// The inconsistent naming convention here (\"user\" vs. \"filelist\") is for backwards compatibility.  At some point I would like to rename this and other events all the way through the stack.\n\t\t\t\tsocket.on(\"user\", OctMethods.socketListeners.filelist);\n\t\t\t\tsocket.on(\"fileadd\", OctMethods.socketListeners.fileadd);\n\t\t\t\tsocket.on(\"plotd\", OctMethods.socketListeners.plotd);\n\t\t\t\tsocket.on(\"plote\", OctMethods.socketListeners.plote);\n\t\t\t\tsocket.on(\"ctrl\", OctMethods.socketListeners.ctrl);\n\t\t\t\tsocket.on(\"workspace\", OctMethods.socketListeners.vars);\n\t\t\t\tsocket.on(\"sesscode\", OctMethods.socketListeners.sesscode);\n\t\t\t\tsocket.on(\"init\", OctMethods.socketListeners.init);\n\t\t\t\tsocket.on(\"files-ready\", OctMethods.socketListeners.filesReady);\n\t\t\t\tsocket.on(\"destroy-u\", OctMethods.socketListeners.destroyu);\n\t\t\t\tsocket.on(\"disconnect\", OctMethods.socketListeners.disconnect);\n\t\t\t\tsocket.on(\"reload\", OctMethods.socketListeners.reload);\n\t\t\t\tsocket.on(\"instructor\", OctMethods.socketListeners.instructor);\n\t\t\t\tsocket.on(\"bucket-info\", OctMethods.socketListeners.bucketInfo);\n\t\t\t\tsocket.on(\"bucket-created\", OctMethods.socketListeners.bucketCreated);\n\t\t\t\tsocket.on(\"bucket-deleted\", OctMethods.socketListeners.bucketDeleted);\n\t\t\t\tsocket.on(\"all-buckets\", OctMethods.socketListeners.allBuckets);\n\t\t\t\tsocket.on(\"oo.create-bucket-error\", OctMethods.socketListeners.createBucketError);\n\t\t\t\tsocket.on(\"oo.change-bucket-shortlink-response\", OctMethods.socketListeners.changeBucketShortlinkResponse);\n\t\t\t\tsocket.on(\"oo.pong\", OctMethods.socketListeners.pong);\n\t\t\t\t// Flavors are no longer supported:\n\t\t\t\t// socket.on(\"oo.flavor-list\", OctMethods.socketListeners.flavorList);\n\t\t\t\t// socket.on(\"oo.touch-flavor\", OctMethods.socketListeners.touchFlavor);\n\t\t\t\tsocket.on(\"restart-countdown\", OctMethods.socketListeners.restartCountdown);\n\t\t\t\tsocket.on(\"change-directory\", OctMethods.socketListeners.changeDirectory);\n\t\t\t\tsocket.on(\"edit-file\", OctMethods.socketListeners.editFile);\n\t\t\t\tsocket.on(\"payload-paused\", OctMethods.socketListeners.payloadPaused);\n\t\t\t},\n\t\t\tdata: function(data){\n\t\t\t\tswitch(data.type){\n\t\t\t\t\tcase \"stdout\":\n\t\t\t\t\t\tOctMethods.console.write(data.data);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"stderr\":\n\t\t\t\t\t\tOctMethods.console.writeError(data.data);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"url\":\n\t\t\t\t\t\tOctMethods.console.writeUrl(data.url, data.linkText);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"exit\":\n\t\t\t\t\t\tconsole.log(\"exit status: \" + JSON.stringify(data.code));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tconsole.log(\"unknown data type: \" + data.type);\n\t\t\t\t}\n\t\t\t},\n\t\t\talert: function(message) {\n\t\t\t\tutils.alert(message);\n\t\t\t},\n\t\t\tprompt: function(data){\n\t\t\t\tvar lineNumber = data.line_number || 0;\n\n\t\t\t\t// Turn on the input prompt and set the current line number\n\t\t\t\tOctMethods.prompt.currentLine = lineNumber;\n\t\t\t\tOctMethods.prompt.enable();\n\n\t\t\t\t// Perform other cleanup logic\n\t\t\t\tif(OctMethods.editor.running){\n\t\t\t\t\tif(lineNumber > 0){\n\t\t\t\t\t\tOctMethods.editor.running = false;\n\t\t\t\t\t}else{\n\t\t\t\t\t\tOctMethods.prompt.focus();\n\t\t\t\t\t}\n\t\t\t\t}else if(isMobile && lineNumber>1){\n\t\t\t\t\tOctMethods.prompt.focus();\n\t\t\t\t\tsetTimeout(function(){\n\t\t\t\t\t\t// Does not quite work\n\t\t\t\t\t\twindow.scrollTo(0,document.body.scrollHeight);\n\t\t\t\t\t}, 500);\n\t\t\t\t}else if(!isMobile){\n\t\t\t\t\tOctMethods.prompt.focus();\n\t\t\t\t}\n\t\t\t},\n\t\t\tsaved: function(data){\n\t\t\t\tif (!data.success) return;\n\t\t\t\tvar octfile = viewModel.getOctFileFromName(data.filename);\n\t\t\t\tif (!octfile) return;\n\t\t\t\tif (octfile.md5() === data.md5sum) {\n\t\t\t\t\toctfile.savedContent(octfile.content());\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(\"Mismatched MD5! Local:\", octfile.md5(), \"Server:\", data.md5sum);\n\t\t\t\t}\n\t\t\t},\n\t\t\trenamed: function(data){\n\t\t\t\tvar oldname = data.oldname, newname = data.newname;\n\t\t\t\tvar octfile = viewModel.getOctFileFromName(oldname);\n\t\t\t\tif(!octfile) return;\n\n\t\t\t\t// Rename the file throughout the schema\n\t\t\t\toctfile.filename(newname);\n\t\t\t\tallOctFiles.sort(OctFile.sorter);\n\t\t\t},\n\t\t\tdeleted: function(data){\n\t\t\t\tvar octfile = viewModel.getOctFileFromName(data.filename);\n\t\t\t\tif(!octfile) return;\n\t\t\t\tif (viewModel.openFile() === octfile) {\n\t\t\t\t\tOctMethods.editor.close();\n\t\t\t\t}\n\t\t\t\tOctMethods.editor.remove(octfile);\n\t\t\t},\n\t\t\tbinary: function(data){\n\t\t\t\t// Attempt to download the file\n\t\t\t\tconsole.log(\"Downloading binary file\", data.filename);\n\t\t\t\tvar blob = b64ToBlob(data.base64data, data.mime);\n\t\t\t\treturn download(blob, data.filename);\n\t\t\t},\n\t\t\tauthuser: function(data){\n\t\t\t\tdata = data && data.user;\n\n\t\t\t\tif (!OctMethods.editor.seenAuthUser) {\n\t\t\t\t\tOctMethods.editor.seenAuthUser = true;\n\n\t\t\t\t\t// Ads setup\n\t\t\t\t\tif (data && data.adsDisabled) {\n\t\t\t\t\t\t$(\"#abox\").hideSafe();\n\t\t\t\t\t\t$(\"#main\").css(\"top\", 0);\n\t\t\t\t\t\t$(\"#main\").css(\"right\", 0);\n\t\t\t\t\t\tif (window.oo_disabledAds) {\n\t\t\t\t\t\t\twindow.oo_disabledAds();\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (window.oo_enableAds) {\n\t\t\t\t\t\twindow.oo_enableAds();\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!data) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Trigger Knockout\n\t\t\t\t\tdata.name = data.name || data.displayName;\n\t\t\t\t\tviewModel.authUser(data);\n\n\t\t\t\t\t// Set up the UI\n\t\t\t\t\tonboarding.showUserPromo(data);\n\t\t\t\t\tonboarding.hideScriptPromo();\n\t\t\t\t\tonboarding.hideBucketPromo();\n\n\t\t\t\t\t// Welcome Back?\n\t\t\t\t\tvar welcome_back_ms = parseInt(\"86400000!config.client.welcome_back_ms\");\n\t\t\t\t\tif (new Date() - new Date(data.last_activity) >= welcome_back_ms) {\n\t\t\t\t\t\t$(\"#welcome_back\").showSafe();\n\t\t\t\t\t\tanal.welcomeback();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Analytics\n\t\t\t\t\tanal.signedin();\n\t\t\t\t}\n\t\t\t},\n\t\t\twsuser: function(data) {\n\t\t\t\tdata = data && data.user;\n\n\t\t\t\tif (!OctMethods.editor.seenWsUser) {\n\t\t\t\t\tOctMethods.editor.seenWsUser = true;\n\n\t\t\t\t\tif (!data) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Trigger Knockout\n\t\t\t\t\tdata.name = data.name || data.displayName;\n\t\t\t\t\tviewModel.currentUser(data);\n\n\t\t\t\t\t// Legal runtime and other user settings\n\t\t\t\t\tOctMethods.prompt.legalTime = data.legalTime;\n\t\t\t\t\tOctMethods.prompt.countdownExtraTime = data.countdownExtraTime;\n\t\t\t\t\tOctMethods.prompt.countdownRequestTime = data.countdownRequestTime;\n\t\t\t\t\tviewModel.countdownExtraTimeSeconds(data.countdownExtraTime/1000);\n\t\t\t\t}\n\t\t\t},\n\t\t\tfilelist: function(data){\n\t\t\t\t// Load files\n\t\t\t\tif (!data.success) {\n\t\t\t\t\tOctMethods.load.callback();\n\t\t\t\t\treturn utils.alert(data.message);\n\t\t\t\t}\n\t\t\t\tif (allOctFiles().length === 0) {\n\t\t\t\t\t$.each(data.files, function(filename, filedata){\n\t\t\t\t\t\tif(filedata.isText){\n\t\t\t\t\t\t\tOctMethods.editor.add(filename, Base64.decode(filedata.content));\n\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\tOctMethods.editor.addNameOnly(filename);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\t// Set up the UI\n\t\t\t\t\t$(\"#open_container\").showSafe();\n\t\t\t\t\t$(\"#files_container\").showSafe();\n\t\t\t\t\tif (!OctMethods.vars.bucketId && !OctMethods.vars.wsId) {\n\t\t\t\t\t\tonboarding.showSyncPromo();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Fire a window \"resize\" event to make sure everything adjusts,\n\t\t\t\t\t// like the ACE editor in the prompt\n\t\t\t\t\tvar evt = document.createEvent(\"UIEvents\");\n\t\t\t\t\tevt.initUIEvent(\"resize\", true, false, window, 0);\n\t\t\t\t\twindow.dispatchEvent(evt);\n\n\t\t\t\t\t// If we are in a bucket, auto-open the main file.\n\t\t\t\t\tif (viewModel.currentBucket() && viewModel.currentBucket().main()) {\n\t\t\t\t\t\tvar filename = viewModel.currentBucket().mainFilename();\n\t\t\t\t\t\tvar octfile = viewModel.getOctFileFromName(filename);\n\t\t\t\t\t\tif (octfile) {\n\t\t\t\t\t\t\toctfile.open();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t} else {\n\t\t\t\t\t// If the files were already loaded, update the saved content. This will make files show as unsaved if they are out-of-sync with the server.  Don't do anything more drastic, like requesting a file save, because the user might not want a file save in the case of a filelist event being emitted when someone joins a shared workspace session.  Also note that this could have a race condition if a save was performed after the files were read from the server; simply marking the file as unsaved is harmless and won't cause conflicts.\n\t\t\t\t\t$.each(data.files, function(filename, filedata){\n\t\t\t\t\t\tif (!filedata.isText) return;\n\t\t\t\t\t\tvar octfile = viewModel.getOctFileFromName(filename);\n\t\t\t\t\t\tif (octfile) {\n\t\t\t\t\t\t\toctfile.savedContent(Base64.decode(filedata.content));\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t\tfileadd: function(data){\n\t\t\t\tif(data.isText){\n\t\t\t\t\tvar octFile = OctMethods.editor.add(data.filename,\n\t\t\t\t\t\tBase64.decode(data.content));\n\t\t\t\t\tOctMethods.editor.open(octFile);\n\t\t\t\t}else{\n\t\t\t\t\tOctMethods.editor.addNameOnly(data.filename);\n\t\t\t\t}\n\t\t\t},\n\t\t\tplotd: function(data){\n\t\t\t\t// plot data transmission\n\t\t\t\tvar plot = getOrMakePlotById(data.id);\n\t\t\t\tplot.addData(data.content);\n\t\t\t\tconsole.log(\"Received data for plot ID \"+data.id);\n\t\t\t},\n\t\t\tplote: function(data){\n\t\t\t\t// plot data complete\n\t\t\t\tvar plot = getOrMakePlotById(data.id);\n\t\t\t\tplot.lineNumber(data.command_number - 1);\n\t\t\t\tplot.complete(true);\n\n\t\t\t\tif(data.md5 !== plot.md5()){\n\t\t\t\t\t// should never happen\n\t\t\t\t\tconsole.log(\"MD5 discrepancy!\");\n\t\t\t\t\tconsole.log(data);\n\t\t\t\t\tconsole.log(plot.md5());\n\t\t\t\t}\n\t\t\t},\n\t\t\tctrl: function(data){\n\t\t\t\t// command from shell\n\t\t\t\tconsole.log(\"Received ctrl '\", data.command, \"' from server\");\n\t\t\t\tif(data.command === \"clc\"){\n\t\t\t\t\tOctMethods.console.clear();\n\t\t\t\t}else if(data.command.substr(0,4) === \"url=\"){\n\t\t\t\t\tOctMethods.console.writeUrl(data.command.substr(4));\n\t\t\t\t}else if(data.command.substr(0,6) === \"enroll\"){\n\t\t\t\t\tOctMethods.prompt.askForEnroll(data.command.substr(7));\n\t\t\t\t}\n\t\t\t},\n\t\t\tvars: function(data){\n\t\t\t\t// update variables\n\t\t\t\tkoTakeArray(Var, vars, \"symbol\",\n\t\t\t\t\tdata.vars, \"symbol\");\n\t\t\t\tvars.sort(Var.sorter);\n\t\t\t},\n\t\t\tsesscode: function(data){\n\t\t\t\tif (OctMethods.socket.sessCode === data.sessCode) {\n\t\t\t\t\t// Reconnected and matched to our original session.\n\t\t\t\t\tconsole.log(\"Restored connection to:\", data.sessCode);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(\"SESSCODE:\", data.sessCode);\n\t\t\t\t\tOctMethods.socket.sessCode = data.sessCode;\n\t\t\t\t}\n\t\t\t},\n\t\t\treload: function(){\n\t\t\t\twindow.location.reload();\n\t\t\t},\n\t\t\tinstructor: function(data){\n\t\t\t\tdata.users.forEach(function(user){\n\t\t\t\t\tuser.shareUrl = window.location.origin + window.location.pathname\n\t\t\t\t\t\t+ \"?s=\" + user.share_key;\n\t\t\t\t});\n\t\t\t\tviewModel.instructorPrograms.push(data);\n\t\t\t},\n\t\t\tbucketInfo: function(data){\n\t\t\t\tviewModel.currentBucket(Bucket.fromBucketInfo(data));\n\t\t\t},\n\t\t\tbucketCreated: function(data) {\n\t\t\t\tvar bucket = Bucket.fromBucketInfo(data.bucket);\n\t\t\t\t// To stay on this page, the following lines should be run, but since we are always redirecting, they are not necessary and cause the screen to flash.\n\t\t\t\t// viewModel.allBuckets.push(bucket);\n\t\t\t\t// viewModel.newBucket(null);\n\t\t\t\twindow.location.href = bucket.url();\n\t\t\t},\n\t\t\tbucketDeleted: function(data) {\n\t\t\t\tvar bucket = ko.utils.arrayFirst(viewModel.allBuckets(), function(bucket) {\n\t\t\t\t\treturn bucket.id() === data.bucket_id;\n\t\t\t\t}, null);\n\t\t\t\tviewModel.allBuckets.remove(bucket);\n\t\t\t},\n\t\t\tallBuckets: function(data) {\n\t\t\t\t// N-squared loop, but it should be small enough not to be an issue\n\t\t\t\t$.each(data.buckets, function(i, bucketInfo) {\n\t\t\t\t\tvar found = false;\n\t\t\t\t\t$.each(viewModel.allBuckets(), function(j, bucket){\n\t\t\t\t\t\tif (bucket.id() === bucketInfo.bucket_id) {\n\t\t\t\t\t\t\tfound = true;\n\t\t\t\t\t\t\treturn false; // break\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tif (!found) {\n\t\t\t\t\t\tviewModel.allBuckets.push(Bucket.fromBucketInfo(bucketInfo));\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t},\n\t\t\tcreateBucketError: function(data) {\n\t\t\t\tif (data.type === \"invalid-shortlink\") {\n\t\t\t\t\talert(oo_translations[\"buckets.error1\"]);\n\t\t\t\t} else if (data.type === \"duplicate-key\") {\n\t\t\t\t\talert(oo_translations[\"buckets.error2\"] + \"\\n\\n\" + Object.values(data.data)[0]);\n\t\t\t\t}\n\t\t\t\tviewModel.newBucket().showCreateButton(true);\n\t\t\t},\n\t\t\tchangeBucketShortlinkResponse: function(data) {\n\t\t\t\tif (data.success) {\n\t\t\t\t\tif (viewModel.currentBucket().id() === data.bucket.bucket_id) {\n\t\t\t\t\t\tviewModel.currentBucket().shortlink(data.bucket.shortlink);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsole.error(\"Inconsistent bucket:\", viewModel.currentBucket(), data.bucket);\n\t\t\t\t\t}\n\t\t\t\t} else if (data.type === \"invalid-shortlink\") {\n\t\t\t\t\talert(oo_translations[\"buckets.error1\"]);\n\t\t\t\t} else if (data.type === \"duplicate-key\") {\n\t\t\t\t\talert(oo_translations[\"buckets.error2\"] + \"\\n\\n\" + Object.values(data.data)[0]);\n\t\t\t\t}\n\t\t\t},\n\t\t\tpong: function(data) {\n\t\t\t\tvar startTime = parseInt(data.startTime);\n\t\t\t\tvar endTime = new Date().valueOf();\n\t\t\t\tOctMethods.console.write(oo_translations[\"console.pingtime#label\"] + \" \" + (endTime-startTime) + \"ms\\n\");\n\t\t\t\tOctMethods.prompt.enable();\n\t\t\t\tOctMethods.prompt.focus();\n\t\t\t},\n\t\t\trestartCountdown: function(){\n\t\t\t\t// TODO: Is this method dead?\n\t\t\t\tOctMethods.prompt.startCountdown();\n\t\t\t},\n\t\t\tchangeDirectory: function(data) {\n\t\t\t\tviewModel.cwd(data.dir);\n\t\t\t},\n\t\t\teditFile: function(data) {\n\t\t\t\tif (!data || !data.file) return;\n\t\t\t\tvar match = data.file.match(/^\\/home\\/[^/]+\\/(.*)$/);\n\t\t\t\tif (!match) return;\n\t\t\t\tvar filename = match[1];\n\t\t\t\tvar octfile = viewModel.getOctFileFromName(filename);\n\t\t\t\tif (!octfile) {\n\t\t\t\t\t// New file\n\t\t\t\t\toctfile = OctMethods.editor.create(filename);\n\t\t\t\t}\n\t\t\t\tif (octfile) {\n\t\t\t\t\toctfile.open();\n\t\t\t\t}\n\t\t\t},\n\t\t\tpayloadPaused: function(data){\n\t\t\t\tOctMethods.prompt.endCountdown();\n\t\t\t\tOctMethods.prompt.startPayloadTimer(data.delay);\n\n\t\t\t\t// Show the notification message after a small delay in order to let the output buffers flush first.\n\t\t\t\tsetTimeout(function(){\n\t\t\t\t\tOctMethods.console.writeError(\"\\n\" + oo_translations[\"console.payload#alert\"] + \"\\n\");\n\t\t\t\t}, parseInt(\"105!config.session.payloadMessageDelay\"));\n\t\t\t},\n\t\t\tinit: function(){\n\t\t\t\t// Regular session or shared session?\n\t\t\t\tif (OctMethods.vars.wsId) {\n\t\t\t\t\tOctMethods.socket.emit(\"init\", {\n\t\t\t\t\t\taction: \"workspace\",\n\t\t\t\t\t\tinfo: OctMethods.vars.wsId,\n\t\t\t\t\t\tskipCreate: OctMethods.socket.isExited,\n\t\t\t\t\t});\n\n\t\t\t\t}else if(OctMethods.vars.studentId){\n\t\t\t\t\tOctMethods.socket.emit(\"init\", {\n\t\t\t\t\t\taction: \"student\",\n\t\t\t\t\t\tinfo: OctMethods.vars.studentId,\n\t\t\t\t\t\tskipCreate: OctMethods.socket.isExited,\n\t\t\t\t\t});\n\n\t\t\t\t}else if (OctMethods.vars.bucketId){\n\t\t\t\t\tOctMethods.socket.emit(\"init\", {\n\t\t\t\t\t\taction: (viewModel.purpose() === \"bucket\") ? \"bucket\" : \"project\",\n\t\t\t\t\t\tinfo: OctMethods.vars.bucketId,\n\t\t\t\t\t\tsessCode: OctMethods.socket.sessCode,\n\t\t\t\t\t\tskipCreate: OctMethods.socket.isExited,\n\t\t\t\t\t});\n\n\t\t\t\t}else{\n\t\t\t\t\tOctMethods.socket.emit(\"init\", {\n\t\t\t\t\t\taction: \"session\",\n\t\t\t\t\t\tsessCode: OctMethods.socket.sessCode,\n\t\t\t\t\t\tskipCreate: OctMethods.socket.isExited,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// If skipCreate, hide the loader now.  (If a session is being made for us, wait to hide the loader until the session is ready.)\n\t\t\t\tif (OctMethods.socket.isExited) {\n\t\t\t\t\tOctMethods.load.hideLoader();\n\t\t\t\t}\n\t\t\t},\n\t\t\tfilesReady: function(message) {\n\t\t\t\t// hide the coverall loading div if necessary, and perform other initialization tasks\n\t\t\t\tOctMethods.load.callback(message);\n\t\t\t},\n\t\t\tdestroyu: function(message){\n\t\t\t\tOctMethods.console.writeError(oo_translations[\"console.exited#alert\"] + \" \" + message + \"\\n\");\n\n\t\t\t\tOctMethods.console.writeRestartBtn();\n\t\t\t\tOctMethods.socket.isExited = true;\n\t\t\t\tOctMethods.load.hideLoader();\n\n\t\t\t\t// Clean up UI\n\t\t\t\tOctMethods.prompt.disable();\n\t\t\t\tOctMethods.prompt.endCountdown();\n\t\t\t},\n\t\t\tdisconnect: function(){\n\t\t\t\tif (!OctMethods.socket.isExited) {\n\t\t\t\t\tOctMethods.console.writeError(oo_translations[\"console.reconnecting#alert\"] + \"\\n\");\n\t\t\t\t}\n\n\t\t\t\t// Clean up UI\n\t\t\t\tOctMethods.prompt.disable();\n\t\t\t\tOctMethods.prompt.endCountdown();\n\t\t\t\tOctMethods.load.showLoader();\n\t\t\t},\n\t\t},\n\n\t\t// Editor Methods\n\t\teditor: {\n\t\t\tinstance: null,\n\t\t\tdefaultFilename: \"my_script.m\",\n\t\t\tdefaultContent: \"disp(\\\"\" + oo_translations[\"newfile.helloworld\"] + \"\\\");\\n\",\n\t\t\trunning: false,\n\t\t\t// AuthUser is the user who is currently signed in; WsUser is the user who owns the currently loaded workspace.\n\t\t\tseenAuthUser: false,\n\t\t\tseenWsUser: false,\n\t\t\tbucketWarned: false,\n\t\t\tsave: function(octfile){\n\t\t\t\tif (viewModel.purpose() === \"bucket\" && !OctMethods.editor.bucketWarned) {\n\t\t\t\t\tutils.alert(oo_translations[\"console.readonly#alert@2\"]+\"\\n\\n\"+(viewModel.currentBucket()&&viewModel.currentBucket().shortlink()));\n\t\t\t\t\tOctMethods.editor.bucketWarned = true;\n\t\t\t\t}\n\t\t\t\treturn OctMethods.socket.save(octfile);\n\t\t\t},\n\t\t\tadd: function(filename, content){\n\t\t\t\tvar octfile = new OctFile(filename, content, true);\n\t\t\t\tallOctFiles.push(octfile);\n\t\t\t\tallOctFiles.sort(OctFile.sorter);\n\t\t\t\treturn octfile;\n\t\t\t},\n\t\t\taddNameOnly: function(filename){\n\t\t\t\tvar octfile = new OctFile(filename, \"\", false);\n\t\t\t\tallOctFiles.push(octfile);\n\t\t\t\tallOctFiles.sort(OctFile.sorter);\n\t\t\t\treturn octfile;\n\t\t\t},\n\t\t\tcreate: function(filename){\n\t\t\t\t// check to see if the file already exists\n\t\t\t\tif (viewModel.fileNameExists(filename)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\t// check for valid filename\n\t\t\t\tif(!OctFile.regexps.filename.test(filename)){\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tvar octfile = OctMethods.editor.add(\n\t\t\t\t\tfilename,\n\t\t\t\t\tOctMethods.editor.defaultContent);\n\t\t\t\tOctMethods.editor.save(octfile);\n\t\t\t\treturn octfile;\n\t\t\t},\n\t\t\tremove: function(octfile){\n\t\t\t\tallOctFiles.remove(octfile);\n\t\t\t},\n\t\t\tdeleteit: function(octfile){\n\t\t\t\treturn OctMethods.socket.deleteit(octfile);\n\t\t\t},\n\t\t\trun: function(octfile){\n\t\t\t\tvar cmd = octfile.command();\n\t\t\t\tif(!cmd) return false;\n\t\t\t\tOctMethods.console.command(cmd);\n\t\t\t\tOctMethods.editor.running = true;\n\t\t\t\tanal.runfile();\n\t\t\t\treturn true;\n\t\t\t},\n\t\t\trename: function(octfile){\n\t\t\t\tvar oldName = octfile.filename();\n\t\t\t\tvar newName = prompt(oo_translations[\"rename.label\"], oldName);\n\t\t\t\tif (!newName || oldName === newName) return false;\n\t\t\t\tif (viewModel.fileNameExists(newName)){\n\t\t\t\t\tutils.alert(oo_translations[\"rename.alert\"]);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn OctMethods.socket.rename(octfile, newName);\n\t\t\t},\n\t\t\tdownload: function(octfile){\n\t\t\t\t// Two cases: front-end text file or back-end binary file.\n\t\t\t\tif(octfile.editable){\n\t\t\t\t\t// If it's a text file, we can download it now\n\t\t\t\t\tvar mime = \"text/x-octave;charset=utf-8\";\n\t\t\t\t\tvar blob = new Blob([octfile.content()], { type: mime });\n\t\t\t\t\treturn download(blob, octfile.filename());\n\t\t\t\t}else{\n\t\t\t\t\t// If it's a binary file, we have to request it from the server\n\t\t\t\t\treturn OctMethods.socket.binary(octfile);\n\t\t\t\t}\n\t\t\t},\n\t\t\tprint: function(octfile){\n\t\t\t\t// Make a new window and a temporary document object\n\t\t\t\tvar w = window.open();\n\t\t\t\tvar doc = $(\"<div>\");\n\n\t\t\t\t// Add a title line\n\t\t\t\tvar h1 = $(\"<h1>\");\n\t\t\t\th1.append(octfile.filename());\n\t\t\t\th1.css(\"font\", \"bold 14pt/14pt 'Trebuchet MS',Verdana,sans-serif\");\n\t\t\t\th1.css(\"margin\", \"6pt\");\n\t\t\t\tdoc.append(h1);\n\n\t\t\t\t// Create the Ace highlighter\n\t\t\t\tvar highlight = aceStaticHighlight.render(\n\t\t\t\t\toctfile.content(),\n\t\t\t\t\tnew (require(\"ace/mode/octave\").Mode)(),\n\t\t\t\t\trequire(\"ace/theme/crimson_editor\")\n\t\t\t\t);\n\n\t\t\t\t// Create the Ace stylesheet\n\t\t\t\tvar ss = $(\"<style type='text/css'></style>\");\n\t\t\t\tss.append(highlight.css);\n\n\t\t\t\t// Append the Ace highlighter and stylesheet\n\t\t\t\tvar editorDiv = $(\"<div></div>\");\n\t\t\t\teditorDiv.append(highlight.html);\n\t\t\t\tdoc.append(ss);\n\t\t\t\tdoc.append(editorDiv);\n\n\t\t\t\t// Add a credit line at the bottom\n\t\t\t\tvar creditDiv = $(\"<div></div>\");\n\t\t\t\tcreditDiv.append(oo_translations[\"print.p1\"] + \" \" + (viewModel.authUser() || { name: \"Anonymous\" }).name);\n\t\t\t\tcreditDiv.append(\"<br/>\");\n\t\t\t\tcreditDiv.append(oo_translations[\"print.p2\"]);\n\t\t\t\tcreditDiv.append(\"<br/>\");\n\t\t\t\tcreditDiv.append(\"http://octave-online.net\");\n\t\t\t\tcreditDiv.css(\"font\", \"10pt/10pt 'Trebuchet MS',Verdana,sans-serif\");\n\t\t\t\tcreditDiv.css(\"text-align\", \"right\");\n\t\t\t\tcreditDiv.css(\"margin-top\", \"16pt\");\n\t\t\t\tdoc.append(creditDiv);\n\n\t\t\t\t// Add the document data to the window\n\t\t\t\tw.document.body.innerHTML += doc.html();\n\n\t\t\t\t// Trigger Print\n\t\t\t\tw.window.print();\n\t\t\t},\n\t\t\topen: function(octfile){\n\t\t\t\tviewModel.openFile(octfile);\n\t\t\t},\n\t\t\tclose: function(){\n\t\t\t\tviewModel.openFile(null);\n\t\t\t},\n\t\t\treset: function(){\n\t\t\t\tviewModel.openFile(null);\n\t\t\t\tallOctFiles.removeAll();\n\t\t\t},\n\t\t},\n\n\t\t// Editor Callback Functions\n\t\teditorListeners: {\n\t\t\tnewCB: function(){\n\t\t\t\tvar filename = OctMethods.editor.defaultFilename;\n\t\t\t\t// do..while to protect against duplicate file names\n\t\t\t\tdo{\n\t\t\t\t\tfilename = prompt(oo_translations[\"newfile.label\"], filename);\n\t\t\t\t} while(filename && !OctMethods.editor.create(filename));\n\t\t\t},\n\t\t\trefresh: function(){\n\t\t\t\tif(confirm(oo_translations[\"console.refresh#alert\"])){\n\t\t\t\t\tOctMethods.editor.reset();\n\t\t\t\t\tOctMethods.socket.refresh();\n\t\t\t\t}\n\t\t\t},\n\t\t\tinfo: function(){\n\t\t\t\tanal.sitecontrol(\"showfilehistory\");\n\t\t\t\t$(\"#file_history_box\").showSafe();\n\t\t\t},\n\t\t\trun: function(){\n\t\t\t\tOctMethods.editor.run(viewModel.openFile());\n\t\t\t},\n\t\t\tkeyRun: function(e){\n\t\t\t\te.preventDefault();\n\t\t\t\tif(viewModel.openFile()){\n\t\t\t\t\tOctMethods.editor.run(viewModel.openFile());\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tload: {\n\t\t\tfirstConnection: true,\n\t\t\tloaderVisible: true,\n\t\t\tbePatientTimeout: null,\n\t\t\tcallback: function(message){\n\t\t\t\tOctMethods.load.hideLoader();\n\t\t\t\tvar initCmd = \"\";\n\t\t\t\t// As soon as files are loaded for the first time, execute the .octaverc if it is present\n\t\t\t\t// GNU Octave normally does this automatically, but we pre-start the processes against a clean directory, so .octaverc is not present when GNU Octave starts up\n\t\t\t\t// Do this on the client so that the UI reflects that a command is being run\n\t\t\t\t// Do this every time we receive a files-ready command, indicating that a new session has started\n\t\t\t\tif (message && message.hasOctaverc) {\n\t\t\t\t\tinitCmd += \"source(\\\".octaverc\\\"); \";\n\t\t\t\t}\n\t\t\t\tif(OctMethods.load.firstConnection){\n\t\t\t\t\tOctMethods.load.firstConnection = false;\n\n\t\t\t\t\t// UI setup\n\t\t\t\t\t$(\"#type_here\").showSafe();\n\t\t\t\t\t$(\"#agpl_icon\").showSafe();\n\t\t\t\t\t$(\"#plot_opener\").hideSafe();\n\t\t\t\t\t$(\"#vars_panel\").showSafe();\n\n\t\t\t\t\t// Initial bucket command\n\t\t\t\t\tif (viewModel.currentBucket() && viewModel.currentBucket().mainFilename() && viewModel.currentBucket().mainFilename() !== \".octaverc\") {\n\t\t\t\t\t\tinitCmd += \"source(\\\"\" + viewModel.currentBucket().mainFilename() + \"\\\"); \";\n\t\t\t\t\t}\n\n\t\t\t\t\t// Evaluate the query string command\n\t\t\t\t\ttry{\n\t\t\t\t\t\tvar hashParams = new URLSearchParams(new URL(window.location.href).hash.slice(1));\n\t\t\t\t\t\tvar purlCmd = hashParams.get(\"cmd\");\n\t\t\t\t\t\tif (purlCmd) initCmd += purlCmd;\n\t\t\t\t\t}catch(e){\n\t\t\t\t\t\tconsole.log(e);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif(initCmd){\n\t\t\t\t\tOctMethods.console.command(initCmd);\n\t\t\t\t}\n\t\t\t},\n\t\t\tshowLoader: function(){\n\t\t\t\tif (OctMethods.load.loaderVisible) return;\n\t\t\t\tOctMethods.load.loaderVisible = true;\n\t\t\t\t$(\"#site_loading\").showSafe();\n\t\t\t},\n\t\t\thideLoader: function(){\n\t\t\t\tif (!OctMethods.load.loaderVisible) return;\n\t\t\t\tOctMethods.load.loaderVisible = false;\n\t\t\t\tOctMethods.load.stopPatience();\n\t\t\t\t$(\"#site_loading\").fadeOutSafe(500);\n\t\t\t},\n\t\t\tstartPatience: function(){\n\t\t\t\tOctMethods.load.stopPatience();\n\t\t\t\tOctMethods.load.bePatientTimeout = setTimeout(function(){\n\t\t\t\t\t$(\"#site_loading_patience\").showSafe();\n\t\t\t\t\tanal.patience();\n\t\t\t\t\tOctMethods.load.bePatientTimeout = setTimeout(function(){\n\t\t\t\t\t\t$(\"#site_loading_patience\").hideSafe();\n\t\t\t\t\t\t$(\"#site_loading_more_patience\").showSafe();\n\t\t\t\t\t\tOctMethods.load.bePatientTimeout = null;\n\t\t\t\t\t}, 35000);\n\t\t\t\t}, 10000);\n\t\t\t},\n\t\t\tstopPatience: function(){\n\t\t\t\t$(\"#site_loading_patience\").hideSafe();\n\t\t\t\t$(\"#site_loading_more_patience\").hideSafe();\n\t\t\t\tif (!OctMethods.load.bePatientTimeout) return;\n\t\t\t\tclearTimeout(OctMethods.load.bePatientTimeout);\n\t\t\t\tOctMethods.load.bePatientTimeout = null;\n\t\t\t}\n\t\t},\n\n\t\t// Other accessor properties\n\t\tko: {\n\t\t\tviewModel: viewModel,\n\t\t\tallOctFiles: allOctFiles,\n\t\t\tavailableSkins: availableSkins\n\t\t},\n\t\tvars: {\n\t\t\twsId: null,\n\t\t\tstudentId: null,\n\t\t\tbucketId: null,\n\t\t}\n\t};\n\n\tviewModel.countdownExtraTimeSeconds(OctMethods.prompt.countdownExtraTime/1000);\n\n\t// Expose\n\texports.console = OctMethods.console;\n\texports.prompt = OctMethods.prompt;\n\texports.promptListeners = OctMethods.promptListeners;\n\texports.plot = OctMethods.plot;\n\texports.socket = OctMethods.socket;\n\texports.socketListeners = OctMethods.socketListeners;\n\texports.editor = OctMethods.editor;\n\texports.editorListeners = OctMethods.editorListeners;\n\texports.load = OctMethods.load;\n\texports.ko = OctMethods.ko;\n\texports.vars = OctMethods.vars;\n\n}); // AMD Define\n"
  },
  {
    "path": "client/app/js/detectmobilebrowser.js",
    "content": "/* eslint-disable */\n// based on http://detectmobilebrowsers.com/\ndefine(function(){\n\tvar ua = navigator.userAgent || navigator.vendor || window.opera;\n\tvar regex1 = /(android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i;\n\tvar regex2 = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\\-(n|u)|c55\\/|capi|ccwa|cdm\\-|cell|chtm|cldc|cmd\\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\\-s|devi|dica|dmob|do(c|p)o|ds(12|\\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\\-|_)|g1 u|g560|gene|gf\\-5|g\\-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd\\-(m|p|t)|hei\\-|hi(pt|ta)|hp( i|ip)|hs\\-c|ht(c(\\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\\-(20|go|ma)|i230|iac( |\\-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc\\-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|\\-[a-w])|libw|lynx|m1\\-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m\\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\\-2|po(ck|rt|se)|prox|psio|pt\\-g|qa\\-a|qc(07|12|21|32|60|\\-[2-7]|i\\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\\-|oo|p\\-)|sdk\\/|se(c(\\-|0|1)|47|mc|nd|ri)|sgh\\-|shar|sie(\\-|m)|sk\\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\\-|v\\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\\-|tdg\\-|tel(i|m)|tim\\-|t\\-mo|to(pl|sh)|ts(70|m\\-|m3|m5)|tx\\-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\\-|your|zeto|zte\\-/i;\n\treturn regex1.test(ua) || regex2.test(ua.substr(0,4));\n});"
  },
  {
    "path": "client/app/js/download.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine([\"filesaver\"], function(saveAs){\n\t// FileSaver does not support IE9.\n\treturn function(blob, filename){\n\t\tif(!saveAs(blob, filename)){\n\t\t\talert(\"File download is unfortunately not supported in your \" +\n\t\t\t\"browser.\\n\\nConsider taking a screenshot to save your plot \" +\n\t\t\t\"or manually copy/paste your script file into an editor.\");\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t};\n});\n"
  },
  {
    "path": "client/app/js/flex-resize.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine([\"jquery\", \"knockout\"], function($, ko){\n\tvar active = false, obsArr, xRef, index, element;\n\tfunction cbmove(e){\n\t\tif (!active) return;\n\t\te.preventDefault();\n\t\tvar arr = obsArr();\n\t\tvar i, j;\n\n\t\t// Try to be a little bit smart as to how much of a jump we need.\n\t\t// This calculation is an approximation only.  The second section of\n\t\t// this function will fix any errors.\n\t\tvar windowWidth = $(window).width();\n\t\tvar flexWidth = arr.reduce(function(s,x){ return s+x; }, 0);\n\t\tvar dist = e.pageX - $(element).offset().left - xRef;\n\t\tvar jump = Math.round(windowWidth/flexWidth*dist);\n\t\tvar m;\n\t\tif(jump > 0){\n\t\t\t// Move Right\n\t\t\tfor(i=index; jump>0 && i<arr.length; i++){\n\t\t\t\tm = Math.min(jump, arr[i]);\n\t\t\t\tarr[index-1] += m;\n\t\t\t\tarr[i] -= m;\n\t\t\t\tjump -= m;\n\t\t\t}\n\t\t}else if(jump < 0){\n\t\t\t// Move Left\n\t\t\tfor(i=index-1; jump<0 && i>-1; i--){\n\t\t\t\tm = Math.min(Math.abs(jump), arr[i]);\n\t\t\t\tarr[i] -= m;\n\t\t\t\tarr[index] += m;\n\t\t\t\tjump += m;\n\t\t\t}\n\t\t}\n\t\tobsArr.valueHasMutated();\n\n\t\t// Now do fine-tuning\n\t\t// Limit to 25 iterations to help prevent infinite loops\n\t\t// Move Right\n\t\tfor(i=0; e.pageX - $(element).offset().left > xRef && i<25; i++){\n\t\t\tfor(j=index; arr[j]===0 && j<arr.length; j++){/* no-op */}\n\t\t\tif (j===arr.length) break;\n\n\t\t\tarr[index-1] += 1;\n\t\t\tarr[j] -= 1;\n\t\t\tobsArr.valueHasMutated();\n\t\t}\n\t\t// Move Left\n\t\tfor(i=0; e.pageX - $(element).offset().left < xRef && i<25; i++){\n\t\t\tfor(j=index-1; arr[j]===0 && j>-1; j--){/* no-op */}\n\t\t\tif (j===-1) break;\n\n\t\t\tarr[j] -= 1;\n\t\t\tarr[index] += 1;\n\t\t\tobsArr.valueHasMutated();\n\t\t}\n\n\t\t// Update the reference point\n\t\txRef = e.pageX - $(element).offset().left;\n\t}\n\tfunction cbdown(e, _obsArr){\n\t\tactive = true;\n\t\tobsArr = _obsArr;\n\t\txRef = e.pageX - $(this).offset().left;\n\t\tindex = $(this).data(\"index\");\n\t\telement = this;\n\t\t$(document).on(\"mousemove\", cbmove);\n\t\t$(document).on(\"touchmove\", cbmove);\n\t\t$(document).on(\"mouseup\", cbup);\n\t\t$(document).on(\"touchend\", cbup);\n\t}\n\tfunction cbup(){\n\t\tactive = false;\n\t\t$(document).off(\"mousemove\", cbmove);\n\t\t$(document).off(\"touchmove\", cbmove);\n\t\t$(document).off(\"mouseup\", cbup);\n\t\t$(document).off(\"touchend\", cbup);\n\n\t\t// Fire a window \"resize\" event to make sure everything adjusts,\n\t\t// like the ACE editor\n\t\tvar evt = document.createEvent(\"UIEvents\");\n\t\tevt.initUIEvent(\"resize\", true, false, window, 0);\n\t\twindow.dispatchEvent(evt);\n\t}\n\n\tko.bindingHandlers.resizeFlex = {\n\t\tinit: function(element, valueAccessor /*, allBindings, viewModel, bindingContext */) {\n\t\t\tvar obj = valueAccessor();\n\t\t\tvar index = ko.utils.unwrapObservable(obj.index);\n\t\t\tif (index === 0) return;\n\t\t\tvar handle = $(\"<div>\");\n\t\t\thandle.addClass(\"handle\");\n\t\t\thandle.data(\"index\", index);\n\t\t\thandle.on(\"mousedown\", function(e){\n\t\t\t\tcbdown.call(handle, e, obj.group);\n\t\t\t});\n\t\t\thandle.on(\"touchstart\", function(e){\n\t\t\t\tcbdown.call(handle, e, obj.group);\n\t\t\t});\n\t\t\twindow.viewModel.flex.shown.subscribe(function(newValue){\n\t\t\t\thandle.toggleSafe(newValue);\n\t\t\t});\n\t\t\thandle.hideSafe();\n\t\t\t$(element).append(handle);\n\t\t},\n\t\tupdate: function(element, valueAccessor /*, allBindings, viewModel, bindingContext */) {\n\t\t\tvar obj = valueAccessor();\n\t\t\tvar sizes = ko.utils.unwrapObservable(obj.group);\n\t\t\tvar index = ko.utils.unwrapObservable(obj.index);\n\t\t\tvar styleString = sizes[index] + \"px\";\n\t\t\tif ($(element).css(\"flex-basis\") !== styleString) {\n\t\t\t\t$(element).css(\"flex-basis\", styleString);\n\t\t\t}\n\t\t}\n\t};\n\n\t// This part of the code is not portable\n\t$(document).ready(function(){\n\t\t$(document).on(\"mouseover\", function(e){\n\t\t\tif (!window.viewModel) return;\n\t\t\twindow.viewModel.flex.shown(\n\t\t\t\tactive\n\t\t\t\t|| $(e.target).attr(\"data-hover\") === \"flex\"\n\t\t\t\t|| $(e.target).hasClass(\"handle\"));\n\t\t});\n\t});\n});"
  },
  {
    "path": "client/app/js/ko-ace.js",
    "content": "// Much of this file originates from https://github.com/probonogeek/knockout-ace/blob/master/knockout-ace.js\n\ndefine([\"knockout\", \"ace/ace\"], function(ko, ace){\n\n\tko.bindingHandlers.ace = {\n\t\tinit: function(element, valueAccessor, allBindings, viewModel, bindingContext) {\n\t\t\tvar obj = ko.utils.unwrapObservable(valueAccessor());\n\t\t\tvar text = ko.utils.unwrapObservable(obj.text);\n\t\t\tvar skin = ko.utils.unwrapObservable(obj.skin);\n\t\t\tvar wrap = ko.utils.unwrapObservable(obj.wrap);\n\t\t\tvar octfile = ko.utils.unwrapObservable(obj.octfile);\n\n\t\t\t// Make the editor\n\t\t\tvar editor = ace.edit(element.id);\n\t\t\teditor.setTheme(skin.aceTheme);\n\t\t\teditor.getSession().setMode(\"ace/mode/octave\");\n\t\t\teditor.getSession().setUseWrapMode(wrap);\n\t\t\teditor.setValue(text);\n\t\t\teditor.gotoLine(0);\n\t\t\teditor.commands.addCommand({\n\t\t\t\tname: \"save\",\n\t\t\t\tbindKey: { mac: \"Command-S\", win: \"Ctrl-S\" },\n\t\t\t\texec: octfile.save,\n\t\t\t\treadOnly: false\n\t\t\t});\n\t\t\teditor.setOptions({ enableBasicAutocompletion: true });\n\t\t\teditor.setBehavioursEnabled(false);\n\t\t\teditor.focus();\n\t\t\tsetTimeout(editor.resize.bind(editor), 0);\n\n\t\t\t// Bind to Knockout changes\n\t\t\tvar onAceChange = function(){\n\t\t\t\tvar _obj = valueAccessor();\n\t\t\t\tif (ko.isWriteableObservable(_obj.text)) {\n\t\t\t\t\t_obj.text(editor.getValue());\n\t\t\t\t}\n\t\t\t};\n\t\t\teditor.getSession().on(\"change\", onAceChange);\n\n\t\t\t// Attach OT to the editor instance\n\t\t\tvar otClient = octfile.getOtClient();\n\t\t\tif (otClient) otClient.attachEditor(editor);\n\n\t\t\t// Save reference\n\t\t\tif (bindingContext.editor) {\n\t\t\t\tconsole.log(\"WARNING: bindingContext.editor already set\");\n\t\t\t}\n\t\t\tbindingContext.editor = editor;\n\n\t\t\t// Destroy Handler\n\t\t\tko.utils.domNodeDisposal.addDisposeCallback(element, function() {\n\t\t\t\teditor.getSession().off(\"change\", onAceChange);\n\t\t\t\tif (otClient) otClient.attachEditor(null);\n\t\t\t\tdelete bindingContext.editor;\n\t\t\t\teditor.destroy();\n\t\t\t});\n\t\t},\n\t\tupdate: function(element, valueAccessor, allBindings, viewModel, bindingContext) {\n\t\t\tvar obj = ko.utils.unwrapObservable(valueAccessor());\n\t\t\tvar text = ko.utils.unwrapObservable(obj.text);\n\t\t\tvar skin = ko.utils.unwrapObservable(obj.skin);\n\t\t\tvar wrap = ko.utils.unwrapObservable(obj.wrap);\n\t\t\tvar editor = bindingContext.editor;\n\n\t\t\tif(editor.getValue() !== text){\n\t\t\t\teditor.setValue(text);\n\t\t\t\teditor.gotoLine(0);\n\t\t\t}\n\n\t\t\tif(editor.getTheme() !== skin.aceTheme){\n\t\t\t\teditor.setTheme(skin.aceTheme);\n\t\t\t}\n\n\t\t\tif(editor.getSession().getUseWrapMode() !== wrap){\n\t\t\t\teditor.getSession().setUseWrapMode(wrap);\n\t\t\t}\n\t\t}\n\t};\n});\n"
  },
  {
    "path": "client/app/js/ko-flash.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine(\"ko-flash\", [\"knockout\", \"jquery\"], function(ko, $){\n\tko.bindingHandlers.flash = {\n\t\tinit: function(element /*, valueAccessor, allBindings, viewModel, bindingContext */) {\n\t\t\t// This will be called when the binding is first applied to an element\n\t\t\t// Set up any initial state, event handlers, etc. here\n\t\t\t\n\t\t\t$(element).addClass(\"transition-property-bgcolor\");\n\t\t},\n\t\tupdate: function(element, valueAccessor /*, allBindings, viewModel, bindingContext */) {\n\t\t\t// This will be called once when the binding is first applied to an element,\n\t\t\t// and again whenever any observables/computeds that are accessed change\n\t\t\t// Update the DOM element based on the supplied values here.\n\t\t\t\n\t\t\t// Prevent this handler from being garbage collected\n\t\t\t$(element).attr(\"dummy-attribute\", ko.unwrap(valueAccessor()));\n\n\t\t\t$(element).removeClass(\"transition-duration-medium\");\n\t\t\t$(element).addClass(\"transition-duration-instant\");\n\t\t\t$(element).addClass(\"ko-flash\");\n\t\t\tsetTimeout(function(){\n\t\t\t\t$(element).removeClass(\"transition-duration-instant\");\n\t\t\t\t$(element).addClass(\"transition-duration-medium\");\n\t\t\t\t$(element).removeClass(\"ko-flash\");\n\t\t\t}, 500);\n\t\t}\n\t};\n});"
  },
  {
    "path": "client/app/js/ko-takeArray.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine(\"ko-takeArray\", [\"knockout\"], function(ko){\n\treturn function(ObsClass, obsArr, objKey, dataArr, dataKey){\n\t\tvar obsKeys = ko.utils.arrayMap(obsArr(), function(v){\n\t\t\treturn v[objKey]();\n\t\t});\n\t\tvar dataKeys = ko.utils.arrayMap(dataArr, function(v){\n\t\t\treturn v[dataKey];\n\t\t});\n\n\t\tvar intersection = [];\n\t\tvar obsOnly = [];\n\t\tvar dataOnly = [];\n\n\t\tko.utils.arrayForEach(obsKeys, function(k, i1){\n\t\t\tvar i2 = dataKeys.indexOf(k);\n\t\t\tif(i2 !== -1){\n\t\t\t\t// Found a Match\n\t\t\t\tintersection.push([obsArr()[i1], dataArr[i2]]);\n\t\t\t}else{\n\t\t\t\t// Item to Remove\n\t\t\t\tobsOnly.push(obsArr()[i1]);\n\t\t\t}\n\t\t});\n\t\tko.utils.arrayForEach(dataKeys, function(k, i2){\n\t\t\tvar i1 = obsKeys.indexOf(k);\n\t\t\tif(i1 === -1){\n\t\t\t\t// Item to Add\n\t\t\t\tdataOnly.push(dataArr[i2]);\n\t\t\t}\n\t\t});\n\n\t\t// Update the Matches\n\t\tko.utils.arrayForEach(intersection, function(vv){\n\t\t\tvar obs = vv[0], dat = vv[1];\n\t\t\tobs.take(dat);\n\t\t});\n\n\t\t// Remove expired values\n\t\tko.utils.arrayForEach(obsOnly, function(obs){\n\t\t\tobsArr.remove(obs);\n\t\t});\n\n\t\t// Add new values\n\t\tko.utils.arrayForEach(dataOnly, function(dat){\n\t\t\tvar obs = new ObsClass();\n\t\t\tobs.take(dat);\n\t\t\tobsArr.push(obs);\n\t\t});\n\t};\n});"
  },
  {
    "path": "client/app/js/modernizr-201406b.js",
    "content": "/* eslint-disable */\n/* Modernizr 2.6.2 (Custom Build) | MIT & BSD\n * Build: http://modernizr.com/download/#-fontface-canvas-svg-teststyles\n */\n;window.Modernizr=function(a,b,c){function v(a){i.cssText=a}function w(a,b){return v(prefixes.join(a+\";\")+(b||\"\"))}function x(a,b){return typeof a===b}function y(a,b){return!!~(\"\"+a).indexOf(b)}function z(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:x(f,\"function\")?f.bind(d||b):f}return!1}var d=\"2.6.2\",e={},f=b.documentElement,g=\"modernizr\",h=b.createElement(g),i=h.style,j,k={}.toString,l={svg:\"http://www.w3.org/2000/svg\"},m={},n={},o={},p=[],q=p.slice,r,s=function(a,c,d,e){var h,i,j,k,l=b.createElement(\"div\"),m=b.body,n=m||b.createElement(\"body\");if(parseInt(d,10))while(d--)j=b.createElement(\"div\"),j.id=e?e[d]:g+(d+1),l.appendChild(j);return h=[\"&#173;\",'<style id=\"s',g,'\">',a,\"</style>\"].join(\"\"),l.id=g,(m?l:n).innerHTML+=h,n.appendChild(l),m||(n.style.background=\"\",n.style.overflow=\"hidden\",k=f.style.overflow,f.style.overflow=\"hidden\",f.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),f.style.overflow=k),!!i},t={}.hasOwnProperty,u;!x(t,\"undefined\")&&!x(t.call,\"undefined\")?u=function(a,b){return t.call(a,b)}:u=function(a,b){return b in a&&x(a.constructor.prototype[b],\"undefined\")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!=\"function\")throw new TypeError;var d=q.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(q.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(q.call(arguments)))};return e}),m.canvas=function(){var a=b.createElement(\"canvas\");return!!a.getContext&&!!a.getContext(\"2d\")},m.fontface=function(){var a;return s('@font-face {font-family:\"font\";src:url(\"https://\")}',function(c,d){var e=b.getElementById(\"smodernizr\"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||\"\":\"\";a=/src/i.test(g)&&g.indexOf(d.split(\" \")[0])===0}),a},m.svg=function(){return!!b.createElementNS&&!!b.createElementNS(l.svg,\"svg\").createSVGRect};for(var A in m)u(m,A)&&(r=A.toLowerCase(),e[r]=m[A](),p.push((e[r]?\"\":\"no-\")+r));return e.addTest=function(a,b){if(typeof a==\"object\")for(var d in a)u(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b==\"function\"?b():b,typeof enableClasses!=\"undefined\"&&enableClasses&&(f.className+=\" \"+(b?\"\":\"no-\")+a),e[a]=b}return e},v(\"\"),h=j=null,e._version=d,e.testStyles=s,e}(this,this.document);\n"
  },
  {
    "path": "client/app/js/octfile.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine([\n\t\"knockout\", \"require\", \"js/ws-shared\", \"jquery\",\n\t\"jquery.md5\"], function(ko, require, WsShared, $){\n\n\tvar OctMethods = require(\"js/client\");\n\n\t// OctFile MVVM class\n\tfunction OctFile(filename, content, editable){\n\t\t// the \"self\" variable enables us to refer to the OctFile context even when\n\t\t// we are programming within callback function contexts\n\t\tvar self = this;\n\n\t\t// Main Bindings\n\t\tself.filename = ko.observable(filename);\n\t\tself.content = ko.observable(content);\n\t\tself.editable = editable;\n\n\t\t// Identifier: needs to be URL hash safe\n\t\tself.identifier = ko.computed(function(){\n\t\t\t// Replace all nonalphanumeric characters with underscores\n\t\t\treturn self.filename().replace(/\\W/g, \"_\");\n\t\t});\n\t\tself.hash = ko.computed(function(){\n\t\t\treturn \"#\"+self.identifier();\n\t\t});\n\n\t\t// Methods relating to running the file\n\t\tself.baseName = ko.computed(function(){\n\t\t\tvar nameMatch = OctFile.regexps.functionname.exec(self.filename());\n\t\t\tif (!nameMatch || nameMatch.length < 1) return false;\n\t\t\treturn nameMatch[1];\n\t\t});\n\t\tself.isFunction = ko.computed(function(){\n\t\t\treturn OctFile.regexps.isFunction.test(self.content());\n\t\t});\n\t\tself.getFunctionParameters = ko.computed(function(){\n\t\t\tif(!self.isFunction()) return false;\n\t\t\tvar match = OctFile.regexps.matchParameters.exec(self.content());\n\t\t\tif(!match || match[1]===\"\") return [];\n\t\t\treturn match[1].split(/\\s*,\\s*/);\n\t\t});\n\t\tvar argumentsStore = [];\n\t\tself.command = function(){\n\t\t\tif (!self.runnable()) return false;\n\t\t\tvar parameters = self.getFunctionParameters();\n\t\t\tvar baseName = self.baseName();\n\t\t\tif (parameters) {\n\t\t\t\tvar arg;\n\t\t\t\tfor(var i=0; i<parameters.length; i++){\n\t\t\t\t\tif(typeof argumentsStore[i] === \"undefined\"){\n\t\t\t\t\t\targumentsStore.push(\"\");\n\t\t\t\t\t}\n\t\t\t\t\targ = argumentsStore[i];\n\t\t\t\t\tif((arg=prompt(parameters[i]+\" = ?\", arg))===null){\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\targumentsStore[i] = arg;\n\t\t\t\t}\n\t\t\t\treturn (self.dirpart()?\"source(\\\"\"+self.filename()+\"\\\"); \":\"\")+baseName+\"(\"+argumentsStore.join(\", \")+\")\";\n\t\t\t} else {\n\t\t\t\treturn \"source(\\\"\"+self.filename()+\"\\\")\";\n\t\t\t}\n\t\t};\n\t\tself.runnable = ko.computed(function(){\n\t\t\treturn OctFile.regexps.filename.test(self.filename());\n\t\t});\n\n\t\t// Display functions\n\t\tself.dirpart = ko.computed(function(){\n\t\t\tvar slashIndex = self.filename().lastIndexOf(\"/\");\n\t\t\tif (slashIndex === -1) return \"\";\n\t\t\treturn self.filename().substring(0, slashIndex + 1);\n\t\t});\n\t\tself.filepart = ko.computed(function(){\n\t\t\tvar slashIndex = self.filename().lastIndexOf(\"/\");\n\t\t\tif (slashIndex === -1) return self.filename();\n\t\t\treturn self.filename().substring(slashIndex + 1);\n\t\t});\n\n\t\t// Open file in the editor\n\t\tself.open = function(){\n\t\t\tOctMethods.editor.open(self);\n\t\t};\n\n\t\t// Toolbar functions\n\t\tself.runit = function(){\n\t\t\tif(!OctMethods.editor.run(self)){\n\t\t\t\talert(\"You can only run Octave *.m files. :-)\");\n\t\t\t}\n\t\t};\n\t\tself.deleteit = function(){\n\t\t\tif(confirm(\"You are about to delete the following file. After deleting the file, you may still be able to recover it from the file revision history viewer.\\n\\n\"+self.filename())){\n\t\t\t\tif(!OctMethods.editor.deleteit(self)){\n\t\t\t\t\talert(\"Can't delete file. Did you disconnect from \" +\n\t\t\t\t\t\"Octave Online?\");\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tself.savedContent = ko.observable(content);\n\t\tself.save = function(){\n\t\t\tif(!OctMethods.editor.save(self)){\n\t\t\t\talert(\"Can't save file. Did you disconnect from Octave Online?\");\n\t\t\t}\n\t\t};\n\t\tself.md5 = function(){\n\t\t\treturn $.md5(self.content());\n\t\t};\n\t\tself.print = function(){\n\t\t\tOctMethods.editor.print(self);\n\t\t};\n\t\tself.rename = function(){\n\t\t\tOctMethods.editor.rename(self);\n\t\t};\n\t\tself.download = function(){\n\t\t\tOctMethods.editor.download(self);\n\t\t};\n\t\tself.share = function(){\n\t\t\tOctMethods.ko.viewModel.startNewBucket(self);\n\t\t};\n\t\tself.isActive = ko.computed(function(){\n\t\t\treturn self === OctMethods.ko.viewModel.openFile();\n\t\t});\n\t\tself.isModified = ko.computed(function(){\n\t\t\treturn self.content() !== self.savedContent();\n\t\t});\n\t\tself.buttonsShown = ko.observable(true);\n\t\tself.buttonsShown.subscribe(function(){\n\t\t\t// Fire the \"resize\" event here so that the Ace editor redraws itself.\n\t\t\t// This is probably not the most efficient way to achieve that end goal.\n\t\t\t// Do it in a setTimeout so that the other buttonsShown callbacks finish\n\t\t\t// first.\n\t\t\tsetTimeout(function(){\n\t\t\t\tvar evt = document.createEvent(\"UIEvents\");\n\t\t\t\tevt.initUIEvent(\"resize\", true, false, window, 0);\n\t\t\t\twindow.dispatchEvent(evt);\n\t\t\t}, 0);\n\t\t});\n\t\tself.wrap = ko.observable(true);\n\n\t\tself.getOtClient = function(){\n\t\t\treturn WsShared.clientForFilename(self.filename());\n\t\t};\n\n\t\t// toString method\n\t\tself.toString = function(){\n\t\t\treturn \"[File:\"+self.filename()+\" \"+self.content()+\"]\";\n\t\t};\n\t}\n\tOctFile.sorter = function(a, b){\n\t\treturn a.filename() === b.filename() ? 0 : (\n\t\t\ta.filename() < b.filename() ? -1 : 1\n\t\t);\n\t};\n\tOctFile.regexps = {};\n\tOctFile.regexps.isFunction = /^(?:[\\t\\f ]*(?:[\\%\\#].*)?\\n)*\\s*function\\s/;\n\tOctFile.regexps.matchParameters = /function[^\\(]+\\(\\s*([^\\)]*?)\\s*\\)/;\n\tOctFile.regexps.filename = /^(?:\\.?[^\\/\\.\\0]+\\/){0,3}\\.?[^\\/\\.\\0]+[^\\/\\0]*$/;\n\tOctFile.regexps.functionname = /([^\\/\\.\\0]+)\\.m$/;\n\n\t// Expose interface\n\treturn OctFile;\n\n});\n"
  },
  {
    "path": "client/app/js/onboarding.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Handle the \"onboarding\" demonstration div\n\ndefine([\"jquery\", \"js/anal\", \"jquery.cookie\", \"js/utils\"], function($, anal){\n\tvar $onboarding = $(\"#onboarding\"),\n\t\t$announcement = $(\"#announcement-box\"),\n\t\t$scriptPromo = $(\"#login-promo\"),\n\t\t$instructorPromo = $(\"#instructor-promo\"),\n\t\t$syncPromo = $(\"#sync-promo\"),\n\t\t$sharePromo = $(\"#share-promo\"),\n\t\t$createBucketPromo = $(\"#create-bucket-promo\"),\n\t\t$bucketPromo = $(\"#bucket-promo\"),\n\t\tMIN_TIME = 1000;\n\n\tvar announcementDisplay = $announcement.data(\"announcementDisplay\");\n\tvar showAnnouncement = function() {\n\t\t$announcement.fadeInSafe(500);\n\t};\n\n\tif ($onboarding.length) {\n\t\t// Check for the onboarding cookie now\n\t\tif ($.cookie(\"oo_onboarding_complete\") === \"true\") {\n\t\t\t// Case 1: Returning user who has completed onboarding\n\t\t\t// Hide the onboarding dialog and show the announcement if required\n\t\t\t$onboarding.fadeOutSafe(500);\n\t\t\tif (announcementDisplay === \"on\" || announcementDisplay === \"returning\") {\n\t\t\t\tshowAnnouncement();\n\t\t\t}\n\t\t} else {\n\t\t\t// Case 2: New user who has not completed onboarding\n\t\t\t// Set event listeners for the onboarding div\n\t\t\t// Show the announcement after the user dismisses the onboarding\n\t\t\t$onboarding.find(\"[data-purpose='close']\").click(function(){\n\t\t\t\t$.cookie(\"oo_onboarding_complete\", \"true\", {\n\t\t\t\t\texpires: MIN_TIME\n\t\t\t\t});\n\t\t\t\tif (announcementDisplay === \"on\") {\n\t\t\t\t\tshowAnnouncement();\n\t\t\t\t}\n\t\t\t\tanal.dismiss(\"Welcome Message\");\n\t\t\t});\n\t\t}\n\t} else {\n\t\t// Case 3: Onboarding is disabled\n\t\t// Show the announcement if required\n\t\tif (!$onboarding.length && announcementDisplay === \"on\") {\n\t\t\tshowAnnouncement();\n\t\t}\n\t}\n\n\t// Set up the script promo onboarding\n\tif(!$.cookie(\"oo_script_promo_dismissed\")){\n\t\t$scriptPromo.showSafe();\n\t\t$scriptPromo.find(\"[data-purpose='close']\").click(function(){\n\t\t\t// Make this cookie expire on browser being closed\n\t\t\t$.cookie(\"oo_script_promo_dismissed\", \"true\");\n\t\t\tanal.dismiss(\"Sign in for Scripts\");\n\t\t\t$scriptPromo.fadeOutSafe(500);\n\t\t});\n\t}\n\n\t// Set up the instructor promo onboarding\n\tif(!$.cookie(\"oo_instructor_promo_dismissed\")){\n\t\t$instructorPromo.showSafe();\n\t\t$instructorPromo.find(\"[data-purpose='close']\").click(function(){\n\t\t\t$.cookie(\"oo_instructor_promo_dismissed\", \"true\", {\n\t\t\t\texpires: MIN_TIME\n\t\t\t});\n\t\t\tanal.dismiss(\"Instructors\");\n\t\t\t$instructorPromo.fadeOutSafe(500);\n\t\t});\n\t}\n\n\t// Set the listener for create-bucket\n\tvar createBucketPromoShown = false;\n\t$createBucketPromo.find(\"[data-purpose='close']\").click(function(){\n\t\t$.cookie(\"oo_create_bucket_promo_dismissed\", \"true\", {\n\t\t\texpires: MIN_TIME\n\t\t});\n\t\tanal.dismiss(\"Create Bucket\");\n\t\t$createBucketPromo.fadeOutSafe(500);\n\t\tcreateBucketPromoShown = false;\n\t});\n\n\t// Expose an API\n\tvar onboarding = {\n\t\treset: function(){\n\t\t\t// Delete the cookie\n\t\t\t$.cookie(\"oo_onboarding_complete\", null);\n\t\t},\n\t\tshowUserPromo: function(data) {\n\t\t\t// Show tier upgrade screen the first time the user makes a pledge\n\t\t\tif (data.patreon && data.patreon.currently_entitled_amount_cents > 0) {\n\t\t\t\tif(!$.cookie(\"oo_new_upgrade_dismissed\")){\n\t\t\t\t\t$(\"#upgrade_to_tier\").showSafe();\n\t\t\t\t\t$.cookie(\"oo_new_upgrade_dismissed\", \"true\", {\n\t\t\t\t\t\texpires: MIN_TIME\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tshowSyncPromo: function(){\n\t\t\t// Set up the Octave Online Sync onboarding\n\t\t\tif(!$.cookie(\"oo_sync_promo_dismissed\")){\n\t\t\t\t$syncPromo.fadeInSafe(500);\n\t\t\t\t$syncPromo.find(\"[data-purpose='close']\").click(function(){\n\t\t\t\t\t$.cookie(\"oo_sync_promo_dismissed\", \"true\", {\n\t\t\t\t\t\texpires: MIN_TIME\n\t\t\t\t\t});\n\t\t\t\t\tanal.dismiss(\"Octave Online Sync\");\n\t\t\t\t\t$syncPromo.fadeOutSafe(500);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Also use this function to set up the Share onboarding\n\t\t\tif(!$.cookie(\"oo_share_promo_dismissed\")){\n\t\t\t\t$sharePromo.fadeInSafe(500);\n\t\t\t\t$sharePromo.find(\"[data-purpose='close']\").click(function(){\n\t\t\t\t\t$.cookie(\"oo_share_promo_dismissed\", \"true\", {\n\t\t\t\t\t\texpires: MIN_TIME\n\t\t\t\t\t});\n\t\t\t\t\tanal.dismiss(\"Set up Sharing\");\n\t\t\t\t\t$sharePromo.fadeOutSafe(500);\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\ttoggleCreateBucketPromo: function(show) {\n\t\t\tif (!show && createBucketPromoShown) {\n\t\t\t\t$createBucketPromo.hideSafe();\n\t\t\t\tcreateBucketPromoShown = false;\n\t\t\t} else if (show && !createBucketPromoShown && !$.cookie(\"oo_create_bucket_promo_dismissed\")) {\n\t\t\t\t$createBucketPromo.fadeInSafe(500);\n\t\t\t\tcreateBucketPromoShown = true;\n\t\t\t\t// Hack: we need to wait until the editor is shown before repositioning\n\t\t\t\tsetTimeout(onboarding.reposition, 0);\n\t\t\t}\n\t\t},\n\t\thideScriptPromo: function(){\n\t\t\t$scriptPromo.hideSafe();\n\t\t\t$sharePromo.hideSafe();\n\t\t},\n\t\tshowBucketPromo: function() {\n\t\t\t$scriptPromo.hideSafe();\n\t\t\t$bucketPromo.showSafe();\n\t\t\t$bucketPromo.find(\"[data-purpose='close']\").click(function(){\n\t\t\t\t// Don't persist the dismissal of this one.\n\t\t\t\tanal.dismiss(\"Sign in for Buckets\");\n\t\t\t\t$bucketPromo.fadeOutSafe(500);\n\t\t\t});\n\t\t},\n\t\thideBucketPromo: function() {\n\t\t\t$bucketPromo.hideSafe();\n\t\t},\n\t\treposition: function() {\n\t\t\tvar $syncButton = $(\"#files_toolbar_info\");\n\t\t\tvar $cbucketButton = $(\"#editor_share\");\n\t\t\tif ($syncButton.length) {\n\t\t\t\t$syncPromo.css(\"left\", ($syncButton.offset().left-2) + \"px\");\n\t\t\t}\n\t\t\tif ($cbucketButton.length) {\n\t\t\t\t$createBucketPromo.css(\"left\", ($cbucketButton.offset().left-2) + \"px\");\n\t\t\t}\n\t\t}\n\t};\n\treturn onboarding;\n});"
  },
  {
    "path": "client/app/js/ot-client.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine([\"js/ace-adapter\", \"ot\", \"js/polyfill\"], function(AceAdapter, ot){\n\tfunction OTClientWrapper(docId, observable){\n\t\tthis.id = docId;\n\t\tthis.observable = observable || null;\n\t\tthis.content = \"\";\n\t\tthis.attachEditor(null);\n\t}\n\n\tfunction ObservableAdapter(observable){\n\t\tthis.applyOperation = function(operation){\n\t\t\tvar content = observable();\n\t\t\tcontent = operation.apply(content);\n\t\t\tobservable(content);\n\t\t};\n\t\tthis.getValue = function(){\n\t\t\treturn observable();\n\t\t};\n\n\t\t// TODO: Use these other methods to remember where people's cursors are,\n\t\t// so they can be set as soon as the document is actually opened in ACE.\n\t\tthis.getCursor = function(){};\n\t\tthis.setOtherCursor = function(){};\n\t\tthis.detach = function(){};\n\t}\n\n\tOTClientWrapper.prototype.applyContent = function(){\n\t\tif (!this.adapter) return;\n\t\tvar currentContent = this.adapter.getValue();\n\t\tif (currentContent === this.content) return;\n\n\t\tvar op = new ot.TextOperation();\n\t\top[\"delete\"](currentContent.length);\n\t\top.insert(this.content);\n\t\tthis.adapter.applyOperation(op);\n\t};\n\n\tOTClientWrapper.prototype.initWith = function(rev, content){\n\t\tthis.otClient = new ot.Client(rev);\n\t\tthis.otClient.sendOperation = this._sendOperation.bind(this);\n\t\tthis.otClient.applyOperation = this._applyOperation.bind(this);\n\n\t\tthis.content = content;\n\t\tthis.applyContent();\n\t};\n\n\tOTClientWrapper.prototype.attachEditor = function(editor){\n\t\tif (this.adapter) {\n\t\t\tthis.adapter.detach(); // removes event listeners\n\t\t\tdelete this.adapter;\n\t\t\tthis.cursor = {};\n\t\t}\n\n\t\tif (editor) {\n\t\t\t// Attach to the ACE Editor Adapter.\n\t\t\t// \n\t\t\t// Note that the ACE Editor is itself bound to the observable via\n\t\t\t// ko-ace.js, so we don't need to attach to the observable from here.\n\t\t\tthis.adapter = new AceAdapter(editor);\n\t\t\tthis.adapter.addEventListener(\"change\", this._onChange.bind(this), false);\n\t\t\tthis.adapter.addEventListener(\"cursorActivity\", this._onCursor.bind(this), false);\n\t\t\tthis.adapter.addEventListener(\"focus\", this._onFocusBlur.bind(this), false);\n\t\t\tthis.adapter.addEventListener(\"blur\", this._onFocusBlur.bind(this), false);\n\t\t\tthis.cursor = this.adapter.getCursor();\n\n\t\t} else if (this.observable) {\n\t\t\t// Attach to the Knockout observable directly, enabling edit and save\n\t\t\t// actions to propogate even when the file is not open in the editor.\n\t\t\t// \n\t\t\t// TODO: Make this two-way communication by adding event listeners here.\n\t\t\tthis.adapter = new ObservableAdapter(this.observable);\n\t\t}\n\n\t\tthis.applyContent();\n\t};\n\n\tOTClientWrapper.prototype.changeDocId = function(newDocId){\n\t\tthis.id = newDocId;\n\t};\n\n\tOTClientWrapper.prototype.destroy = function(){\n\t\tthis.attachEditor(null);\n\t\tdelete this.adapter;\n\t\tdelete this.observable;\n\t\tthis.callbacks = {};\n\t};\n\n\t// SERVER -> OT\n\tOTClientWrapper.prototype.applyServer = function(operation){\n\t\tif (this.otClient) this.otClient.applyServer(operation);\n\t};\n\tOTClientWrapper.prototype.serverAck = function(){\n\t\tthis.otClient.serverAck();\n\t};\n\n\t// ACE -> OT\n\tOTClientWrapper.prototype._onChange = function(operation /*, inverse */){\n\t\tthis.otClient.applyClient(operation);\n\t\tthis.content = operation.apply(this.content);\n\t};\n\n\t// OT -> SERVER\n\tOTClientWrapper.prototype._sendOperation = function(revision, operation){\n\t\tthis.dispatchEvent(\"send\", revision, operation);\n\t};\n\n\t// OT -> ACE\n\tOTClientWrapper.prototype._applyOperation = function(operation){\n\t\tif (this.adapter) this.adapter.applyOperation(operation);\n\t\tthis.content = operation.apply(this.content);\n\t};\n\n\t// CURSORS\n\tOTClientWrapper.prototype._onCursor = function(){\n\t\tvar cursor = this.adapter.getCursor();\n\t\tif(this.cursor.position !== cursor.position\n\t\t\t|| this.cursor.selectionEnd !== cursor.selectionEnd){\n\t\t\tthis.cursor = cursor;\n\t\t\tthis.dispatchEvent(\"cursor\", cursor);\n\t\t}\n\t};\n\tOTClientWrapper.prototype._onFocusBlur = function(){\n\t};\n\tOTClientWrapper.prototype.setOtherCursor = function(){\n\t\tif (!this.adapter) return;\n\t\tthis.adapter.setOtherCursor.apply(this.adapter, arguments);\n\t};\n\n\t// Begin EventTarget Implementation\n\tOTClientWrapper.prototype.addEventListener = function(event, cb){\n\t\tif(!this.callbacks) this.callbacks = {};\n\t\tif(!this.callbacks[event]) this.callbacks[event] = [];\n\t\tthis.callbacks[event].push(cb);\n\t};\n\tOTClientWrapper.prototype.removeEventListener = function(event, cb){\n\t\tif(!this.callbacks || !this.callbacks[event]) return;\n\t\tfor (var i=0; i<this.callbacks[event].length; i++) {\n\t\t\tif(this.callbacks[event][i] === cb){\n\t\t\t\tthis.callbacks.splice(i, 1);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t};\n\tOTClientWrapper.prototype.dispatchEvent = function(event){\n\t\tif(!this.callbacks || !this.callbacks[event]) return;\n\t\tfor (var i=0; i<this.callbacks[event].length; i++) {\n\t\t\tthis.callbacks[event][i].apply(this,\n\t\t\t\tArray.prototype.slice.call(arguments, 1));\n\t\t}\n\t};\n\t// End EventTarget Implementation\n\n\t// TextOperation polyfill\n\n\treturn OTClientWrapper;\n});"
  },
  {
    "path": "client/app/js/ot-handler.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine([\"js/client\", \"js/ot-client\", \"ot\", \"js/polyfill\"], function(OctMethods, OtClient, ot){\n\tvar clients = [];\n\n\tfunction findDocWithId(docId){\n\t\tfor (var i = clients.length - 1; i >= 0; i--) {\n\t\t\tif (clients[i].id === docId) {\n\t\t\t\treturn clients[i];\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tvar clientListeners = {\n\t\tsend: function(revision, operation){\n\t\t\tOctMethods.socket.emit(\"ot.change\", {\n\t\t\t\tdocId: this.id,\n\t\t\t\trev: revision,\n\t\t\t\top: operation\n\t\t\t});\n\t\t},\n\t\tcursor: function(cursor){\n\t\t\tOctMethods.socket.emit(\"ot.cursor\", {\n\t\t\t\tdocId: this.id,\n\t\t\t\tcursor: cursor\n\t\t\t});\n\t\t}\n\t};\n\n\tvar socketListeners = {\n\t\tsubscribe: function(socket) {\n\t\t\tsocket.on(\"ot.doc\", socketListeners.doc);\n\t\t\tsocket.on(\"ot.broadcast\", socketListeners.broadcast);\n\t\t\tsocket.on(\"ot.ack\", socketListeners.ack);\n\t\t\tsocket.on(\"ot.cursor\", socketListeners.cursor);\n\t\t},\n\t\tdoc: function(obj){\n\t\t\tvar otClient = findDocWithId(obj.docId);\n\t\t\tif (!otClient) return;\n\n\t\t\totClient.initWith(obj.rev, obj.content);\n\t\t},\n\t\tbroadcast: function(obj){\n\t\t\tvar otClient = findDocWithId(obj.docId);\n\t\t\tif (!otClient) return;\n\n\t\t\tvar op = ot.TextOperation.fromJSON(obj.ops);\n\t\t\totClient.applyServer(op);\n\t\t},\n\t\tack: function(obj){\n\t\t\tvar otClient = findDocWithId(obj.docId);\n\t\t\tif (!otClient) return;\n\n\t\t\totClient.serverAck();\n\t\t},\n\t\tcursor: function(obj){\n\t\t\tvar otClient = findDocWithId(obj.docId);\n\t\t\tif (!otClient) return;\n\n\t\t\totClient.setOtherCursor(obj.cursor, \"#F00\", \"Remote User\");\n\t\t}\n\t};\n\n\tfunction create(docId, observable) {\n\t\tvar otClient = findDocWithId(docId);\n\t\tif (otClient) return otClient;\n\n\t\totClient = new OtClient(docId, observable);\n\t\totClient.addEventListener(\"send\", clientListeners.send.bind(otClient));\n\t\totClient.addEventListener(\"cursor\", clientListeners.cursor.bind(otClient));\n\t\tclients.push(otClient);\n\t\treturn otClient;\n\t}\n\n\tfunction destroy(docId) {\n\t\tfor (var i = clients.length - 1; i >= 0; i--) {\n\t\t\tif (clients[i].id === docId) {\n\t\t\t\tclients.splice(i, 1);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tcreate: create,\n\t\tdestroy: destroy,\n\t\tlisteners: socketListeners,\n\t\t_clients: clients\n\t};\n});\n"
  },
  {
    "path": "client/app/js/polyfill.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine(function(){\n\t// Function.prototype.bind polyfill\n\tif (!Function.prototype.bind) {\n\t\tFunction.prototype.bind = function(oThis) {\n\t\t\tif (typeof this !== \"function\") {\n\t\t\t\t// closest thing possible to the ECMAScript 5\n\t\t\t\t// internal IsCallable function\n\t\t\t\tthrow new TypeError(\"Function.prototype.bind - what is trying to be bound is not callable\");\n\t\t\t}\n\n\t\t\tvar aArgs = Array.prototype.slice.call(arguments, 1);\n\t\t\tvar fToBind = this;\n\t\t\tvar fNOP = function() {};\n\t\t\tvar fBound = function() {\n\t\t\t\treturn fToBind.apply(this instanceof fNOP\n\t\t\t\t\t? this\n\t\t\t\t\t: oThis,\n\t\t\t\taArgs.concat(Array.prototype.slice.call(arguments)));\n\t\t\t};\n\n\t\t\tfNOP.prototype = this.prototype;\n\t\t\tfBound.prototype = new fNOP();\n\n\t\t\treturn fBound;\n\t\t};\n\t}\n});\n"
  },
  {
    "path": "client/app/js/runtime.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// RequireJS config for live application loads.\n\n// New require.config for live application loads.\nrequire.config({\n\tpaths: {\n\t\t// Social Plugins\n\t\t\"twitter\": [\n\t\t\t\"https://platform.twitter.com/widgets\",\n\t\t\t\"http://platform.twitter.com/widgets\",\n\t\t\t\"/js-default/twitter\"\n\t\t],\n\t\t\"addthis\": [\n\t\t\t\"https://s7.addthis.com/js/300/addthis_widget\", // #pubid=ra-51d65a00598f1528\n\t\t\t\"http://s7.addthis.com/js/300/addthis_widget\", // #pubid=ra-51d65a00598f1528\n\t\t\t\"/js-default/addthis\"\n\t\t],\n\t\t\"uservoice\": [\n\t\t\t\"https://widget.uservoice.com/{!uservoice!}\",\n\t\t\t\"http://widget.uservoice.com/{!uservoice!}\",\n\t\t\t\"/js-default/uservoice\"\n\t\t],\n\t\t\"analytics\": [\n\t\t\t\"https://www.google-analytics.com/analytics\",\n\t\t\t\"http://www.google-analytics.com/analytics\",\n\t\t\t\"/js-default/analytics\"\n\t\t],\n\t\t\"gtag\": [\n\t\t\t\"https://www.googletagmanager.com/gtag/js?id={!gtagid!}\",\n\t\t\t\"http://www.googletagmanager.com/gtag/js?id={!gtagid!}\",\n\t\t\t\"/js-default/gtag\"\n\t\t],\n\t\t\"webfont\": [\n\t\t\t\"https://ajax.googleapis.com/ajax/libs/webfont/1.4.7/webfont\",\n\t\t\t\"http://ajax.googleapis.com/ajax/libs/webfont/1.4.7/webfont\",\n\t\t\t\"/js-default/webfont\"\n\t\t],\n\t\t\"persona\": [\n\t\t\t\"https://login.persona.org/include\",\n\t\t\t\"/js-default/persona\"\n\t\t],\n\t\t\"recaptcha\": [\n\t\t\t\"https://www.recaptcha.net/recaptcha/api\",\n\t\t\t\"/js-default/recaptcha\"\n\t\t],\n\t\t\"fuse\": [\n\t\t\t// TODO: Make this URL configurable\n\t\t\t\"https://cdn.fuseplatform.net/publift/tags/2/2356/fuse\",\n\t\t\t\"/js-default/fuse\"\n\t\t]\n\t},\n\tshim:{\n\t\t\"twitter\": {\n\t\t\texports: \"twttr\"\n\t\t},\n\t\t\"webfont\": {\n\t\t\texports: \"WebFont\"\n\t\t},\n\t\t\"analytics\": {\n\t\t\texports: \"ga\"\n\t\t},\n\t\t\"persona\": {\n\t\t\texports: \"navigator.id\"\n\t\t}\n\t}\n});\n\n// CSS shim\nrequire.css = function(url){\n\tvar link = document.createElement(\"link\");\n\tlink.type = \"text/css\";\n\tlink.rel = \"stylesheet\";\n\tlink.href = url;\n\tdocument.getElementsByTagName(\"head\")[0].appendChild(link);\n};\n\n// Load from Google Fonts\n// NOTE: There are no fonts from Google Fonts currently in use.\n// require([\"webfont\"], function(WebFont){\n// \tWebFont.load({\n// \t\tgoogle: {\n// \t\t\tfamilies: [\"Rambla\", \"Bangers\"]\n// \t\t}\n// \t});\n// });\n\n// Load Google Analytics\nrequire([\"js/anal\"], function(anal){\n\tanal.pageview();\n});\n\n// Load DejaVu Sans Mono (lower priority)\nsetTimeout(function(){\n\trequire.css(\"fonts/dejavusansmono_book/stylesheet.css\");\n}, 250);\n\n// Load ReCAPTCHA (lower priority)\nsetTimeout(function(){\n\trequire([\"recaptcha\"]);\n}, 250);\n\n// Load Social Bloatware (lowest priority)\nsetTimeout(function(){\n\trequire([\"uservoice\"]);\n}, 500);\n\n"
  },
  {
    "path": "client/app/js/utils.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Assorted utility functions and polyfills\n\ndefine([\"jquery\", \"knockout\", \"js/anal\"], function($, ko, anal){\n\t// Safe Show/Hide Functions that retain display properties like flex.\n\t$.fn.hideSafe = function(){\n\t\t$(this).attr(\"aria-hidden\", \"true\");\n\t};\n\t$.fn.showSafe = function(){\n\t\t$(this).attr(\"aria-hidden\", \"false\");\n\t};\n\t$.fn.toggleSafe = function(bool){\n\t\tif(bool === true || $(this).attr(\"aria-hidden\") === \"true\"){\n\t\t\t$(this).showSafe();\n\t\t\treturn true;\n\t\t}else{\n\t\t\t$(this).hideSafe();\n\t\t\treturn false;\n\t\t}\n\t};\n\t$.fn.fadeInSafe = function(duration){\n\t\t$(this).showSafe();\n\t\t$(this).css(\"display\", \"none\");\n\t\t$(this).fadeIn(duration, function(){\n\t\t\t$(this).css(\"display\", \"\");\n\t\t});\n\t};\n\t$.fn.fadeOutSafe = function(duration){\n\t\t$(this).showSafe();\n\t\t$(this).css(\"display\", \"block\");\n\t\t$(this).fadeOut(duration, function(){\n\t\t\t$(this).hideSafe();\n\t\t\t$(this).css(\"display\", \"\");\n\t\t});\n\t};\n\tko.bindingHandlers.vizSafe = {\n\t\tinit: function(element, valueAccessor /*, allBindings, viewModel, bindingContext */) {\n\t\t\t$(element).toggleSafe(ko.unwrap(valueAccessor()));\n\t\t},\n\t\tupdate: function(element, valueAccessor /*, allBindings, viewModel, bindingContext */) {\n\t\t\t$(element).toggleSafe(ko.unwrap(valueAccessor()));\n\t\t}\n\t};\n\n\t// Fade in/out bindings\n\tko.bindingHandlers.fade = {\n\t\tinit: function(element, valueAccessor /*, allBindings, viewModel, bindingContext */) {\n\t\t\t$(element).toggleSafe(ko.unwrap(valueAccessor()));\n\t\t},\n\t\tupdate: function(element, valueAccessor /*, allBindings, viewModel, bindingContext */) {\n\t\t\tif(ko.unwrap(valueAccessor())){\n\t\t\t\t$(element).fadeInSafe(250);\n\t\t\t}else{\n\t\t\t\t$(element).fadeOutSafe(250);\n\t\t\t}\n\t\t}\n\t};\n\tko.bindingHandlers.fadeIn = {\n\t\tinit: function(element, valueAccessor /*, allBindings, viewModel, bindingContext */) {\n\t\t\t$(element).toggleSafe(ko.unwrap(valueAccessor()));\n\t\t},\n\t\tupdate: function(element, valueAccessor /*, allBindings, viewModel, bindingContext */) {\n\t\t\tif(ko.unwrap(valueAccessor())){\n\t\t\t\t$(element).fadeInSafe(250);\n\t\t\t}else{\n\t\t\t\t$(element).hideSafe();\n\t\t\t}\n\t\t}\n\t};\n\tko.bindingHandlers.fadeOut = {\n\t\tinit: function(element, valueAccessor /*, allBindings, viewModel, bindingContext */) {\n\t\t\t$(element).toggleSafe(ko.unwrap(valueAccessor()));\n\t\t},\n\t\tupdate: function(element, valueAccessor /*, allBindings, viewModel, bindingContext */) {\n\t\t\tif(ko.unwrap(valueAccessor())){\n\t\t\t\t$(element).showSafe();\n\t\t\t}else{\n\t\t\t\t$(element).fadeOutSafe(250);\n\t\t\t}\n\t\t}\n\t};\n\n\t// Toggle observable function\n\tko.observable.fn.toggle = function () {\n\t\tvar self = this;\n\t\treturn function () {\n\t\t\tself(!self());\n\t\t};\n\t};\n\n\t// Add an extender to back an observable by localStorage\n\tko.extenders.localStorage = function(obs, key) {\n\t\tkey = \"oo:\" + key;\n\n\t\t// Restore value from localStorage\n\t\tif (window.localStorage[key]) {\n\t\t\tobs(JSON.parse(window.atob(window.localStorage[key])));\n\t\t}\n\n\t\t// Save changes to localStorage\n\t\tobs.subscribe(function(value){\n\t\t\twindow.localStorage[key] = window.btoa(JSON.stringify(value));\n\t\t});\n\t};\n\n\t// Additional utility functions\n\treturn {\n\t\tbinarySearch: function(arr, value, getter) {\n\t\t\tvar lo = 0;\n\t\t\tvar hi = arr.length;\n\t\t\twhile (lo + 1 < hi) {\n\t\t\t\tvar mid = Math.floor((hi+lo)/2);\n\t\t\t\tvar candidate = getter(arr[mid]);\n\t\t\t\tif (value === candidate) {\n\t\t\t\t\tlo = mid;\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (value < candidate) {\n\t\t\t\t\thi = mid;\n\t\t\t\t} else {\n\t\t\t\t\tlo = mid;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (lo < arr.length && value === getter(arr[lo])) {\n\t\t\t\treturn arr[lo];\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t\t// Returns all elements of \"universe\" that are not in \"remove\".\n\t\t// Both arrays must be sorted.\n\t\tsortedFilter: function(universe, remove, getter) {\n\t\t\tvar i1 = 0;\n\t\t\tvar i2 = 0;\n\t\t\tvar result = [];\n\t\t\twhile (i1 < universe.length && i2 < remove.length) {\n\t\t\t\tvar v1 = getter(universe[i1]);\n\t\t\t\tvar v2 = getter(remove[i2]);\n\t\t\t\tif (v1 < v2) {\n\t\t\t\t\tresult.push(universe[i1]);\n\t\t\t\t\ti1++;\n\t\t\t\t} else if (v1 === v2) {\n\t\t\t\t\ti1++;\n\t\t\t\t\ti2++;\n\t\t\t\t} else {\n\t\t\t\t\ti2++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (var i = i1; i < universe.length; i++) {\n\t\t\t\tresult.push(universe[i]);\n\t\t\t}\n\t\t\treturn result;\n\t\t},\n\t\t// Shows an alert box, and logs it to Google Analytics.\n\t\talert: function(message) {\n\t\t\tanal.alert(message);\n\t\t\twindow.alert(message);\n\t\t}\n\t};\n});\n"
  },
  {
    "path": "client/app/js/vars.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine([\"knockout\"], function(ko){\n\n\t// MVVM class for variables\n\tfunction Var(){\n\t\t// the \"self\" variable enables us to refer to the OctFile context even when\n\t\t// we are programming within callback function contexts\n\t\tvar self = this;\n\n\t\t// Main Bindings\n\t\tself.scope = ko.observable();\n\t\tself.symbol = ko.observable();\n\t\tself.class_name = ko.observable();\n\t\tself.dimension = ko.observable();\n\t\tself.value = ko.observable();\n\t\tself.complex_flag = ko.observable();\n\n\t\t// Take values from object\n\t\tself.take = function(values){\n\t\t\tko.utils.objectForEach(values, function(key, value){\n\t\t\t\tself[key](value);\n\t\t\t});\n\t\t};\n\n\t\t// Type string\n\t\tself.typeString = ko.computed(function(){\n\t\t\tvar isScalar = (self.dimension() === \"1x1\");\n\t\t\tvar isComplex = (self.complex_flag());\n\t\t\tvar isChar = (self.class_name() === \"char\");\n\t\t\tvar isCell = (self.class_name() === \"cell\");\n\t\t\tvar isFn = (self.class_name() === \"function_handle\");\n\t\t\tvar isLogicl = (self.class_name() === \"logical\");\n\t\t\tvar isNumeric = (self.class_name() === \"double\");\n\t\t\tvar isStruct = (self.class_name() === \"struct\");\n\t\t\tvar isSym = (self.class_name() === \"sym\");\n\t\t\tvar isTF = (self.class_name() === \"tf\");\n\t\t\tvar isImg = (self.class_name() === \"uint8\");\n\n\t\t\tif ( isChar                ) return \"(abc)\";\n\t\t\tif ( isSym                 ) return \"$\";\n\t\t\tif ( isTF                  ) return \"ℒ\";\n\t\t\tif ( isStruct              ) return \"⊡\";\n\t\t\tif ( isLogicl &&  isScalar ) return \"¬\";\n\t\t\tif ( isLogicl              ) return \"[\"+self.dimension()+\"]¬\";\n\t\t\tif ( isImg    &&  isComplex) return \"❬\"+self.dimension()+\"❭*\";\n\t\t\tif ( isImg    && !isComplex) return \"❬\"+self.dimension()+\"❭\";\n\t\t\tif ( isCell   &&  isComplex) return \"{\"+self.dimension()+\"}*\";\n\t\t\tif ( isCell   && !isComplex) return \"{\"+self.dimension()+\"}\";\n\t\t\tif ( isFn     &&  isComplex) return \"@*\";\n\t\t\tif ( isFn     && !isComplex) return \"@\";\n\t\t\tif (!isNumeric             ) return \"?\";\n\t\t\tif ( isScalar &&  isComplex) return \"#*\";\n\t\t\tif ( isScalar && !isComplex) return \"#\";\n\t\t\tif (              isComplex) return \"[\"+self.dimension()+\"]*\";\n\t\t\tif (             !isComplex) return \"[\"+self.dimension()+\"]\";\n\t\t});\n\n\t\t// Type explanation\n\t\tself.typeExplanation = ko.computed(function(){\n\t\t\tvar isScalar = (self.dimension() === \"1x1\");\n\t\t\tvar isComplex = (self.complex_flag());\n\t\t\tvar isChar = (self.class_name() === \"char\");\n\t\t\tvar isCell = (self.class_name() === \"cell\");\n\t\t\tvar isFn = (self.class_name() === \"function_handle\");\n\t\t\tvar isLogicl = (self.class_name() === \"logical\");\n\t\t\tvar isNumeric = (self.class_name() === \"double\");\n\t\t\tvar isStruct = (self.class_name() === \"struct\");\n\t\t\tvar isSym = (self.class_name() === \"sym\");\n\t\t\tvar isTF = (self.class_name() === \"tf\");\n\t\t\tvar isImg = (self.class_name() === \"uint8\");\n\t\t\t\n\t\t\tif ( isChar                ) return \"characters\";\n\t\t\tif ( isSym                 ) return \"symbolic\";\n\t\t\tif ( isTF                  ) return \"transfer function\";\n\t\t\tif ( isStruct              ) return \"struct\";\n\t\t\tif ( isLogicl              ) return \"logical (boolean)\";\n\t\t\tif ( isImg                 ) return \"uint8 data (images)\";\n\t\t\tif ( isCell   &&  isComplex) return \"complex cell array\";\n\t\t\tif ( isCell   && !isComplex) return \"cell array\";\n\t\t\tif ( isFn     &&  isComplex) return \"complex function handle\";\n\t\t\tif ( isFn     && !isComplex) return \"function handle\";\n\t\t\tif (!isNumeric             ) return self.class_name();\n\t\t\tif ( isScalar &&  isComplex) return \"complex scalar\";\n\t\t\tif ( isScalar && !isComplex) return \"scalar\";\n\t\t\tif (              isComplex) return \"complex matrix\";\n\t\t\tif (             !isComplex) return \"matrix\";\n\t\t});\n\n\t\t// Click Method\n\t\tself.showDetails = function(){\n\t\t\talert(self.symbol()+\" = \"+self.value());\n\t\t};\n\n\t\t// Listen on value change\n\t\tself.value.subscribe(function(){});\n\n\t\t// toString method\n\t\tself.toString = function(){\n\t\t\treturn \"[Var:\"+self.symbol()+\" \"+self.value()+\"]\";\n\t\t};\n\t}\n\tVar.sorter = function(a, b){\n\t\treturn a.symbol() === b.symbol() ? 0 : (\n\t\t\ta.symbol() < b.symbol() ? -1 : 1\n\t\t);\n\t};\n\n\t// Expose interface\n\treturn Var;\n\n});"
  },
  {
    "path": "client/app/js/ws-shared.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndefine([\"js/client\", \"js/ot-handler\", \"js/polyfill\"], function(OctMethods, OtHandler){\n\n\tvar documentClients = {};\n\n\tvar socketListeners = {\n\t\tsubscribe: function(socket) {\n\t\t\tsocket.on(\"ws.command\", socketListeners.command);\n\t\t\tsocket.on(\"ws.save\", socketListeners.save);\n\t\t\tsocket.on(\"ws.promptid\", socketListeners.promptid);\n\t\t\tsocket.on(\"ws.doc\", socketListeners.doc);\n\t\t\tsocket.on(\"ws.rename\", socketListeners.renamed);\n\t\t\tsocket.on(\"ws.delete\", socketListeners.deleted);\n\t\t},\n\t\tcommand: function(cmd){\n\t\t\tOctMethods.console.command(cmd, true);\n\t\t},\n\t\tsave: function(data){\n\t\t\tvar octFile = OctMethods.ko.viewModel.getOctFileFromName(data.filename);\n\t\t\tif (!octFile)\n\t\t\t\tOctMethods.editor.add(data.filename, data.content);\n\t\t\telse\n\t\t\t\toctFile.savedContent(data.content);\n\t\t},\n\t\tpromptid: function(promptId){\n\t\t\tif (!promptId) return;\n\t\t\tconsole.log(\"OT PROMPT:\", promptId);\n\n\t\t\tvar otClient = OtHandler.create(promptId);\n\t\t\totClient.attachEditor(OctMethods.prompt.instance);\n\t\t},\n\t\tdoc: function(obj){\n\t\t\tif (!obj) return;\n\t\t\tconsole.log(\"OT SCRIPT:\", obj.docId, obj.filename);\n\n\t\t\tvar octFile = OctMethods.ko.viewModel.getOctFileFromName(obj.filename);\n\t\t\tif (!octFile) {\n\t\t\t\t// TODO: What to do in this case?\n\t\t\t\tconsole.log(\"Could not find OctFile:\", obj.filename);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar otClient = OtHandler.create(obj.docId, octFile.content);\n\t\t\tdocumentClients[obj.filename] = otClient;\n\t\t},\n\t\trenamed: function(obj){\n\t\t\tvar oldname = obj.oldname;\n\t\t\tvar newname = obj.newname;\n\t\t\tvar newDocId = obj.newDocId;\n\n\t\t\tif (!documentClients[oldname]) return;\n\t\t\tif (documentClients[newname]) return;\n\n\t\t\tvar otClient = documentClients[oldname];\n\t\t\tdelete documentClients[oldname];\n\t\t\tdocumentClients[newname] = otClient;\n\n\t\t\totClient.changeDocId(newDocId);\n\t\t},\n\t\tdeleted: function(obj){\n\t\t\tvar filename = obj.filename, docId = obj.docId;\n\t\t\tif (!documentClients[filename]) return;\n\n\t\t\tvar otClient = documentClients[filename];\n\t\t\tdelete documentClients[filename];\n\t\t\totClient.destroy();\n\n\t\t\tOtHandler.destroy(docId);\n\t\t}\n\t};\n\n\tfunction clientForFilename(filename) {\n\t\treturn documentClients[filename];\n\t}\n\n\tfunction forEachDocClient(cb){\n\t\tfor (var filename in documentClients) {\n\t\t\tif (!Object.prototype.hasOwnProperty.call(documentClients, filename)) continue;\n\t\t\tcb(filename, documentClients[filename]);\n\t\t}\n\t}\n\n\treturn {\n\t\tlisteners: socketListeners,\n\t\tclientForFilename: clientForFilename,\n\t\tforEachDocClient: forEachDocClient\n\t};\n});"
  },
  {
    "path": "client/app/main.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Main RequireJS configuration for local builds.\n\nrequire.config({\n\twaitSeconds: 0,\n\tpaths: {\n\t\t// jQuery and Plugins\n\t\t\"jquery\": \"vendor/jquery/dist/jquery\",\n\t\t\"jquery.cookie\": \"vendor/jquery.cookie/jquery.cookie\",\n\t\t\"jquery.md5\": \"vendor/jquery-md5/jquery.md5\",\n\n\t\t// Vendor Libraries\n\t\t\"canvg\": \"vendor/canvg/dist/canvg.bundle\",\n\t\t\"knockout\": \"vendor/knockoutjs/dist/knockout.debug\",\n\t\t\"splittr\": \"vendor/splittr/splittr\",\n\t\t\"filesaver\": \"vendor/FileSaver/FileSaver\",\n\t\t\"canvas-toblob\": \"vendor/canvas-toBlob.js/canvas-toBlob\",\n\t\t\"blob\": \"vendor/blob/Blob\",\n\t\t\"ot\": \"vendor/ot/dist/ot\",\n\n\t\t// NPM Libraries\n\t\t\"SocketIOFileUpload\": \"../node_modules/socketio-file-upload/client\",\n\t\t\"socket.io\": \"../node_modules/socket.io-client/dist/socket.io\",\n\n\t\t// Local Libraries\n\t\t\"ismobile\": \"js/detectmobilebrowser\",\n\t\t\"base64\": \"js/base64v1.module\",\n\t\t\"base64-toblob\": \"js/base64-toBlob\",\n\t\t\"ko-takeArray\": \"js/ko-takeArray\",\n\t\t\"ko-flash\": \"js/ko-flash\"\n\t},\n\tpackages: [\n\t\t{\n\t\t\tname: \"ace\",\n\t\t\tlocation: \"vendor/ace/lib/ace\",\n\t\t\tmain: \"ace\"\n\t\t}\n\t],\n\tshim: {\n\t\t// jQuery Plugins\n\t\t\"jquery.md5\": [\"jquery\"],\n\n\t\t// CanVG\n\t\t\"canvg\": {\n\t\t\texports: \"canvg\"\n\t\t},\n\n\t\t// ot.js\n\t\t\"ot\": {\n\t\t\texports: \"ot\"\n\t\t},\n\n\t\t// Other Libraries\n\t\t\"filesaver\": {\n\t\t\tdeps: [\"canvas-toblob\", \"blob\"]\n\t\t}\n\t}\n});\n"
  },
  {
    "path": "client/app/privacy.txt",
    "content": "Privacy Policy\n==============\n\n<!-- Privacy TXT -->\n\n\nEnd User License Agreement (last updated January 2, 2015)\n=========================================================\n\n<!-- EULA TXT -->\n"
  },
  {
    "path": "client/app/privacy_standalone.txt",
    "content": "Octave Online LLC values the privacy of our users.  In order to provide excellent software, we collect and save the following information:\n\n- Scripts and other files you upload to or edit in Octave Online.\n- Historical transcripts of Octave commands and output, which may be associated with your account or IP address.\n- When using Email Sign In: your email address.\n- When using Google+ Sign In: your email address, name, and basic Google account information, including gender and locale.\n\nThe above information is stored on Google Cloud Platform based on Council Bluffs, Iowa, USA.  Additional copies of the data may be stored as backups in other physical locations and not necessarily in the Rackspace network.  The information may be stored indefinitely.\n\nDeleting a file from your account on your own may not constitute full deletion from our servers.  To inquire about full deletion, open a support ticket or send an email as described below.\n\nYour activity in Octave Online is recorded by Google Analytics.  Google Analytics may use Google AdSense cookies in your browser to record demographics about the users of Octave Online, including, but not limited to, age and gender.  Note that this information is NOT personally identifiable.  You may opt out from Google Analytics by following the instructions on the following URL.\n\nhttps://tools.google.com/dlpage/gaoptout/\n\nCalifornia Consumer Privacy Act (“CCPA”): Under CCPA, Californian residents have the right to declare their preferences on the sale of data for advertising and marketing purposes. If you wish to change your preferences, click this link to launch our preference portal:\n\n<div data-fuse-privacy-tool></div>\n\nWe use a third-party to provide monetisation technologies for our site. You can review their privacy and cookie policy here:\n\nhttps://www.publift.com/privacy-policy\n\nUnless required by law, we do not give or sell personally identifiable information to third parties. \n\nIf your email address changes, you may open a support ticket asking to restore access to your account.  We will ask you questions about your old account to verify your identity.\n\nOctave Online LLC reserves the right to send you email announcements about Octave Online.  Such announcements will be released only for major updates and reports.\n\nOctave Online LLC may also save cookies to your computer.  These cookies help us keep you logged in during a session and between sessions.  Disabling cookies will limit the features we are able to provide.\n\nTo inquire about accessing or deleting personal data, or to ask questions about the privacy policy, you can (1) open a support ticket at https://octaveonline.uservoice.com, or (2) send an email to support@octave-online.net.\n"
  },
  {
    "path": "client/app/styl/all.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n@require(\"mixins.styl\")\n\n@import \"../../node_modules/normalize-styl/normalize\"\n\nbody {\n\t// debug-backgrounds()\n\tfont: 400 14px/1.2em body_font_family\n\tcolor: colorfgd\n\tbackground-color: colorbkg\n}\nh1, h2, h3, h4, h5, h6 {\n\tfont-family: heading_font_family\n}\nkbd {\n\twhite-space: nowrap\n}\n.modified {\n\tfont-style: italic\n\ttext-decoration: underline\n}\n.divide {\n\tdisplay: inline-block\n\twidth: 0.6em\n}\na img {\n\tborder: none\n}\n.theme-header {\n\tbackground-color: color1\n\theight: toolbar_height\n\tline-height: toolbar_height\n\tvertical-align: middle\n\tborder: none\n\tfont-size: 1.1em\n\tpadding: 0 1ex\n\twhite-space: nowrap\n\toverflow: hidden\n}\n.clickable {\n\ttext-decoration: underline\n\tcursor: pointer\n}\n.action-link {\n\tpadding: 0.5em\n\tborder-radius: 0.5em\n\tcolor: colorfgd\n\tbackground-color: color2\n\tcursor: pointer\n\ttext-decoration: underline\n\n\t& > * {\n\t\tcolor: inherit\n\t}\n\t&:hover{\n\t\tbackground-color: colorfg1\n\t}\n\t&:active{\n\t\tbackground-color: colorfg4\n\t}\n}\n\n@require(\"flexbox.styl\")\n@require(\"header.styl\")\n@require(\"output_panel.styl\")\n@require(\"editor.styl\")\n@require(\"callouts.styl\")\n@require(\"modals.styl\")\n@require(\"print.styl\")\n"
  },
  {
    "path": "client/app/styl/callouts.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n@require(\"mixins.styl\")\n\ncallout_color = #369AFF\ncallout(shadowoffset, direction=\"up\"){\n\tposition: absolute\n\tbackground-color: colorbkg\n\tborder: 3px solid callout_color\n\tborder-radius: 5px\n\tbox-shadow: 0px shadowoffset 16px 0px rgba(0, 0, 0, 0.75)\n\n\t// Arrow\n\t&::before{\n\t\tcontent: \" \"\n\t\tposition: absolute\n\t\twidth: 0\n\t\theight: 0\n\t\tborder: 10px solid\n\t\tif direction is \"none\" {\n\t\t\tborder-color: transparent\n\t\t} else if direction is \"up\" {\n\t\t\tborder-color: transparent callout_color callout_color transparent\n\t\t} else if direction is \"left\" {\n\t\t\tborder-color: transparent transparent callout_color callout_color\n\t\t} else {\n\t\t\tborder-color: callout_color transparent transparent callout_color\n\t\t}\n\t}\n}\n\n#instructor-promo {\n\tcallout(0px, none)\n\ttop: 10px\n\tleft: 10px\n\twidth: 260px\n\theight: auto\n\tpadding: 5px\n\ttext-align: center\n\tz-index: 10\n\n\t// Text Flow\n\t& > span {\n\t\tdisplay: block\n\t\tline-height: 1em\n\t\t&.l1 {\n\t\t\tfont-size: 1.3em\n\t\t}\n\t\t&.l2 {\n\t\t\tmargin-top: 5px\n\t\t\tfont-size: 1em\n\t\t}\n\t\t&.l3 {\n\t\t\tfont-size: 1em\n\t\t\tmargin-top: 10px\n\t\t\tcursor: pointer\n\t\t\ttext-decoration: underline\n\t\t}\n\t}\n\t\n\t@media (max-width: responsive_width_three) {\n\t\tdisplay: none\n\t}\n}\n\n#type_here {\n\tcallout(0px, down)\n\tbottom: 25px\n\tleft: 10px\n\theight: auto\n\twidth: 330px\n\tmax-width: 80%\n\tpadding: 10px\n\tz-index: 10\n\n\t// Arrow\n\t&::before {\n\t\tleft: 10px\n\t\tbottom: -20px\n\t}\n\n\t// Text Flow\n\t& > span {\n\t\tdisplay: block\n\t\tline-height: 1em\n\t\t&.l1{\n\t\t\tfont-size: 2em\n\t\t}\n\t\t&.l2{\n\t\t\tmargin-top: 5px\n\t\t\tfont-size: 1.2em\n\t\t\tkbd {\n\t\t\t\tcolor: green\n\t\t\t}\n\t\t}\n\t\t&.l3{\n\t\t\tmargin-top: 10px\n\t\t\tfont-size: 1.2em\n\t\t\tcolor: #999\n\t\t}\n\t\ta{\n\t\t\tcolor: inherit\n\t\t}\n\t}\n}\n\n#login-promo, #bucket-promo {\n\tcallout(4px, up)\n\tdisplay: block\n\ttop: header_height + 15px\n\tright: 10px\n\twidth: 200px\n\theight: auto\n\ttext-align: center\n\tz-index: 204\n\tpadding: 10px 10px\n\n\t// Arrow\n\t&::before {\n\t\tright: 10px\n\t\ttop: -20px\n\t}\n\n\t// Text Flow\n\t&#login-promo > span {\n\t\tdisplay: block\n\t\tline-height: 1em\n\t\t&.l1{\n\t\t\tfont-size: 2em\n\t\t}\n\t\t// &.l2{\n\t\t// \tfont-size: 3em\n\t\t// }\n\t\t&.l3{\n\t\t\tfont-size: 1.3em\n\t\t\tmargin-top: 10px\n\t\t}\n\t\t&.l4{\n\t\t\tfont-size: 1em\n\t\t\tmargin-top: 10px\n\t\t\tcursor: pointer\n\t\t\ttext-decoration: underline\n\t\t}\n\t}\n\n\t&#bucket-promo > span {\n\t\tdisplay: block\n\t\tline-height: 1em\n\t\t&.l1{\n\t\t\tfont-size: 1.25em\n\t\t}\n\t\t&.l2{\n\t\t\tfont-size: 1em\n\t\t\tmargin-top: 10px\n\t\t\tcursor: pointer\n\t\t\ttext-decoration: underline\n\t\t}\n\t}\n\n\t@media (max-width: responsive_width_one) {\n\t\tdisplay: none\n\t}\n}\n\n#share-promo{\n\tcallout(4px, up)\n\tdisplay: block\n\ttop: header_height + 15px\n\tright: 10px\n\twidth: 220px\n\theight: auto\n\ttext-align: center\n\tz-index: 204\n\tpadding: 10px 10px\n\n\t// Arrow\n\t&::before {\n\t\tright: 10px\n\t\ttop: -20px\n\t}\n\n\t// Text Flow\n\t& > span {\n\t\tdisplay: block\n\t\tline-height: 1.2em\n\t\t&.l0{\n\t\t\tfont-size: 1.4em\n\t\t}\n\t\t&.l1{\n\t\t\tfont-size: 1.3em\n\t\t\tmargin-top: 4px\n\t\t}\n\t\t// &.l2{\n\t\t// \tfont-size: 1.4em\n\t\t// \tmargin-top: 4px\n\t\t// }\n\t\t&.l3{\n\t\t\tfont-size: 1em\n\t\t\tmargin-top: 10px\n\t\t\tcursor: pointer\n\t\t\ttext-decoration: underline\n\t\t}\n\t}\n}\n\n#sync-promo{\n\tcallout(4px, left)\n\ttop: header_height + toolbar_height*2 + 20px\n\tleft: 60px\t// left position is set via JavaScript; this is a default\n\twidth: 130px\n\theight: auto\n\ttext-align: center\n\tz-index: 203\n\tpadding: 10px 10px\n\n\t// Arrow\n\t&::before {\n\t\tleft: 10px\n\t\ttop: -20px\n\t}\n\n\t// Text Flow\n\t& > span {\n\t\tdisplay: block\n\t\tline-height: 1em\n\t\t&.l1{\n\t\t\tfont-size: 1.3em\n\t\t}\n\t\t&.l2{\n\t\t\tfont-size: 1.1em\n\t\t\tmargin-top: 10px\n\t\t}\n\t\t&.l3{\n\t\t\tfont-size: 1em\n\t\t\tmargin-top: 10px\n\t\t\tcursor: pointer\n\t\t\ttext-decoration: underline\n\t\t}\n\t}\n}\n\n#create-bucket-promo{\n\tcallout(4px, left)\n\ttop: header_height + toolbar_height*2 + 20px\n\tleft: 150px\t// left position is set via JavaScript; this is a default\n\twidth: 160px\n\theight: auto\n\ttext-align: center\n\tz-index: 202\n\tpadding: 10px 10px\n\n\t// Arrow\n\t&::before {\n\t\tleft: 10px\n\t\ttop: -20px\n\t}\n\n\t// Text Flow\n\t& > span {\n\t\tdisplay: block\n\t\tline-height: 1em\n\t\t&.l1{\n\t\t\tfont-size: 1.3em\n\t\t}\n\t\t&.l2{\n\t\t\tfont-size: 1.1em\n\t\t\tmargin-top: 10px\n\t\t}\n\t\t&.l3{\n\t\t\tfont-size: 1em\n\t\t\tmargin-top: 10px\n\t\t\tcursor: pointer\n\t\t\ttext-decoration: underline\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "client/app/styl/editor.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n@require(\"mixins.styl\")\n\nlower-toolbar() {\n\tbackground-color: color2\n\theight: toolbar_height\n\tline-height: toolbar_height\n\tvertical-align: middle\n\twhite-space: nowrap\n\toverflow: hidden\n\ttext-align: center\n}\n\ntop-right-control() {\n\tcursor: pointer\n\tdisplay: block\n\tposition: absolute\n\ttop: 0\n\tright: 0\n\tpadding: 0 5px\n\tbackground-color: color2\n}\n\n#files_container {\n\t#files_toolbar {\n\t\t#files_toolbar_create {\n\t\t\tflaticon-button(new, toolbar_height - 6px)\n\t\t\ttop-right-control()\n\t\t\theight: toolbar_height\n\t\t}\n\t}\n\t\n\t#files_toolbar_lower {\n\t\tlower-toolbar()\n\n\t\t#files_toolbar_upload {\n\t\t\tflaticon-button(upload, toolbar_height - 6px)\n\t\t}\n\n\t\t#files_toolbar_refresh {\n\t\t\tflaticon-button(reload, toolbar_height - 6px)\n\t\t}\n\n\t\t#files_toolbar_info {\n\t\t\tflaticon-button(timemachine, toolbar_height - 6px)\n\t\t}\n\t}\n\t\n\t#files_list_container {\n\t\tposition: absolute\n\t\tleft: 0\n\t\tright: 0\n\t\tbottom: 0\n\t\ttop: 2*toolbar_height\n\t\tborder-top: 2px solid color3\n\t\tbackground-color: color2\n\t\toverflow: auto\n\t\t-ms-overflow-style: -ms-autohiding-scrollbar\n\n\t\t&.is_bucket {\n\t\t\ttop: toolbar_height\n\t\t}\n\n\t\tul {\n\t\t\tlist-style-type: none\n\t\t\tmargin: 0\n\t\t\tpadding-left: 0\n\t\t\t\n\t\t\tli {\n\t\t\t\tcursor: pointer\n\t\t\t\tpadding-left: 2px\n\t\t\t\tline-height: 1.5em\n\t\t\t\twhite-space: nowrap\n\t\t\t\toverflow: hidden\n\n\t\t\t\t&.file_active {\n\t\t\t\t\tbackground-color: color3\n\t\t\t\t\tcolor: colorbkg\n\t\t\t\t\t.dirpart {\n\t\t\t\t\t\tcolor: mix(colorbkg, color3, 35%)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.dirpart {\n\t\t\t\t\tcolor: mix(colorfgd, color2, 35%)\n\t\t\t\t\tpadding-right: 1px\n\t\t\t\t\tletter-spacing: -1px\n\t\t\t\t}\n\t\t\t\t.filepart {\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t#files_drop_notification {\n\t\t\tdisplay: block;\n\t\t\tpadding: 10px 10px 0;\n\t\t\ttext-align: center;\n\t\t}\n\n\t\t&.drag-over {\n\t\t\t&, & *{\n\t\t\t\tbackground-color: colorerr !important\n\t\t\t\tcolor: black !important\n\t\t\t}\n\t\t}\n\t}\n}\n\n#open_container {\n\t#editor_toolbar {\n\t\t#editor_hamburger {\n\t\t\thamburger-button(colorfgd)\n\t\t\tcursor: pointer\n\t\t\tdisplay: inline-block\n\t\t\tposition: relative\n\t\t\ttop: 2px\n\t\t\twidth: 16px\n\t\t\theight: 1em\n\t\t\tmargin-right: 2px\n\t\t}\n\n\t\t#editor_filename {}\n\n\t\t#editor_runit {\n\t\t\ttop-right-control()\n\t\t}\n\t}\n\t\n\t#editor_btn_container {\n\t\tlower-toolbar()\n\n\t\t#editor_rename {\n\t\t\tflaticon-button(edit, toolbar_height - 6px)\n\t\t}\n\t\t#editor_save {\n\t\t\tflaticon-button(save, toolbar_height - 6px)\n\t\t}\n\t\t#editor_print {\n\t\t\tflaticon-button(print, toolbar_height - 6px)\n\t\t}\n\t\t#editor_download {\n\t\t\tflaticon-button(download, toolbar_height - 6px)\n\t\t}\n\t\t#editor_delete {\n\t\t\tflaticon-button(trash, toolbar_height - 6px)\n\t\t}\n\t\t#editor_share {\n\t\t\tflaticon-button(share, toolbar_height - 6px)\n\t\t}\n\t\t#wrap_checkbox {\n\t\t\tflaticon-button(wrap, toolbar_height - 6px)\n\t\t\t& > input {\n\t\t\t\tdisplay: none\n\t\t\t}\n\t\t}\n\t\t\n\t\t& > * {\n\t\t\tmargin: 1px 2px\n\t\t}\n\t}\n\t\n\t#editor {\n\t\tposition: absolute\n\t\tleft: 0\n\t\tright: 0\n\t\tbottom: 0\n\t\tborder-top: 2px solid color3\n\t\t\n\t\t&.taller {\n\t\t\ttop: toolbar_height\n\t\t}\n\t\t&.shorter {\n\t\t\ttop: toolbar_height*2\n\t\t}\n\t}\n\t\n\t.editor_nofile {\n\t\tposition: absolute\n\t\tleft: 0\n\t\tright: 0\n\t\tbottom: 0\n\t\toverflow: auto\n\t\tpadding: 5px\n\t\t\n\t\t&.fullheight {\n\t\t\ttop: 0\n\t\t}\n\t\t&.taller {\n\t\t\ttop: toolbar_height\n\t\t\tborder-top: 2px solid color3\n\t\t}\n\t\t&.shorter {\n\t\t\ttop: toolbar_height*2\n\t\t\tborder-top: 2px solid color3\n\t\t}\n\n\t\th2{\n\t\t\tmargin: 0\n\t\t\tfont-size: 1.5em\n\t\t\tline-height: 1.5em\n\t\t}\n\n\t\tdl{\n\t\t\tdt{\n\t\t\t\tfont-family: $code_font_family\n\t\t\t\tdisplay: inline-block\n\t\t\t\tpadding: 2px 3px 0px\n\t\t\t\tborder-width: 1px\n\t\t\t\tborder-style: dotted\n\t\t\t}\n\t\t\tdd::before{\n\t\t\t\tfont-family: $unicode_font_family\n\t\t\t\tcontent: \"\\2192\"\n\t\t\t}\n\t\t\tdd{\n\t\t\t\tmargin: 2px 0px 5px 2px\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "client/app/styl/flexbox.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n@require(\"mixins.styl\")\n\n#abox {\n\t// Actual sizes overridden in config.ads.abox_html\n\tposition: absolute\n\tdisplay: none\n\ttop: 0\n\tright: 0\n\twidth: 0\n\theight: 0\n\t\n\tbackground-color: color2\n\ttext-align: center\n\t\n\t.abox_content {\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 50%;\n\t\ttransform: translate(-50%, -50%);\n\t}\n\n\t#abox_default {\n\t\t// Width should be set via media query in config.ads.abox_html\n\t\theight: auto;\n\t\tfont-size: 20px\n\t\tline-height: 25px\n\t\tpadding: 5px\n\t\tbox-sizing: border-box\n\t\tcursor: pointer\n\t}\n}\n\n#main {\n\t// Actual sizes overridden in config.ads.abox_html\n\tposition: absolute\n\tdisplay: block\n\ttop: 0\n\tright: 0\n\tbottom: 0\n\tleft: 0\n}\n\n#flexbox {\n\tdisplay: flex\n\tflex-flow: column nowrap\n\t\n\tposition: absolute\n\ttop: 0\n\tright: 0\n\tbottom: 0\n\tleft: 0\n\t\n\t& > header {\n\t\tflex: 0 0 header_height\n\t}\n\t\n\t& > section {\n\t\tflex: 1 1 1px\n\t\tdisplay: flex\n\t\tflex-flow: row nowrap\n\t\tpadding-top: gutter_size\n\t\tpadding-bottom: gutter_size\n\t\tbackground-color: color3\n\t\n\t\t& > #files_container {\n\t\t\tflex: 2 4 100px\n\t\t\tmargin-right: gutter_size\n\t\t\tbackground-color: colorbkg\n\t\t}\n\t\t\n\t\t& > #open_container {\n\t\t\tflex: 6 4 400px\n\t\t\tmargin-right: gutter_size\n\t\t\tbackground-color: colorbkg\n\t\t}\n\t\t\n\t\t& > #output_panel {\n\t\t\tflex: 12 4 400px\n\t\t\toverflow: hidden\n\t\t\tdisplay: flex\n\t\t\tflex-flow: column nowrap\n\t\t\t\n\t\t\t& > #plot_container {\n\t\t\t\tflex: 1 1 300px\n\t\t\t\toverflow: hidden\n\t\t\t\tmargin-bottom: gutter_size\n\t\t\t\tdisplay: flex\n\t\t\t\t\n\t\t\t\t#plot_figure_container{\n\t\t\t\t\tflex: 2 1 80%\n\t\t\t\t\toverflow: hidden\n\t\t\t\t\tmargin-right: gutter_size\n\t\t\t\t\tbackground-color: colorbkg\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t#plot_controls_container{\n\t\t\t\t\tflex: 1 1 20%\n\t\t\t\t\toverflow: auto\n\t\t\t\t\t-ms-overflow-style: -ms-autohiding-scrollbar\n\t\t\t\t\tbackground-color: colorbkg\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t& > #vars_console_container {\n\t\t\t\tflex: 4 1 300px\n\t\t\t\toverflow: hidden\n\t\t\t\tdisplay: flex\n\t\t\t\tflex-flow: row nowrap\n\n\t\t\t\t& > #vars_panel {\n\t\t\t\t\tflex: 1 1 75px\n\t\t\t\t\tmin-width: 0\n\t\t\t\t\tmargin-right: gutter_size\n\t\t\t\t\tbackground-color: color2\n\t\t\t\t\tposition: relative\n\n\t\t\t\t\t@media (max-width: responsive_width_two) {\n\t\t\t\t\t\tdisplay: none\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t& > #console_container {\n\t\t\t\t\tflex: 8 1 325px\n\t\t\t\t\toverflow: hidden\n\t\t\t\t\tdisplay: flex\n\t\t\t\t\tflex-flow: column nowrap\n\t\t\t\t\tposition: relative\n\t\t\t\t\t\n\t\t\t\t\t& > #console_output_container {\n\t\t\t\t\t\tflex: 1 1 auto\n\t\t\t\t\t\tposition: relative\n\t\t\t\t\t\tmargin-bottom: 2px\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t& > #console_prompt_container {\n\t\t\t\t\t\tflex: 0 0 auto\n\t\t\t\t\t\tposition: relative\n\t\t\t\t\t\tmargin-right: 2px\n\t\t\t\t\t\t// Leave some space at the bottom to accommodate browser tooltips\n\t\t\t\t\t\tmargin-bottom: 10px\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\t& > #main_menu {\n\t\t\tflex: 0 0 220px\n\t\t\tmargin-left: gutter_size\n\t\t\tbackground-color: colorbkg\n\t\t}\n\t\t\n\t\t& > * {\n\t\t\tposition: relative\n\t\t}\n\t}\n}\n\n.handle {\n\tposition: absolute\n\twidth: 15px\n\theight: 20%\n\ttop: (50% - @height/2)\n\tleft: 0\n\tbackground-color: color3\n\tcursor: col-resize\n\tz-index: 150\n\t\n\t&::after {\n\t\tcontent: \"\\3008 \\3009\"\n\t\tcolor: colorbkg\n\t\tline-height: 20px\n\t\tposition: absolute\n\t\ttop: s('calc(%s - %s)', 50%, 10px)\n\t\tleft: -8px\n\t}\n}\n\n"
  },
  {
    "path": "client/app/styl/hamburger.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n/*!\n * Based on Hamburgers\n * @description Tasty CSS-animated hamburgers\n * @author Jonathan Suh @jonsuh\n * @site https://jonsuh.com/hamburgers\n * @link https://github.com/jonsuh/hamburgers\n */\n\n@require(\"mixins.styl\")\n\n.hamburger {\n\tdisplay: inline-block\n\ttransition-property: opacity, filter\n\ttransition-duration: 0.15s\n\ttransition-timing-function: linear\n\n\t&:hover {\n\t\topacity: 0.7\n\t}\n\n\t.hamburger-box {\n\t\twidth: 40px\n\t\theight: 24px\n\t\tposition: relative\n\t}\n\n\t.hamburger-inner {\n\t\ttop: 50%\n\t\tmargin-top: -2px\n\t}\n\n\t.hamburger-inner, .hamburger-inner::before, .hamburger-inner::after {\n\t\twidth: 40px\n\t\theight: 4px\n\t\tbackground-color: colorfgd\n\t\tborder-radius: 4px\n\t\tposition: absolute\n\t\ttransition-property: transform\n\t\ttransition-duration: 0.15s\n\t\ttransition-timing-function: ease\n\t}\n\n\t.hamburger-inner::before, .hamburger-inner::after {\n\t\tcontent: \"\"\n\t\tdisplay: block\n\t}\n\n\t.hamburger-inner {\n\t\ttop: 0\n\t}\n\n\t.hamburger-inner::before {\n\t\ttop: 10px\n\t\ttransition-property: transform, opacity\n\t\ttransition-timing-function: ease\n\t\ttransition-duration: 0.2s\n\t}\n\n\t.hamburger-inner::after {\n\t\ttop: 20px\n\t}\n\n\t&.is-active .hamburger-inner {\n\t\ttransform: translate3d(0, 10px, 0) rotate(45deg)\n\t}\n\t&.is-active .hamburger-inner::before {\n\t\ttransform: rotate(-45deg) translate3d(-5.71429px, -6px, 0)\n\t\topacity: 0\n\t}\n\t&.is-active .hamburger-inner::after {\n\t\ttransform: translate3d(0, -20px, 0) rotate(-90deg)\n\t}\n}\n"
  },
  {
    "path": "client/app/styl/header.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n@require(\"mixins.styl\")\n@require(\"hamburger.styl\")\n\nheader {\n\tbackground-color: color1\n\theight: header_height\n\tposition: relative\n\t\n\t#extra_header_text {\n\t\t&.bucket, &.project {\n\t\t\tcursor: pointer;\n\t\t\ttext-decoration: underline;\n\t\t}\n\t}\n\n\t#hamburger {\n\t\tposition: absolute\n\t\tcursor: pointer\n\t\tdisplay: block\n\t\ttop: 11px\n\t\tright: 9px\n\t\t\n\t\t.hamburger-memo {\n\t\t\tdisplay: inline-block\n\t\t\tposition: absolute\n\t\t\ttop: 1px\n\t\t\tright: 44px\n\t\t\tfont-family: narrow_font_family\n\t\t\tfont-weight: bold\n\t\t\tfont-size: 20px\n\t\t\tline-height: @font-size\n\t\n\t\t\t@media (max-width: responsive_width_one) {\n\t\t\t\tdisplay: none\n\t\t\t}\n\t\t}\n\t}\n\n\th1 {\n\t\tmargin: 0\n\t\tfloat: left\n\n\t\timg {\n\t\t\tdisplay: inline-block\n\t\t\tpadding-top: 2px\n\t\t\tpadding-left: 3px\n\t\t\theight: 36px\n\t\t}\n\t}\n\t\n\tsmall {\n\t\tdisplay: inline\n\t\tline-height: header_height\n\t\tvertical-align: middle\n\t\tpadding-left: 5px\n\n\t\tfont-size: 20px\n\t\tfont-family: narrow_font_family\n\t}\n}\n\n#main_menu_content {\n\tposition: absolute\n\ttop: 0\n\tright: 0\n\tbottom: 0\n\tleft: 0\n\n\tpadding: 5px\n\toverflow: auto\n\t-ms-overflow-style: -ms-autohiding-scrollbar\n\t\n\t.login_btn{\n\t\tcursor: pointer\n\t\tdisplay: block\n\t\tmargin: 5px auto\n\t\twidth: 200px\n\t\theight: 40px\n\t}\n\t\n\t#userbox {\n\t\tflaticon(user)\n\t\theight: 50px\n\t\tpadding-left: 60px\n\t\tpadding-top: 10px\n\t\tfont-size: 1.2em\n\t\tbackground-repeat: no-repeat\n\t\tbackground-position: left center\n\t\t\n\t\ta {\n\t\t\tcolor: inherit\n\t\t}\n\t}\n\t\n\t.share-url-box {\n\t\ttext-align: center\n\t\t\n\t\t& > * {\n\t\t\tdisplay: block\n\t\t}\n\t\t\n\t\tinput {\n\t\t\twidth: 90%\n\t\t\tmargin: 1ex auto\n\t\t}\n\t}\n\t\n\t.instructor-programs {\n\t\tmargin-bottom: 1ex\n\t\tborder-bottom: 1px solid color3\n\t\t\n\t\tul {\n\t\t\tpadding-left: 1em\n\t\t\ttext-indent: -1em\n\t\t\tlist-style-type: none\n\t\t}\n\t\t\n\t\ta {\n\t\t\tcolor: colorfg1\n\t\t\ttext-decoration: underline\n\t\t\tcursor: pointer\n\t\t}\n\t}\n\t\n\t.i18n-language-selector {\n\t\ttext-align: center\n\t\tfont-size: 1.2em\n\t\tmargin: 0 auto 7px\n\n\t\t.flaticon-i18n {\n\t\t\tflaticon(i18n)\n\t\t\tbackground-repeat: no-repeat\n\t\t\tbackground-position: center center\n\t\t\tbackground-size: 24px 24px\n\t\t\tdisplay: inline-block\n\t\t\twidth: 24px\n\t\t\theight: 24px\n\t\t\tvertical-align: bottom\n\t\t}\n\t}\n\n\t.preference-inline-item {\n\t\ttext-align: center\n\t\tfont-size: 1.0em\n\t\tmargin: 1ex auto\n\t\tlabel {\n\t\t\tcursor: pointer\n\t\t}\n\t}\n\t\n\t.site-control-item{\n\t\t@extend .action-link\n\n\t\tposition: relative\n\t\tdisplay: block\n\t\twidth: 190px\n\t\tfont-size: 1.2em\n\t\tmargin: 5px auto\n\t\ttext-align: center\n\n\t\t& > * {\n\t\t\ttext-decoration: underline\n\t\t}\n\t\t\n\t\t& > a {\n\t\t\tdisplay: block\n\t\t\tcursor: pointer\n\t\t\twidth: 100%\n\t\t\theight: 100%\n\t\t}\n\t}\n\n\t.all-buckets {\n\t\tstrong {\n\t\t\tdisplay: block\n\t\t\tmargin-top: 8px\n\t\t\tborder-bottom: 2px solid colorfg2\n\t\t\tpadding-left: 4px\n\t\t\tfont-size: 1.2em\n\t\t}\n\n\t\tul {\n\t\t\tmargin: 0 0 3px 0\n\t\t\tpadding-left: 0\n\t\t\tlist-style-type: none\n\t\t}\n\t\t\n\t\tli {\n\t\t\tclear: both\n\t\t\tborder-bottom: 1px solid colorfg2\n\t\t\tline-height: 1.2em\n\t\t}\n\n\t\t.bucket-name {\n\t\t\tcolor: colorfg1\n\t\t\tfont-size: 1.2em\n\t\t\tfont-family: narrow_font_family\n\t\t}\n\t\t\n\t\t.bucket-time {\n\t\t\tfont-size: 0.9em\n\t\t}\n\n\t\t.bucket-delete {\n\t\t\tfloat: right\n\t\t\theight: 2.4em\n\t\t\tline-height: 2.4em\n\t\t\tvertical-align: middle\n\t\t\tmargin-right: 3px\n\t\t\tcursor: pointer\n\t\t}\n\t\t\n\t\t.bucket-icon-bucket, .bucket-icon-project {\n\t\t\tfloat: left\n\t\t\twidth: 1.2em\n\t\t\theight: 2.4em\n\t\t\tmargin-right: 0.5em\n\t\t\tbackground-repeat: no-repeat\n\t\t\tbackground-position: left center\n\t\t}\n\t\t\n\t\t.bucket-icon-bucket {\n\t\t\tflaticon(share)\n\t\t}\n\t\t\n\t\t.bucket-icon-project {\n\t\t\tflaticon(project)\n\t\t}\n\t}\n\t\n\t#footnotes {\n\t\tmargin-top: 20px\n\t\tfont-size: 0.8em\n\t\tfont-style: italic\n\t\tcolor: colorfg1\n\n\t\t& > * {\n\t\t\tcolor: inherit\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "client/app/styl/mixins.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Variables\nheader_height = 40px\ngutter_size = 5px\nconsole_font_size = 14px\nprompt_font_size = 18px\nprompt_height = 28px\nprompt_sign_width = 20px\ntoolbar_height = 24px\nad_height = 90px\nad_width = 120px\n\nresponsive_width_one = 480px\nresponsive_width_two = 700px\nresponsive_width_three = 800px\nresponsive_width_four = 1200px\n\n// Fonts\nbody_font_family = \"Trebuchet MS\", \"Arial\", sans-serif\ncode_font_family = \"DejaVu Sans Mono\", monospace\nunicode_font_family = \"Arial Unicode\", \"Lucida Sans Unicode\", \"DejaVu Sans\", \"GNU Unifont\", sans-serif\nheading_font_family = \"Trebuchet MS\", \"Impact\", sans-serif\nnarrow_font_family = \"Arial Narrow\", \"Abadi MT Condensed Light\", \"Helvetica CY\", sans-serif;\n\n// Flaticons\nflaticon(name) {\n\tbackground-image: embedurl(\"../images/flaticons/\"+icon_name+\"/\"+name+\".svg\")\n}\nflaticon-button(name, dim=16px) {\n\tflaticon(name)\n\n\tbackground-repeat: no-repeat\n\tbackground-position: center center\n\tbackground-size: dim dim\n\twidth: dim+2px\n\theight: dim+4px\n\tdisplay: inline-block\n\tcursor: pointer\n}\n\n// Utilities\nabsolute-full-size() {\n\tposition: absolute\n\ttop: 0\n\tbottom: 0\n\twidth: 100%\n\theight: 100%\n}\ndebug-backgrounds() {\n\tbackground-color: #FFF !important\n\t& > * {\n\t\tbackground-color: #EEE !important\n\t\t& > * {\n\t\t\tbackground-color: #DDD !important\n\t\t\t& > * {\n\t\t\t\tbackground-color: #CCC !important\n\t\t\t\t& > * {\n\t\t\t\t\tbackground-color: #BBB !important\n\t\t\t\t\t& > * {\n\t\t\t\t\t\tbackground-color: #AAA !important\n\t\t\t\t\t\t& > * {\n\t\t\t\t\t\t\tbackground-color: #999 !important\n\t\t\t\t\t\t\t& > * {\n\t\t\t\t\t\t\t\tbackground-color: #888 !important\n\t\t\t\t\t\t\t\t& > * {\n\t\t\t\t\t\t\t\t\tbackground-color: #777 !important\n\t\t\t\t\t\t\t\t\t& > * {\n\t\t\t\t\t\t\t\t\t\tbackground-color: #666 !important\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\nhamburger-button(color){\n\tbackground-image: linear-gradient(to bottom,\n\t\tcolor 0%,\n\t\tcolor 20%,\n\t\ttransparent 20%,\n\t\ttransparent 40%,\n\t\tcolor 40%,\n\t\tcolor 60%,\n\t\ttransparent 60%,\n\t\ttransparent 80%,\n\t\tcolor 80%,\n\t\tcolor 100%);\n}\n\n// Transition Delay Setup\n.transition-duration-instant{\n\ttransition-duration: 0s\n}\n.transition-duration-fast{\n\ttransition-duration: 0.5s\n}\n.transition-duration-medium{\n\ttransition-duration: 1s\n}\n.transition-duration-slow{\n\ttransition-duration: 2s\n}\n.transition-property-bgcolor{\n\ttransition-property: background-color\n}\n"
  },
  {
    "path": "client/app/styl/modals.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nmodal_content_padding = 20px\nmodal_header_height = 30px\n\n// Popover Box\npopover(_width, _height, _zindex){\n\tposition: absolute\n\ttop: 0\n\tleft: 0\n\tright: 0\n\tbottom: 0\n\tz-index: _zindex\n\tbackground: rgba(128, 128, 128, .8)\n\n\t& > div {\n\t\tposition: absolute\n\t\ttop: 0%\n\t\tleft: 0%\n\t\twidth: 100%\n\t\theight: 100%\n\n\t\t& > div {\n\t\t\tposition: absolute\n\t\t\ttop: 10%\n\t\t\tleft: 10%\n\t\t\ttop: s(\"calc(50% - (%s / 2))\", _height)\n\t\t\tleft: s(\"calc(50% - (%s / 2))\", _width)\n\t\t\twidth: _width\n\t\t\theight: _height\n\t\t\toverflow: auto\n\t\t\t-ms-overflow-style: -ms-autohiding-scrollbar\n\t\t\tbox-sizing: border-box\n\t\t\tpadding: modal_content_padding\n\t\t\tbackground-color: colorbkg\n\t\t\toutline: 2px solid colorfgd\n\n\t\t\th2.popover-title-bar {\n\t\t\t\tposition: relative\n\t\t\t\tmargin: -1*modal_content_padding -1*modal_content_padding modal_content_padding\n\t\t\t\theight: modal_header_height\n\t\t\t\tbackground-color: color2\n\n\t\t\t\tfont-size: 20px\n\t\t\t\tline-height: @height\n\t\t\t\tvertical-align: middle\n\t\t\t\ttext-align: center\n\t\t\t}\n\n\t\t\t.closebtn {\n\t\t\t\tflaticon-button(cross)\n\t\t\t\tdisplay: block\n\t\t\t\tposition: absolute\n\t\t\t\ttop: 5px\n\t\t\t\tright: 5px\n\t\t\t}\n\n\t\t\tkbd {\n\t\t\t\tbackground-color: color2\n\t\t\t\tborder-color: color3\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Privacy Policy\n#privacy {\n\tpopover(90%, 90%, 310)\n\n\tarticle {\n\t\twhite-space: pre-wrap\n\t}\n}\n\n// Email Token\n#email_token {\n\tpopover(50%, 60%, 320)\n\n\ttext-align: center\n\tfont-size: 1.3em\n\tline-height: 1.4em\n\n\tinput[type=\"email\"] {\n\t\tfont-size: 1.4em\n\t\tdisplay: block\n\t\twidth: 95%\n\t\tmargin: 10px auto\n\n\t\t// valid/invalid not currently used\n\t\t&.invalid {\n\t\t\tbackground-color: #E094A5\n\t\t}\n\t\t&.valid {\n\t\t\tbackground-color: #94E0D4\n\t\t}\n\t}\n\n\t.g-recaptcha > div {\n\t\tmargin: 10px auto\n\t}\n}\n\n// Email Password\n#email_password {\n\tpopover(50%, 60%, 320)\n\n\ttext-align: center\n\tfont-size: 1.3em\n\tline-height: 1.4em\n\n\tinput[type=\"email\"], input[type=\"password\"] {\n\t\tfont-size: 1.3em\n\t\tdisplay: block\n\t\twidth: 95%\n\t\tmargin: 10px auto\n\t}\n\n\tp {\n\t\tmargin: 0 auto 1em\n\t}\n\n\t.g-recaptcha > div {\n\t\tmargin: 10px auto\n\t}\n}\n\n// Change Password\n#change_password {\n\tpopover(50%, 50%, 320)\n\n\ttext-align: center\n\tfont-size: 1.3em\n\tline-height: 1.4em\n\n\tinput[type=\"password\"] {\n\t\tfont-size: 1.3em\n\t\tdisplay: block\n\t\twidth: 95%\n\t\tmargin: 10px auto\n\t}\n\n\tp {\n\t\tmargin: 0 auto 1em\n\t}\n}\n\n// Bucket/Project Info\n#bucket_info {\n\tpopover(50%, 50%, 320)\n\t\n\t.shortlink {\n\t}\n\t\n\t.shortlink-url {\n\t\tcolor: colorfg3\n\t\tbackground-color: color2\n\t\tdisplay: inline-block\n\t\tfont-size: 16px\n\t\tpadding: 5px\n\t\tborder: 1px solid colorfg3\n\t\tuser-select: all\n\t}\n\t\n\t.edit-shortlink {\n\t\tflaticon-button(edit, 16px)\n\t\theight: 26px\n\t\tvertical-align: -7px\n\t}\n\t\n\t.action-link {\n\t\tdisplay: inline-block\n\t}\n}\n\n// Create Bucket/Project\n#create_bucket {\n\tpopover(80%, 80%, 310)\n\tbucket_bar_height = 40px\n\n\t#create-bucket-content {\n\t\tposition: absolute\n\t\ttop: modal_header_height\n\t\tleft: 0\n\t\tright: 0\n\t\tbottom: bucket_bar_height\n\t\toverflow: auto\n\n\t\tp {\n\t\t\tfont-size: 1.1em\n\t\t\tmargin: 10px\n\t\t}\n\n\t\th3 {\n\t\t\tfont-size: 1.4em\n\t\t\tborder-bottom: 2px solid color2\n\t\t\tmargin: 10px\n\t\t\tclear: both\n\t\t}\n\t\t\n\t\t.bucket-butype {\n\t\t\tposition: relative\n\t\t\ttext-align: center\n\n\t\t\tlabel {\n\t\t\t\tcursor: pointer\n\t\t\t\tposition: relative\n\t\t\t\tz-index: 0\n\t\t\t\tfont-size: 1.2em\n\t\t\t\tmargin: 0 0.5em\n\n\t\t\t\tinput {\n\t\t\t\t\tposition: relative\n\t\t\t\t\tz-index: 2\n\t\t\t\t}\n\t\t\t\tspan {\n\t\t\t\t\tposition: relative\n\t\t\t\t\tbackground-color: color2\n\t\t\t\t\tposition: inline-block\n\t\t\t\t\tpadding: 0.2em 0.4em 0.2em 1.2em\n\t\t\t\t\tmargin-left: -1em\n\t\t\t\t\tz-index: 1\n\t\t\t\t}\n\t\t\t\tinput[type=\"radio\"]:checked + span {\n\t\t\t\t\tbackground-color: color1\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.bucket-files-fieldset {\n\t\t\twidth: 40%\n\t\t\tmargin: 0 2% 15px\n\t\t\tpadding: 1%\n\t\t\theight: 150px\n\n\t\t\tlegend {\n\t\t\t\tfont-weight: bold\n\t\t\t}\n\n\t\t\tselect {\n\t\t\t\tdisplay: block\n\t\t\t\twidth: 100%\n\t\t\t\theight: 130px\n\t\t\t}\n\n\t\t\t&.left {\n\t\t\t\tfloat: left\n\t\t\t}\n\t\t\t&.right {\n\t\t\t\tfloat: right\n\t\t\t}\n\t\t}\n\n\t\t.bucket-file-move-btns {\n\t\t\tmargin-top: 80px\n\t\t\ttext-align: center\n\t\t}\n\n\t\t.last {\n\t\t\tclear: both\n\t\t\tmargin-top: 15px\n\t\t\tborder-top: 2px solid color2\n\t\t\tpadding-top: 5px\n\t\t}\n\t}\n\n\t#create-bucket-bar {\n\t\tposition: absolute\n\t\tbottom: 0\n\t\tleft: 0\n\t\tright: 0\n\t\theight: bucket_bar_height\n\t\tbackground-color: color1\n\t\ttext-align: right\n\t\tborder-bottom: 5px solid colorbtn\n\n\t\t#create-bucket-btn {\n\t\t\tbackground-color: transparent\n\t\t\tfont-size: 28px\n\t\t\tpadding-top: 4px\n\t\t\tmargin-right: 4px\n\t\t\tborder: none\n\t\t\tcursor: pointer\n\t\t\tcolor: colorbtn\n\t\t}\n\t}\n\n\t#create-bucket-spinner {\n\t\tposition: absolute;\n\t\ttop: 50%;\n\t\tleft: 50%;\n\t\theight: auto;\n\t\twidth: 20%;\n\t\tmargin-left: -10%;\n\t\tmargin-top: -10%;\n\t}\n}\n\n// Upgrade to Flavor\n#upgrade_to_flavor {\n\tpopover(50%, 50%, 320)\n\n\ttext-align: center\n\tfont-size: 1.3em\n\tline-height: 1.4em\n\n\tp {\n\t\tmargin: 0 auto 1em\n\t}\n}\n\n// Upgrade to Tier\n#upgrade_to_tier {\n\tpopover(50%, 50%, 320)\n\n\ttext-align: center\n\tfont-size: 1.3em\n\tline-height: 1.4em\n\n\tp {\n\t\tmargin: 0 auto 1em\n\t}\n\n\t.action-links a {\n\t\t@extend .action-link\n\t\tdisplay: inline-block\n\t\twhite-space: nowrap\n\t}\n\n\t.minor-action-links {\n\t\tfont-size: 0.9em\n\t\tcolor: colorfg1\n\t\tfont-style: italic\n\n\t\ta {\n\t\t\tcolor: inherit\n\t\t}\n\t}\n\n\t.bigger {\n\t\tfont-size: 1.5em\n\t}\n}\n\n// File History\n#file_history_box {\n\tpopover(50%, 50%, 320)\n\n\tbutton {\n\t\tcursor: pointer\n\t}\n}\n\n// Onboarding Div\n#onboarding {\n\tpopover(80%, 80%, 300)\n\n\tfont-size: 1.3em\n\tline-height: 1.3em\n\n\th2{\n\t\tmargin: 0\n\t\tpadding: 0\n\t\tfont-size: 2em\n\t\tline-height: 1em\n\t}\n\n\tfigure{\n\t\twidth: 90%;\n\t\tmargin: 10px auto;\n\t\tpadding: 5px;\n\t\tborder-radius: 10px;\n\t\tborder: 1px solid black;\n\n\t\t& > img{\n\t\t\tdisplay: block;\n\t\t\tmargin: 5px auto;\n\t\t\tmax-width: 100%\n\t\t}\n\t}\n\n\t#onboarding-start{\n\t\tcolor: colorbtn\n\t\tbackground-color: color1\n\t\tbackground-image: linear-gradient(color2, color1, to bottom);\n\t\tbackground-image: radial-gradient(ellipse at center,\n\t\t\tcolor2 0%,\n\t\t\tcolor1 100%);\n\t\tdisplay: block;\n\t\tmargin: 10px auto;\n\t\tpadding: 0.5em;\n\t\tfont-size: 1.5em;\n\t\tcursor: pointer;\n\t\tborder-radius: 1em;\n\t\tborder: none;\n\t\tbackground-color: blue;\n\n\t\t&:active{\n\t\t\tbackground-color: #ffa84c;\n\t\t\tbackground-image: linear-gradient(color2, color3, to bottom);\n\t\t\tbackground-image: radial-gradient(ellipse at center,\n\t\t\t\tcolor2 0%,\n\t\t\t\tcolor3 100%);\n\t\t}\n\t}\n}\n\n#announcement-box {\n\tpopover(60%, 60%, 250)\n\n\tfont-size: 1.3em\n\tline-height: 1.3em\n}\n\n#welcome_back {\n\tpopover(60%, 60%, 240)\n\n\tfont-size: 1.3em\n\tline-height: 1.3em\n\t\n\tbutton {\n\t\tcolor: colorbtn\n\t\tbackground-color: color1\n\t\tcursor: pointer\n\t\tborder: none\n\t\tborder-radius: 1em\n\t\tpadding: 0.5em\n\t}\n}\n\n// Main site loading spinner\n#site_loading {\n\tposition: fixed\n\tz-index: 200\n\twidth: 100%\n\theight: 100%\n\tpointer-events: none\n\n\t#cosine-pulse {\n\t\tbackground-image: embedurl(\"../images/logo_collections/\"+logo_collection+\"/cosine.svg\")\n\t\tbackground-repeat: repeat-x\n\t\tbackground-size: 2000px 100%\n\t\t// TODO: Change to side-relative values when browser support is better\n\t\tbackground-position-x: -3000px\n\t\tanimation: 6s cosine-width infinite cubic-bezier(.8,0,.8,1) alternate forwards, 12s cosine-position infinite ease-in-out alternate forwards\n\t\tanimation-delay: 6s, 0s\n\t\tposition: absolute\n\t\ttop: 30%\n\t\tleft: 0\n\t\twidth: 100%\n\t\theight: 40%\n\t}\n\n\t@keyframes cosine-width {\n\t\tfrom {\n\t\t\tbackground-size: 2000px 100%\n\t\t}\n\t\tto {\n\t\t\tbackground-size: 150px 100%\n\t\t}\n\t}\n\n\t@keyframes cosine-position {\n\t\tfrom {\n\t\t\tbackground-position-x: -3000px\n\t\t}\n\t\tto {\n\t\t\tbackground-position-x: 4000px\n\t\t}\n\t}\n\n\t#site_loading_patience {\n    position: relative;\n\t\ttop: 75%;\n\t\twidth: 50%;\n\t\tmargin: 0 auto;\n\t\tmin-width: 18em;\n\n\t\ttext-align: center;\n\t\tfont-size: 1.2em;\n\t\tline-height: 1.2em;\n\t}\n\n\t#site_loading_more_patience {\n    position: relative;\n\t\ttop: 75%;\n\t\twidth: 50%;\n\t\tmargin: 0 auto;\n\t\tmin-width: 18em;\n\n\t\ttext-align: center;\n\t\tfont-size: 1.2em;\n\t\tline-height: 1.2em;\n\t}\n}\n"
  },
  {
    "path": "client/app/styl/output_panel.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Contains styles for the output panel, console window, prompt, plot,\n// and variables.\n\n@require(\"mixins.styl\")\n\nplot-svg-styles()\n\timage {\n\t\t-ms-interpolation-mode: nearest-neighbor\n\t\timage-rendering: -webkit-optimize-contrast\n\t\timage-rendering: -moz-crisp-edges\n\t\timage-rendering: crisp-edges\n\t\timage-rendering: pixelated\n\t}\n\n#console_output_container {\n\tbackground-color: colorbkg\n\n\tpre#console {\n\t\tposition: absolute\n\t\ttop: 0\n\t\tright: 0\n\t\tbottom: 0\n\t\tleft: 0\n\n\t\toverflow: auto\n\t\t-ms-overflow-style: -ms-autohiding-scrollbar\n\n\t\ttext-align: left\n\t\tfont-size: console_font_size\n\t\tline-height: 1.3em\n\t\tfont-family: code_font_family\n\t\tpadding: 1ex\n\t\tmargin: 0\n\n\t\t&.console-wrap{\n\t\t\twhite-space: pre-wrap\n\t\t}\n\n\t\tspan.prompt_row{\n\t\t\tfont-weight: bold;\n\t\t\tcolor: colorfg1;\n\t\t}\n\t\tspan.prompt_error{\n\t\t\tcolor: colorerr\n\t\t\tfont-style: italic;\n\t\t}\n\t\ta{\n\t\t\tcolor: color3\n\t\t}\n\n\t\tdiv.inline-plot{\n\t\t\tdisplay: block\n\t\t\tposition: relative\n\t\t\tmargin: 5px 0\n\n\t\t\t@media (max-width: responsive_width_two) {\n\t\t\t\t// Hacky way to get an absolute aspect ratio\n\t\t\t\twidth: 95%\n\t\t\t\twidth: calc(100% - 2ex)\n\t\t\t\theight: 0\n\t\t\t\tpadding-top: 73%\n\t\t\t}\n\n\t\t\t@media (min-width: responsive_width_two) {\n\t\t\t\twidth: 560px\n\t\t\t\theight: 420px\n\t\t\t}\n\t\t\t\n\t\t\tsvg, .inline-plot-loading {\n\t\t\t\tdisplay: block\n\t\t\t\tposition: absolute\n\t\t\t\ttop: 0\n\t\t\t\tleft: 0\n\t\t\t\twidth: 100%\n\t\t\t\theight: 100%\n\t\t\t}\n\t\t\t\n\t\t\tsvg {\n\t\t\t\tplot-svg-styles()\n\t\t\t}\n\t\t\t\n\t\t\t.inline-plot-loading {\n\t\t\t\tbackground-image: url(\"../../images/spinner.svg\")\n\t\t\t\tbackground-position: center center\n\t\t\t\tbackground-repeat: no-repeat\n\t\t\t\tbackground-size: 30% 30%\n\t\t\t}\n\t\t}\n\t}\n\t\n\t#cwd_box {\n\t\tdisplay: inline-block\n\t\tposition: absolute\n\t\ttop: 0\n\t\tleft: 0\n\t\tpadding: 3px\n\t\tbackground-color: alpha(color3, 0.5)\n\t\tcolor: colorfgd\n\t\tfont-style: italic\n\t}\n\n\t#tier_background {\n\t\tposition: absolute\n\t\ttop: 100px\n\t\tleft: 50%\n\n\t\t& > div {\n\t\t\tposition: absolute\n\t\t\ttop: -75px\n\t\t\tleft: -75px\n\t\t\twidth: 150px\n\t\t\theight: 150px\n\t\t\tcursor: pointer\n\n\t\t\t&.root {\n\t\t\t\tflaticon(tier-root-sq)\n\t\t\t}\n\t\t\t&.plus {\n\t\t\t\tflaticon(tier-plus-sq)\n\t\t\t}\n\t\t\t&.times {\n\t\t\t\tflaticon(tier-times-sq)\n\t\t\t}\n\t\t\t&.exp {\n\t\t\t\tflaticon(tier-exp-sq)\n\t\t\t}\n\t\t}\n\t}\n}\n\n#console_prompt_container {\n\n\tdiv#runtime_controls_container{\n\t\theight: prompt_height\n\t\tbackground-color: colorbkg\n\n\t\t#signal{\n\t\t\tflaticon-button(cross, 12px)\n\t\t\tposition: relative\n\t\t\ttop: 5px\n\t\t\tleft: 5px\n\t\t}\n\t\t#seconds_remaining_container{\n\t\t\tposition: absolute\n\t\t\ttop: 5px\n\t\t\tleft: 25px\n\t\t\t\n\t\t\t#seconds_remaining{\n\t\t\t\tfont-family: code_font_family\n\t\t\t}\n\t\t}\n\t\t#add_time_container, #payload_acknowledge_container {\n\t\t\tposition: absolute\n\t\t\ttop: 5px\n\t\t\tleft: 200px\n\t\t}\n\t}\n\n\tdiv#prompt_sign {\n\t\tfloat: left\n\t\tfont-size: prompt_font_size\n\t\tmargin-top: 6px\n\t\tcolor: colorbkg\n\t\tmargin-right: -5px\n\t\tfont-family: unicode_font_family\n\t}\n\n\tdiv#prompt {\n\t\tfont-size: prompt_font_size\n\t\tline-height: prompt_font_size\n\t}\n}\n\n#plot_opener {\n\tflaticon(chart)\n\tcursor: pointer\n\tposition: absolute\n\twidth: 40px\n\theight: 40px\n\ttop: 5px\n\tright: 5px\n\tbackground-color: colorbkg\n\tbackground-repeat: no-repeat\n\tbackground-position: left center\n}\n\n#agpl_icon {\n\tposition: absolute\n\ttop: 5px\n\tright: 5px\n\twidth: 120px\n}\n\n#flavor_upgrade_div {\n\tposition: absolute\n\tright: 8px\n\tbottom: 8px\n\ttext-align: right\n\n\tfont-size: 12px\n\t\n\t#upgrade_btn {\n\t\tdisplay: inline-block\n\t\tpadding: 5px 10px\n\t\tborder-radius: 11px\n\t\tborder: 1px solid colorfgd\n\t\tbox-shadow: 0px 0px 5px color3\n\t\tvertical-align: middle\n\t\tbackground-color: colorfg2\n\t\tcolor: colorfgd\n\t\t// border: none\n\t\tcursor: pointer\n\n\t\t.disk {\n\t\t\t&::before {\n\t\t\t\tcontent: \"⦾\"\n\t\t\t}\n\n\t\t\t&.available {\n\t\t\t\t&::before {\n\t\t\t\t\tcontent: \"⦿\"\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfont-size: 1.2em\n\t\t}\n\t}\n\t\n\t#flavor_status {\n\t\tdisplay: inline-block\n\t\tpadding: 3px 5px\n\t\tbackground-color: color2\n\t}\n}\n\t\t\n#plot_container {\n\tposition: relative\n\n\t#plot_figure_container{\n\t\tposition: relative\n\n\t\t#plot_svg_container, svg {\n\t\t\tabsolute-full-size()\n\t\t}\n\t\t\n\t\tsvg {\n\t\t\tplot-svg-styles()\n\t\t}\n\n\t\t#plot_loading {\n\t\t\tposition: absolute\n\t\t\ttop: 50%\n\t\t\tleft: 50%\n\t\t\twidth: 0\n\t\t\theight: 0\n\n\t\t\t& > img {\n\t\t\t\tposition: absolute\n\t\t\t\ttop: -50px\n\t\t\t\tleft: -50px\n\t\t\t\twidth: 100px\n\t\t\t\theight: 100px\n\t\t\t}\n\t\t}\n\n\t\t&.fullscreen {\n\t\t\tposition: fixed\n\t\t\ttop: header_height + gutter_size\n\t\t\tright: 0\n\t\t\tbottom: 0\n\t\t\tleft: 0\n\t\t\tmargin: 0 !important\n\t\t\tz-index: 900\n\t\t}\n\n\t\t// for rendering downloadable bitmaps\n\t\t#plot_canvas_container {\n\t\t\tposition: absolute\n\t\t\twidth: 0\n\t\t\theight: 0\n\t\t\toverflow: hidden\n\t\t}\n\t}\n\t\n\t#plot_controls_container {\n\t\ttext-align: center\n\t\t\n\t\t.plot-nav-btn {\n\t\t\tfont-size: 3em\n\t\t\tcursor: pointer\n\n\t\t\t&.disabled {\n\t\t\t\tvisibility: hidden\n\t\t\t}\n\t\t}\n\t\t\n\t\t#plot_png_download_btn {\n\t\t\tflaticon-button(download-png, 40px)\n\t\t}\n\t\t#plot_svg_download_btn {\n\t\t\tflaticon-button(download-svg, 40px)\n\t\t}\n\t}\n}\n\n// Push down the content of the console to the bottom of the screen\n#console::before {\n\tcontent: \"\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\\000A\";\n}\n\n#vars_panel {\n\t.theme-header {\n\t\ttext-align: center\n\t}\n\n\t#vars_content {\n\t\tposition: absolute\n\t\ttop: toolbar_height\n\t\tright: 0\n\t\tbottom: 0\n\t\tleft: 0\n\n\t\tfont-size: 1.2em\n\t\tline-height: 1.2em\n\t\toverflow: auto\n\t\t-ms-overflow-style: -ms-autohiding-scrollbar\n\n\t\tul {\n\t\t\tlist-style-type: none\n\t\t\tpadding-left: 0\n\t\t\tmargin: 0\n\n\t\t\tli {\n\t\t\t\tcursor: pointer\n\t\t\t\tpadding: 0.1em\n\t\t\t\twhite-space: nowrap\n\t\t\t\tborder: none\n\n\t\t\t\t.vars_type {\n\t\t\t\t\tcolor: colorfg3\n\t\t\t\t}\n\t\t\t\t&.ko-flash {\n\t\t\t\t\tbackground-color: color2lt\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "client/app/styl/print.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Print stylesheet for Octave Online\n\n@media print{\n\n\t// Remove all absolute positioning\n\t// and hide everything by default\n\t* {\n\t\tposition: static !important;\n\t\tdisplay: none;\n\t}\n\n\t// Show the essential container elements\n\thtml, body, header, #flexbox, #flexbox section {\n\t\tdisplay: block;\n\t}\n\t\n\t// Octave Online title bar\n\th1 {\n\t\tdisplay: block;\n\t\t* {\n\t\t\tdisplay: inline;\n\t\t\tposition: relative !important;\n\t\t}\n\t}\n\n\t// Console\n\t#flexbox section {\n\t\tbackground-color: white !important;\n\t}\n\t#output_panel, #vars_console_container, #console_container, #console_output_container, #console {\n\t\tdisplay: inline !important;\n\t}\n\t#console {\n\t\tbackground: none !important;\n\t}\n\t#console::before {\n\t\tcontent: \"\\000A\" !important;\n\t}\n\t#console * {\n\t\tdisplay: inline !important;\n\t}\n\n\t// Plot\n\t#plot_container, #plot_figure_container, #plot_svg_container {\n\t\tdisplay: inline !important;\n\t}\n\t#plot_container {\n\t\tbox-shadow: none !important;\n\t}\n\t#plot_svg_container * {\n\t\tdisplay: inline !important;\n\t}\n}\n"
  },
  {
    "path": "client/app/styl/themes/official/fire.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ncolor1 = #FF4B33\ncolor2 = #FF715E\ncolor3 = #5C0B00\ncolorbkg = #FFFFFF\ncolorfgd = #000000\ncolorerr = #918500\ncolorfg1 = color1 // high contrast with colorbkg\ncolorfg2 = color2 // medium alternate color\ncolorfg3 = color3 // high contrast with color2\ncolorfg4 = color3 // darker color, for pressed button\ncolor2lt = #FFC9C2 // for changed variables\ncolorbtn = #FFFFFF // for the text on buttons with background-color color1\nicon_name = fire\nlogo_collection = official\n\n@require(\"../../all.styl\")\n\n// Flashing Style\n.ko-flash{\n\t// background-color: lighten(color2, 20%);\n}\n\n.patience{\n\tbackground-color: rgba(255, 255, 255, 0.8);\n}\n"
  },
  {
    "path": "client/app/styl/themes/official/ice.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ncolor1 = #1F71FF\ncolor2 = #6BA1FF\ncolor3 = #154596\ncolorbkg = #FFFFFF\ncolorfgd = #000000\ncolorerr = #918500\ncolorfg1 = color1 // high contrast with colorbkg\ncolorfg2 = color2 // medium alternate color\ncolorfg3 = color3 // high contrast with color2\ncolorfg4 = color3 // darker color, for pressed button\ncolor2lt = #ADCBFF // for changed variables\ncolorbtn = #FFFFFF // for the text on buttons with background-color color1\nicon_name = fire\nlogo_collection = official\n\n@require(\"../../all.styl\")\n\n// Flashing Style\n.ko-flash{\n\t// background-color: lighten(color2, 20%);\n}\n\n.patience{\n\tbackground-color: rgba(255, 255, 255, 0.8);\n}\n"
  },
  {
    "path": "client/app/styl/themes/official/lava.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ncolor1 = #2E2422\ncolor2 = #46322C\ncolor3 = #64443A\ncolorbkg = #1c1c1c // matches Ace editor\ncolorfgd = #E6D2CC\ncolorerr = #C2C484\ncolorfg1 = #965341 // high contrast with colorbkg\ncolorfg2 = color3 // high contrast with colorbkg\ncolorfg3 = colorfgd // high contrast with color2\ncolorfg4 = color2 // darker color, for pressed button\ncolor2lt = #594D4B // for changed variables\ncolorbtn = colorfg1 // for the text on buttons with background-color color1\nicon_name = lava\nlogo_collection = official\n\n@require(\"../../all.styl\")\n\n// Flashing Style\n.ko-flash{\n\t// background-color: lighten(color2, 20%)\n}\n\n.patience{\n\tbackground-color: rgba(0, 0, 0, 0.8)\n}\n"
  },
  {
    "path": "client/app/styl/themes/official/sun.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ncolor1 = #FFB433\ncolor2 = #FFC45E\ncolor3 = #5C3A00\ncolorbkg = #FFFFFF\ncolorfgd = #000000\ncolorerr = #918500\ncolorfg1 = color1 // high contrast with colorbkg\ncolorfg2 = color2 // medium alternate color\ncolorfg3 = color3 // high contrast with color2\ncolorfg4 = color3 // darker color, for pressed button\ncolor2lt = #FFE9C2 // for changed variables\ncolorbtn = colorfgd // for the text on buttons with background-color color1\nicon_name = fire\nlogo_collection = official\n\n@require(\"../../all.styl\")\n\n// Flashing Style\n.ko-flash{\n\t// background-color: lighten(color2, 20%);\n}\n\n.patience{\n\tbackground-color: rgba(255, 255, 255, 0.8);\n}\n"
  },
  {
    "path": "client/app/styl/themes/server/fire.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ncolor1 = #AD928E\ncolor2 = #C7B5B5\ncolor3 = #7D6363\ncolorbkg = #FFFFFF\ncolorfgd = #000000\ncolorerr = #918500\ncolorfg1 = color1 // high contrast with colorbkg\ncolorfg2 = color2 // medium alternate color\ncolorfg3 = color3 // high contrast with color2\ncolorfg4 = color3 // darker color, for pressed button\ncolor2lt = #E6E6E6 // for changed variables\ncolorbtn = #FFFFFF // for the text on buttons with background-color color1\nicon_name = fire\nlogo_collection = server\n\n@require(\"../../all.styl\")\n\n.patience{\n\tbackground-color: rgba(255, 255, 255, 0.8);\n}\n"
  },
  {
    "path": "client/app/styl/themes/server/ice.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ncolor1 = #9090E0\ncolor2 = #B0B0E0\ncolor3 = #606090\ncolorbkg = #FFFFFF\ncolorfgd = #000000\ncolorerr = #918500\ncolorfg1 = color1 // high contrast with colorbkg\ncolorfg2 = color2 // medium alternate color\ncolorfg3 = color3 // high contrast with color2\ncolorfg4 = color3 // darker color, for pressed button\ncolor2lt = #DEE0FF // for changed variables\ncolorbtn = #FFFFFF // for the text on buttons with background-color color1\nicon_name = fire\nlogo_collection = server\n\n@require(\"../../all.styl\")\n\n.patience{\n\tbackground-color: rgba(255, 255, 255, 0.8);\n}\n"
  },
  {
    "path": "client/app/styl/themes/server/lava.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ncolor1 = #292929\ncolor2 = #303030\ncolor3 = #494949\ncolorbkg = #1c1c1c // matches Ace editor\ncolorfgd = #E6D2CC\ncolorerr = #C2C484\ncolorfg1 = #757575 // high contrast with colorbkg\ncolorfg2 = color3 // medium alternate color\ncolorfg3 = colorfgd // high contrast with color2\ncolorfg4 = color2 // darker color, for pressed button\ncolor2lt = #575757 // for changed variables\ncolorbtn = colorfgd // for the text on buttons with background-color color1\nicon_name = lava\nlogo_collection = server\n\n@require(\"../../all.styl\")\n\n.patience{\n\tbackground-color: rgba(0, 0, 0, 0.8)\n}\n"
  },
  {
    "path": "client/app/styl/themes/server/sun.styl",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ncolor1 = #CCBDA3\ncolor2 = #E6D9C3\ncolor3 = #5C5445\ncolorbkg = #FFFFFF\ncolorfgd = #000000\ncolorerr = #918500\ncolorfg1 = color1 // high contrast with colorbkg\ncolorfg2 = color2 // medium alternate color\ncolorfg3 = color3 // high contrast with color2\ncolorfg4 = color3 // darker color, for pressed button\ncolor2lt = #FFFAF2 // for changed variables\ncolorbtn = colorfgd // for the text on buttons with background-color color1\nicon_name = fire\nlogo_collection = server\n\n@require(\"../../all.styl\")\n\n.patience{\n\tbackground-color: rgba(255, 255, 255, 0.8);\n}\n"
  },
  {
    "path": "client/bower.json",
    "content": "{\n  \"name\": \"octave-online\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"requirejs\": \"latest\",\n    \"jquery\": \"#^1\",\n    \"knockoutjs\": \"latest\",\n    \"jquery.cookie\": \"latest\",\n    \"jquery-md5\": \"latest\",\n    \"canvg\": \"latest\",\n    \"ace\": \"https://github.com/vote539/ace/archive/oo.zip\",\n    \"splittr\": \"latest\",\n    \"canvas-toBlob.js\": \"latest\",\n    \"blob\": \"latest\",\n    \"FileSaver\": \"1.3.2\",\n    \"ot\": \"latest\"\n  }\n}\n"
  },
  {
    "path": "client/package.json",
    "content": "{\n  \"name\": \"@oo/client\",\n  \"version\": \"0.0.0\",\n  \"description\": \"Web client front end for Octave Online\",\n  \"main\": \"app/main.js\",\n  \"scripts\": {\n    \"grunt\": \"grunt\",\n    \"bower\": \"bower\",\n    \"stylus-watch\": \"npm run stylus-watch:server\",\n    \"stylus-watch:server\": \"stylus --watch -o app/css/themes app/styl/themes/server\"\n  },\n  \"author\": \"Octave Online LLC\",\n  \"license\": \"AGPL-3.0\",\n  \"engines\": {\n    \"node\": \"18.x\"\n  },\n  \"devDependencies\": {\n    \"@oo/shared\": \"file:../shared\",\n    \"grunt\": \"^1.4.1\",\n    \"grunt-contrib-copy\": \"^1.0.0\",\n    \"grunt-contrib-requirejs\": \"^1.0.0\",\n    \"grunt-contrib-stylus\": \"^1.2.0\",\n    \"grunt-contrib-uglify\": \"^4.0.1\",\n    \"grunt-contrib-watch\": \"^1.1.0\",\n    \"grunt-regex-replace\": \"^0.4.0\",\n    \"grunt-sync\": \"^0.8.2\",\n    \"normalize-styl\": \"^8.0.1\"\n  },\n  \"dependencies\": {\n    \"bower\": \"^1.8.12\",\n    \"grunt-cli\": \"^1.4.3\",\n    \"socket.io-client\": \"^2.4.0\",\n    \"socketio-file-upload\": \"^0.4\"\n  }\n}\n"
  },
  {
    "path": "config.sample.hjson",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n##########\n# NOTICE #\n##########\n#\n# For additional information about these settings, refer to\n# config_defaults.json\n#\n\n# # # # # # # # # # # # #\n# Common configurations #\n# # # # # # # # # # # # #\n\nredis: {\n\thostname: localhost\n\tport: 6379\n\toptions: {\n\t\tauth_pass: xxxxxxxxx\n\t}\n}\n\nmongo: {\n\thostname: localhost\n\tport: 27019\n\tdb: oo\n}\n\nemail: {\n\tprovider: mailgun\n\tmailgun: {\n\t\tapi_key: xxxxxxxxx\n\t\tdomain: localhost\n\t}\n\tpostmark: {\n\t\tserverToken: xxxx-xxxx-xxxx\n\t\ttemplateAlias: xxxxxxxxx\n\t}\n}\n\nrecaptcha: {\n\tsiteKey: xxxxxxxxx\n\tsecretKey: xxxxxxxxx\n}\n\ngcp: {\n\tartifacts_bucket: artifacts.PROJECT_ID.appspot.com\n}\n\n# # # # # # # # # # # # # # # #\n# Back Server configurations  #\n# # # # # # # # # # # # # # # #\n\nworker: {\n\tlogDir: /tmp/oo_logs\n}\n\nsession: {\n\timplementation: docker\n}\n\ngit: {\n\thostname: localhost\n}\n\n# # # # # # # # # # # # # # # #\n# Front Server configurations #\n# # # # # # # # # # # # # # # #\n\nauth: {\n\tgoogle: {\n\t\toauth_key: xxxxxxxxx.apps.googleusercontent.com\n\t\toauth_secret: xxxxxxxxx\n\t}\n\teasy: {\n\t\t### Make up a long random string! ###\n\t\tsecret: xxxxxxxxx\n\t}\n\tutils_admin: {\n\t\tusers: {\n\t\t\t### Make up a long random string! ###\n\t\t\tadmin: xxxxxxxxx\n\t\t}\n\t}\n}\n\nfront: {\n\tprotocol: http\n\thostname: localhost\n\tport: 8080\n\tlisten_port: 8080\n\n\t# Use \"client/app\" for debugging\n\tstatic_path: client/dist\n\n\tcookie: {\n\t\t### Make up a long random string! ###\n\t\tsecret: xxxxxxxxx\n\t}\n}\n\n# # # # # # # # # # # # #\n# Client configurations #\n# # # # # # # # # # # # #\n\nclient: {\n\tgtagid: xxxxxxxxx\n}\n"
  },
  {
    "path": "config_defaults.hjson",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n\n#############\n# IMPORTANT #\n#############\n#\n# This file contains default configurations for Octave Online.\n#\n# Do NOT edit this file directly, unless you are making a contribution to\n# Octave Online Server and wish to change these defaults for all users.\n#\n# To make custom local configurations, create a file \"config.hjson\" in this\n# directory and add the keys you want to override into that file. You can use\n# \"config.sample.hjson\" as a template for \"config.hjson\".\n#\n\n\n# # # # # # # # # # # # #\n# Common configurations #\n# # # # # # # # # # # # #\n\n# Redis connection info\n# To disable Redis (for testing only), set the hostname to \"\"\nredis: {\n\thostname: redis\n\tport: 6379\n\toptions: {\n\t\t# Add a password here if your instance requires authentication:\n\t\t# auth_pass: xxxxxxxxx\n\t}\n\n\t# RATE LIMITING\n\t# Most keys added to Redis will have an expiration and an interval to touch the expiration. Setting these too low may overload Redis with expiration touch requests.\n\t# maxPayload determines when a message is too big to be sent over the main pub-sub pipe; a special client will be opened to receive the message. This makes that message slightly slower but clears up the pipe for other messages. This is most often used when transmitting large amounts of data like plot images.\n\texpire: {\n\t\tinterval: 5000\n\t\ttimeout: 16000\n\t\ttimeoutShort: 6000\n\t}\n\tmaxPayload: 10000\n}\n\n# MongoDB connection info\n# To disable MongoDB (for testing only), set the hostname to \"\"\nmongo: {\n\thostname: mongod\n\tport: 27017\n\tdb: oo\n}\n\n# Email SaaS settings\nemail: {\n\t# The email provider should be either \"mailgun\" or \"postmark\".\n\tprovider: mailgun\n\n\t# Default headers for sending email\n\tfrom: Octave Online Server <example@example.com>\n\n\t# productName and supportUrl are used only for postmark\n\tproductName: Octave Online Server\n\tsupportUrl: http://localhost/\n}\n\n# Mailgun connection info\nmailgun: {\n\tapi_key: xxxxxxxxx\n\tdomain: localhost\n}\n\n# Postmark connection info\npostmark: {\n\tserverToken: xxxx-xxxx-xxxx\n\t# Postmark is configured to send email using a template. Please create a template in Postmark with the fields product_name, token_string, action_url, and support_url.\n\ttemplateAlias: xxxxxxxxx\n\t# MessageStream and Template to use for on-demand file snapshot emails. The template should have fields product_name, archive_desc, action_url, and support_url.\n\tonDemandSnapshots: {\n\t\tstream: \"on-demand-file-snapshots\",\n\t\ttemplate: \"on-demand-file-snapshots\",\n\t}\n}\n\n# ReCAPTCHA connection info\nrecaptcha: {\n\tsiteKey: xxxxxxxxx\n\tsecretKey: xxxxxxxxx\n}\n\n# Patreon info\npatreon: {\n\tclient_id: xxxxxxxxx\n\tclient_secret: xxxxxxxxx\n\tredirect_url: http://localhost/auth/patreon/callback\n\t# Send webhooks to /auth/patreon/webhook\n\twebhook_secret: xxxxxxxxx\n\t# A secret used for encoding the state to authenticate OAuth requests (make up a custom string)\n\tstate_secret: xxxxxxxxx\n\tstate_max_token_age: 1800000\n\t# Where to redirect the user if they link Patreon and aren't already a member\n\tlogin_redirect: https://www.patreon.com/bePatron?u=xxxxxxxxx&redirect_uri=http%3A%2F%2Flocalhost%2Fauth%2Fpatreon\n\n\t# A mapping from Patreon tiers to Octave Online Server tiers. Multiple Patreon tiers can map to the same Octave Online tier.\n\t# Four tier names are supported with icon assets: \"root\", \"plus\", \"times\", and \"exp\".\n\t# Example: Patreon tier with ID \"12345\" has vanity name \"plus\" and maps to the Octave Online \"vip\" tier.\n\ttiers: {\n\t\t# \"12345\": {\n\t\t# \tname: plus\n\t\t# \too_tier: vip\n\t\t# }\n\t}\n}\n\n# Rackspace connection info\nrackspace: {\n\tusername: xxxxxxxxx\n\tapi_key: xxxxxxxxx\n\tidentity_base_url: https://identity.api.rackspacecloud.com/v2.0/\n\t# Fill in the following with the region as xxx and the tenant ID as yyy\n\tservers_base_url: https://xxx.servers.api.rackspacecloud.com/v2/yyy/\n\t# The personality filename to send metadata to the instance\n\tpersonality_filename: /etc/oo-personality.json\n}\n\n# GCP connection info\ngcp: {\n\t# Credentials for service worker to access GCP resources. Take the JSON blob that you download from the GCP cloud console and paste it here:\n\t# credentials: {\n\t# \t\"type\": \"service_account\",\n\t# \t\"project_id\": \"...\",\n\t# \t\"private_key_id\": \"...\",\n\t# \t\"private_key\": \"...\",\n\t# \t...\n\t# }\n\tcredentials: false\n\t# Zone for compute API calls\n\tzone: us-central1-f\n\t# Managed Instance Group (MIG) autoscaler configurations\n\tinstance_group_name: xxxxxxxxx\n\tinstance_group_removal_method: abandon\n\thealth_check_port: 3030\n\t# Cloud Storage Bucket for artifacts used for i18next locales; see cloudbuild.yaml in https://github.com/octave-online/oos-translations\n\tartifacts_bucket: artifacts.PROJECT_ID.appspot.com\n  i18next_locales_tar_gz: objects/oos-translations/i18next_locales.tar.gz\n  # Cloud Storage Bucket for user file snapshots\n  snapshots_bucket: reposnapshots.PROJECT_ID.appspot.com\n  # Cloud Storage Bucket for user file archive\n  archive_bucket: repoarchive.PROJECT_ID.appspot.com\n  # Amount of time for snapshot links to be valid (default: 72 hours = 259,200 seconds)\n  snapshots_duration: 259200000\n}\n\n# StatsD connection info\nstatsd: {\n\thostname: localhost\n\tport: 8125\n}\n\n# Gith server connection info (for git-http-backend and php-fpm)\n# TODO: Consider making the service ports configurable.\ngith: {\n\thostname: utils-gith\n}\n\n# # # # # # # # # # # # # # # #\n# Back Server configurations  #\n# # # # # # # # # # # # # # # #\n\n# Runtime details for the back server worker process\nworker: {\n\n\t# LOGGING\n\t# The log directory should exist and be writeable by the server process.\n\t# \"token\" is the name of the worker written at the top of the log file.\n\tlogDir: /srv/logs\n\ttoken: local\n\tmonitorLogs: {\n\t\tsubdir: monitor\n\t}\n\tsessionLogs: {\n\t\tsubdir: sessions\n\t\tdepth: 3\n\t}\n\n\t# SESSION QUEUE AGGRESSIVENESS\n\t# How frequently to check the queue for new jobs (ms).\n\t# When maxSessions is reached, don't get any more from the queue.\n\tclockInterval: {\n\t\tmin: 1500\n\t\tmax: 2500\n\t}\n\tmaxSessions: 12\n\t# clockStrategy:\n\t#   - random = randomly choose a delay between clockInterval.min and clockInterval.max\n\t#   - weighted = choose a shorter delay if there are fewer online sessions, and a longer delay if there are more online sessions\n\tclockStrategy: random\n\n\t# PERMISSIONS\n\t# uid to use for file ownership; set this to the uid of the user running Octave.\n\tuid: 1000\n\n\t# SESSION HANDLING\n\t# When set to \"destroy\" (default), destroy sessions immediately when the client disconnects.  When set to \"ignore\", don't destroy sessions immediately, and let them be cleaned up when they expire (see redis.expire).  When set to \"expireShort\", set the expire timeout to config.redis.expire.timeoutShort and then ignore.\n\tonDisconnect: destroy\n}\n\n# Octave session settings\nsession: {\n\n\t# COMMAND AND SESSION TIMEOUTS\n\t# All times are in milliseconds.\n\t# legalTime is the amount of time initially given to commands; different values for guests and signed-in users.\n\tlegalTime: {\n\t\tguest: 5000\n\t\tuser: 10000\n\t}\n\t# countdownExtraTime is how much time to add when the user clicks the \"Add __ Seconds\" button near the timeout.\n\t# countdownRequestTime determines when to show the \"Add ___ Seconds\" button, based on the amount of time left on the clock.\n\t# countdownRequestTimeBuffer is how much sooner the server should allow extra-time requests; this is in order to prevent race conditions if the client-side and server-side clocks are slightly out of sync.\n\tcountdownExtraTime: 15000\n\tcountdownRequestTime: 3000\n\tcountdownRequestTimeBuffer: 1000\n\t# timewarnTime is when to display the warning message in timewarnMessage that the session will be terminated soon.\n\t# timeoutTime is the amount of time a session is allowed to be idle before it is terminated by the server due to inactivity.\n\t# 10 min, 15 min\n\ttimewarnTime: 600000\n\ttimeoutTime: 900000\n\ttimewarnMessage: 'NOTICE: Due to inactivity, your session will expire in five minutes.'\n\n\t# DATA SIZE LIMITS\n\t# All data sizes are in bytes.\n\t# payloadLimit is the number of bytes in command output before the command is paused; this prevents large amounts of data clogging the messaging pipeline and also reduces bandwidth for the user, possibly also preventing their browser from freezing. Different values for guests and signed-in users.\n\tpayloadLimit: {\n\t\tguest: 5000\n\t\tuser: 10000\n\t}\n\t# payloadMessageDelay is how much time to wait before allowing the user to restart the process; this is primarily to allow the buffer to flush before displaying the restart message.\n\t# payloadAcknowledgeDelay is how much time the user is given to restart the process.\n\tpayloadMessageDelay: 100\n\tpayloadAcknowledgeDelay: 5000\n\t# textFileSizeLimit is the maximum number of bytes per file to transmit for editing to the client.  This is in place for the same reason as payloadLimit.\n\ttextFileSizeLimit: 50000\n\t# jsonMaxMessageLength is a hard ceiling on the amount of data that the GNU Octave process can send per message to the Octave Online session host process.  This sometimes manifests itself in errors involving plot images that are oversize.\n\tjsonMaxMessageLength: 1000000\n\n\t# URLREAD\n\t# For security, all HTTP requests from Octave processes (e.g., via the function urlread) are blocked by Octave Online Server by default.  This whitelist determines regular expression patterns to match against domain names to allow requests to those domains to pass through.\n\turlreadPatterns: [\n\t\t^example\\.com$\n\t\t^(.*\\.)?coursera\\.org$\n\t]\n\t# urlreadMaxBytes is the maximum file size of URLs read from urlread/urlwrite. These bytes have to be piped between Node and Octave, similar to plot images but in the other direction.\n\turlreadMaxBytes: 5000000\n\n\t# OCTAVE SESSION HOST IMPLEMENTATION\n\t# Choices: \"selinux\", \"docker\", and \"unsafe\".\n\t# Refer to the README file for more details.\n\timplementation: unsafe\n}\n\n# Session pool settings\nsessionManager: {\n\t# How often to log the currently active sessions (ms) and check command lag time.\n\tlogInterval: 60000\n\t# How many sessions to keep in the pool: sessions without an owner that are pre-allocated so that when a user joins, a session is ready for them.\n\tpoolSize: 2\n\t# How long to wait between creating sessions (ms), for rate limiting.\n\tpoolInterval: 5000\n\t# Maximum time that a session can use to start up (ms); above this, the session will be destroyed before it reaches the pool.\n\tstartupTimeLimit: 30000\n\t# Tier-dependent override to select a different pool than the tier name. Set to false to use the tier name as the pool name.\n\tpoolTier: false\n\t# Tier-dependent boost to give sessions in the queue (used on front server).\n\tqueueBoostTime: 0\n}\n\n# Server auto-maintenance settings\n# Maintenance is designed for when multiple back servers are in a cluster; one of the servers in the cluster can take itself offline and perform maintenance, like cleaning up caches or doing a system reboot.\nmaintenance: {\n\tinterval: 1800000\n\trequestInterval: 5000\n\tresponseWaitTime: 3000\n\tpauseDuration: 15000\n\tmaxNodesInMaintenance: 1\n\tminNodesInCluster: 2\n}\n\n# Git file server settings\ngit: {\n\t# Hostname for the git file server; it is expected that this server exposes a Git daemon (git:// protocol) on default port 9418 and the create-repo service on default port 3003 (see back-filesystem/README.md).\n\thostname: utils-gitd\n\t# Author as shown in commit messages.\n\tauthor: {\n\t\tname: Octave Online Server\n\t\temail: localhost@localhost\n\t}\n\tgitDaemonPort: 9418\n\tcreateRepoPort: 3003\n\t# Time limit for committing to Git; if the time limit is exceeded, the commit is aborted.\n\tcommitTimeLimit: 30000\n\t# Interval (in ms) for committing without the user having to request; behaves like an \"auto-save\" where changes to script files are persisted to the Git server.\n\tautoCommitInterval: 300000\n\t# Base URL of the HTTP frontend for the Git file server\n\thttpUrl: http://localhost/\n\t# Set this to false if your git version does not support \"--allow-unrelated-histories\" (before Git 2.9)\n\tsupportsAllowUnrelatedHistories: true\n}\n\n# Docker implementation settings\ndocker: {\n\tcwd: /home/oo\n\tgitdir: /srv/git\n\tcpuShares: 512\n\tmemoryShares: 256m\n\tdiskQuotaKiB: 20480\n\timages: {\n\t\tfilesystemSuffix: files\n\t\toctaveSuffix: 'octave:prod'\n\t}\n}\n\n# SELinux implementation settings\nselinux: {\n\tcgroup: {\n\t\t# Name of the CGroup to create and use for Octave processes\n\t\tname: oo/octave\n\t\t# Configuration to mirror in /etc/cgconfig.d/oo.conf\n\t\tconf:\n\t\t\t'''\n\t\t\tgroup oo/octave {\n\t\t\t\tperm {\n\t\t\t\t\tadmin {\n\t\t\t\t\t\tuid = root;\n\t\t\t\t\t\tgid = root;\n\t\t\t\t\t}\n\t\t\t\t\ttask {\n\t\t\t\t\t\tuid = oo;\n\t\t\t\t\t\tgid = oo;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcpu {\n\t\t\t\t\tcpu.shares = 128;\n\t\t\t\t\tcpu.cfs_period_us = 10000;\n\t\t\t\t\tcpu.cfs_quota_us = 8000;\n\t\t\t\t}\n\t\t\t}\n\t\t\t'''\n\t}\n\tprlimit: {\n\t\t# The amount of memory (address space) available to an Octave process. The default is 1GB. Note that the Octave runtime counts against the address space; it consumes in the ballpark of ~250MB, leaving the other 750MB to the user.\n\t\taddressSpace: 1000000000\n\t}\n}\n\n# Tier settings\n# Tiers allow you to customize a number of different settings for different groups of users.  For example, this could be used to implement a free tier and a paid tier, where the paid tier gets more resources.\n# A default tier, named _default, is always available.\n# The following settings are customizable by tier:\n#  - ads.disabled\n#  - sessionManager.poolSize\n#  - sessionManager.poolTier\n#  - sessionManager.queueBoostTime\n#  - selinux.cgroup.name\n#  - selinux.prlimit.addressSpace\n#  - session.legalTime.user\n#  - session.payloadLimit.user\n#  - session.countdownExtraTime\n#  - session.countdownRequestTime\n#  - session.timewarnTime\n#  - session.timeoutTime\ntiers: {\n\t# Default tier: no settings need to be customized.\n\t_default: {}\n\n\t# Maxima tier: set limits to very high values.\n\t_maxima: {\n\t\tsessionManager.poolSize: 1\n\t\tselinux.prlimit.addressSpace: -1\n\t\t# 15 min, 10 min, 5 min, 16 min, 21 min\n\t\tsession.legalTime.user: 900000\n\t\tsession.countdownExtraTime: 600000\n\t\tsession.countdownRequestTime: 300000\n\t\tsession.timewarnTime: 960000\n\t\tsession.timeoutTime: 1260000\n\t}\n\n\t# Example custom tier: a tier named \"vip\" that gets additional memory (4GB)\n\t# vip: {\n\t# \tselinux.prlimit.addressSpace: 4000000000\n\t# }\n}\n\n# Flavor settings\n# Flavors are different server configurations where an entire custom server spins up to handle a session.  The code currently handles this feature only on the Rackspace cloud.\nflavors: {\n\t# Example custom flavor: named \"basic\" and mapped to the Rackspace flavor \"memory1-15\"\n\t# basic: {\n\t# \trackspaceFlavor: memory1-15\n\t# \t# The image UUID to use when spinning up flavor servers\n\t# \timage_uuid: xxxx-xxxx-xxxx\n\t# \t# The UUID of the private network to which to add this server\n\t# \tnetwork_uuid: xxxx-xxxx-xxxx\n\t# }\n}\n\n# Settings common to all flavors; may be overriden by custom flavors\nflavorCommon: {\n\tdefaultClusterSize: 2\n\tidleTime: 600000\n\tstatusInterval: 300000\n\tblockVolume: true\n}\n\n# # # # # # # # # # # # # # # #\n# Front Server configurations #\n# # # # # # # # # # # # # # # #\n\n# Authentication settings\nauth: {\n\tgoogle: {\n\t\toauth_key: xxxxxxxxx.apps.googleusercontent.com\n\t\toauth_secret: xxxxxxxxx\n\t}\n\teasy: {\n\t\tsecret: xxxxxxxxx\n\t\tmax_token_age: 900000\n\t}\n\tpassword: {\n\t\tsalt_rounds: 10\n\t\t# Additional delay, in milliseconds, during auth requests\n\t\tdelay: 100\n\t}\n\t# Basic Auth users for utils-admin and utils-auth\n\tutils_admin: {\n\t\tusers: {\n\t\t}\n\t}\n}\n\n# Front server settings\nfront: {\n\t# SERVER HOST SETTINGS\n\t# protocol, hostname, and port are the *publicly-visible* URLs to be used for links, redirects, etc.\n\tprotocol: http\n\thostname: localhost\n\tport: 8080\n\t# listen_port is the port where the Node process should attach.  This might be different than the publicly-visible port if there is a proxy or other middleware.\n\tlisten_port: 8080\n\t# static_path is the directory to serve; should be either the \"app\" (debug) or \"dist\" (production) directory of the client project.\n\t# A relative path is relative to the top-level projects directory (the directory where this file is located).\n\tstatic_path: client/dist\n\n\t# LOCALE SETTINGS\n\t# Both of these settings may be overriden in either buildData.json or front_setup.js. See src/app.ts for details.\n\t# locales_path is the path matching i18next translation files, with \"{{lng}}\" standing in for the language. A relative path is relative to the top-level projects directory.\n\tlocales_path: \"front/locales/{{lng}}.yaml\"\n\t# locales is a list of languages to load from the above directory.\n\tlocales: [\"en\"]\n\n\t# COOKIE SETTINGS\n\t# For the primary Octave Online Server session ID; there may be other cookies set on the client side, like those used to dismiss onboarding boxes.\n\t# Please set a custom cookie secret!\n\tcookie: {\n\t\tname: oo.sid\n\t\tsecret: xxxxxxxxx\n\t\tmax_age: 7889231400  # (3 months)\n\t}\n\n\t# OTHER OPTIONS\n\t# Timeout (in ms) for logging status of flavor servers.\n\tflavor_log_interval: 60000\n\t# Socket.io path. There may be situations where you need to change this, such as when performing server upgrades.\n\tsocket_io_path: /socket.io\n\t# Whether to redirect HTTP to HTTPS in Express\n\trequire_https: false\n\t# Interval (in ms) to clear the Express template view cache. This enables live changes to index.ejs and other template files in production. Set to 0 to disable clearing of the cache.\n\tview_cache_clear_interval: 120000\n}\n\n# Redirect Service settings\n# These settings power \"octav.onl\" in official Octave Online.\nredirect: {\n\t# e.g., \"octav.onl\"\n\thostname: localhost\n}\n\n# Operational Transformation settings\n# These are timeouts (in ms) that control the lifecycle of OT operations in Redis, used for shared workspaces.\not: {\n\toperation_expire: 120000\n\tstats_interval: 60000\n\tdocument_expire: {\n\t\tinterval: 120000\n\t\ttimeout: 86400000\n\t}\n}\n\n# # # # # # # # # # # # #\n# Client configurations #\n# # # # # # # # # # # # #\n\n# Advertisement settings\nads: {\n\t# Whether to hide ads from an authenticated user.\n\tdisabled: false,\n\n\t# HTML code for the <head> block.\n\t# Example for AdSense:\n\t#\n\t#     '''\n\t#     <script data-ad-client=\"ca-pub-xxxxxxxx\" async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>\n\t#     '''\n\t#\n\t# The function \"oo_enableAds()\" will be called when it is confirmed that the current session is not from a paid subscriber:\n\t#\n\t#     '''\n\t#     <script type=\"text/javascript\">\n\t#     window.oo_enableAds = function() {\n\t#         require([\"fuse\"]);\n\t#     }\n\t#     </script>\n\t#     '''\n\thead_html:\n\t\t'''\n\t\t'''\n\n\t# HTML code for the \"abox\" div in the <body> tag. The div must be enabled via CSS in a <style> tag.\n\t# Example for AdSense, creating a fixed 300x100 slot (use media queries to change the slot size based on screen size):\n\t#\n\t#     '''\n\t#     <style type=\"text/css\">\n\t#     #abox { display: block; width: 100%; height: 100px; }\n\t#     .abox_content { width: 300px; height: 100px; }\n\t#     #main { top: 100px; right: 0; }\n\t#     </style>\n\t#     <ins class=\"adsbygoogle abox_content\" id=\"abox_responsive\"\n\t#       style=\"display:block;\"\n\t#       data-ad-client=\"ca-pub-xxxxxxxx\"\n\t#       data-ad-slot=\"xxxxxxxx\"\n\t#       data-full-width-responsive=\"false\"></ins>\n\t#     <script>\n\t#     (adsbygoogle = window.adsbygoogle || []).push({});\n\t#     </script>\n\t#     '''\n\tabox_html:\n\t\t'''\n\t\t'''\n}\n\nclient: {\n\t# THEME COLLECTION\n\t# Choices: \"server\", \"official\", or some other theme collection that you create manually.\n\t# To avoid confusing the end user, do not use the theme collection \"official\" unless you are running on octave-online.net.\n\ttheme_collection: server\n\n\t# FRONTEND OPTIONS\n\t# Title and meta-tag description to use, based on a key in the translation file. Before being added to the translation files, these options were \"title\" and \"description\" directly in this configuration file.\n\ttitle_key: \"header.meta.server.title\"\n\tdescription_key: \"header.meta.server.desc\"\n\t# Theme color; should be consistent with color1 in fire.styl\n\ttheme_color: AD928E\n\t# Brand name, same across languages.\n\tapp_name: Octave Online Server\n\t# Whether to show the splash screen and other onboarding bubbles to help new users learn how to use the software.\n\tonboarding: false\n\t# Number of milliseconds since the user's last_activity before displaying the Welcome Back message. Defaults to 200 days.\n\twelcome_back_ms: 17280000000\n\n\t# ANNOUNCEMENT MESSAGE\n\t# Set announcement_display to enable a dialog that is shown to users when visiting the page.  There are three options:\n\t#   - \"on\" => show the message to all visitors; this could announce expected system downtime, for example\n\t#   - \"returning\" => show the message to returning users only (those who have dismissed the onboarding screen); this could announce new feature to existing users, for example.  This setting only works if onboarding is set to true\n\t#   - \"off\" => disable the announcement screen\n\t# TODO: Re-engineer a way to set the announcement without rebuilding the app.\n\tannouncement_display: \"off\"\n\t# Use the following variable to control the HTML shown in the announcement box.\n\tannouncement_html:\n\t\t'''\n\t\t<p>Example announcement</p>\n\t\t'''\n\n\t# OTHER CLIENT OPTIONS\n\t# Application identifier for Google Universal Analytics (deprecated)\n\tgacode: xxxxxxxxx\n\t# Google Tag ID (GA4 Measurement ID). Instructions for how to get this:\n\t# <https://support.google.com/analytics/answer/9539598>\n\tgtagid: xxxxxxxxx\n\t# Application identifier for UserVoice\n\tuservoice: xxxxxxxxx\n}\n\n"
  },
  {
    "path": "containers/README.md",
    "content": "Containers for Octave Online Server\n===================================\n\nThis directory contains up-to-date container definitions for various pieces of Octave Online Server.\n\nInside most directories, you will find at least two files:\n\n- Dockerfile: The scripts to build the container.\n- cloudbuild.yaml: For hooking up with Google Cloud Build.  You can ignore this file unless you want to use Google Cloud Build to build the containers.\n\nSome directories contain additional assets used for building the respective containers.\n\n**Important:** You should build all of the containers from the repository root and specify the Dockerfile via the `--file` option to `docker build` or `docker-compose`.  If necessary, your *config.hjson* file should be present when you run `docker build`.\n\nThe containers are:\n\n- octave-\\*: GNU Octave containers\n- oo-front: Front server container\n- oo-back: Back server container\n- utils-gitd: Essential Git file server\n- oo-gith: Frontend for the human-friendly file history viewer\n- utils-gith: Backend for the human-friendly file history viewer\n- utils-admin: Optional administration panel\n\n## Running with Docker Compose\n\n[Docker Compose](https://docs.docker.com/compose/) lets you configure and run multiple containers from a single configuration file.  Octave Online Server ships with *containers/oos-quick-start/docker-compose.yml* to get you off the ground quickly.\n\n:bangbang: **Important:** With this installation method, Octave sessions are sandboxed from the host machine, but they are *not* sandboxed from each other or from the network!  You ***should not*** use this installation method if you plan to make Octave Online Server open to untrusted users.  The intended audience for the Docker Compose version of Octave Online Server are research labs and other environments where users are known and trusted.\n\n### Installing Docker Compose\n\n1. [Install Docker Engine](https://docs.docker.com/engine/install/)\n2. [Install Docker Compose](https://docs.docker.com/compose/install/)\n3. Optional: [Set up Docker with a non-root user](https://docs.docker.com/engine/install/linux-postinstall/)\n\n### Building All Images\n\nFrom the repository root:\n\n```bash\n$ docker-compose -f containers/oos-quick-start/docker-compose.yaml build\n```\n\nIt takes approximately one hour to build from scratch.\n\nIf you see an error such as \"npm ERR! code ENOENT\", please run the command again until it succeeds.\n\n### Running Octave Online Server\n\nFrom the repository root:\n\n```bash\n$ docker-compose -f containers/oos-quick-start/docker-compose.yaml run --publish 8080:8080 -d oo-front\n$ docker-compose -f containers/oos-quick-start/docker-compose.yaml run -d oo-back\n```\n\nOctave Online Server should now be running on port 8080.\n\nTo run the file history server on port 8008:\n\n```bash\n$ docker-compose -f containers/oos-quick-start/docker-compose.yaml run --publish 8008:8008 -d oo-gith\n```\n\n### Configuration File with Docker Compose\n\nOctave Online Server should run out of the box on Docker Compose without a custom configuration file.  To customize settings, create a config.hjson file as usual and rebuild the images.\n\nWhen creating a custom config.hjson file, do *not* overwrite the various \"hostname\" and \"port\" settings for services that run in containers.  The default settings are required for the Docker containers to talk to each other.  Additionally, the \"unsafe\" mode for session.implementation is the only option known to work when the back server is running inside a container.\n\nExamples of settings that you may want to configure:\n\n- session.legalTime.\\* (amount of time allocated to running commands)\n- mailgun.\\* (Mailgun settings for email)\n- auth.google.\\* (Google OAuth settings)\n- auth.easy.secret (salt for encrypting email tokens)\n- front.cookie.secret (salt for encrypting session cookies)\n\n### Optional: Create custom volumes for application data\n\nBy default, Docker will create volumes under */var/lib/docker/volumes* for Octave Online Server application data.  If you want to customize where application data is stored, you can create your own volumes in Docker.\n\n```bash\n# Loopback device to store MongoDB data (2 GB)\n$ sudo dd if=/dev/zero of=/mnt/docker_mongodb.img bs=100M count=20\n$ sudo mkfs.xfs /mnt/docker_mongodb.img\n$ sudo losetup -fP /mnt/docker_mongodb.img\n\n# Loopback device to store Git user data (4 GB)\n$ sudo dd if=/dev/zero of=/mnt/docker_git.img bs=100M count=40\n$ sudo mkfs.xfs /mnt/docker_git.img\n$ sudo losetup -fP /mnt/docker_git.img\n\n# Check the loopback mount locations:\n$ losetup -a\n/dev/loop0: []: (/mnt/docker_mongodb.img)\n/dev/loop1: []: (/mnt/docker_git.img)\n\n# Create the volumes in Docker; set the device paths according to `losetup -a`\n$ docker volume create --driver local \\\n\t--opt type=xfs \\\n\t--opt device=/dev/loop0 \\\n\toosquickstart_mongodb\n$ docker volume create --driver local \\\n\t--opt type=xfs \\\n\t--opt device=/dev/loop1 \\\n\toosquickstart_git\n```\n\nA third volume, `oosquickstart_logs`, is used to save session logs (input/output on a per-session basis).  If you don't wish to save that information, you can mount a tmpfs as `oosquickstart_logs`.  If you do wish to save that information, you can create devices as shown above for `oosquickstart_mongodb` and `oosquickstart_git`.\n\n## About the GNU Octave Containers\n\nThere are four containers that are intended to be built in sequence, each one depending on the previous one.  The order is:\n\n1. octave-deps (install build dependencies)\n2. octave-stable (build vanilla GNU Octave from source)\n3. octave-pkg (build packages)\n4. octave-oo (build extensions for Octave Online Server)\n\nExample commands to build these four containers in sequence (**Note: You do not need to run these commands if you are using docker-compose**)\n\n```bash\n# Run these commands from the top level directory\n$ export SHORT_SHA=$(git rev-parse HEAD | cut -c 1-7)\n$ docker build \\\n\t--tag=octave-deps:$SHORT_SHA \\\n\t--file=containers/octave-deps/Dockerfile \\\n\t.\n$ docker build \\\n\t--build-arg=FULL_BASE_IMAGE=octave-deps:$SHORT_SHA \\\n\t--tag=octave-stable:$SHORT_SHA \\\n\t--file=containers/octave-stable/Dockerfile \\\n\t.\n$ docker build \\\n\t--build-arg=FULL_BASE_IMAGE=octave-stable:$SHORT_SHA \\\n\t--tag=octave-pkg:$SHORT_SHA \\\n\t--file=containers/octave-pkg/Dockerfile \\\n\t.\n$ docker build \\\n\t--build-arg=FULL_BASE_IMAGE=octave-pkg:$SHORT_SHA \\\n\t--tag=octave-oo:$SHORT_SHA \\\n\t--file=containers/octave-oo/Dockerfile \\\n\t.\n```\n\nThere are also *cloudbuild.yaml* files in each directory in case you want to use the Google Cloud Build service to build the Docker images.  With these files, you build each image in sequence, and when you get a clean build, set that image's tag as the `_BASE_REV` substitution on the subsequent image.  Each tag gets built based on the previous tag, so the tag in the end will have four short SHAs in sequence: \"octave-oo:aaaaaaa-bbbbbbb-ccccccc-ddddddd\"\n"
  },
  {
    "path": "containers/octave-deps/Dockerfile",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\nFROM rockylinux:8\n\nWORKDIR /root\n\n# Development tools for Octave\nRUN yum groupinstall -y \"Development Tools\"\nRUN yum install -y \\\n\tcmake \\\n\tepel-release \\\n\tgit \\\n\tnet-tools \\\n\tlibrsvg2-tools \\\n\ttraceroute \\\n\twget \\\n\tyum-utils\n\n# EL 8:\nRUN dnf config-manager --set-enabled powertools\n\n# EL 9:\n# RUN dnf config-manager --set-enabled crb\n\n# Library dependencies for Octave (and other deps only in EPEL)\nRUN yum install -y \\\n\tarpack-devel \\\n\tbzip2-devel \\\n\teigen3-devel \\\n\tfftw-devel \\\n\tfltk-devel \\\n\tgl2ps-devel \\\n\tglpk-devel \\\n\tgnuplot \\\n\tgperf \\\n\tGraphicsMagick-c++-devel \\\n\thdf5-devel \\\n\ticoutils \\\n\tjava-17-openjdk-devel \\\n\tlapack-devel \\\n\tlibqhull \\\n\tlibsndfile-devel \\\n\tllvm-devel \\\n\tmercurial \\\n\topenblas-devel \\\n\tpcre-devel \\\n\tportaudio-devel \\\n\tqhull-devel \\\n\tqrupdate-devel \\\n\tsuitesparse-devel \\\n\ttexinfo \\\n\ttexinfo-tex \\\n\ttransfig \\\n\tzlib-devel\n\n# sundials-devel is in EPEL 8:\nRUN yum install -y sundials-devel\n\n# sundials-devel is missing from EPEL 9, so we need to install it separately.\n# https://github.com/LLNL/sundials/issues/244\n# RUN git clone -b v6.5.0 --depth 1 https://github.com/LLNL/sundials.git && \\\n#\tcd sundials && \\\n#\tgit submodule update --init && \\\n#\tmkdir BUILDDIR && \\\n#\tcd BUILDDIR && \\\n#\tcmake -DENABLE_KLU=ON -DKLU_INCLUDE_DIR=/usr/include/suitesparse -DKLU_LIBRARY_DIR=/usr/lib64 .. && \\\n#\ttime make -j8 && \\\n#\tmake install\n\n# Manually install rapidjson; see comments in configure.ac\nRUN git clone https://github.com/Tencent/rapidjson.git && \\\n\tcd rapidjson && \\\n\tgit reset --hard fd3dc29a5c2852df569e1ea81dbde2c412ac5051 && \\\n\tgit submodule update --init && \\\n\tmkdir build && \\\n\tcd build && \\\n\tcmake .. && \\\n\ttime make -j8 && \\\n\tmake install\n\n# TODO: It's not clear which is the \"correct\" way to set the environment variable\nRUN echo \"export JAVA_HOME=/usr/lib/jvm/java-17-openjdk\" > /etc/profile.d/oo.sh\nENV JAVA_HOME /usr/lib/jvm/java-17-openjdk\n\n# TODO\n# arpack-devel\n# atlas-devel\n# bison\n# libcurl-devel\n# desktop-file-utils\n# fftw-devel\n# flex\n# fltk-devel\n# ftgl-devel\n# gcc-gfortran\n# ghostscript\n# gl2ps-devel\n# glpk-devel\n# gnuplot\n# gperf\n# GraphicsMagick-c++-devel\n# hdf5-devel\n# less\n# libX11-devel\n# llvm-devel\n# mesa-libGL-devel\n# mesa-libGLU-devel\n# ncurses-devel\n# pcre-devel\n# qhull-devel\n# qrupdate-devel\n# qscintilla-devel\n# readline-devel\n# suitesparse-devel\n# texinfo\n# texinfo-tex\n# zlib-devel\n\n# When building without --disable-docs, the following additional packages are required:\n# texlive-collection-latexrecommended\n# texlive-metapost\n"
  },
  {
    "path": "containers/octave-deps/cloudbuild.yaml",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n#########################################################\n# NOTE: All local paths are relative to repository root #\n#########################################################\n\nsteps:\n- name: gcr.io/cloud-builders/docker\n  args:\n    - build\n    - --tag=gcr.io/$PROJECT_ID/octave-deps:$SHORT_SHA\n    - --file=containers/octave-deps/Dockerfile\n    - '.'\n  timeout: 7200s\ntimeout: 7200s\nimages: ['gcr.io/$PROJECT_ID/octave-deps:$SHORT_SHA']\n"
  },
  {
    "path": "containers/octave-oo/Dockerfile",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\nARG FULL_BASE_IMAGE\nFROM $FULL_BASE_IMAGE\n\nRUN yum install -y \\\n\tjson-c-devel \\\n\tlibuv-devel\n\n# Copy and compile host.c\n# Note: path is relative to repository root\nCOPY back-octave back-octave\nRUN cd back-octave && make && make install\n\n# Apply patches\n\n### 4.0.1-rc1 ###\n# RUN cd octave && \\\n# \thg update 323e92c4589f && \\\n# \thg import ../back-octave/oo-changesets/001-d38b7c534496.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/002-d3de6023e846.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/003-4d28376c34a8.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/004-6ff3e34eea77.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/005-9e73fe0d92d5.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/006-15d21ceec728.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/007-4d778d6ebbd0.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/008-e8ef7f3333bf.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/009-05f7272c001e.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/010-4a1afb661c55.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/011-7327936fa23e.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/012-84390db50239.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/013-f4110d638cdb.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/014-21fd506b7530.hg.txt\n\n### 4.2.1 ###\n# RUN cd octave && \\\n# \thg import ../back-octave/oo-changesets/100-2d1fd5fdd1d5.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/101-bc8cd93feec5.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/102-30d8ba0fbc32.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/103-352b599bc533.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/104-9475120a3110.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/105-ccbef5c9b050.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/106-91cb270ffac0.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/107-80081f9d8ff7.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/108-9b39ca8bcbfd.hg.txt\n\n### 5.2 ###\n# RUN cd octave && \\\n# \thg import ../back-octave/oo-changesets/200-84cbf166497f.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/201-b993253f19d0.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/202-d9d23f97ba78.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/203-d6b5ffb8e4cc.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/204-e61d7b8918e2.hg.txt\n\n### 6.0.1 (based on 9e7b2625e574) ###\n# RUN cd octave && \\\n# \thg import ../back-octave/oo-changesets/300-d78448f9c483.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/301-97f7d1f4fe83.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/302-8900d7cf8554.hg.txt\n\n### 6.0.1 (based on 171a2857d6d1) ###\n# RUN cd octave && \\\n# \thg import ../back-octave/oo-changesets/310-1e1c91e6cddc.hg.txt\n\n### 6.4.0 (based on 8d7671609955) ###\n# RUN cd octave && \\\n# \thg import ../back-octave/oo-changesets/320-8d4683a83238.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/321-faad58416a3a.hg.txt\n\n### 7.0.1 (based on 117ebe363f56) ###\n# RUN cd octave && \\\n# \thg import ../back-octave/oo-changesets/400-7ade2492e023.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/401-1b33dc797ec9.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/402-b01fa2864d4d.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/403-2813cb96e10f.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/404-acb523f25bb9.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/405-6ad34b0b69e1.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/406-d0df6f16f41e.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/407-df206dd11399.hg.txt && \\\n# \thg import ../back-octave/oo-changesets/408-8184a51579f3.hg.txt\n\n### 7.4 (based on 601e7a142a15) ###\n# RUN cd octave && \\\n#\thg import ../back-octave/oo-changesets/420-4c3d80dd9e65.hg.txt && \\\n#\thg import ../back-octave/oo-changesets/421-de16dd99ab0e.hg.txt && \\\n#\thg import ../back-octave/oo-changesets/422-de16dd99ab0e.hg.txt\n\n### 8.4 (based on 78c13a2594f3) ###\nRUN cd octave && \\\n\thg import ../back-octave/oo-changesets/430-d2250ae9bddd.hg.patch\n\n# Re-build with patches\nRUN cd octave/.build && make -j8\nRUN cd octave/.build && make install\n\n# Monkey-patch bug #42352\n# https://savannah.gnu.org/bugs/?42352\n# RUN touch /usr/local/share/octave/4.2.1/etc/macros.texi\n\n# # Monkey-patch json-c runtime errors\n# ENV LD_LIBRARY_PATH /usr/local/lib\n\n# Install site octaverc.m (formerly part of \"install-site-m\" in Makefile)\nCOPY containers/octave-pkg/octaverc.m /usr/local/share/octave/site/m/startup/octaverc\n\n# Install the java.opts file\nCOPY containers/octave-oo/java.opts /usr/local/share/octave/8.0.1/m/java/java.opts\n"
  },
  {
    "path": "containers/octave-oo/cloudbuild.yaml",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n#########################################################\n# NOTE: All local paths are relative to repository root #\n#########################################################\n\nsteps:\n- name: 'gcr.io/cloud-builders/docker'\n  args:\n    - build\n    - --build-arg=FULL_BASE_IMAGE=gcr.io/$PROJECT_ID/octave-pkg:$_BASE_REV\n    - --tag=gcr.io/$PROJECT_ID/octave-oo:$_BASE_REV-$SHORT_SHA\n    - --tag=gcr.io/$PROJECT_ID/octave-oo:latest\n    - --file=containers/octave-oo/Dockerfile\n    - '.'\n  timeout: 7200s\ntimeout: 7200s\noptions:\n  machineType: 'N1_HIGHCPU_8'\nsubstitutions:\n  _BASE_REV: unknown\nimages: \n  - gcr.io/$PROJECT_ID/octave-oo:$_BASE_REV-$SHORT_SHA\n  - gcr.io/$PROJECT_ID/octave-oo:latest\n"
  },
  {
    "path": "containers/octave-oo/java.opts",
    "content": "-Xms4m\n-Xmx32m\n-XX:MetaspaceSize=4m\n-XX:MaxMetaspaceSize=32m\n-XX:-TieredCompilation\n"
  },
  {
    "path": "containers/octave-pkg/Dockerfile",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\nARG FULL_BASE_IMAGE\nFROM $FULL_BASE_IMAGE\n\n# Install some popular Octave Forge packages.\n# If a package fails to install, try building the image again and it might work the second time.\n# Most packages are auto-loaded via octaverc (since version 4.2.1) except for the following packages that shadow core library functions or are slow to load: tsa, stk, ltfat, and nan.\n# Note: The package list gets written to /usr/local/share/octave/octave_packages\n\n# rpmfusion is required for ffmpeg\nRUN dnf install -y --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm\nRUN yum install -y \\\n\tunits \\\n\tmpfr-devel \\\n\tportaudio-devel \\\n\tpatch \\\n\tncurses-devel \\\n\tlibicu-devel \\\n\tnetcdf-devel \\\n\tnettle-devel \\\n\tgdal-devel \\\n\tpython3-pip\n\n# EL 8:\nRUN yum install -y ffmpeg-devel\nRUN pip3 install sympy==1.9\n\n# EL 9:\n# RUN yum install -y compat-ffmpeg4-devel;\n# RUN pip3 install sympy==1.11.1\n\nARG PKG_BASE_URL=https://downloads.sourceforge.net/project/octave/Octave%20Forge%20Packages/Individual%20Package%20Releases\n\nRUN mkdir pkg-downloads\n\nRUN cd pkg-downloads && wget https://github.com/apjanke/octave-tablicious/releases/download/v0.3.7/tablicious-0.3.7.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install tablicious-0.3.7.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/symbolic-3.1.1.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install symbolic-3.1.1.tar.gz;\"\n\nRUN cd pkg-downloads && wget https://github.com/gnu-octave/pkg-control/releases/download/control-3.6.1/control-3.6.1.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install control-3.6.1.tar.gz;\"\n\n# NOTE: There is an open Merge Request to fix a variables issue in signal-1.4.5\n# https://sourceforge.net/p/octave/signal/merge-requests/4/\nRUN cd pkg-downloads && wget $PKG_BASE_URL/signal-1.4.4.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install signal-1.4.4.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/communications-1.2.6.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install communications-1.2.6.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/struct-1.0.18.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install struct-1.0.18.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/io-2.6.4.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install io-2.6.4.tar.gz;\"\n\nRUN cd pkg-downloads && wget -O statistics-1.6.0.tar.gz https://github.com/gnu-octave/statistics/archive/refs/tags/release-1.6.0.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install statistics-1.6.0.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/optim-1.6.2.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install optim-1.6.2.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/image-2.14.0.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install image-2.14.0.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/general-2.1.3.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install general-2.1.3.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/matgeom-1.2.3.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install matgeom-1.2.3.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/linear-algebra-2.2.3.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install linear-algebra-2.2.3.tar.gz;\"\n\n# RUN cd pkg-downloads && wget $PKG_BASE_URL/geometry-4.0.0.tar.gz && \\\n# \tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install geometry-4.0.0.tar.gz;\"\n\n# NOTE: The geometry package has a compile error with modern GCC:\n# https://sourceforge.net/p/octave/geometry/ci/04965cda30b5f9e51774194c67879e7336df1710/\nRUN cd pkg-downloads && \\\n\thg clone -r 04965c http://hg.code.sf.net/p/octave/geometry && \\\n\t(cd geometry && hg archive geometry.tar.gz) && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install geometry/geometry.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/data-smoothing-1.3.0.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install data-smoothing-1.3.0.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/nan-3.7.0.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install nan-3.7.0.tar.gz;\"\n\n# NOTE: This package is below the download threshold, but it is still receiving updates\nRUN cd pkg-downloads && wget $PKG_BASE_URL/tsa-4.6.3.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install tsa-4.6.3.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/miscellaneous-1.3.0.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install miscellaneous-1.3.0.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/interval-3.2.1.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install interval-3.2.1.tar.gz;\"\n\n# NOTE: This package is below the download threshold, but it is still receiving updates\nRUN cd pkg-downloads && wget https://github.com/stk-kriging/stk/releases/download/2.8.1/stk-2.8.1-octpkg.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install stk-2.8.1-octpkg.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/mapping-1.4.2.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install mapping-1.4.2.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/financial-0.5.3.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install financial-0.5.3.tar.gz;\"\n\nRUN cd pkg-downloads && wget https://github.com/ltfat/ltfat/releases/download/v2.6.0/ltfat-2.6.0-of.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install ltfat-2.6.0-of.tar.gz;\"\n\n# Workaround for bug in package ltfat:\n# https://github.com/ltfat/ltfat/issues/115\n#COPY containers/octave-pkg/ltfat.patch pkg-downloads/ltfat.patch\n#RUN cd pkg-downloads && wget $PKG_BASE_URL/ltfat-2.3.1.tar.gz && \\\n#\ttar zxf ltfat-2.3.1.tar.gz && \\\n#\tpatch --ignore-whitespace -p0 < ltfat.patch && \\\n#\ttar czf ltfat_fix.tar.gz ltfat && \\\n#\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install ltfat_fix.tar.gz;\"\n\nRUN cd pkg-downloads && wget https://github.com/Andy1978/octave-video/releases/download/2.1.1/video-2.1.1.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install video-2.1.1.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/netcdf-1.0.17.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install netcdf-1.0.17.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/dataframe-1.2.0.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install dataframe-1.2.0.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/mvn-1.1.0.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install mvn-1.1.0.tar.gz;\"\n\nRUN cd pkg-downloads && wget $PKG_BASE_URL/fuzzy-logic-toolkit-0.4.6.tar.gz && \\\n\tLC_ALL=C /usr/local/bin/octave -q --eval \"pkg install fuzzy-logic-toolkit-0.4.6.tar.gz;\"\n\n# Policy for adding or removing packages:\n# - Add packages that garner at least 40 downloads/week\n# - Remove packages that fall below 10 downloads/week\n\n# Former packages:\n# - mechanics-1.3.1 (dropped due to only 4 downloads/week)\n# - divand-1.1.2 (dropped due to only 8 downloads/week)\n\n# Generate package metadata, used for warning messages\nRUN cd /usr/local/share/octave/site/m && /usr/local/bin/octave -q --eval \"\\\n\tpackages = {}; \\\n\tfor p=pkg('list'); \\\n\t\tpackages = {packages{:} pkg('describe', '-verbose', p{1}.name){:}}; \\\n\tendfor; \\\n\tsave('package_metadata.mat', 'packages'); \"\n"
  },
  {
    "path": "containers/octave-pkg/README.md",
    "content": "Packages for Octave Online Server\n=================================\n\n## Methodology\n\nThere are 70+ packages on Octave Forge and many others hosted elsewhere.\n\nIn order to determine the relative popularity of packages, the page [Individual Package Releases](https://sourceforge.net/projects/octave/files/Octave%20Forge%20Packages/Individual%20Package%20Releases/) is referenced.  The following methodology is used to determine packages to add or remove:\n\n1. Add packages that have at least 40 downloads/week.\n2. Keep packages that have less than 40 but at least 10 downloads/week.\n3. Drop packages that have less than 10 downloads/week.\n\nIn addition, no packages whose primary purpose is interacting with hardware devices, since Octave Online Server does not support hardware devices.\n"
  },
  {
    "path": "containers/octave-pkg/cloudbuild.yaml",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n#########################################################\n# NOTE: All local paths are relative to repository root #\n#########################################################\n\nsteps:\n- name: 'gcr.io/cloud-builders/docker'\n  args:\n    - build\n    - --build-arg=FULL_BASE_IMAGE=gcr.io/$PROJECT_ID/octave-stable:$_BASE_REV\n    - --tag=gcr.io/$PROJECT_ID/octave-pkg:$_BASE_REV-$SHORT_SHA\n    - --file=containers/octave-pkg/Dockerfile\n    - '.'\n  timeout: 7200s\ntimeout: 7200s\nsubstitutions:\n  _BASE_REV: unknown\nimages: ['gcr.io/$PROJECT_ID/octave-pkg:$_BASE_REV-$SHORT_SHA']\n"
  },
  {
    "path": "containers/octave-pkg/ltfat.patch",
    "content": "diff -u -r ltfat/inst/nonstatgab/nsdgt.m ltfat/inst/nonstatgab/nsdgt.m\n--- ltfat/inst/nonstatgab/nsdgt.m\t2018-06-21 15:03:11.000000000 +0000\n+++ ltfat/inst/nonstatgab/nsdgt.m\t2020-07-05 01:52:46.072676195 +0000\n@@ -149,8 +149,8 @@\n         col = ceil(Lg/M(ii));\n\n         temp = zeros(col*M(ii),W,assert_classname(f,g{1}));\n-        temp([end-floor(Lg/2)+1:end,1:ceil(Lg/2)],:) = bsxfun(@ ...\n-                                                          times,f(win_range,:),g{ii}(idx));\n+        temp([end-floor(Lg/2)+1:end,1:ceil(Lg/2)],:) = bsxfun(@times, ...\n+                                                          f(win_range,:),g{ii}(idx));\n\n         temp = reshape(temp,M(ii),col,W);\n         X = squeeze(fft(sum(temp,2)));\n"
  },
  {
    "path": "containers/octave-pkg/octaverc.m",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n# Clear Vars Patch from wiki.octave.org\nfunction clear (varargin)\n  args = sprintf (', \"%s\"', varargin{:});\n\n  # this line is kind-of funky, but without it, running \"clear\"\n  # without any arguments does not work\n  if strcmp(args, ', \"')\n    args = '';\n  end\n\n  evalin (\"caller\", ['builtin (\"clear\"' args ')']);\n  pkglist = pkg (\"list\");\n  loadedpkg = cell (0);\n  for ii = 1:numel (pkglist)\n    if (pkglist{ii}.loaded)\n      loadedpkg{end+1} = pkglist{ii}.name;\n    endif\n  endfor\n  if exist(\"~/.octaverc\")\n    source(\"~/.octaverc\");\n  endif\n  source(\"/usr/local/share/octave/site/m/startup/octaverc\");\n  if (numel (loadedpkg) != 0)\n    pkg (\"load\", loadedpkg{:});\n  endif\nendfunction\n\n% Set environment variables\n% 2022-03-06: Java does not have enough memory to support xlsopen, so disable it by default\nsetenv(\"PYTHON\", \"python3\");\nsetenv(\"OCTAVE_JAVA_DIR\", \"/dev/null\");\n\n% Auto-load packages (no more pkg-auto since 4.2.1)\n% Packages that are installed but not auto-loaded have a reason, such as shadows.\n\npkg load communications;\npkg load control;\npkg load dataframe;\npkg load fuzzy-logic-toolkit;\npkg load general;\npkg load image;\npkg load interval;\npkg load linear-algebra;\npkg load miscellaneous;\npkg load mvn;\npkg load signal;\npkg load struct;\npkg load symbolic;\npkg load video;\n\n% 2023-01-03: The statistics package contains known shadows in Octave 8.4.\n% See: https://github.com/gnu-octave/statistics/issues/121\n% Remove this condition upon upgrading to Octave 9.\nwarning(\"off\",\"Octave:shadowed-function\");\npkg load statistics;\nwarning(\"on\",\"Octave:shadowed-function\");\n\n% Packages not auto-loaded because of the JVM dependency or other performance tradeoff:\n% pkg load data-smoothing;\n% pkg load financial;\n% pkg load io;\n% pkg load mapping;\n% pkg load netcdf;\n% pkg load optim;\n% pkg load stk;\n\n% Packages not auto-loaded due to shadow functions:\n% pkg load geometry;\n% pkg load ltfat;\n% pkg load matgeom;\n% pkg load nan;\n% pkg load tablicious;\n% pkg load tsa;\n"
  },
  {
    "path": "containers/octave-stable/Dockerfile",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\nARG FULL_BASE_IMAGE\nFROM $FULL_BASE_IMAGE\n\n# Enlist and Configure the correct Octave revision\n### 4.2.1 ###\n# RUN hg clone --insecure -r b9d482dd90f3 http://www.octave.org/hg/octave\n\n### 5.2-rc ###\n# RUN hg clone --insecure -r 56dd7419d7aa http://www.octave.org/hg/octave\n\n### 6.0.1 ###\n# RUN hg clone --insecure -r 9e7b2625e574 http://www.octave.org/hg/octave\n\n### 6.0.1, after fix to bug #58698 ###\n# RUN hg clone --insecure -r 171a2857d6d1 http://www.octave.org/hg/octave\n\n### 6.4.0 ###\n# RUN hg clone --insecure -r 8d7671609955 http://www.octave.org/hg/octave\n\n### 7.0.1 ###\n# RUN hg clone --insecure -r 117ebe363f56 http://www.octave.org/hg/octave\n\n### 7.4 ###\n# RUN hg clone -r 601e7a142a15 https://hg.octave.org/octave\n\n### 8.4 ###\nRUN hg clone --insecure -r 78c13a2594f3 http://hg.octave.org/octave\n\nRUN cd octave && \\\n\t./bootstrap && \\\n\tmkdir .build\n\n### 4.0.1-rc1 ###\n# RUN\tcd octave/.build && \\\n# \t../configure --disable-readline --disable-gui --disable-docs\n\n### 4.2.1 ###\n# Note: set GNUPLOT=... if you are using a custom gnuplot!\n# RUN cd octave/.build && \\\n# \t../configure --disable-readline --disable-docs --disable-atomic-refcount --without-qt\n\n### 5.2-rc ###\n# Note: rsvg-convert is not to be found in the CentOS repos, but it is not really necessary for a command-line build, so replace it with \"echo\"\n# RUN cd octave/.build && \\\n# \tRSVG_CONVERT=echo ICOTOOL=echo ../configure --disable-readline --disable-docs --disable-atomic-refcount --without-qt --without-opengl\n\n### 6.4.0 ###\n### 7.0.1 ###\n### 7.4 ###\nRUN cd octave/.build && \\\n\tICOTOOL=echo ../configure --disable-readline --disable-docs --without-qt --without-opengl\n\n# Build Octave\n# This is the slowest part of the Dockerfile\nRUN cd octave/.build && make -j8\nRUN cd octave/.build && make install\n"
  },
  {
    "path": "containers/octave-stable/cloudbuild.yaml",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n#########################################################\n# NOTE: All local paths are relative to repository root #\n#########################################################\n\nsteps:\n- name: 'gcr.io/cloud-builders/docker'\n  args:\n    - build\n    - --build-arg=FULL_BASE_IMAGE=gcr.io/$PROJECT_ID/octave-deps:$_BASE_REV\n    - --tag=gcr.io/$PROJECT_ID/octave-stable:$_BASE_REV-$SHORT_SHA\n    - --file=containers/octave-stable/Dockerfile\n    - '.'\n  timeout: 7200s\ntimeout: 7200s\noptions:\n  machineType: 'N1_HIGHCPU_8'\nsubstitutions:\n  _BASE_REV: unknown\nimages: ['gcr.io/$PROJECT_ID/octave-stable:$_BASE_REV-$SHORT_SHA']\n"
  },
  {
    "path": "containers/oo-back/Dockerfile",
    "content": "# Copyright © 2020, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\nARG FULL_BASE_IMAGE\nFROM $FULL_BASE_IMAGE\n\n# Core tmpfs directories:\nVOLUME \\\n\t/run \\\n\t/tmp\n\n# Install dependencies, including Node.js\nRUN dnf module install -y nodejs:18/common\nRUN dnf install -y gcc-c++ make python3\nRUN npm config set prefix /workspace\n\n# Copy the application code into the container\nRUN mkdir -p /srv/oo/projects\nCOPY shared /srv/oo/projects/shared\nCOPY back-filesystem /srv/oo/projects/back-filesystem\nCOPY back-master /srv/oo/projects/back-master\nCOPY config*.hjson /srv/oo/projects/\n\n# Build Node.js projects for oo-back\n# Use npm ci to install deps from lockfiles\nRUN \\\n\tcd /srv/oo/projects/shared && npm ci && \\\n\tcd /srv/oo/projects/back-filesystem && npm ci && \\\n\tcd /srv/oo/projects/back-master && npm ci\n\nCMD DEBUG=* node /srv/oo/projects/back-master/app.js\n"
  },
  {
    "path": "containers/oo-back/cloudbuild.yaml",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n#########################################################\n# NOTE: All local paths are relative to repository root #\n#########################################################\n\nsteps:\n\n# Pull additional data from GCP, including config.hjson.\n# NOTE: You probably need to change this, depending on how you store your config.hjson.\n- name: gcr.io/cloud-builders/gcloud\n  entrypoint: bash\n  args:\n    - -c\n    - |\n      # Download config file from private repo\n      # Log the current commit info in the build log\n      gcloud source repos clone oo-misc1 &&\n      (cd oo-misc1 && git log -n1) &&\n      cp oo-misc1/gcp_config.hjson config.hjson &&\n      cp oo-misc1/octave-online-866c0deeb0d1.json . &&\n      rm -rf oo-misc1;\n\n# Perform build steps using same distro as the production VM:\n- name: rockylinux:8\n  entrypoint: bash\n  timeout: 7200s\n  args:\n    - -c\n    - |\n      # Install dependencies, including Node.js\n      dnf module install -y nodejs:18/common &&\n      yum install -y gcc-c++ make python3 &&\n      npm config set prefix /workspace &&\n      [[ $? == 0 ]] || exit $?;  # exit if failure\n\n      # Build Node.js projects for oo-back\n      # Use npm ci to install deps from lockfiles\n      (\n        echo \"oo npm ci: shared\" &&\n        cd shared &&\n        npm ci --verbose\n      ) && (\n        echo \"oo npm ci: back-filesystem\" &&\n        cd back-filesystem &&\n        npm ci --verbose\n      ) && (\n        echo \"oo npm ci: back-master\" &&\n        cd back-master &&\n        npm ci --verbose\n      ) && (\n        echo \"oo npm ci: shared/stackdriver\" &&\n        cd shared/stackdriver &&\n        npm ci --verbose\n      ) && (\n        echo \"oo npm ci: shared/gcp\" &&\n        cd shared/gcp &&\n        npm ci --verbose\n      );\n      [[ $? == 0 ]] || exit $?;  # exit if failure\n\n      # Create exit.js to reboot instance\n      echo 'module.exports = require(\"../shared/gcp/reboot_or_remove_self.js\")' > entrypoint/exit.js\n      [[ $? == 0 ]] || exit $?;  # exit if failure\n\n      # Create tar.gz package\n      tar zcf /tmp/$_OUTPUT_FILENAME . &&\n      mv /tmp/$_OUTPUT_FILENAME . &&\n      ls -ldh `pwd`;\n\ntimeout: 7200s\n\n# Save the tar.gz package to storage\nsubstitutions:\n  _OUTPUT_FILENAME: oo_back_snapshot_rocky8_node18.tar.gz\nartifacts:\n  objects:\n    location: gs://artifacts.octave-online.appspot.com/objects/oo-back/\n    paths:\n      - $_OUTPUT_FILENAME\n"
  },
  {
    "path": "containers/oo-front/Dockerfile",
    "content": "# Copyright © 2020, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n###############\n# BOILERPLATE #\n###############\n\nFROM node:18\nWORKDIR /root\n\n# Install oo-front\n# Note: paths are relative to repository root\nRUN mkdir -p /srv/oo/projects\nCOPY config*.hjson /srv/oo/projects/\nCOPY shared /srv/oo/projects/shared\nCOPY client /srv/oo/projects/client\nCOPY front /srv/oo/projects/front\nCOPY entrypoint /srv/oo/projects/entrypoint\nRUN \\\n\tcd /srv/oo/projects/shared && \\\n\t\tnpm ci && \\\n\tcd /srv/oo/projects/front && \\\n\t\tnpm ci && \\\n\t\tnpm install --only=dev && \\\n\t\tnpm run build && \\\n\tcd /srv/oo/projects/client && \\\n\t\tnpm ci && \\\n\t\tnpm install --only=dev && \\\n\t\tnpm run bower -- --allow-root install && \\\n\t\tnpm run grunt\n\n############\n# METADATA #\n############\n\n# Ports:\n# 8080 = express\nEXPOSE 8080/tcp\n\n##################\n# CONFIGURATIONS #\n##################\n\nCMD DEBUG=oo:* node /srv/oo/projects/front/dist/app.js\n"
  },
  {
    "path": "containers/oo-front/cloudbuild.yaml",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n#########################################################\n# NOTE: All local paths are relative to repository root #\n#########################################################\n\nsteps:\n\n# Pull additional data from GCP, including config.hjson.\n# NOTE: You probably need to change this, depending on how you store your config.hjson.\n- name: gcr.io/cloud-builders/gcloud\n  entrypoint: bash\n  args:\n    - -c\n    - |\n      # Download config file from private repo\n      # Copy AppEngine files to the root directory\n      # Log the current commit info in the build log\n      gcloud source repos clone oo-misc1 &&\n      (cd oo-misc1 && git log -n1) &&\n      cp oo-misc1/gcp_config.hjson config.hjson &&\n      cp oo-misc1/octave-online-866c0deeb0d1.json . &&\n      find oo-misc1/appengine/oo-front -mindepth 1 -maxdepth 1 -exec cp -R {} . \\; &&\n      mv static/ front/ &&\n      rm -f package-lock.json &&\n      rm -rf oo-misc1;\n\n# Build the client project and the front server typescript\n- name: node:18\n  entrypoint: bash\n  args:\n    - -c\n    - |\n      (\n        node -v &&\n        npm -v &&\n        cd shared &&\n        npm ci &&\n        cd ../client &&\n        npm ci &&\n        npm run bower -- --allow-root install &&\n        npm run grunt\n      );\n      [[ $? == 0 ]] || exit $?;  # exit if failure\n\n      (\n        cd front &&\n        npm ci &&\n        npm run build\n      ) && (\n        cd shared/gcp &&\n        npm ci\n      );\n      [[ $? == 0 ]] || exit $?;  # exit if failure\n\n      # Create front_setup.js to download translations from GCP\n      echo 'module.exports = require(\"../shared/gcp/fetch_translations.js\")' > entrypoint/front_setup.js\n      [[ $? == 0 ]] || exit $?;  # exit if failure\n\n# Deploy to AppEngine\n# Use \"gcloud beta app deploy\" to work around bug: https://stackoverflow.com/questions/62575138/network-session-affinitytrue-property-of-app-yaml-file-is-not-reflecting-in\n- name: gcr.io/cloud-builders/gcloud\n  entrypoint: bash\n  timeout: 3600s\n  args:\n    - -c\n    - |\n      gcloud beta app deploy $_DEPLOY_OPTS;\n\ntimeout: 3600s\nsubstitutions:\n  _DEPLOY_OPTS: \"--promote\"\n"
  },
  {
    "path": "containers/oo-gith/Dockerfile",
    "content": "# Copyright © 2020, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n###################################\n# oo-gith: front-end for Git HTTP #\n# =============================== #\n# - nginx                         #\n# - utils-auth                    #\n###################################\n\n###############\n# BOILERPLATE #\n###############\n\nFROM ubuntu:jammy\n\nWORKDIR /root\n\n# Disable all prompts when using apt-get\nENV DEBIAN_FRONTEND=noninteractive\n\n# Core tmpfs directories:\nVOLUME \\\n\t/run \\\n\t/tmp\n\n# Essential setup\nRUN \\\n\tmkdir -p /srv/oo && \\\n\tapt-get update && \\\n\tapt-get install -y \\\n\t\tcurl \\\n\t\tca-certificates \\\n\t\topenssl \\\n\t\tapt-transport-https \\\n\t\tgnupg \\\n\t\tsupervisor\n\n# NodeSource\nRUN \\\n\tcurl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \\\n\techo 'deb https://deb.nodesource.com/node_16.x jammy main' > /etc/apt/sources.list.d/nodesource.list && \\\n\techo 'deb-src https://deb.nodesource.com/node_16.x jammy main' >> /etc/apt/sources.list.d/nodesource.list\n\n####################\n# MAIN BUILD RULES #\n####################\n\nRUN apt-get update && \\\n\tapt-get install --no-install-recommends -y \\\n\t\tnodejs \\\n\t\tnginx \\\n\t\tgit \\\n\t\tunzip\n\n# Install utils-auth\n# Note: paths are relative to repository root\nRUN mkdir -p /srv/oo/projects\nCOPY config*.hjson /srv/oo/projects/\nCOPY utils-auth /srv/oo/projects/utils-auth\nCOPY shared /srv/oo/projects/shared\nRUN \\\n\tcd /srv/oo/projects/shared && npm ci && \\\n\tcd /srv/oo/projects/utils-auth && npm ci\n\n# Download GitList for static file serving\nRUN \\\n\tcurl -L -k https://github.com/octave-online/gitlist/archive/oo.zip -o gitlist.zip && \\\n\tunzip gitlist.zip -d /srv/oo\n\n############\n# METADATA #\n############\n\n# Ports:\n# 80 = nginx\nEXPOSE 80/tcp\n\n# Additional tmpfs directories:\nVOLUME \\\n\t/run/oosocks \\\n\t/var/log/nginx \\\n\t/var/lib/nginx\n\n##################\n# CONFIGURATIONS #\n##################\n\n# Note: paths are relative to repository root.\nCOPY utils-auth/configs/custom_4xx.html /var/www/html/\nCOPY containers/oo-gith/supervisord.conf /etc/supervisor/conf.d/oo.conf\nCOPY containers/oo-gith/nginx.conf /etc/nginx/sites-available/oo.conf\n\n# TODO: In some environments, an error occurs due to modules-enabled. Why?\nRUN \\\n\techo \"daemon off;\" >> /etc/nginx/nginx.conf && \\\n\trm /etc/nginx/modules-enabled/* && \\\n\tsed -i \"s/\taccess_log .*/\taccess_log \\/dev\\/stdout;/\" /etc/nginx/nginx.conf && \\\n\tsed -i \"s/\terror_log .*/\terror_log \\/dev\\/stderr;/\" /etc/nginx/nginx.conf && \\\n\t(cd /etc/nginx/sites-enabled && rm default) && \\\n\t(cd /etc/nginx/sites-enabled && ln -s ../sites-available/oo.conf) && \\\n\texport GITH_HOST=$(node -e \"console.log(require('/srv/oo/projects/shared').config.gith.hostname)\") && \\\n\techo \"Gith Host: $GITH_HOST\" && \\\n\tsed -i \"s/oo-utils/$GITH_HOST/g\" /etc/nginx/sites-available/oo.conf\n\nCMD [\"/usr/bin/supervisord\", \"-c\", \"/etc/supervisor/conf.d/oo.conf\"]\n"
  },
  {
    "path": "containers/oo-gith/cloudbuild.yaml",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n#########################################################\n# NOTE: All local paths are relative to repository root #\n#########################################################\n\nsteps:\n\n# Pull additional data from GCP, including config.hjson.\n# NOTE: You probably need to change this, depending on how you store your config.hjson.\n- name: gcr.io/cloud-builders/gcloud\n  entrypoint: bash\n  args:\n    - -c\n    - |\n      # Download config file from private repo\n      # Log the current commit info in the build log\n      gcloud source repos clone oo-misc1 &&\n      (cd oo-misc1 && git log -n1) &&\n      cp oo-misc1/gcp_config.hjson config.hjson &&\n      rm -rf oo-misc1;\n\n# Build the image\n- name: 'gcr.io/cloud-builders/docker'\n  args:\n    - build\n    - --tag=gcr.io/$PROJECT_ID/oo-gith:latest\n    - --file=containers/oo-gith/Dockerfile\n    - '.'\n  timeout: 7200s\n\nimages:\n  - gcr.io/$PROJECT_ID/oo-gith:latest\n\ntimeout: 7200s\n"
  },
  {
    "path": "containers/oo-gith/nginx.conf",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n# This is the nginx configuration for the Git file history viewer.\n\nupstream git_fcgi {\n  # Note: \"oo-utils\" gets substituted during the Docker build\n  server oo-utils:3013;\n}\n\nupstream php_fpm {\n  # Note: \"oo-utils\" gets substituted during the Docker build\n  server oo-utils:3023;\n}\n\nupstream utils_auth_service {\n  server unix:/var/run/oosocks/auth.sock;\n}\n\nserver {\n  listen 80;\n  server_name oo-git;\n\n  root /var/www/html;\n  index index.php index.html;\n\n  error_page 400 401 403 404 500 /custom_4xx.html;\n  location = /custom_4xx.html {\n    internal;\n    auth_request off;\n  }\n\n  location = /ping {\n    auth_request off;\n    return 200;\n  }\n\n  # authorization service\n  auth_request /auth;\n  auth_request_set $auth_status $upstream_status;\n\n  # git-http-backend\n  location ~ ^.*\\.git/(HEAD|info/refs|objects/info/.*|git-(upload|receive)-pack)$ {\n    client_max_body_size 0;\n    fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;\n    fastcgi_param GIT_HTTP_EXPORT_ALL \"\";\n    fastcgi_param REMOTE_USER $remote_user;\n    fastcgi_param GIT_PROJECT_ROOT /srv/oo/git/repos;\n    fastcgi_param PATH_INFO $uri;\n    fastcgi_pass git_fcgi;\n    include fastcgi_params;\n  }\n\n  # Web UI\n  location / {\n    root /srv/oo/gitlist-oo;\n    try_files $uri $uri/ @htaccess;\n  }\n  location @htaccess {\n    include fastcgi_params;\n    fastcgi_pass php_fpm;\n    fastcgi_param SCRIPT_FILENAME /srv/oo/gitlist-oo/index.php;\n    fastcgi_param QUERY_STRING $args;\n    fastcgi_param PHP_AUTH_USER $remote_user;\n    fastcgi_param PHP_AUTH_PW $http_authorization;\n  }\n\n  # Auth service\n  location = /auth {\n    internal;\n    proxy_pass http://utils_auth_service;\n    proxy_pass_request_body off;\n    proxy_set_header Content-Length \"\";\n    proxy_set_header X-Original-URI $request_uri;\n  }\n}\n"
  },
  {
    "path": "containers/oo-gith/supervisord.conf",
    "content": "# Copyright © 2020, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n[supervisord]\nnodaemon=true\nlogfile=/dev/null\nlogfile_maxbytes=0\npidfile=/var/run/supervisord.pid\n\n[program:nginx]\ncommand=/usr/sbin/nginx\n\n# TODO: Add a prefix to the application logs. https://github.com/Supervisor/supervisor/issues/1326\nredirect_stderr=true\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\n\n[program:utils-auth]\ncommand=/srv/oo/projects/utils-auth/app.js\nuser=www-data\nenvironment=NODE_ENV=production\n\n# TODO: Add a prefix to the application logs. https://github.com/Supervisor/supervisor/issues/1326\nredirect_stderr=true\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\n"
  },
  {
    "path": "containers/oo-redirect/cloudbuild.yaml",
    "content": "# Copyright © 2021, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n#########################################################\n# NOTE: All local paths are relative to repository root #\n#########################################################\n\nsteps:\n\n# Pull additional data from GCP, including config.hjson.\n# NOTE: You probably need to change this, depending on how you store your config.hjson.\n- name: gcr.io/cloud-builders/gcloud\n  entrypoint: bash\n  args:\n    - -c\n    - |\n      # Download config file from private repo\n      # Copy AppEngine files to the root directory\n      # Delete client/app/node_modules since it doesn't point anywhere\n      # Log the current commit info in the build log\n      gcloud source repos clone oo-misc1 &&\n      (cd oo-misc1 && git log -n1) &&\n      cp oo-misc1/gcp_config.hjson config.hjson &&\n      find oo-misc1/appengine/oo-redirect -type f -exec cp {} . \\; &&\n      rm -f package-lock.json &&\n      rm -f client/app/node_modules &&\n      rm -rf oo-misc1;\n\n# Deploy to AppEngine\n- name: gcr.io/cloud-builders/gcloud\n  entrypoint: bash\n  timeout: 1800s\n  args:\n    - -c\n    - |\n      gcloud app deploy $_DEPLOY_OPTS;\n\ntimeout: 1800s\nsubstitutions:\n  _DEPLOY_OPTS: \"--promote\"\n"
  },
  {
    "path": "containers/oos-quick-start/docker-compose.yaml",
    "content": "# Copyright © 2020, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n##################################################################\n# This is a docker-compose.yaml for a \"quick start\" installation #\n##################################################################\n\n#########################################################\n# NOTE: All local paths are relative to repository root #\n#########################################################\n\nversion: '3'\nservices:\n  utils-gitd:\n    image: octaveonline/utils-gitd:v1.0.0\n    build:\n      context: \"../..\"\n      dockerfile: containers/utils-gitd/Dockerfile\n    ports:\n      - \"3003:3003\"\n      - \"9418:9418\"\n    volumes:\n      - git:/srv/oo/git\n    read_only: true\n    tmpfs:\n      - /run\n      - /tmp\n  utils-gith:\n    image: octaveonline/utils-gith:v1.0.0\n    build:\n      context: \"../..\"\n      dockerfile: containers/utils-gith/Dockerfile\n    ports:\n      - \"3013:3013\"\n      - \"3023:3023\"\n    volumes:\n      - git:/srv/oo/git\n    read_only: true\n    tmpfs:\n      - /run\n      - /tmp\n      - /run/php\n      - /srv/oo/gitlist-oo/cache:uid=33\n  oo-gith:\n    image: octaveonline/oo-gith:v1.0.0\n    build:\n      context: \"../..\"\n      dockerfile: containers/oo-gith/Dockerfile\n    ports:\n      - \"8008:80\"\n    depends_on:\n      - mongod\n      - utils-gith\n    read_only: true\n    tmpfs:\n      - /run\n      - /tmp\n      - /run/oosocks\n      - /var/log/nginx\n      - /var/lib/nginx\n  mongod:\n    image: docker.io/library/mongo:latest\n    ports:\n      - \"27017:27017\"\n    volumes:\n      - mongodb:/data/db\n    read_only: true\n    # Log all commands. (TODO: consider the performance penalty?)\n    command: --slowms=1\n    tmpfs:\n      - /run\n      - /tmp\n  redis:\n    image: docker.io/library/redis:latest\n    ports:\n      - \"6379:6379\"\n    read_only: true\n    tmpfs:\n      - /run\n      - /tmp\n  octave-deps:\n    image: octaveonline/octave-deps:v1.0.0\n    build:\n      context: \"../..\"\n      dockerfile: containers/octave-deps/Dockerfile\n    read_only: true\n  octave-stable:\n    image: octaveonline/octave-stable:v1.0.0\n    build:\n      context: \"../..\"\n      dockerfile: containers/octave-stable/Dockerfile\n      args: [\"FULL_BASE_IMAGE=octaveonline/octave-deps:v1.0.0\"]\n    depends_on: [\"octave-deps\"]\n  octave-pkg:\n    image: octaveonline/octave-pkg:v1.0.0\n    build:\n      context: \"../..\"\n      dockerfile: containers/octave-pkg/Dockerfile\n      args: [\"FULL_BASE_IMAGE=octaveonline/octave-stable:v1.0.0\"]\n    depends_on: [\"octave-stable\"]\n  octave-oo:\n    image: octaveonline/octave-oo:v1.0.0\n    build:\n      context: \"../..\"\n      dockerfile: containers/octave-oo/Dockerfile\n      args: [\"FULL_BASE_IMAGE=octaveonline/octave-pkg:v1.0.0\"]\n    depends_on: [\"octave-pkg\"]\n  oo-back:\n    image: octaveonline/oo-back:v1.0.0\n    build:\n      context: \"../..\"\n      dockerfile: containers/oo-back/Dockerfile\n      args: [\"FULL_BASE_IMAGE=octaveonline/octave-oo:v1.0.0\"]\n    depends_on:\n      - utils-gitd\n      - octave-oo\n      - redis\n    volumes:\n      - logs:/srv/logs\n    read_only: true\n    tmpfs:\n      - /run\n      - /tmp\n  oo-front:\n    image: octaveonline/oo-front:v1.0.0\n    build:\n      context: \"../..\"\n      dockerfile: containers/oo-front/Dockerfile\n    ports:\n      - \"8080:8080\"\n    depends_on:\n      - mongod\n      - redis\n    read_only: true\n    tmpfs:\n      - /run\n      - /tmp\nvolumes:\n  git:\n  mongodb:\n  logs:\n"
  },
  {
    "path": "containers/utils-admin/cloudbuild.yaml",
    "content": "# Copyright © 2020, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n#########################################################\n# NOTE: All local paths are relative to repository root #\n#########################################################\n\nsteps:\n\n# Pull additional data from GCP, including config.hjson.\n# NOTE: You probably need to change this, depending on how you store your config.hjson.\n- name: gcr.io/cloud-builders/gcloud\n  entrypoint: bash\n  args:\n    - -c\n    - |\n      # Download config file from private repo\n      # Copy AppEngine files to the root directory\n      # Delete client/app/node_modules since it doesn't point anywhere\n      # Log the current commit info in the build log\n      gcloud source repos clone oo-misc1 &&\n      (cd oo-misc1 && git log -n1) &&\n      cp oo-misc1/gcp_config.hjson config.hjson &&\n      find oo-misc1/appengine/utils-admin -type f -exec cp {} . \\; &&\n      rm -f package-lock.json &&\n      rm -f client/app/node_modules &&\n      rm -rf oo-misc1;\n\n# Deploy to AppEngine\n- name: gcr.io/cloud-builders/gcloud\n  entrypoint: bash\n  timeout: 1800s\n  args:\n    - -c\n    - |\n      gcloud app deploy $_DEPLOY_OPTS;\n\ntimeout: 1800s\nsubstitutions:\n  _DEPLOY_OPTS: \"--promote\"\n"
  },
  {
    "path": "containers/utils-gitd/Dockerfile",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n######################################\n# utils-gitd: essential Git services #\n# ================================== #\n# - git-daemon                       #\n# - create-repo-service              #\n######################################\n\n###############\n# BOILERPLATE #\n###############\n\n# NOTE: The version of git in newer Ubuntus no longer prints \"fatal: no matching remote head\"\n# when fetching an empty repository, which OO needs in order to ignore the errorful exit.\n# For now, use an older Ubuntu LTS release for this container.\nFROM ubuntu:bionic\n\nWORKDIR /root\n\n# Disable all prompts when using apt-get\nENV DEBIAN_FRONTEND=noninteractive\n\n# Core tmpfs directories:\nVOLUME \\\n\t/run \\\n\t/tmp\n\n# Essential setup\nRUN \\\n\tmkdir -p /srv/oo && \\\n\tapt-get update && \\\n\tapt-get install -y \\\n\t\tcurl \\\n\t\tca-certificates \\\n\t\topenssl \\\n\t\tapt-transport-https \\\n\t\tgnupg \\\n\t\tsupervisor\n\n# The repository root is expected to be mounted here:\nVOLUME /srv/oo/git\n\n####################\n# MAIN BUILD RULES #\n####################\n\nRUN apt-get update && \\\n\tapt-get install --no-install-recommends -y \\\n\t\tgit \\\n\t\tnodejs\n\n############\n# METADATA #\n############\n\n# Ports:\n# 3003 = create-repo-service\n# 9418 = git-daemon\nEXPOSE 3003/tcp 9418/tcp\n\n##################\n# CONFIGURATIONS #\n##################\n\n# Note: paths are relative to repository root.\nCOPY back-filesystem/git/create-repo-service.js /usr/local/bin/create-repo-service\nCOPY containers/utils-gitd/supervisord.conf /etc/supervisor/conf.d/oo.conf\n\nRUN \\\n\tuseradd -m -u 1600 git && \\\n\tchmod a+x /usr/local/bin/create-repo-service\n\nCMD [\"/usr/bin/supervisord\", \"-c\", \"/etc/supervisor/conf.d/oo.conf\"]\n"
  },
  {
    "path": "containers/utils-gitd/cloudbuild.yaml",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n#########################################################\n# NOTE: All local paths are relative to repository root #\n#########################################################\n\nsteps:\n- name: 'gcr.io/cloud-builders/docker'\n  args:\n    - build\n    - --tag=gcr.io/$PROJECT_ID/utils-gitd:latest\n    - --file=containers/utils-gitd/Dockerfile\n    - '.'\n  timeout: 7200s\ntimeout: 7200s\nimages: \n  - gcr.io/$PROJECT_ID/utils-gitd:latest\n"
  },
  {
    "path": "containers/utils-gitd/supervisord.conf",
    "content": "# Copyright © 2020, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n[supervisord]\nnodaemon=true\nlogfile=/dev/null\nlogfile_maxbytes=0\npidfile=/var/run/supervisord.pid\nuser=root\n\n[program:gitd]\ncommand=/usr/bin/git -c daemon.uploadarch=true -c daemon.receivepack=true daemon --verbose --reuseaddr --export-all --base-path=/srv/oo/git /srv/oo/git | awk '{print \"[ERROR] \" $0}'\nuser=git\n\n# TODO: Add a prefix to the application logs. https://github.com/Supervisor/supervisor/issues/1326\nredirect_stderr=true\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\n\n[program:create-repo-service]\ncommand=/usr/local/bin/create-repo-service /srv/oo/git 3003\nenvironment=NODE_ENV=production\nuser=git\n\n# TODO: Add a prefix to the application logs. https://github.com/Supervisor/supervisor/issues/1326\nredirect_stderr=true\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\n"
  },
  {
    "path": "containers/utils-gith/Dockerfile",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n###########################################\n# utils-gith: extra Git services for HTTP #\n# ======================================= #\n# - php-fpm + GitList                     #\n# - fastcgi + git-http-backend            #\n###########################################\n\n###############\n# BOILERPLATE #\n###############\n\nFROM ubuntu:jammy\n\nWORKDIR /root\n\n# Disable all prompts when using apt-get\nENV DEBIAN_FRONTEND=noninteractive\n\n# Core tmpfs directories:\nVOLUME \\\n\t/run \\\n\t/tmp\n\n# Essential setup\nRUN \\\n\tmkdir -p /srv/oo && \\\n\tapt-get update && \\\n\tapt-get install -y \\\n\t\tcurl \\\n\t\tca-certificates \\\n\t\topenssl \\\n\t\tapt-transport-https \\\n\t\tgnupg \\\n\t\tsupervisor\n\n# The repository root is expected to be mounted here:\nVOLUME /srv/oo/git\n\n####################\n# MAIN BUILD RULES #\n####################\n\nRUN apt-get update && \\\n\tapt-get install --no-install-recommends -y \\\n\t\tgit \\\n\t\tnodejs \\\n\t\tphp-fpm \\\n\t\tphp-cli \\\n\t\tphp-json \\\n\t\tphp-xml \\\n\t\tunzip \\\n\t\tfcgiwrap\n\n# Install GitList\n# Note: php-fpm defaults to user \"www-data\" on Ubuntu 18.04\nRUN \\\n\tcurl -L -k https://github.com/octave-online/gitlist/archive/oo.zip -o gitlist.zip && \\\n\tunzip gitlist.zip -d /srv/oo && \\\n\tcd /srv/oo/gitlist-oo && \\\n\t(curl -s http://getcomposer.org/installer | php) && \\\n\tphp composer.phar install --no-dev\n\n############\n# METADATA #\n############\n\n# Ports:\n# 3013 = git-http-backend\n# 3023 = php-fpm\nEXPOSE 3013/tcp 3023/tcp\n\n# Additional tmpfs directories:\nVOLUME \\\n\t/run/php \\\n\t/srv/oo/gitlist-oo/cache\n\n##################\n# CONFIGURATIONS #\n##################\n\n# Note: paths are relative to repository root\nCOPY utils-auth/configs/gitlist.ini /srv/oo/gitlist-oo/config.ini\nCOPY containers/utils-gith/supervisord.conf /etc/supervisor/conf.d/oo.conf\n\nRUN \\\n\tuseradd -m -u 1600 git && \\\n\tsed -i \"s/error_log = .*/error_log = \\/dev\\/stderr/\" /etc/php/7.2/fpm/php-fpm.conf && \\\n\tsed -i \"s/;daemonize = .*/daemonize = no/\" /etc/php/7.2/fpm/php-fpm.conf && \\\n\tsed -i \"s/listen = .*/listen = 3023/\" /etc/php/7.2/fpm/pool.d/www.conf\n\n # && \\\n\t# sed -i \"s/;chroot =/chroot = \\/srv\\/oo/\" /etc/php/7.2/fpm/pool.d/www.conf\n\nCMD [\"/usr/bin/supervisord\", \"-c\", \"/etc/supervisor/conf.d/oo.conf\"]\n"
  },
  {
    "path": "containers/utils-gith/cloudbuild.yaml",
    "content": "# Copyright © 2019, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n#########################################################\n# NOTE: All local paths are relative to repository root #\n#########################################################\n\nsteps:\n- name: 'gcr.io/cloud-builders/docker'\n  args:\n    - build\n    - --tag=gcr.io/$PROJECT_ID/utils-gith:latest\n    - --file=containers/utils-gith/Dockerfile\n    - '.'\n  timeout: 7200s\ntimeout: 7200s\nimages:\n  - gcr.io/$PROJECT_ID/utils-gith:latest\n"
  },
  {
    "path": "containers/utils-gith/supervisord.conf",
    "content": "# Copyright © 2020, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n[supervisord]\nnodaemon=true\nlogfile=/dev/null\nlogfile_maxbytes=0\npidfile=/var/run/supervisord.pid\n\n[program:git-http-backend]\ncommand=/usr/sbin/fcgiwrap -f -s tcp:0.0.0.0:3013 -p /usr/lib/git-core/git-http-backend\nuser=git\n\n# TODO: Add a prefix to the application logs. https://github.com/Supervisor/supervisor/issues/1326\nredirect_stderr=true\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\n\n[program:php-fpm]\ncommand=/usr/sbin/php-fpm7.2\n\n# TODO: Add a prefix to the application logs. https://github.com/Supervisor/supervisor/issues/1326\nredirect_stderr=true\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\n"
  },
  {
    "path": "entrypoint/.eslintrc.yml",
    "content": "rules:\n  # Allow console.log\n  no-console:\n    - off\n"
  },
  {
    "path": "entrypoint/README.md",
    "content": "Octave Online Server: Back Server Entrypoint Scripts\n====================================================\n\nThis directory contains scripts used for actually running the back server.  **These scripts are primarilly intended for use with the SELinux implementation.**\n\n*back-selinux.js* sets up log files and runs the *back-master* project.  This script is useful when running Octave Online Server as a service.  If debugging Octave Online Server, you should run *back-master* directory.\n\n*oo-install-host.service* is a SystemCTL service that installs the latest version of the GNU Octave Host file (see the *back-octave* directory) to the local instance.  It will update the host file every time the machine is booted.  This is useful to help release updates to the host file and make sure that all instances are using up-to-date versions.\n\n*oo.service* is the main SystemCTL service used in the SELinux implementation.  It runs *back-selinux.js* at startup.\n\n*oo_utils_auth.service* is a SystemCTL service that runs the *utils-auth* project.  For more information, see that project directory.\n"
  },
  {
    "path": "entrypoint/back-selinux.js",
    "content": "#!/usr/bin/env node\n/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\n// This file is the entrypoint for back-master, intended for use with the \"SELinux\" backend.  The \"Docker\" backend has its own initialization built in to the Dockerfiles.\n// This file intentionally has no dependencies on npm modules to make it more portable.\n\nconst child_process = require(\"child_process\");\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst stream = require(\"stream\");\n\n// Print basic information about the process\nconsole.log(\"Daemon PID:\", process.pid);\nconsole.log(\"Date:\", new Date().toISOString());\n\n// What files will we be loading?\nconst prefix = (__dirname === \"/usr/local/bin\") ? \"/usr/local/share/oo\" : path.join(__dirname, \"..\");\nconst configFile = path.join(prefix, \"shared/config.js\");\nconst exitFile = path.join(prefix, \"entrypoint/exit.js\");\nconst spawnDirectory = path.join(prefix, \"back-master\");\nconst spawnFile = path.join(prefix, \"back-master/app.js\");\n\n// Wait until the application code is ready before continuing.  Example: network drives being mounted at startup.\n// eslint-disable-next-line no-constant-condition\nwhile (true) {\n\ttry {\n\t\tfs.statSync(configFile);\n\t\tfs.statSync(spawnDirectory);\n\t\tfs.statSync(spawnFile);\n\t\tbreak;\n\t} catch(err) {\n\t\tconsole.log(\"One or more dependencies not available!  Trying again in 5 seconds...\");\n\t\tchild_process.execSync(\"sleep 5\");  // blocking sleep\n\t}\n}\n\n// Load config file dependency\nconst config = require(configFile);\n\n// Load exit routine\nfunction getExitFunction() {\n\tvar exit;\n\ttry {\n\t\texit = require(exitFile);\n\t\tconsole.log(\"Will use exit routine from exit.js\");\n\t} catch(err) {\n\t\tif (/Cannot find module/.test(err.message)) {\n\t\t\t// If exit.js is not provided, set a no-op.\n\t\t\texit = function(){};\n\t\t\tconsole.log(\"Will use no-op exit routine\");\n\t\t} else throw err;\n\t}\n\treturn exit;\n}\n\n// Make log directories\nfunction mkdirSyncNoError(path) {\n\ttry {\n\t\tfs.mkdirSync(path, \"0740\");\n\t} catch(err) {\n\t\tif (!/EEXIST/.test(err.message)) {\n\t\t\tthrow err;\n\t\t}\n\t}\n}\nconst monitorLogPath = path.join(config.worker.logDir, config.worker.monitorLogs.subdir);\nconst sessionLogPath = path.join(config.worker.logDir, config.worker.sessionLogs.subdir);\nmkdirSyncNoError(monitorLogPath);\nmkdirSyncNoError(sessionLogPath);\n\n// Create nested session log dirs (the goal of nesting is to reduce the number of files in each directory)\nfunction makeSessionLogDirsRecursive(prefix, depth) {\n\tif (depth === config.worker.sessionLogs.depth) {\n\t\treturn;\n\t}\n\tfor (let i=0; i<16; i++) {\n\t\tlet letter = \"0123456789abcdef\"[i];\n\t\tlet currpath = path.join(prefix, letter);\n\t\tmkdirSyncNoError(currpath);\n\t\tmakeSessionLogDirsRecursive(currpath, depth + 1);\n\t}\n}\nmakeSessionLogDirsRecursive(sessionLogPath, 0);\n\n// Create log stream\nlet dateStr = new Date().toISOString().replace(/:/g,\"-\").replace(\".\",\"-\").replace(\"T\",\"_\").replace(\"Z\",\"\");\nlet logPath = path.join(monitorLogPath, config.worker.token+\"_\"+dateStr+\".log\");\nlet logFd = fs.openSync(logPath, \"a\", \"0640\");\nlet logStream = fs.createWriteStream(null, { fd: logFd });\nconsole.log(\"Logging to:\", logPath);\n\n// Filter the log stream to remove noisy http2 messages: workaround for https://bugs.centos.org/view.php?id=17047\nconst illegalLineStarts = [\n\tBuffer.from(\"send\"),\n\tBuffer.from(\"recv\"),\n\tBuffer.from(\"stream\"),\n\tBuffer.from(\"inflatehd\"),\n\tBuffer.from(\"deflatehd\"),\n];\nconst transformStream = new stream.Transform({\n\ttransform(chunk, encoding, callback) {\n\t\t// Note: assumes that chunks always start at a line boundary\n\t\tlet i = 0;\n\t\touter:\n\t\tfor (let j = 0; j < chunk.length; j++) {\n\t\t\t// Check for newline (Unix-style)\n\t\t\tif (chunk[j] == 0x0A) {\n\t\t\t\tlet line = chunk.slice(i, j+1);\n\t\t\t\ti = j+1;\n\t\t\t\tfor (let illegal of illegalLineStarts) {\n\t\t\t\t\tif (line.length < illegal.length) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (illegal.compare(line, 0, illegal.length) === 0) {\n\t\t\t\t\t\t// Found a match; skip to the next line.\n\t\t\t\t\t\tcontinue outer;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// No match found; send this line to the log file.\n\t\t\t\tthis.push(line, encoding);\n\t\t\t}\n\t\t}\n\t\tcallback();\n\t}\n});\ntransformStream.pipe(logStream);\n\n// Prepare child process environment and copy all environment variables\nconst spawnOptions = {\n\tcwd: spawnDirectory,\n\tenv: {\n\t\t\"GNUTERM\": \"svg\",\n\t\t\"DEBUG\": \"*\"\n\t},\n\tuid: config.worker.uid,\n\tgid: config.worker.uid,\n\tstdio: [\"inherit\", \"inherit\", \"pipe\"]\n};\nfor (var name in process.env) {\n\tif (!(name in spawnOptions.env)) {\n\t\tspawnOptions.env[name] = process.env[name];\n\t}\n}\n\n// Signal Handling\nvar sigCount = 0;\nvar spwn;\nfunction doExit() {\n\tif (sigCount === 0) {\n\t\tconsole.log(\"RECEIVED FIRST SIGNAL.  Terminating gracefully.\");\n\t\tif (spwn) spwn.kill(\"SIGTERM\");\n\t} else if (sigCount < 5) {\n\t\tconsole.log(\"RECEIVED SIGNAL 2-5.  Ignoring.\");\n\t} else {\n\t\tconsole.log(\"RECEIVED FINAL SIGNAL.  Killing child process now.\");\n\t\tif (spwn) spwn.kill(\"SIGKILL\");\n\t\tprocess.exit(1);\n\t}\n\tsigCount++;\n}\nprocess.on(\"SIGINT\", doExit);\nprocess.on(\"SIGHUP\", doExit);\nprocess.on(\"SIGTERM\", doExit);\n\n// Spawn loop\nfunction runOnce() {\n\tconsole.log(spawnOptions);\n\tspwn = child_process.spawn(\"/usr/bin/env\", [\"node\", spawnFile], spawnOptions);\n\tconsole.log(`Starting child (${spawnFile}) with PID ${spwn.pid}`);\n\tspwn.stderr.pipe(transformStream);\n\tspwn.once(\"exit\", (code, signal) => {\n\t\tconsole.log(`Process exited with code ${code}, signal ${signal}`);\n\t\tif (code !== 0) {\n\t\t\tsetTimeout(runOnce, 500);\n\t\t} else {\n\t\t\tgetExitFunction()();\n\t\t\tlogStream.close(); // also closes the logFd file descriptor\n\t\t}\n\t});\n}\n\n// Run it!\nrunOnce();\n"
  },
  {
    "path": "entrypoint/exit.js.sample",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Example exit.js that performs a reboot upon process exit.\n\nconst child_process = require(\"child_process\");\n\nmodule.exports = function exit() {\n\tconsole.log(\"Spawning reboot and exiting\");\n\t// This needs to be execFileSync (as opposed to execFile) to stop the parent process from exiting and restarting before the child process has had a chance to finish.\n\tchild_process.execFileSync(\"sudo\", [\"reboot\"], {\n\t\tstdio: \"inherit\"\n\t});\n}\n"
  },
  {
    "path": "entrypoint/oo-front.service",
    "content": "[Service]\nExecStart=/srv/oo/projects/front/app.js\nStandardOutput=journal\nStandardError=journal\n#SyslogIdentifier=oo-front\nUser=oo\nGroup=oo\nWorkingDirectory=/srv/oo/projects/front\nEnvironment=NODE_ENV=production\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "entrypoint/oo-no-restart.service",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n[Service]\nExecStart=/usr/local/bin/oo-back-selinux\nStandardOutput=syslog\nStandardError=syslog\nSyslogIdentifier=oo\nUser=1500\nGroup=1500\nEnvironment=NODE_ENV=production\nRestart=no\nKillMode=mixed\nTimeoutStopSec=60\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "entrypoint/oo-reinstall.service",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n[Unit]\nDescription=Install the Octave Online host file\n\n[Service]\nExecStart=/bin/true\nExecStop=/usr/bin/make -C /srv/oo/projects/back-octave install; \\\n\t/usr/bin/make -C /srv/oo/projects reinstall-selinux; \\\n\t/usr/bin/make -C /srv/oo/projects install-site-m;\nType=oneshot\nRemainAfterExit=yes\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "entrypoint/oo.service",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n[Service]\nExecStart=/usr/local/bin/oo-back-selinux\nStandardOutput=syslog\nStandardError=syslog\nSyslogIdentifier=oo\nUser=1500\nGroup=1500\nEnvironment=NODE_ENV=production\nRestart=always\nKillMode=mixed\nTimeoutStopSec=60\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "entrypoint/policy/octave_online.fc",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n/usr/local/lib/octave(/.*)?                 gen_context(system_u:object_r:octave_site_t,s0)\n/tmp/oo-.*(/.*)?                            gen_context(unconfined_u:object_r:oo_tmp_t,s0)\n/tmp/.sandbox-*(/.*)?                       gen_context(unconfined_u:object_r:oo_tmp_t,s0)\n/srv/oo(/.*)?                               gen_context(system_u:object_r:srv_oo_t,s0)\n\n# See supplement.te/if\n#/usr/local/bin/oo-back-selinux          --  gen_context(system_u:object_r:oo_exec_t,s0)\n#/usr/lib/systemd/system/oo.*            --  gen_context(system_u:object_r:oo_unit_file_t,s0)\n"
  },
  {
    "path": "entrypoint/policy/octave_online.if",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n## <summary></summary>\n"
  },
  {
    "path": "entrypoint/policy/octave_online.te",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\npolicy_module(octave_online, 1.9);\n\n### TO SETUP THIS POLICY FILE:\n#\n# 0. Make sure the following packages are installed:\n#\n#    selinux-policy-devel\n#    policycoreutils-sandbox\n#    selinux-policy-sandbox\n#\n# 1. Copy or symlink this file to /etc/selinux/targeted/policy\n#\n# 2. Run the following commands as root:\n#\n#    make -f /usr/share/selinux/devel/Makefile octave_online.pp\n#    semodule -i octave_online.pp\n#\n# 3. Apply file permissions:\n#\n#    restorecon -R -v /usr/local/lib/octave\n#\n###\n\nrequire {\n\ttype file_t;\n\ttype home_root_t;\n\ttype passwd_file_t;\n\ttype sandbox_t;\n\ttype sysfs_t;\n\ttype tmp_t;\n\ttype unconfined_service_t;\n\ttype unconfined_t;\n\ttype unlabeled_t;\n\ttype urandom_device_t;\n\n\tclass chr_file open;\n\tclass dir { read open search getattr write add_name };\n\tclass file { open execute };\n\tclass lnk_file read;\n\tclass process { dyntransition transition sigchld };\n\tclass unix_stream_socket { read write ioctl getattr connectto };\n}\n\n# Create a new file permission context for GNU Octave site files.\ntype octave_site_t;\nfiles_type(octave_site_t);\n\n# Create file type for temporary files\ntype oo_tmp_t;\nfiles_type(oo_tmp_t);\n\n# Create file type for OO server files.\ntype srv_oo_t;\nfiles_type(srv_oo_t);\n\n# Create domains for running the Octave Online process (see supplement.te/if)\n# type oo_t;\n# type oo_exec_t;\n# init_daemon_domain(oo_t, oo_exec_t);\n# type oo_unit_file_t;\n# systemd_unit_file(oo_unit_file_t);\n\n# Allow system_r to run and spawn sandbox_t\n# (required when running application as a systemd service)\nrole system_r types sandbox_t;\nallow unconfined_service_t sandbox_t:process { dyntransition transition };\nallow sandbox_t unconfined_service_t:process sigchld;\n\n# Allow sandbox applications to communicate over UNIX sockets, which is\n# required for non-blocking mode.\nallow sandbox_t unconfined_t:unix_stream_socket { read write ioctl getattr };\nallow sandbox_t unconfined_service_t:unix_stream_socket { read write ioctl getattr };\nallow sandbox_t self:unix_stream_socket connectto;\n\n# I tried making oo_tmp_t, but tmp_t is still being inherited, so\n# the following rule is required.\n#allow sandbox_t tmp_t:dir { write add_name create };\n\n# The following line eliminates more errors from the audit file, and\n# seem to be necessary to enable Octave to load files from the sandbox\n# home path.\nallow sandbox_t home_root_t:dir { search getattr };\n\n# The following line enables Octave to access urandom.  Without this,\n# there are a lot of audit errors, and the oo-events.txt results in a\n# segmentation fault.\n# NOTE: This could instead be allowed using 'global_ssp'\nallow sandbox_t urandom_device_t:chr_file open;\n\n# Give permissions to read and execute octave_site_t to sandbox\nallow sandbox_t octave_site_t:file { open execute map };\nallow sandbox_t octave_site_t:dir { read open search getattr };\nallow sandbox_t octave_site_t:lnk_file read;\n\n# Don't-Audit rules for sandbox to clean up log files a bit\ndontaudit sandbox_t passwd_file_t:file open;\ndontaudit sandbox_t sysfs_t:dir read;\n"
  },
  {
    "path": "entrypoint/policy/octave_online_supplement.if",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\ninterface(`oo_systemctl',`\n\tgen_require(`\n\t\ttype oo_t, oo_unit_file_t;\n\t')\n\n\tsystemd_exec_systemctl($1)\n\tinit_reload_services($1)\n\tsystemd_read_fifo_file_passwd_run($1)\n\tallow $1 oo_unit_file_t:file read_file_perms;\n\tallow $1 oo_unit_file_t:service manage_service_perms;\n\n\tps_process_pattern($1, oo_t)\n')\n\ninterface(`oo_domtrans',`\n\tgen_require(`\n\t\ttype oo_t, oo_exec_t;\n\t')\n\n\tcorecmd_search_bin($1)\n\tdomtrans_pattern($1, oo_exec_t, oo_t)\n')\n\ninterface(`oo_admin',`\n\tgen_require(`\n\t\ttype oo_t, oo_unit_file_t;\n\t')\n\n\tallow $1 oo_t:process { ptrace signal_perms };\n\tps_process_pattern($1, oo_t)\n\n\too_systemctl($1)\n\tadmin_pattern($1, oo_unit_file_t)\n\tallow $1 oo_unit_file_t:service all_service_perms;\n')\n"
  },
  {
    "path": "entrypoint/policy/octave_online_supplement.te",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n# This is the output of audit2allow when running the process under systemd\n# and oo_t.  I currently am not using it because SELinux has macros that can\n# automate most of these rules, but I haven't taken the time to read up on\n# them yet, and this particular part of the application is not as important\n# for security vulnerabilities.\n\nrequire {\n\ttype NetworkManager_t;\n\ttype auditd_t;\n\ttype bin_t;\n\ttype crond_t;\n\ttype device_t;\n\ttype devlog_t;\n\ttype dhcpc_t;\n\ttype docker_t;\n\ttype fixed_disk_device_t;\n\ttype fs_t;\n\ttype fs_t;\n\ttype fsadm_exec_t;\n\ttype getty_t;\n\ttype init_t;\n\ttype kernel_t;\n\ttype kernel_t;\n\ttype loop_control_device_t;\n\ttype lvm_t;\n\ttype mongod_t;\n\ttype mount_exec_t;\n\ttype mount_var_run_t;\n\ttype net_conf_t;\n\ttype oo_t;\n\ttype passwd_file_t;\n\ttype passwd_file_t;\n\ttype policykit_t;\n\ttype postfix_master_t;\n\ttype postfix_pickup_t;\n\ttype postfix_qmgr_t;\n\ttype proc_t;\n\ttype random_device_t;\n\ttype redis_port_t;\n\ttype redis_t;\n\ttype root_t;\n\ttype rsync_exec_t;\n\ttype sandbox_t;\n\ttype semanage_store_t;\n\ttype seunshare_exec_t;\n\ttype shell_exec_t;\n\ttype ssh_exec_t;\n\ttype ssh_home_t;\n\ttype ssh_port_t;\n\ttype sshd_t;\n\ttype sudo_exec_t;\n\ttype sysfs_t;\n\ttype sysfs_t;\n\ttype syslogd_t;\n\ttype system_dbusd_t;\n\ttype systemd_logind_t;\n\ttype tmp_t;\n\ttype tmp_t;\n\ttype tuned_t;\n\ttype udev_t;\n\ttype unconfined_service_t;\n\ttype unconfined_t;\n\ttype unlabeled_t;\n\ttype unlabeled_t;\n\ttype user_home_dir_t;\n\ttype vmblock_t;\n\ttype vmblock_t;\n\ttype vmtools_t;\n\n\tclass blk_file { read write getattr open ioctl };\n\tclass capability { setuid sys_resource setgid chown audit_write dac_override };\n\tclass chr_file { write getattr read open ioctl };\n\tclass dir { search setattr read create mounton write relabelfrom getattr rmdir remove_name relabelto open add_name };\n\tclass fifo_file { write getattr read create unlink open };\n\tclass file { append create execute execute_no_trans getattr ioctl link open read rename setattr unlink write };\n\tclass filesystem { mount unmount getattr };\n\tclass key write;\n\tclass lnk_file { create unlink read getattr };\n\tclass netlink_audit_socket { nlmsg_relay create };\n\tclass netlink_route_socket { bind create getattr nlmsg_read };\n\tclass process { dyntransition signal sigchld execmem setexec setsched setcurrent setcap };\n\tclass sock_file { write create };\n\tclass tcp_socket { getopt name_connect create setopt connect getattr shutdown };\n\tclass udp_socket { create connect getattr };\n\tclass unix_dgram_socket { create connect sendto };\n}\n\n#============= oo_t ==============\nallow oo_t NetworkManager_t:dir { getattr search };\nallow oo_t NetworkManager_t:file { read open };\nallow oo_t auditd_t:dir { getattr search };\nallow oo_t auditd_t:file { read open };\nallow oo_t bin_t:file { execute execute_no_trans };\nallow oo_t crond_t:dir { getattr search };\nallow oo_t crond_t:file { read open };\nallow oo_t device_t:blk_file { read write ioctl open getattr };\nallow oo_t dhcpc_t:dir { getattr search };\nallow oo_t dhcpc_t:file { read open };\nallow oo_t docker_t:dir { getattr search };\nallow oo_t docker_t:file { read open };\nallow oo_t fixed_disk_device_t:blk_file { read write getattr open ioctl };\nallow oo_t fs_t:filesystem getattr;\nallow oo_t fs_t:filesystem { mount unmount };\nallow oo_t fsadm_exec_t:file { read execute open execute_no_trans };\nallow oo_t getty_t:dir { getattr search };\nallow oo_t getty_t:file { read open };\nallow oo_t init_t:file { read open };\nallow oo_t kernel_t:dir { getattr search };\nallow oo_t kernel_t:file { read open };\nallow oo_t kernel_t:process setsched;\nallow oo_t kernel_t:unix_dgram_socket sendto;\nallow oo_t loop_control_device_t:chr_file { read write getattr open ioctl };\nallow oo_t lvm_t:dir { getattr search };\nallow oo_t lvm_t:file { read open };\nallow oo_t mongod_t:dir { getattr search };\nallow oo_t mongod_t:file { read open };\nallow oo_t mount_exec_t:file { read getattr open execute execute_no_trans };\nallow oo_t mount_var_run_t:file { read write getattr open };\nallow oo_t net_conf_t:file { read getattr open };\nallow oo_t passwd_file_t:file { read getattr open };\nallow oo_t policykit_t:dir { getattr search };\nallow oo_t policykit_t:file { read open };\nallow oo_t postfix_master_t:dir { getattr search };\nallow oo_t postfix_master_t:file { read open };\nallow oo_t postfix_pickup_t:dir { getattr search };\nallow oo_t postfix_pickup_t:file { read open };\nallow oo_t postfix_qmgr_t:dir { getattr search };\nallow oo_t postfix_qmgr_t:file { read open };\nallow oo_t proc_t:file { read getattr open };\nallow oo_t random_device_t:chr_file getattr;\nallow oo_t redis_port_t:tcp_socket name_connect;\nallow oo_t redis_t:dir { getattr search };\nallow oo_t redis_t:file { read open };\nallow oo_t root_t:dir mounton;\nallow oo_t rsync_exec_t:file { read execute open execute_no_trans };\nallow oo_t sandbox_t:dir { getattr search };\nallow oo_t sandbox_t:file { read open };\nallow oo_t sandbox_t:process { dyntransition signal };\nallow oo_t self:capability { setuid sys_resource setgid chown audit_write dac_override };\nallow oo_t self:key write;\nallow oo_t self:netlink_audit_socket { nlmsg_relay create };\nallow oo_t self:netlink_route_socket { bind create getattr nlmsg_read };\nallow oo_t self:process execmem;allow oo_t devlog_t:sock_file write;\nallow oo_t self:process { execmem setexec setcurrent setcap setsched };\nallow oo_t self:tcp_socket { getattr shutdown };\nallow oo_t self:tcp_socket { getopt create connect setopt };\nallow oo_t self:udp_socket { create connect getattr };\nallow oo_t self:unix_dgram_socket { create connect };\nallow oo_t semanage_store_t:dir { read open };\nallow oo_t semanage_store_t:file { read getattr open };\nallow oo_t seunshare_exec_t:file { read getattr open execute execute_no_trans };\nallow oo_t shell_exec_t:file { execute execute_no_trans };\nallow oo_t ssh_exec_t:file { read getattr open execute execute_no_trans };\nallow oo_t ssh_home_t:dir getattr;\nallow oo_t ssh_home_t:file { read getattr open };\nallow oo_t ssh_port_t:tcp_socket name_connect;\nallow oo_t sshd_t:dir { getattr search };\nallow oo_t sshd_t:file { read open };\nallow oo_t sudo_exec_t:file { read execute open execute_no_trans };\nallow oo_t sysfs_t:dir read;\nallow oo_t sysfs_t:file { read getattr open };\nallow oo_t sysfs_t:lnk_file read;\nallow oo_t syslogd_t:dir { getattr search };\nallow oo_t syslogd_t:file { read open };\nallow oo_t system_dbusd_t:dir { getattr search };\nallow oo_t system_dbusd_t:file { read open };\nallow oo_t systemd_logind_t:dir { getattr search };\nallow oo_t systemd_logind_t:file { read open };\nallow oo_t tmp_t:dir { setattr read create mounton write relabelfrom rmdir remove_name add_name };\nallow oo_t tmp_t:file { rename link setattr };\nallow oo_t tmp_t:file { write create unlink open };\nallow oo_t tmp_t:lnk_file { create unlink };\nallow oo_t tuned_t:dir { getattr search };\nallow oo_t tuned_t:file { read open };\nallow oo_t udev_t:dir { getattr search };\nallow oo_t udev_t:file { read open };\nallow oo_t unconfined_t:dir { getattr search };\nallow oo_t unconfined_t:file { read open };\nallow oo_t unlabeled_t:dir remove_name;\nallow oo_t unlabeled_t:dir { write rmdir setattr read relabelto create add_name };\nallow oo_t unlabeled_t:file { write read create unlink open };\nallow oo_t user_home_dir_t:dir { getattr mounton };\nallow oo_t vmblock_t:dir { write search getattr add_name };\nallow oo_t vmblock_t:file execute_no_trans;\nallow oo_t vmblock_t:file { execute read create getattr write ioctl open append };\nallow oo_t vmblock_t:lnk_file { read getattr };\nallow oo_t vmtools_t:dir { getattr search };\nallow oo_t vmtools_t:file { read open };\n\n#============= sandbox_t ==============\nallow sandbox_t oo_t:process sigchld;\nallow sandbox_t passwd_file_t:file open;\nallow sandbox_t sysfs_t:dir read;\nallow sandbox_t unlabeled_t:dir { read write create add_name remove_name };\nallow sandbox_t unlabeled_t:fifo_file { write getattr read create unlink open };\nallow sandbox_t unlabeled_t:file { write create unlink };\nallow sandbox_t unlabeled_t:sock_file { write create };\n"
  },
  {
    "path": "front/.eslintrc.yml",
    "content": "parser: '@typescript-eslint/parser'\nplugins:\n  - '@typescript-eslint'\nextends:\n  - plugin:@typescript-eslint/eslint-recommended\n  - plugin:@typescript-eslint/recommended\nrules:\n  # Allow the explicit any type, since there is a lot of interfacing to non-TS libraries\n  '@typescript-eslint/no-explicit-any': off\n  # JSON/Mongo uses snake case\n  '@typescript-eslint/camelcase': off\n  # Explicit void return types are a lot of clutter\n  '@typescript-eslint/explicit-function-return-type': off\n  # The non-null assertion has valid use cases, such as within callback functions\n  '@typescript-eslint/no-non-null-assertion': off\n  # Interfaces that act as interfaces for the purposes of class hierarchy should be prefixed with I; other interfaces, such as those used for declaring object structure, don't need to be prefixed with I\n  '@typescript-eslint/interface-name-prefix': off\n"
  },
  {
    "path": "front/.npmrc",
    "content": "# Please keep this in sync with all other .npmrc files\n# SEE: https://github.com/npm/feedback/discussions/864\ninstall-links=false\n"
  },
  {
    "path": "front/README.md",
    "content": "Octave Online Server: Front\n===========================\n\nThe Front server is responsible for running all Socket.IO connections. It also comes with an Express HTTP server that handles authentication-related requests and serves static files.\n\nThe Front server does not handle HTTPS requests. It is recommended that you put another piece of server software, such as Nginx, in front of the Front server to handle HTTPS. You can also point Nginx to the \"dist\" directory to serve static files and reduce the number of requests going into Express.\n\n## Building and Running\n\nTo perform a one-time production bulid:\n\n```bash\n$ npm ci\n$ npm build\n$ NODE_ENV=production DEBUG=oo:* node dist/app.js\n```\n\nTo perform an auto-updating development build:\n\n```bash\n$ npm install\n$ npm watch &\n$ DEBUG=oo:* node dist/app.js\n```\n\n## Extra Static Files\n\nYou can create a directory parallel to this file named *static*; the files in this directory will be served by Express.\n"
  },
  {
    "path": "front/locales/README.md",
    "content": "Octave Online Localization\n==========================\n\nOctave Online Server supports the localization of UI strings.\n\nThe source language is English, and the strings are stored in *en.yaml* using the i18next JSON format.  Additional translations should be submitted to the oos-translations project:\n\nhttps://github.com/octave-online/oos-translations\n\nWhen new strings are added to *en.yaml*, a corresponding description should be added to *qqq.yaml* to help translators produce more accurate translations.\n\nUse the config options *front.locales_path* and *front.locales* to set an alternate load path for the localization files.\n"
  },
  {
    "path": "front/locales/en.yaml",
    "content": "# Copyright © 2020, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or\n# modify it under the terms of the GNU Affero General Public License as\n# published by the Free Software Foundation, either version 3 of the License,\n# or (at your option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\nheader:\n  sidebar:\n    changeemail#body: Signed in before but need to change your email address? Click \"Feedback\" and create a ticket. We will change it for you.\n    sharedesc#body: Others can use the following link to connect to your workspace.\n    createnewproject#btn: Create New Project\n    changelayout#btn: Change/Reset Layout\n    changepassword#btn: Change Password\n    changetheme#btn: Change Theme\n    disablesharing#btn@2: Disable Collaboration\n    enablesharing#btn@2: Enable Collaboration\n    enablesharing#tooltip: Enabling collaboration allows others to edit your home workspace in real time.\n    enablesharingp#tooltip: Enabling collaboration allows others to edit this project in real time.\n    github#btn: GitHub Project\n    inlineplots#btn: Render New Plots Inline\n    legal#btn: Privacy Policy and EULA\n    patreon#btn: Upgrade with Patreon\n    signinemail#btn: Sign in with Email\n    signingoogle#btn: Sign in with Google\n    signinpassword#btn: Sign in with Password\n    signout#btn: Sign Out\n    support#btn: Feedback and Support\n    textwrap#btn: Wrap Long Lines in Console\n    twitter#btn: Follow on Twitter\n  buckets:\n    delete#btn@2: Delete Bucket/Project\n    title@2: \"Buckets and Projects:\"\n  meta:\n    official:\n      title: \"{{config.client.app_name}} · Cloud IDE compatible with MATLAB\"\n      desc: Create and share scripts for scientific computing with GNU Octave. Free and open-source. Works in your browser, including on Chromebook, iPad, tablet, and smartphone.\n    server:\n      title: \"{{config.client.app_name}}\"\n      desc: The power of {{config.client.app_name}} run on custom hardware. Used under the AGPL license.\n  students:\n    reenroll#btn: Move\n    unenroll#btn: Unenroll\n    subhead1: \"Code:\"\n    title: Students\n  site:\n    menu#btn: MENU\n    adfallback#body: \"Do your part: support Free Software by contributing US$3/mo.\"\n\neditor:\n  toolbar:\n    create#btn: Create empty file\n    delete#btn: DELETE File\n    download#btn: Download File\n    history#btn: File version history\n    print#btn: Print File\n    refresh#btn: Refresh files\n    rename#btn: Rename File\n    run#btn: Run Script\n    save#btn: Save File\n    share#btn: Share File in new Bucket\n    toolbar#btn: Show Toolbar\n    upload#btn: Upload file\n    wrap#btn: Toggle Word Wrap\n    files#title: Files\n    drop#ui: Drop Files Here to Upload\n    run#ui: RUN\n  tips:\n    a1: Full List\n    dd1: Show the auto-completion menu\n    dd2: Save the file\n    dd3: Run the file\n    dd4: Set focus to the prompt\n    p1: \"The files you make on {{config.client.app_name}} will be saved for the next time you visit. They will be deleted after 6 months of inactivity.\"\n    p2: \"Common shortcuts:\"\n    subhead1: Keyboard Shortcuts\n    title: Tips & Tricks\n  unsupported:\n    li1: \"The file type isn't supported in this editor.\"\n    li2: \"The file is in a binary format, like images.\"\n    li3: \"The file is too large and can't be loaded into the editor.\"\n    p1: \"The currently selected file can't be edited online. You can still use the toolbar above to rename, download, and delete the file.  Reasons might include:\"\n    title: \"{{config.client.app_name}} Interactive Editor\"\n\npanel:\n  output:\n    github#btn: \"{{projectName}} on GitHub\"\n    upgraded#ui: Upgraded session\n  plot:\n    download#btn: Download Plot as Image\n    expand#btn: Expand Plot Window\n    next#btn: Next Plot\n    prev#btn: Previous Plot\n    zoom#btn: Click to zoom\n    description#ui: \"Plot: Line {{-lineNumberHTML}}\"\n  prompt:\n    add#btn: Add {{-numSecondsHTML}} Seconds\n    resume#btn: Resume Execution\n    sigint#btn: Stop Currently Running Command (send SIGINT)\n    upgrade#btn: Upgrade\n    seconds#ui: \"Seconds Remaining:\"\n  vars:\n    title: Vars\n\npromos:\n  instructor:\n    p1: Did you know you can use {{config.client.app_name}} in your class?\n    p2: Open a support ticket to inquire about how we can set you up as an instructor.\n  prompt:\n    btn1: Sign In\n    p1: Type expressions here and press enter.\n    p2: \"To use script files:\"\n    title: Octave Command Prompt\n  welcome:\n    title: Welcome to {{config.client.app_name}}\n    p1: \"{{config.client.app_name}} is a web UI for GNU Octave, the open-source alternative to MATLAB.  Thousands of students, educators, and researchers from around the world use {{config.client.app_name}} each day for studying machine learning, control systems, numerical methods, and more.\"\n    p2: Type commands in the prompt like you would in your local copy of GNU Octave or MATLAB.\n    p3: Plot charts and graphs.\n    p4: \"Sign in for more features: script files, buckets, real-time collaboration like Google Docs, extended runtime, and more.\"\n    start#btn: \"Start Using {{config.client.app_name}}\"\n  sync:\n    p1: View historical versions of your script files!\n    p2: Click the icon for more info.\n  bucketcreate:\n    p1: Share snapshots of your script files!\n    p2: Click the icon to create a new bucket.\n  sharing:\n    subhead1: \"Tip:\"\n    p1@2: Enable collaboration to allow others to edit your script files in real time, like Google Docs.\n  login:\n    p1: Want to use scripts?\n    p2: Sign in to create and share script files.\n  bucketsbacklink:\n    p1: Sign in to create your own buckets and use the rest of {{config.client.app_name}}.\n\nmodals:\n  email:\n    label1: \"Enter your email address:\"\n    p1: You will be emailed an 11-digit code that you will need to enter on the next screen. If you do not receive your code, please open a support ticket for assistance.\n  password:\n    subhead1: New user? Forgot your password?\n    p1: Use the \"email token\" sign-in option instead. Once you are signed in, use the \"Change Password\" option in the menu to set a new password.\n  changepwd:\n    title: Change Password\n    p1: Enter your new password below.  To remove your password and disable password-based logins, leave the password field blank and click \"Save Password\".\n    submit#btn: Save Password\n  welcome_back:\n    title: Welcome Back!\n    p1: It has been a while since you've visited {{config.client.app_name}}. You have a clean workspace for a fresh start. To restore your data from last time, please contact support using the button in the menu.\n    btn1: OK\n  bucketinfo:\n    p1: Click below to make your own copy of this bucket or project.\n    label1: \"Created at:\"\n    label2: \"Forked From:\"\n    btn1: \"Fork This Bucket\"\n    btn1p: \"Fork This Project\"\n    btn2: \"Change Short Link\"\n  createbucket:\n    title: Customize Bucket\n    titlep: Customize Project\n    p1@2: A \"bucket\" is the way to share snapshots of script and data files. A \"project\" is an editable workspace to organize script and data files.\n    subhead1@2: \"Optional: Add More Files to Bucket\"\n    subhead1p: \"Optional: Copy Files into Project\"\n    p2: Select files and click the right arrow button.\n    subhead2: Files to Add\n    subhead3: Files in Bucket\n    subhead3p: Files in Project\n    subhead4: \"Optional: Select Main File\"\n    p3: The \"main\" script is automatically run when someone views the bucket.\n    label1: \"Main File:\"\n    subhead5: \"Optional: Custom URL / Short Link\"\n    p5: You can set a custom URL for your bucket or project via our short-link service at {{config.redirect.hostname}}.\n    p4: After clicking below, your browser will refresh into your new bucket.  You can share the URL of the page to which you are redirected.\n    p4p: After clicking below, your browser will refresh into your new project.\n    submit#btn: Create Bucket\n    submitp#btn: Create Project\n  upgrade:\n    title: Upgrade Account\n    btn1: View Plans\n    btn2: Link Patreon Account\n    p1: Thank you for your {{-pledgeAmountHTML}} pledge!\n    btn3: Change on Patreon\n    p2: Want to transfer your pledge?\n    btn4: Unlink Patreon Account\n    p3: Each week, {{config.client.app_name}} connects tens of thousands of students, educators, and researchers in over 100 countries.  We have no paywall.  If you believe in our mission to provide educational software free of charge, please join us by supporting {{config.client.app_name}} on Patreon.\n  history:\n    title: File History\n    subtitle1: Version Control\n    p1: Revisions to your files are tracked by a Git version control system.\n    btn1: Launch Version Control\n    subtitle2: Download Snapshot\n    p2: \"To request a ZIP file snapshot sent to your email, click the link below. Note: If you edited your files this session, click the \\\"Refresh Files\\\" button first to ensure you receive the most up-to-date snapshot.\"\n    btn2: Generate ZIP Archive\n  patience:\n    p1: Hmm, looks like the server is busy today!  We'll get you connected before you know it.  Thanks for your patience.\n    p2: Thanks for waiting.  It's not supposed to take this long.  Please double-check your internet connection.  If you are sure that your internet is working, consider contacting support.\n\njavascript:\n  console:\n    exited#alert: \"Octave Exited. Message:\"\n    payload#alert: \"NOTICE: Execution paused due to large payload\"\n    plotwindow#alert: This button shows and hides the plot window.  Enter an expression that generates a plot.\n    readonly#alert@2: \"Note: This is a read-only bucket; your edits are temporary. To make your edits permanent, fork the bucket by clicking the bucket name:\"\n    reconnecting#alert: \"Connection lost.  Attempting to reconnect...\"\n    refresh#alert: \"This will reload your files from the server. Any unsaved changes will be lost.\"\n    reconnect#btn: Click Here to Reconnect\n    pingtime#label: \"Ping time:\"\n    seeurl#label: \"See:\"\n  newfile:\n    label: \"Please enter a filename:\"\n    helloworld: Hello World\n  print:\n    p1: \"Printed for:\"\n    p2: Powered by {{config.client.app_name}}\n  rename:\n    alert: \"The specified filename already exists.\"\n    label: \"Enter a new filename:\"\n  students:\n    course#label: \"Course:\"\n    name#label: \"Name:\"\n    enroll:\n      p1: You need to sign in to enroll in a course.\n      p2: When you enroll in a course, the instructors for that course will be able to access the files you save. You can cancel your enrollment at any time by running running the following at the command prompt.\n      p3: Press Cancel if you don't know what any of this means.\n    reenroll:\n      p1: \"Enter the course code to which you want to move this student:\"\n      p2: The following student is being re-enrolled.  Reload the page to see the update.\n      p3: \"Error: Could not find the course code:\"\n    unenroll:\n      p1: Are you sure that you want to remove the following student from your course?\n  buckets:\n    # Note: These requirements should match the regex in socket_connect.ts\n    error1: \"Error: invalid short link. Use at least 5 letters, numbers, -, and _.\"\n    error2: \"Error: That short link is already taken. Please try again.\"\n    label1: \"Enter a new short link:\"\n  # Not currently used:\n  togglesharing:\n    p1: \"You cannot disable sharing as a student enrolled in an {{config.client.app_name}} course code:\"\n    p2: \"You need to remove yourself by running this command at at the command prompt:\"\n\ncommon:\n  email#ph: email address\n  submit#btn: Submit\n  caution#label: \"Caution:\"\n  password#ph: password\n  https#btn: Switch to a secured connection (https)\n  https#p: You are using an unsecured connection (http). People might be able to see what you submit.\n  loading#ui: Loading…\n  dismiss#btn: dismiss\n  loginrequired: Please sign in to perform this action\n  bucket: Bucket\n  project: Project\n\nconstants:\n  shortlink_prefix: https://{{config.redirect.hostname}}/\n"
  },
  {
    "path": "front/locales/qqq.yaml",
    "content": "# Copyright © 2020, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or\n# modify it under the terms of the GNU Affero General Public License as\n# published by the Free Software Foundation, either version 3 of the License,\n# or (at your option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\nheader:\n  sidebar:\n    changeemail#body: (OBSOLETE) Paragraph of body text\n    sharedesc#body: Paragraph of body text\n    createnewproject#btn: Button text\n    changelayout#btn: Button text\n    changepassword#btn: Button text\n    changetheme#btn: Button text\n    disablesharing#btn@2: Button text\n    enablesharing#btn@2: Button text\n    enablesharing#tooltip: \"Tooltip (additional explanation of what a button will do)\"\n    enablesharingp#tooltip: \"Tooltip (additional explanation of what a button will do)\"\n    github#btn: Link text\n    inlineplots#btn: Button text\n    legal#btn: Button text\n    patreon#btn: Button text; \"Patreon\" should not be translated\n    signinemail#btn: Button text\n    signingoogle#btn: Button text\n    signinpassword#btn: Button text\n    signout#btn: Button text\n    support#btn: Button text\n    textwrap#btn: Button text\n    twitter#btn: Button text\n  buckets:\n    delete#btn@2: Button text\n    title@2: Label, followed by a number\n  meta:\n    official:\n      title: Site Title\n      desc: Paragraph of body tex\n    server:\n      title: Site Title (does not need translation)\n      desc: Paragraph of body tex\n  students:\n    reenroll#btn: Button text\n    unenroll#btn: Button text\n    subhead1: Label, followed by a list\n    title: Section header\n  site:\n    menu#btn: Button text to show the menu (appears prominently on page)\n    adfallback#body: Text shown when advertisement is unavailable\n\neditor:\n  toolbar:\n    create#btn: Button text\n    delete#btn: Button text\n    download#btn: Button text\n    history#btn: Button text\n    print#btn: Button text\n    refresh#btn: Button text\n    rename#btn: Button text\n    run#btn: Button text\n    save#btn: Button text\n    share#btn: Button text\n    toolbar#btn: Button text\n    upload#btn: Button text\n    wrap#btn: Button text\n    files#title: Section header\n    drop#ui: Instruction for the user\n    run#ui: Button text to evaluate a script (appears prominently on page)\n  tips:\n    a1: Link text\n    dd1: Description of what a button will do\n    dd2: Description of what a button will do\n    dd3: Description of what a button will do\n    dd4: Description of what a button will do\n    p1: Paragraph of body text\n    p2: Label, followed by a list\n    subhead1: Section header\n    title: Section header\n  unsupported:\n    li1: Error text\n    li2: Error text\n    li3: Error text\n    p1: Error text, ending with a label, followed by a list\n    title: Section header\n\npanel:\n  output:\n    github#btn: Link text\n    upgraded#ui: Description when hovering over an image\n  plot:\n    download#btn: Button text\n    expand#btn: Button text\n    next#btn: Button text\n    prev#btn: Button text\n    zoom#btn: Button text\n    description#ui: Paragraph of body text\n  prompt:\n    add#btn: Button text with interpolated number; for example, \"Add 15 seconds\"\n    resume#btn: Button text\n    sigint#btn: Button text; \"SIGINT\" should not be translated\n    upgrade#btn: Button text\n    seconds#ui: Label, followed by a number\n  vars:\n    title: Section header\n\npromos:\n  instructor:\n    p1: Paragraph of body text\n    p2: Call to action, paragraph of body text\n  prompt:\n    btn1: Call to action, button text\n    p1: Paragraph of body text\n    p2: Label, followed by a call to action button\n    title: Section header\n  welcome:\n    title: Section header (shown prominently on page)\n    p1: Paragraph of body text\n    p2: Paragraph of body text describing what you can do on the site\n    p3: Paragraph of body text describing what you can do on the site\n    p4: Paragraph of body text with a call to action and also describing what you can do on the site\n    start#btn: Call to action, button text\n  sync:\n    p1: Call to action, paragraph of body text\n    p2: Call to action, paragraph of body text\n  bucketcreate:\n    p1: Call to action, paragraph of body text\n    p2: Call to action, paragraph of body text\n  sharing:\n    subhead1: Section header\n    p1@2: Call to action, paragraph of body text\n  login:\n    p1: Rhetorical question asked of the user\n    p2: Call to action, paragraph of body text\n  bucketsbacklink:\n    p1: Call to action, paragraph of body text\n\nmodals:\n  email:\n    label1: Call to action, form field label\n    p1: Paragraph of body text\n  password:\n    subhead1: Rhetorical question asked of the user\n    p1: Call to action, paragraph of body text\n  changepwd:\n    title: Button text\n    p1: Call to action, paragraph of body text\n    submit#btn: Button text\n  welcome_back:\n    title: Title of dialog box\n    p1: Paragraph of body text\n    btn1: Button text\n  bucketinfo:\n    p1: Call to action, paragraph of body text\n    label1: Label, followed by a date and time\n    label2: Label, followed by a link\n    btn1: Button text\n    btn1p: Button text\n    btn2: Button text\n  createbucket:\n    title: Title of dialog box\n    titlep: Title of dialog box\n    p1@2: Paragraph of body text\n    subhead1@2: Call to action, section header\n    subhead1p: Call to action, section header\n    p2: Call to action, paragraph of body text\n    subhead2: Section header, followed by a list\n    subhead3: Section header, followed by a list\n    subhead3p: Section header, followed by a list\n    subhead4: Call to action, section header\n    p3: Paragraph of body text\n    label1: Section header, followed by a list\n    subhead5: Call to action, section header\n    p5: Paragraph of body text\n    p4: Paragraph of body text\n    p4p: Paragraph of body text\n    submit#btn: Button text\n    submitp#btn: Button text\n  upgrade:\n    title: Title of dialog box\n    btn1: Button text\n    btn2: Button text; \"Patreon\" should not be translated\n    p1: Paragraph of body text\n    btn3: Button text; \"Patreon\" should not be translated\n    p2: Rhetorical question asked of the user\n    btn4: Button text; \"Patreon\" should not be translated\n    p3: Paragraph of body text; \"Patreon\" should not be translated\n  history:\n    title: Title of dialog box\n    subtitle1: Section title\n    p1: Paragraph of body text\n    btn1: Button text\n    subtitle2: Section title\n    p2: Paragraph of body text\n    btn2: Button text\n  patience:\n    p1: Paragraph of body text\n    p2: Paragraph of body text\n\njavascript:\n  console:\n    exited#alert: Label, followed by an error code\n    payload#alert: Paragraph of body text\n    plotwindow#alert: Paragraph of body text with call to action\n    readonly#alert@2: Error message\n    reconnecting#alert: Paragraph of body text\n    refresh#alert: Paragraph of body text\n    reconnect#btn: Button text\n    pingtime#label: Label, followed by a number\n    seeurl#label: Label, followed by a URL link to documentation\n  newfile:\n    label: Label for form field\n    helloworld: Sample string used in programming\n  print:\n    p1: Label, followed by a name or email\n    p2: Body text\n  rename:\n    alert: Body text\n    label: Label for form field\n  students:\n    course#label: Label, followed by a course code (like \"Math_101\")\n    name#label: Label, followed by a name or email\n    enroll:\n      p1: Paragraph of body text\n      p2: Paragraph of body text\n      p3: Press Cancel if you don't know what any of this means.\n    reenroll:\n      p1: Label for form field\n      p2: Paragraph of body text\n      p3: Label, followed by a course code (like \"Math_101\")\n    unenroll:\n      p1: Rhetorical question asked of the user\n  buckets:\n    error1: Paragraph of body text\n    error2: Paragraph of body text\n    label1: Label for form field\n  togglesharing:\n    p1: Paragraph of body text, followed by a course code (like \"Math_101\")\n    p2: Call to action, followed by a computer command\n\ncommon:\n  email#ph: Placeholder text for form field\n  submit#btn: Button text\n  caution#label: Label, followed by another sentence\n  password#ph: Placeholder text for form field\n  https#btn: Button text\n  https#p: Paragraph of body text\n  loading#ui: Filler text when a component is not ready yet\n  dismiss#btn: Button text\n  loginrequired: Error message\n  bucket: Name for a collection of files shared with other users; do not translate in Latin-script languages\n  project: Name for a workspace with files for editing and collaboration\n\nconstants:\n  shortlink_prefix: DO NOT TRANSLATE\n"
  },
  {
    "path": "front/package.json",
    "content": "{\n  \"name\": \"@oo/front\",\n  \"version\": \"0.0.0\",\n  \"description\": \"Front server for Octave Online, 2019 refresh\",\n  \"main\": \"dist/app.js\",\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"watch\": \"tsc --watch\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"author\": \"Shane F. Carr\",\n  \"license\": \"AGPL-3.0\",\n  \"engines\": {\n    \"node\": \"18.x\"\n  },\n  \"dependencies\": {\n    \"@oo/shared\": \"file:../shared\",\n    \"async\": \"^3.1.0\",\n    \"base-x\": \"^3.0.8\",\n    \"bcrypt\": \"^5.0.0\",\n    \"body-parser\": \"^1.19.0\",\n    \"compression\": \"^1.7.4\",\n    \"connect-mongo\": \"^4.6.0\",\n    \"easy-no-password\": \"^1.3.0\",\n    \"ejs\": \"^3.0.1\",\n    \"express\": \"^4.17.1\",\n    \"express-session\": \"^1.17.0\",\n    \"flat\": \"^5.0.2\",\n    \"hjson\": \"^3.2.2\",\n    \"i18next\": \"^19.5.4\",\n    \"i18next-fs-backend\": \"^1.0.7\",\n    \"i18next-http-middleware\": \"^3.0.2\",\n    \"mongoose\": \"^6.8.2\",\n    \"npm\": \"^8.19.3\",\n    \"ot\": \"0.0.15\",\n    \"passport\": \"^0.6\",\n    \"passport-google-oauth\": \"^2.0.0\",\n    \"passport-local\": \"^1.0.0\",\n    \"postmark\": \"^2.7.0\",\n    \"pseudo-localization\": \"^2.1.1\",\n    \"recaptcha2\": \"^1.3.3\",\n    \"serve-static\": \"^1.14.1\",\n    \"simple-oauth2\": \"^3.4.0\",\n    \"socket.io\": \"^4\",\n    \"socketio-file-upload\": \"^0.7.0\",\n    \"socketio-wildcard\": \"^2.0.0\",\n    \"uuid\": \"^3.3.3\"\n  },\n  \"devDependencies\": {\n    \"@types/async\": \"^3.0.3\",\n    \"@types/bcrypt\": \"^3.0.0\",\n    \"@types/body-parser\": \"^1.17.1\",\n    \"@types/compression\": \"^1.0.1\",\n    \"@types/express\": \"^4.17.15\",\n    \"@types/express-session\": \"^1.17.5\",\n    \"@types/flat\": \"^5.0.1\",\n    \"@types/hjson\": \"^2.4.3\",\n    \"@types/passport\": \"^1.0.2\",\n    \"@types/passport-google-oauth\": \"^1.0.41\",\n    \"@types/passport-local\": \"^1.0.33\",\n    \"@types/recaptcha2\": \"^1.3.0\",\n    \"@types/serve-static\": \"^1.13.3\",\n    \"@types/simple-oauth2\": \"^2.5.2\",\n    \"@types/uuid\": \"^3.4.6\",\n    \"typescript\": \"^4.1.3\"\n  }\n}\n"
  },
  {
    "path": "front/src/app.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport Fs = require(\"fs\");\nimport Path = require(\"path\");\n\nimport { config, logger } from \"./shared_wrap\";\nimport * as Mongo from \"./mongo\";\nimport * as Passport from \"./passport_setup\";\nimport * as Middleware from \"./session_middleware\";\nimport * as ExpressApp from \"./express_setup\";\nimport * as SocketIoApp from \"./socketio\";\n\nconst log = logger(\"app\");\n\nasync function main() {\n\tlet buildData: ExpressApp.BuildData = {};\n\ttry {\n\t\tbuildData = require(Path.join(__dirname, \"..\", \"..\", config.front.static_path, \"build_data.json\"));\n\t\tlog.trace(\"Loaded buildData:\", buildData);\n\t} catch(err: any) {\n\t\tlog.warn(\"Failed to load buildData; will serve development resources:\", err?.message);\n\t}\n\n\ttry {\n\t\tconst setupFn = require(\"../../entrypoint/front_setup\");\n\t\tawait setupFn(buildData);\n\t\tlog.trace(\"Successfully ran front_setup. New buildData:\", buildData);\n\t} catch (err: any) {\n\t\tif (/Cannot find module/.test(err?.message)) {\n\t\t\tlog.warn(\"Tip: create entrypoint/front_setup.js to run code at startup:\", err?.message);\n\t\t} else {\n\t\t\tlog.error(\"front_setup error:\", err);\n\t\t}\n\t}\n\tif (!buildData.locales_path) {\n\t\tbuildData.locales_path = Path.join(__dirname, \"..\", \"..\", config.front.locales_path);\n\t}\n\tif (!buildData.locales) {\n\t\tbuildData.locales = config.front.locales;\n\t}\n\n\tbuildData.privacy_html = await Fs.promises.readFile(Path.join(__dirname, \"..\", \"..\", config.front.static_path, \"privacy.txt\"), { encoding: \"utf-8\" });\n\n\ttry {\n\t\tlog.trace(\"Connecting to Mongo...\");\n\t\tawait Mongo.connect();\n\t\tlog.info(\"Connected to Mongo\");\n\t} catch(err) {\n\t\tlog.warn(\"Could not connect to Mongo:\", err);\n\t}\n\n\tPassport.init();\n\tMiddleware.init();\n\tExpressApp.init(buildData);\n\tSocketIoApp.init();\n}\n\nmain().catch((err) => {\n\tlog.error(err);\n});\n"
  },
  {
    "path": "front/src/back_server_handler.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport { EventEmitter } from \"events\";\n\nimport { config, newRedisMessenger, newRedisQueue, IRedisQueue, logger, ILogger } from \"./shared_wrap\";\nimport { octaveHelper } from \"./octave_session_helper\";\n\nconst outputClient = newRedisMessenger();\noutputClient.subscribeToOutput();\nconst destroyUClient = newRedisMessenger();\ndestroyUClient.subscribeToDestroyU();\nconst expireClient = newRedisMessenger();\nexpireClient.subscribeToExpired();\nconst redisMessenger = newRedisMessenger();\n\noutputClient.setMaxListeners(100);\ndestroyUClient.setMaxListeners(100);\nexpireClient.setMaxListeners(100);\n\nexport class BackServerHandler extends EventEmitter {\n\tpublic sessCode: string|null = null;\n\tprivate touchInterval: any;\n\tprivate redisQueue: IRedisQueue|null = null;\n\tprivate _log: ILogger;\n\n\tconstructor() {\n\t\tsuper();\n\t\tthis._log = logger(\"back-handler:uninitialized\");\n\t}\n\n\tpublic setSessCode(sessCode: string|null) {\n\t\tthis.sessCode = sessCode;\n\t\tif (this.redisQueue) {\n\t\t\tthis.redisQueue.reset();\n\t\t\tthis.redisQueue = null;\n\t\t}\n\t\tif (sessCode) {\n\t\t\tthis.redisQueue = newRedisQueue(sessCode);\n\t\t\tthis.redisQueue.on(\"message\", (name, content) => {\n\t\t\t\tthis.emit(\"data\", name, content);\n\t\t\t});\n\t\t\tthis._log = logger(\"back-handler:\" + sessCode);\n\t\t} else {\n\t\t\tthis._log = logger(\"back-handler:uninitialized\");\n\t\t}\n\t\tthis.touch();\n\t}\n\n\tpublic dataD(name: string, data: any) {\n\t\tif (this.sessCode === null) {\n\t\t\tthis.emit(\"data\", \"alert\", \"ERROR: Please reconnect! Your action was not performed: \" + name);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tredisMessenger.input(this.sessCode, name, data);\n\t\t} catch(err) {\n\t\t\tthis._log.error(\"ATTACHMENT ERROR\", err);\n\t\t}\n\t}\n\n\tpublic subscribe() {\n\t\t// Prevent duplicate listeners\n\t\tthis.unsubscribe();\n\n\t\t// Create listeners to Redis\n\t\toutputClient.on(\"message\", this.pMessageListener);\n\t\tdestroyUClient.on(\"destroy-u\", this.destroyUListener);\n\t\texpireClient.on(\"expired\", this.expireListener);\n\t\tthis.touch();\n\t\tthis.touchInterval = setInterval(this.touch, config.redis.expire.interval);\n\t}\n\n\tpublic unsubscribe() {\n\t\toutputClient.removeListener(\"message\", this.pMessageListener);\n\t\tdestroyUClient.removeListener(\"destroy-u\", this.destroyUListener);\n\t\texpireClient.removeListener(\"expired\", this.expireListener);\n\t\tclearInterval(this.touchInterval);\n\t}\n\n\tprivate touch = () => {\n\t\tif (!this.depend([\"sessCode\"])) return;\n\t\tredisMessenger.touchInput(this.sessCode, false);\n\t};\n\n\tprivate depend(props: string[], log=false) {\n\t\tfor (let i = 0; i < props.length; i++){\n\t\t\tif (!(this as any)[props[i]]) {\n\t\t\t\tif (log) this._log.warn(\"UNMET DEPENDENCY\", props[i], arguments.callee.caller);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\tprivate pMessageListener = (sessCode: string, name: string, getData: any) => {\n\t\tif (!this.depend([\"sessCode\"])) return;\n\n\t\t// Check if this message is for us.\n\t\tif (sessCode !== this.sessCode) return;\n\n\t\t// Everything from here down will be run only if this instance is associated with the sessCode of the message.\n\t\tthis.redisQueue!.enqueueMessage(name, getData);\n\t};\n\n\tprivate destroyUListener = (sessCode: string, message: any) => {\n\t\tif (!this.depend([\"sessCode\"])) return;\n\t\tif (sessCode !== this.sessCode) return;\n\t\tthis.emit(\"destroy-u\", message);\n\t};\n\n\tprivate expireListener = (sessCode: string, channel: string) => {\n\t\tif (!this.depend([\"sessCode\"])) return;\n\t\tif (sessCode !== this.sessCode) return;\n\t\t// If the session becomes expired, trigger a destroy event\n\t\t// both upstream and downstream.\n\t\tthis._log.trace(\"Detected Expired:\", channel);\n\t\toctaveHelper.sendDestroyD(this.sessCode, \"Octave Session Expired\");\n\t\tthis.emit(\"destroy-u\", \"Octave Session Expired\");\n\t};\n}\n"
  },
  {
    "path": "front/src/bucket_model.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Mongoose Bucket: stores metadata about a bucket or project.\n\nimport Mongoose = require(\"mongoose\");\nimport { IUser } from \"./user_model\";\nimport { logger, ILogger } from \"./shared_wrap\";\n\ntype Err = Error | null;\n\n// Initialize the schema\nconst bucketSchema = new Mongoose.Schema({\n\t/// bucket_id is the public ID for the bucket. Generated in socket_connect.ts (formerly in octave-session.js)\n\tbucket_id: String,\n\n\t/// user_id is the owner of the bucket.\n\tuser_id: Mongoose.Schema.Types.ObjectId,\n\n\t/// main is the entrypoint script for the bucket, if the bucket has one.\n\tmain: String,\n\n\t/// butype is the bucket type:\n\t/// - \"readonly\" = public, read-only (bucket~)\n\t/// - \"editable\" = private, read-write (project~)\n\t/// - \"collab\" = public, read-write, shared workspace (project~)\n\tbutype: {\n\t\ttype: String,\n\t\tenum: [\"readonly\", \"editable\", \"collab\"],\n\t\tdefault: \"readonly\",\n\t},\n\n\t/// base_bucket_id is the bucket from which this bucket was cloned, if applicable\n\tbase_bucket_id: String,\n\n\t/// shortlink is a String accepted by the shortlink service, optimized for ease of manual entry. Can be custom, but the default is generated in client/app/js/bucket.js\n\tshortlink: String,\n\n\tlast_activity: {\n\t\ttype: Date,\n\t\tdefault: Date.now\n\t},\n});\n\nbucketSchema.index({\n\tbucket_id: 1\n}, {\n\tunique: true\n});\n\nbucketSchema.index({\n\tshortlink: 1\n}, {\n\tunique: true\n});\n\n// Workaround to make TypeScript apply signatures to the method definitions\ninterface IBucketMethods {\n\tcheckAccessPermissions(user: IUser|null): boolean;\n\tisOwnedBy(user: IUser|null): boolean;\n\ttouchLastActivity(next: (err: Err) => void): void;\n\tremoveRepo(next: (err: Err) => void): void;\n\tisValidAction(this: IBucket, action: string): boolean;\n\tlogf(): ILogger;\n}\n\nexport interface IBucket extends Mongoose.Document, IBucketMethods {\n\t_id: Mongoose.Types.ObjectId;\n\tbucket_id: string;\n\tuser_id: Mongoose.Types.ObjectId;\n\tmain?: string;\n\tbutype: string;\n\tbase_bucket_id?: string|null;\n\tshortlink?: string;\n\tlast_activity: Date;\n\n\t// Virtuals\n\tcreatedTime: Date;\n\tconsoleText: string;\n\tdisplayName: string;\n\tbaseModel: IBucket;\n\townerModel: IUser;\n}\n\n// Define the methods in a class to help TypeScript\nclass BucketMethods implements IBucketMethods {\n\tisValidAction(this: IBucket, action: string): boolean {\n\t\tif (action === \"bucket\") {\n\t\t\treturn this.butype === \"readonly\";\n\t\t} else if (action === \"project\") {\n\t\t\treturn this.butype === \"editable\" || this.butype === \"collab\";\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tcheckAccessPermissions(this: IBucket, user: IUser|null): boolean {\n\t\tif (this.butype === \"editable\" && (!user || !user._id.equals(this.user_id))) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tisOwnedBy(this: IBucket, user: IUser|null): boolean {\n\t\tif (!user || !user._id.equals(this.user_id)) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\ttouchLastActivity(this: IBucket, next: (err: Err) => void): void {\n\t\tthis.logf().trace(\"Touching last activity\", this.consoleText);\n\t\tthis.last_activity = new Date();\n\t\tthis.save(next);\n\t}\n\n\tremoveRepo(this: IBucket, next: (err: Err) => void): void {\n\t\t// See comment in socket_connect.ts\n\t\tnext(new Error(\"Unimplemented\"));\n\t\t/*\n\t\t(got as Got)(\"http://\" + config.git.hostname + \":\" + config.git.createRepoPort, {\n\t\t\tsearchParams: {\n\t\t\t\ttype: \"buckets\",\n\t\t\t\tname: this.bucket_id,\n\t\t\t\taction: \"delete\",\n\t\t\t},\n\t\t\tretry: 0,\n\t\t}).then((response) => {\n\t\t\tif (response.statusCode === 200) {\n\t\t\t\tnext(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tnext(new Error(response.body));\n\t\t}).catch(next);\n\t\t*/\n\t}\n\n\tlogf(this: IBucket): ILogger {\n\t\treturn logger(\"bucket:\" + this.id.valueOf());\n\t}\n}\n\n// Copy the methods into bucketSchema\nbucketSchema.methods.isValidAction = BucketMethods.prototype.isValidAction;\nbucketSchema.methods.checkAccessPermissions = BucketMethods.prototype.checkAccessPermissions;\nbucketSchema.methods.isOwnedBy = BucketMethods.prototype.isOwnedBy;\nbucketSchema.methods.touchLastActivity = BucketMethods.prototype.touchLastActivity;\nbucketSchema.methods.removeRepo = BucketMethods.prototype.removeRepo;\nbucketSchema.methods.logf = BucketMethods.prototype.logf;\n\n// Virtuals\nbucketSchema.virtual(\"createdTime\").get(function (this: IBucket) {\n\treturn this._id.getTimestamp();\n});\nbucketSchema.virtual(\"consoleText\").get(function(this: IBucket) {\n\treturn `[Bucket ${this.bucket_id}; ${this.shortlink}; ${this.butype}${this.base_bucket_id?`; Base ${this.base_bucket_id}`:``}]`;\n});\nbucketSchema.virtual(\"displayName\").get(function(this: IBucket) {\n\t// TODO: Localize these strings\n\tif (this.butype === \"editable\" || this.butype === \"collab\") {\n\t\treturn `Project ${this.bucket_id.slice(0, 4)}`;\n\t} else {\n\t\treturn `Bucket ${this.bucket_id.slice(0, 4)}`;\n\t}\n});\nbucketSchema.virtual(\"baseModel\", {\n\tref: \"Bucket\",\n\tlocalField: \"base_bucket_id\",\n\tforeignField: \"bucket_id\",\n\tjustOne: true,\n});\nbucketSchema.virtual(\"ownerModel\", {\n\tref: \"User\",\n\tlocalField: \"user_id\",\n\tforeignField: \"_id\",\n\tjustOne: true,\n});\n\nbucketSchema.set(\"toJSON\", {\n\tvirtuals: true\n});\n\nexport const Bucket = Mongoose.model<IBucket>(\"Bucket\", bucketSchema);\n\nBucket.on(\"index\", err => {\n\tif (err) logger(\"bucket-index\").error(err);\n\telse logger(\"bucket-index\").info(\"Init Success\");\n});\n"
  },
  {
    "path": "front/src/email.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport Postmark = require(\"postmark\");\n\nimport { config, logger } from \"./shared_wrap\";\n\nconst log = logger(\"email\");\n\nlet postmarkClient: Postmark.ServerClient|null = null;\n\nif (config.email.provider === \"mailgun\") {\n\tlog.warn(\"Mailgun is no longer supported. Feel free to open a PR to add support. See #43\");\n} else {\n\tpostmarkClient = new Postmark.ServerClient(config.postmark.serverToken);\n}\n\nexport async function sendLoginToken(email: string, token: string, url: string) {\n\tif (postmarkClient) {\n\t\tconst response = await postmarkClient.sendEmailWithTemplate({\n\t\t\tTemplateAlias: config.postmark.templateAlias,\n\t\t\tFrom: config.email.from,\n\t\t\tTo: email,\n\t\t\tTemplateModel: {\n\t\t\t\tproduct_name: config.email.productName,\n\t\t\t\ttoken_string: token,\n\t\t\t\taction_url: url,\n\t\t\t\tsupport_url: config.email.supportUrl\n\t\t\t}\n\t\t});\n\t\tlog.trace(response);\n\t} else {\n\t\tlog.error(\"Unable to send email: please configure an email client for Octave Online\");\n\t}\n}\n\nexport async function sendZipArchive(email: string, desc: string, url: string) {\n\tif (postmarkClient) {\n\t\tconst response = await postmarkClient.sendEmailWithTemplate({\n\t\t\tTemplateAlias: config.postmark.onDemandSnapshots.template,\n\t\t\tFrom: config.email.from,\n\t\t\tTo: email,\n\t\t\tTemplateModel: {\n\t\t\t\tproduct_name: config.email.productName,\n\t\t\t\tarchive_desc: desc,\n\t\t\t\taction_url: url,\n\t\t\t\tsupport_url: config.email.supportUrl\n\t\t\t},\n  \t\tMessageStream: config.postmark.onDemandSnapshots.stream,\n\t\t});\n\t\tlog.trace(response);\n\t} else {\n\t\tlog.error(\"Unable to send email: please configure an email client for Octave Online\");\n\t}\n}\n"
  },
  {
    "path": "front/src/express_setup.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport Http = require(\"http\");\nimport Path = require(\"path\");\n\nimport BodyParser = require(\"body-parser\");\nimport Compression = require(\"compression\");\nimport Express = require(\"express\");\nimport Flatten = require(\"flat\");\nimport I18next = require(\"i18next\");\nimport I18nextFsBackend = require(\"i18next-fs-backend\");\nimport I18nextMiddleware = require(\"i18next-http-middleware\");\nimport PseudoLocalization = require(\"pseudo-localization\");\nimport Passport = require(\"passport\");\nimport ReCAPTCHA = require(\"recaptcha2\");\nimport ServeStatic = require(\"serve-static\");\nimport Siofu = require(\"socketio-file-upload\");\n\nimport * as SessionMiddleware from \"./session_middleware\";\nimport * as Patreon from \"./patreon\";\nimport { config, logger } from \"./shared_wrap\";\n\nconst log = logger(\"express-setup\");\n\nconst recaptcha = new ReCAPTCHA({\n\tsiteKey: config.recaptcha.siteKey,\n\tsecretKey: config.recaptcha.secretKey,\n});\n\nconst PORT = process.env.PORT || config.front.listen_port;\nconst STATIC_PATH_1 = Path.join(__dirname, \"..\", \"..\", config.front.static_path);\nconst STATIC_PATH_2 = Path.join(__dirname, \"..\", \"static\");\nconst STATIC_OPTS: ServeStatic.ServeStaticOptions = {\n\tmaxAge: \"7d\",\n\tsetHeaders: (res, path, stat) => {\n\t\tswitch (Path.extname(path)) {\n\t\t\tcase \".html\":\n\t\t\t\tres.setHeader(\"Cache-Control\", \"public, max-age=0\");\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n};\n\nexport let app: Express.Application;\nexport let server: Http.Server;\n\nexport interface BuildData {\n\tlocales_path?: string;\n\tlocales?: string[];\n\tprivacy_html?: string;\n}\n\nfunction getT(req: Express.Request) {\n\tlet t = (req as any).t as I18next.TFunction;\n\tconst requestedLanguage = (req as any).language as string;\n\tif (requestedLanguage === \"en-XA\") {\n\t\tlet old_t = t;\n\t\tt = function(key: string, options: any) {\n\t\t\treturn PseudoLocalization.localize(old_t(key, options));\n\t\t};\n\t}\n\treturn t;\n}\n\nfunction getCurrentLanguage(req: Express.Request, buildData: BuildData) {\n\t// Figure out the current language from the resource bundles\n\tconst resolvedLanguages = (req as any).languages as string[];\n\tlet currentLanguage;\n\tfor (let language of resolvedLanguages) {\n\t\tif (buildData.locales!.indexOf(language) !== -1) {\n\t\t\tcurrentLanguage = language;\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn currentLanguage;\n}\n\nfunction getJSTranslations(req: Express.Request, t: I18next.TFunction) {\n\t// Get the JavaScript translations\n\tlet oo_translations: {[key: string]: string} = {};\n\tlet jsKeys: {[key: string]: unknown} = Flatten((req as any).i18n.getDataByLanguage(\"en\").translation.javascript);\n\tfor (let key of Object.keys(jsKeys)) {\n\t\tlet key_string = key as string;\n\t\too_translations[key_string] = t(`javascript.${key_string}`, { config });\n\t}\n\tlet commonKeys: {[key: string]: unknown} = Flatten((req as any).i18n.getDataByLanguage(\"en\").translation.common);\n\tfor (let key of Object.keys(commonKeys)) {\n\t\tlet key_string = key as string;\n\t\too_translations[\"common.\" + key_string] = t(`common.${key_string}`, { config });\n\t}\n\tlet constantsKeys: {[key: string]: unknown} = Flatten((req as any).i18n.getDataByLanguage(\"en\").translation.constants);\n\tfor (let key of Object.keys(constantsKeys)) {\n\t\tlet key_string = key as string;\n\t\too_translations[\"constants.\" + key_string] = t(`constants.${key_string}`, { config });\n\t}\n\treturn oo_translations;\n}\n\nexport function init(buildData: BuildData){\n\tlog.info(\"Serving static files from:\", STATIC_PATH_1);\n\tlog.info(\"Loading locales from:\", buildData.locales_path);\n\n\t// Work around bug in i18next TypeScript definition?\n\tconst i18next = (I18next as unknown as I18next.i18n);\n\ti18next\n\t\t.use(I18nextMiddleware.LanguageDetector)\n\t\t.use(I18nextFsBackend)\n\t\t.init({\n\t\t\tbackend: {\n\t\t\t\tloadPath: buildData.locales_path!,\n\t\t\t},\n\t\t\tfallbackLng: \"en\",\n\t\t\tpreload: buildData.locales!,\n\t\t\tsaveMissing: true,\n\t\t\tmissingKeyHandler: function(lng, ns, key, fallbackValue) {\n\t\t\t\tlog.error(\"i18next missing key:\", lng, ns, key, fallbackValue);\n\t\t\t},\n\t\t\tmissingInterpolationHandler: function(text, value) {\n\t\t\t\tlog.error(\"i18next missing interpolation:\", text, value);\n\t\t\t}\n\t\t}, (err) => {\n\t\t\tif (err) {\n\t\t\t\tlog.error(\"i18next failed to initialize:\", err);\n\t\t\t} else {\n\t\t\t\tlog.info(\"i18next initialized with languages:\", Object.keys(i18next.store.data));\n\t\t\t}\n\t\t});\n\n\tapp = Express()\n\t\t.use(function(req, res, next) {\n\t\t\t// Redirect HTTP to HTTPS\n\t\t\tif (!config.front.require_https) return next();\n\t\t\tif (req.headers[\"x-forwarded-proto\"] === \"https\") return next();\n\t\t\tif (req.protocol === \"https\") return next();\n\t\t\tres.redirect(301, `https://${req.hostname}${req.url}`);\n\t\t})\n\t\t.use(Compression())\n\t\t.get(/\\/[a-z]+~\\w+$/, function(req, res, next) {\n\t\t\t// Internally rewrite the path to index.html\n\t\t\treq.url = \"/index.html\";\n\t\t\tnext(\"route\");\n\t\t})\n\t\t.use(ServeStatic(STATIC_PATH_1, STATIC_OPTS))\n\t\t.use(ServeStatic(STATIC_PATH_2, STATIC_OPTS))\n\t\t.use(SessionMiddleware.middleware)\n\t\t.use(BodyParser.urlencoded({ extended: true }))\n\t\t.use(BodyParser.json({\n\t\t\tverify: function(req, res, buf, encoding) {\n\t\t\t\t// Save the buffer for verification later\n\t\t\t\t(req as any).rawBody = buf;\n\t\t\t}\n\t\t}))\n\t\t.use(I18nextMiddleware.handle(i18next))\n\t\t.use(Passport.initialize())\n\t\t.use(Passport.session())\n\t\t.use(Siofu.router)\n\t\t.set(\"views\", Path.join(__dirname, \"..\", \"src\", \"views\"))\n\t\t.set(\"view engine\", \"ejs\")\n\t\t.get(\"/ping\", function(req, res){\n\t\t\tres.sendStatus(204);\n\t\t})\n\t\t.get([\"/\", \"/index.html\"], function(req, res) {\n\t\t\tres.setHeader(\"Cache-Control\", \"public, max-age=0\");\n\t\t\tconst t = getT(req);\n\t\t\tres.status(200).render(\"index\", {\n\t\t\t\tconfig,\n\t\t\t\tbuildData,\n\t\t\t\tt,\n\t\t\t\too_translations: getJSTranslations(req, t),\n\t\t\t\tcurrentLanguage: getCurrentLanguage(req, buildData),\n\t\t\t});\n\t\t})\n\t\t.post(\"/auth/persona\", Passport.authenticate(\"persona\"), function(req, res){\n\t\t\tres.sendStatus(204);\n\t\t})\n\t\t.get(\"/auth/tok\", Passport.authenticate(\"easy\", {\n\t\t\tsuccessRedirect: \"/\",\n\t\t\tfailureRedirect: \"/auth/failure\"\n\t\t}))\n\t\t.post(\"/auth/tok\", function(req, res, next) {\n\t\t\trecaptcha.validateRequest(req, req.ip).then(function(){\n\t\t\t\t// validated and secure\n\t\t\t\tlog.trace(\"/auth/tok: ReCAPTCHA OK\");\n\t\t\t\tnext();\n\t\t\t}).catch(function(errorCodes){\n\t\t\t\t// invalid\n\t\t\t\t// NOTE: The \"p\" field is a honeypot in /auth/tok.\n\t\t\t\tlog.info(\"/auth/tok: ReCAPTCHA Failure: Query:\", JSON.stringify(req.body), \"Message:\", recaptcha.translateErrors(errorCodes));\n\t\t\t\tres.status(400).render(\"captcha_error\", { config });\n\t\t\t});\n\t\t}, Passport.authenticate(\"easy\"), function(req, res) {\n\t\t\tres.redirect(\"/auth/entry?s=\" + encodeURIComponent(req.body && req.body.s));\n\t\t})\n\t\t.get(\"/auth/entry\", function(req, res) {\n\t\t\tres.status(200).render(\"token_page\", { config, query: req.query });\n\t\t})\n\t\t.post(\"/auth/pwd\", function(req, res, next) {\n\t\t\t// One star to disable ReCAPTCHA; two stars to enable it\n\t\t\t/*/\n\t\t\trecaptcha.validateRequest(req, req.ip).then(function(){\n\t\t\t\t// validated and secure\n\t\t\t\tlog.trace(\"/auth/pwd: ReCAPTCHA OK\");\n\t\t\t\tnext();\n\t\t\t}).catch(function(errorCodes){\n\t\t\t\t// invalid\n\t\t\t\t// NOTE: \"p\" could contain plaintext passwords, so scrub it from the log\n\t\t\t\tif (\"p\" in req.body) req.body.p = \"<redacted>\";\n\t\t\t\tlog.info(\"/auth/pwd: ReCAPTCHA Failure: Query:\", JSON.stringify(req.body), \"Message:\", recaptcha.translateErrors(errorCodes));\n\t\t\t\tres.status(400).render(\"captcha_error\", { config });\n\t\t\t});\n\t\t\t/*/\n\t\t\tnext();\n\t\t\t/**/\n\t\t}, function(req, res, next) {\n\t\t\tPassport.authenticate(\"local\", function(err, user, /* info, status */) {\n\t\t\t\tif (err) return next(err);\n\t\t\t\tif (!user) return res.redirect(\"/auth/incorrect?s=\" + encodeURIComponent(req.body && req.body.s));\n\t\t\t\t// Since we overrode the Passport callback function, we need to manually call res.logIn().\n\t\t\t\treq.logIn(user, {}, function(err) {\n\t\t\t\t\tif (err) return next(err);\n\t\t\t\t\t// Administrative user, first sign-in; generate default fields\n\t\t\t\t\tif (user && !user.parametrized) {\n\t\t\t\t\t\tlog.trace(\"Administrative User\", user.consoleText);\n\t\t\t\t\t\tuser.save().then(() => {\n\t\t\t\t\t\t\tlog.info(\"Administrative User Fields Set\", user.consoleText);\n\t\t\t\t\t\t\tres.redirect(\"/\");\n\t\t\t\t\t\t}).catch(next);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tres.redirect(\"/\");\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t})(req, res, next);\n\t\t})\n\t\t.get(\"/auth/incorrect\", function(req, res) {\n\t\t\tres.status(400).render(\"incorrect_page\", { config, query: req.query });\n\t\t})\n\t\t.get(\"/auth/google\", Passport.authenticate(\"google\", {\n\t\t\tscope: \"profile email\"\n\t\t}))\n\t\t.get(\"/auth/google/callback\", Passport.authenticate(\"google\", {\n\t\t\tsuccessRedirect: \"/\",\n\t\t\tfailureRedirect: \"/auth/failure\"\n\t\t}))\n\t\t.get(\"/auth/patreon\", Patreon.phase1)\n\t\t.get(\"/auth/patreon/callback\", Patreon.phase2)\n\t\t.get(\"/auth/patreon/revoke\", Patreon.revoke)\n\t\t.get(\"/auth/patreon/webhook\", Patreon.webhook)\n\t\t.post(\"/auth/patreon/webhook\", Patreon.webhook)\n\t\t.get(\"/auth/patreon/plans\", function(req, res) {\n\t\t\tres.redirect(config.patreon.login_redirect);\n\t\t})\n\t\t.get(\"/auth/failure\", function(req, res) {\n\t\t\tres.status(400).render(\"login_error\", { config });\n\t\t})\n\t\t.get(\"/logout\", function(req, res, next){\n\t\t\treq.logout((err) => {\n\t\t\t\tif (err) return next(err);\n\t\t\t\tres.redirect(\"/\");\n\t\t\t});\n\t\t})\n\t\t.get(\"/js-default/:id.js\", function(req, res){\n\t\t\tres.setHeader(\"Content-Type\", \"text/javascript\");\n\t\t\tres.end(\"define('\"+req.params.id+\"',function(){return function(){}});\");\n\t\t})\n\t\t.get(\"*\", function(req, res){\n\t\t\tres.sendStatus(404);\n\t\t})\n\t\t.use(function (err: any, req: any, res: any, next: any) {\n\t\t\tlog.error(\"Express Error\", err);\n\t\t\tres.sendStatus(500);\n\t\t});\n\n\tserver = app.listen(PORT);\n\n\tlog.info(\"Initialized Express Server on port:\", PORT);\n\tlog.debug(\"App Settings:\", (app as any).settings);\n\n\tif (app.get(\"view cache\") && config.front.view_cache_clear_interval) {\n\t\tsetInterval(() => {\n\t\t\tlog.trace(\"Clearing EJS Cache:\", Object.keys(require(\"ejs\").cache._data));\n\t\t\trequire(\"ejs\").clearCache();\n\t\t}, config.front.view_cache_clear_interval);\n\t}\n}\n"
  },
  {
    "path": "front/src/flavor_record_model.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Mongoose Flavor Record: logs the amount of time using flavor servers.\n\nimport Mongoose = require(\"mongoose\");\n\n// Initialize the schema\nconst flavorRecordSchema = new Mongoose.Schema({\n\tuser_id: Mongoose.Schema.Types.ObjectId,\n\tsesscode: String,\n\tstart: Date,\n\tcurrent: Date,\n\tflavor: String\n});\n\nexport interface IFlavorRecord extends Mongoose.Document {\n\t_id: Mongoose.Types.ObjectId;\n\tuser_id: Mongoose.Types.ObjectId;\n\tsesscode: string;\n\tstart: Date;\n\tcurrent: Date;\n\tflavor: string;\n}\n\nexport const FlavorRecord = Mongoose.model<IFlavorRecord>(\"FlavorRecord\", flavorRecordSchema);\n"
  },
  {
    "path": "front/src/mongo.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport Mongoose = require(\"mongoose\");\n\nimport { config } from \"./shared_wrap\";\n\nlet client: Mongoose.Mongoose;\n\nexport async function connect(): Promise<Mongoose.Mongoose> {\n    if (!client) {\n        const url = `mongodb://${config.mongo.hostname}:${config.mongo.port}/${config.mongo.db}`;\n        client = await Mongoose.connect(url);\n    }\n    return client;\n}\n"
  },
  {
    "path": "front/src/octave_session_helper.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport Crypto = require(\"crypto\");\nimport { EventEmitter } from \"events\";\n\nimport { newRedisMessenger, redisUtil, logger, ILogger, config2 } from \"./shared_wrap\";\n\ntype Err = Error|null;\n\nexport enum SessionState {\n\tNeeded = \"NEEDED\",\n\tLoading = \"LOADING\",\n\tLive = \"LIVE\"\n}\n\nconst redisMessenger = newRedisMessenger();\n\nclass OctaveSessionHelper extends EventEmitter {\n\tprivate _log: ILogger;\n\n\tconstructor() {\n\t\tsuper();\n\t\tthis._log = logger(\"octave-helper\");\n\t}\n\n\tpublic getNewSessCode(sessCodeGuess: string|null, next: (err: Err, sessCode: string, state: SessionState) => void){\n\t\tlet sessCode = sessCodeGuess;\n\n\t\t// Check for proper sessCode format (possible attack vector)\n\t\tif (!redisUtil.isValidSessCode(sessCode)) sessCode = null;\n\n\t\tif (!sessCode) {\n\t\t\t// Case 1: No sessCode given\n\t\t\tthis.makeSessCode((err,sessCode)=>{\n\t\t\t\tnext(err, sessCode, SessionState.Needed);\n\t\t\t});\n\n\t\t} else {\n\t\t\tthis.isValid(sessCode, (state)=> {\n\t\t\t\tif (state === SessionState.Needed) {\n\t\t\t\t\t// Case 2: Invalid sessCode given\n\t\t\t\t\tthis.makeSessCode((err,sessCode)=>{\n\t\t\t\t\t\tnext(err, sessCode, SessionState.Needed);\n\t\t\t\t\t});\n\n\t\t\t\t} else {\n\t\t\t\t\t// Case 3: Valid sessCode given\n\t\t\t\t\tnext(null, sessCode!, state);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\tpublic askForOctave(sessCode: string, content: any, next: (err: Err) => void) {\n\t\tconst millisecondBoost = content.user ? config2.tier(content.user.tier)[\"sessionManager.queueBoostTime\"] : 0;\n\t\tthis._log.trace(\"Millisecond boost:\", millisecondBoost, content.user ? content.user.consoleText : \"(no user)\");\n\t\t/*\n\t\tif (content.flavor) {\n\t\t\tredisMessenger.putSessCodeFlavor(sessCode, millisecondBoost, content.flavor, content);\n\t\t\t// TODO: Move this call somewhere it could be configurable.\n\t\t\track.createFlavorServer(content.flavor, (err: Err) => {\n\t\t\t\tif (err) return this._log.error(\"RACKSPACE ERROR\", err);\n\t\t\t\tthis._log.trace(\"Spinning up new server with flavor\", content.flavor);\n\t\t\t});\n\t\t} else {\n\t\t*/\n\t\tredisMessenger.putSessCode(sessCode, millisecondBoost, content);\n\t\t/*\n\t\t}\n\t\t*/\n\t\t// TODO: Should we do something more interesting for the callback?\n\t\tprocess.nextTick(() => {\n\t\t\tnext(null);\n\t\t});\n\t}\n\n\tpublic sendDestroyD(sessCode: string, message: string) {\n\t\tthis._log.trace(\"Sending Destroy-D\", message, sessCode);\n\t\tredisMessenger.destroyD(sessCode, message);\n\t}\n\n\tprivate makeSessCode(next: (err: Err, sessCode: string) => void) {\n\t\tCrypto.pseudoRandomBytes(12, (err, buf) => {\n\t\t\tif (err) {\n\t\t\t\treturn next(err, \"zzz\");\n\t\t\t}\n\n\t\t\tconst sessCode = buf.toString(\"hex\");\n\t\t\tnext(null, sessCode);\n\t\t});\n\t}\n\n\tprivate isValid(sessCode: string, next: (valid: SessionState) => void) {\n\t\tredisMessenger.isValid(sessCode, (err: Err, valid: string|null) => {\n\t\t\tif (err) return this._log.error(\"REDIS ERROR\", err);\n\t\t\tconst state = (valid === null) ? SessionState.Needed\n\t\t\t\t: ((valid === \"false\") ? SessionState.Loading : SessionState.Live);\n\t\t\tnext(state);\n\t\t});\n\t}\n}\n\nexport const octaveHelper = new OctaveSessionHelper();\n"
  },
  {
    "path": "front/src/ot_document.ts",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport { EventEmitter } from \"events\";\n\nimport Ot = require(\"ot\");\nimport Uuid = require(\"uuid\");\n\nimport { config, newRedisMessenger, logger, ILogger } from \"./shared_wrap\";\n\ntype Err = Error|null;\n\n// Make Redis connections for OT\nconst redisMessenger = newRedisMessenger();\nredisMessenger.enableOtScriptsSync();\nconst otListenClient = newRedisMessenger();\notListenClient.subscribeToOtMsgs();\n\n// A single workspace could account for 50 or more listeners, because each document listens on the same connection.\notListenClient.setMaxListeners(200);\n\nexport class OtDocument extends EventEmitter {\n\tprivate id: string;\n\tprivate chgIds: string[] = [];\n\tprivate crsIds: string[] = [];\n\tprivate touchInterval: any;\n\tprivate _log: ILogger;\n\tprivate _mlog: ILogger;\n\tprivate initialContent: string;\n\tpublic opsReceivedCounter = 0;\n\tpublic setContentCounter = 0;\n\n\tconstructor (id: string, safeId: string, initialContent: string) {\n\t\tsuper();\n\t\tthis.id = id;\n\t\tthis._log = logger(\"ot-doc:\" + safeId) as ILogger;\n\t\tthis._mlog = logger(\"ot-doc:\" + safeId + \":minor\") as ILogger;\n\t\tthis.initialContent = initialContent;\n\t\tthis.load();\n\t}\n\n\tpublic logFilename(filename: string) {\n\t\tthis._log.trace(\"Filename:\", filename);\n\t}\n\n\tpublic subscribe() {\n\t\tthis.unsubscribe();\n\n\t\totListenClient.on(\"ot-sub\", this.otMessageListener);\n\t\tthis.touch();\n\t\tthis.touchInterval = setInterval(this.touch, config.ot.document_expire.interval);\n\t}\n\n\tpublic unsubscribe() {\n\t\totListenClient.removeListener(\"ot-sub\", this.otMessageListener);\n\t\tclearInterval(this.touchInterval);\n\t}\n\n\tpublic dataD(name: string, value: any) {\n\t\tif (this.id !== value.docId) return;\n\n\t\tswitch(name){\n\t\t\tcase \"ot.change\":\n\t\t\t\tthis.onOtChange(value);\n\t\t\t\tbreak;\n\n\t\t\tcase \"ot.cursor\":\n\t\t\t\tthis.onOtCursor(value);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tpublic changeDocId(newDocId: string) {\n\t\tif (this.id === newDocId) return;\n\n\t\tconst oldDocId = this.id;\n\t\tthis.id = newDocId;\n\n\t\t// Note that multiple clients may all demand the rename simultaneously.\n\t\t// This shouldn't be a problem, as long as at least one of them succeeds.\n\t\tredisMessenger.changeOtDocId(oldDocId, newDocId);\n\t}\n\n\tpublic destroy() {\n\t\t// Same note as above about (many clients performing this simultaneously)\n\t\tredisMessenger.destroyOtDoc(this.id);\n\t}\n\n\tprivate load() {\n\t\tredisMessenger.loadOtDoc(this.id, (err: Err, rev: number, content: string) => {\n\t\t\tif (err) this._log.error(\"REDIS ERROR\", err);\n\t\t\telse {\n\t\t\t\tthis._log.trace(\"Loaded doc: rev\", rev);\n\t\t\t\tif (rev === -1) {\n\t\t\t\t\tthis.setContent(this.initialContent);\n\t\t\t\t\tthis.emit(\"data\", \"ot.doc\", {\n\t\t\t\t\t\tdocId: this.id,\n\t\t\t\t\t\trev: 0,\n\t\t\t\t\t\tcontent: this.initialContent\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tthis.emit(\"data\", \"ot.doc\", {\n\t\t\t\t\t\tdocId: this.id,\n\t\t\t\t\t\trev,\n\t\t\t\t\t\tcontent\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate touch() {\n\t\tredisMessenger.touchOtDoc(this.id);\n\t}\n\n\tprivate otMessageListener = (docId: string, obj: any) => {\n\t\tif (docId !== this.id) return;\n\n\t\tlet i;\n\t\tswitch(obj.type){\n\t\t\tcase \"cursor\":\n\t\t\t\tif (!obj.data) return;\n\t\t\t\ti = this.crsIds.indexOf(obj.data.id);\n\t\t\t\tif (i > -1) this.crsIds.splice(i, 1);\n\t\t\t\telse {\n\t\t\t\t\tthis._mlog.trace(\"Received another user's cursor:\", obj.data.cursor);\n\t\t\t\t\tthis.emit(\"data\", \"ot.cursor\", {\n\t\t\t\t\t\tdocId: this.id,\n\t\t\t\t\t\tcursor: obj.data.cursor\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"operation\":\n\t\t\t\tif (!obj.chgId || !obj.ops) return;\n\t\t\t\ti = this.chgIds.indexOf(obj.chgId);\n\t\t\t\tif (i > -1) {\n\t\t\t\t\tthis._mlog.trace(\"Received ack for ops:\", obj.ops.length);\n\t\t\t\t\tthis.chgIds.splice(i, 1);\n\t\t\t\t\tthis.emit(\"data\", \"ot.ack\", {\n\t\t\t\t\t\tdocId: this.id\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tthis._mlog.trace(\"Received broadcast of ops:\", obj.ops.length);\n\t\t\t\t\tthis.emit(\"data\", \"ot.broadcast\", {\n\t\t\t\t\t\tdocId: this.id,\n\t\t\t\t\t\tops: obj.ops\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t};\n\n\tprivate onOtChange = (obj: any) => {\n\t\tif (!obj\n\t\t\t|| typeof obj.op === \"undefined\"\n\t\t\t|| typeof obj.rev === \"undefined\")\n\t\t\treturn;\n\n\t\tconst op = Ot.TextOperation.fromJSON(obj.op);\n\t\tthis.receiveOperation(obj.rev, op);\n\t};\n\n\tprivate onOtCursor = (obj: any) => {\n\t\tif (!obj || !obj.cursor) return;\n\n\t\tconst crsId = Uuid.v4();\n\t\tthis.crsIds.push(crsId);\n\t\tredisMessenger.otMsg(this.id, {\n\t\t\ttype: \"cursor\",\n\t\t\tdata: {\n\t\t\t\tid: crsId,\n\t\t\t\tcursor: obj.cursor\n\t\t\t}\n\t\t});\n\t};\n\n\tprivate receiveOperation(rev: number, op: Ot.ITextOperation) {\n\t\tconst chgId = Uuid.v4();\n\n\t\tthis._log.trace(\"Applying operation\", rev, chgId);\n\n\t\tconst message = {\n\t\t\ttype: \"operation\",\n\t\t\tdocId: this.id,\n\t\t\tchgId: chgId,\n\t\t\tops: op.toJSON()\n\t\t};\n\n\t\tthis.chgIds.push(chgId);\n\t\tredisMessenger.applyOtOperation(this.id, rev, message);\n\t\tthis.opsReceivedCounter++;\n\t}\n\n\tprivate setContent(content: string) {\n\t\tconst chgId = Uuid.v4();\n\n\t\tthis._log.trace(\"Setting content\", content.length);\n\n\t\tconst message = {\n\t\t\ttype: \"operation\",\n\t\t\tdocId: this.id,\n\t\t\tchgId: chgId\n\t\t};\n\n\t\t// note: don't push chgId to this.chgIds so that the operation reply gets sent to our own client\n\t\tredisMessenger.setOtDocContent(this.id, content, message);\n\t\tthis.setContentCounter++;\n\t}\n}\n"
  },
  {
    "path": "front/src/passport_setup.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport EasyNoPassword = require(\"easy-no-password\");\nimport Mongoose = require(\"mongoose\");\nimport GoogleOAuth = require(\"passport-google-oauth\");\nimport Local = require(\"passport-local\");\nimport Passport = require(\"passport\");\n\nimport * as Utils from \"./utils\";\nimport { config, logger } from \"./shared_wrap\";\nimport { User, HydratedUser } from \"./user_model\";\nimport { sendLoginToken } from \"./email\";\n\ntype Err = Error | null;\n\nconst log = logger(\"passport-setup\");\n\nconst baseUrl = `${config.front.protocol}://${config.front.hostname}:${config.front.port}/`;\nconst googCallbackUrl = baseUrl + \"auth/google/callback\";\n\nasync function findOrCreateUser(email: string, profile: any) {\n\tlet user = await User.findOne({\n\t\temail: email\n\t});\n\n\t// Returning user\n\tif (user) {\n\t\tlog.info(\"Returning User\", user.consoleText);\n\t\treturn user;\n\t}\n\n\t// Make a new user\n\tlog.trace(\"Creating New User\");\n\tuser = new User({\n\t\temail: email,\n\t\tprofile: profile\n\t});\n\tawait user.save();\n\tlog.info(\"New User\", user.consoleText);\n\treturn user;\n}\n\nenum PasswordStatus { UNKNOWN, INCORRECT, VALID }\n\ninterface UserWithPasswordResponse {\n\tuser?: HydratedUser;\n\tstatus: PasswordStatus;\n}\n\nasync function findWithPasswordPromise(email: string, password: string): Promise<UserWithPasswordResponse> {\n\tlet user = await User.findOne({\n\t\temail: email\n\t});\n\n\t// Returning user\n\tif (user) {\n\t\t// Returning user.  Check password\n\t\tlet valid = await user.checkPassword(password);\n\t\tif (valid) {\n\t\t\treturn {\n\t\t\t\tuser,\n\t\t\t\tstatus: PasswordStatus.VALID\n\t\t\t};\n\t\t} else {\n\t\t\treturn {\n\t\t\t\tuser,\n\t\t\t\tstatus: PasswordStatus.INCORRECT\n\t\t\t};\n\t\t}\n\t} else {\n\t\treturn {\n\t\t\tstatus: PasswordStatus.UNKNOWN\n\t\t};\n\t}\n}\n\nfunction findWithPassword(email: string, password: string, done: (err: Err, status?: PasswordStatus, user?: HydratedUser) => void) {\n\tfindWithPasswordPromise(email, password).then((response) => {\n\t\tdone(null, response.status, response.user);\n\t}).catch((err) => {\n\t\tdone(err);\n\t});\n}\n\nconst googleStrategy = new (GoogleOAuth.OAuth2Strategy)({\n\tcallbackURL: googCallbackUrl,\n\tclientID: config.auth.google.oauth_key,\n\tclientSecret: config.auth.google.oauth_secret,\n\tuserProfileURL: \"https://www.googleapis.com/oauth2/v3/userinfo\"\n},\nfunction (accessToken, refreshToken, profile, done) {\n\tconst email = profile.emails?.[0].value;\n\tif (!email) {\n\t\tlog.warn(\"No email returned from Google\", profile);\n\t\treturn done(new Error(\"No email returned from Google\"));\n\t}\n\tlog.info(\"Google Login\", Utils.emailHash(email));\n\tfindOrCreateUser(email, profile._json).then((user) => {\n\t\tdone(null, user);\n\t}, (err) => {\n\t\tdone(err);\n\t});\n});\n\nconst easyStrategy = new (EasyNoPassword.Strategy)({\n\tsecret: config.auth.easy.secret,\n\tmaxTokenAge: config.auth.easy.max_token_age\n},\nfunction (req) {\n\tif (req.body && req.body.s) {\n\t\treturn { stage: 1, username: req.body.s };\n\t} else if (req.query && req.query.u && req.query.t) {\n\t\treturn { stage: 2, username: req.query.u, token: req.query.t };\n\t} else {\n\t\treturn null;\n\t}\n},\nfunction (email, token, done) {\n\tconst url = `${baseUrl}auth/tok?u=${encodeURIComponent(email)}&t=${token}`;\n\tsendLoginToken(email, token, url)\n\t\t.then(() => {\n\t\t\tdone(null);\n\t\t})\n\t\t.catch((err: any) => {\n\t\t\tlog.error(\"Failed sending email:\", email, err);\n\t\t\tdone(err);\n\t\t});\n},\nfunction (email: string, done: (err: Err, user?: unknown, info?: any) => void) {\n\tlog.info(\"Easy Callback\", Utils.emailHash(email));\n\tfindOrCreateUser(email, { method: \"easy\" }).then((user) => {\n\t\tdone(null, user);\n\t}, (err) => {\n\t\tdone(err);\n\t});\n});\n\nconst passwordStrategy = new (Local.Strategy)({\n\tusernameField: \"s\",\n\tpasswordField: \"p\"\n},\nfunction(username, password, done) {\n\tfindWithPassword(username, password, function(err, status, user) {\n\t\tif (err) return done(err);\n\t\tlog.trace(\"Password delay\", config.auth.password.delay);\n\t\tsetTimeout(() => {\n\t\t\tif (status === PasswordStatus.UNKNOWN) {\n\t\t\t\tlog.info(\"Password Callback Unknown User\", status, username);\n\t\t\t\treturn done(null, false);\n\t\t\t} else if (status === PasswordStatus.INCORRECT) {\n\t\t\t\tlog.info(\"Password Callback Incorrect\", status, user!.consoleText);\n\t\t\t\treturn done(null, false);\n\t\t\t} else if (status == PasswordStatus.VALID) {\n\t\t\t\tlog.info(\"Password Callback Success\", status, user!.consoleText);\n\t\t\t\treturn done(null, user);\n\t\t\t}\n\t\t\treturn done(\"Invalid password status: \" + status);\n\t\t}, config.auth.password.delay);\n\t});\n}\n);\n\nexport function init(){\n\tPassport.use(googleStrategy);\n\tPassport.use(easyStrategy);\n\tPassport.use(passwordStrategy);\n\tPassport.serializeUser((user: any, cb) => {\n\t\tcb(null, user?.id);\n\t});\n\tPassport.deserializeUser((id: Mongoose.Types.ObjectId, cb) => {\n\t\tUser.findById(id, cb);\n\t});\n\n\tlog.info(\"Initialized Passport\");\n}\n"
  },
  {
    "path": "front/src/patreon.ts",
    "content": "/*\n * Copyright © 2020, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport Crypto = require(\"crypto\");\n\nimport Async = require(\"async\");\nimport EasyNoPassword = require(\"easy-no-password\");\nimport SimpleOAuth2 = require(\"simple-oauth2\");\n\nimport { config, logger } from \"./shared_wrap\";\nimport { User, HydratedUser } from \"./user_model\";\n\ninterface PatreonInfo {\n\tuser_id: string;\n\tcurrently_entitled_amount_cents: number;\n\tcurrently_entitled_tier: string|null;\n}\n\ninterface PatreonPhase1AsyncAuto {\n\tenp: string;\n\traw_user: HydratedUser|null;\n\tuser: HydratedUser;\n\tresolve: any;\n}\n\ninterface PatreonPhase2AsyncAuto {\n\tenp: boolean;\n\traw_user: HydratedUser|null;\n\tuser: HydratedUser;\n\ttokenObject: any;\n\tpatreonInfo: PatreonInfo;\n\texistingUser: HydratedUser|null;\n\tresolve: any;\n}\n\ninterface PatreonRevokeAsyncAuto {\n\traw_user: HydratedUser|null;\n\tuser: HydratedUser;\n\trevoke: any;\n\tresolve: any;\n}\n\nconst log = logger(\"patreon\");\n\nconst oauth2 = SimpleOAuth2.create({\n\tclient: {\n\t\tid: config.patreon.client_id,\n\t\tsecret: config.patreon.client_secret\n\t},\n\tauth: {\n\t\ttokenHost: \"https://www.patreon.com\",\n\t\ttokenPath: \"/api/oauth2/token\",\n\t\tauthorizePath: \"/oauth2/authorize\"\n\t}\n});\n\nconst scope = \"identity\";\n\n// Use EasyNoPassword to create CSRF tokens for OAuth\nconst enp = EasyNoPassword(config.patreon.state_secret, config.patreon.state_max_token_age);\n\nfunction processMembership(membership: any, user_id: string|null): PatreonInfo {\n\tlog.trace(\"Processing membership:\", user_id, membership);\n\tconst currently_entitled_amount_cents = (membership?.attributes?.currently_entitled_amount_cents) as number ?? 0;\n\tconst tiers = membership?.relationships?.currently_entitled_tiers?.data ?? [];\n\tconst currently_entitled_tier = (tiers.length > 0) ? (tiers[0].id as string) : null;\n\tif (!user_id) {\n\t\tuser_id = membership?.relationships?.user?.data.id as string;\n\t}\n\tif (!user_id) {\n\t\tlog.error(\"Could not find user_id in membership\", JSON.stringify(membership));\n\t\tthrow new Error(\"Could not find Patreon user_id\");\n\t}\n\tif (!currently_entitled_tier && currently_entitled_amount_cents > 0) {\n\t\tlog.warn(\"No tier, but pledge is > 0:\", user_id, JSON.stringify(membership));\n\t}\n\treturn {\n\t\tcurrently_entitled_amount_cents,\n\t\tcurrently_entitled_tier,\n\t\tuser_id\n\t};\n}\n\nexport function phase1(req: any, res: any, next: any) {\n\tAsync.auto<PatreonPhase1AsyncAuto>({\n\t\traw_user: (_next) => {\n\t\t\tconst sess = req.session;\n\t\t\tconst userId = sess && sess.passport && sess.passport.user;\n\n\t\t\tif (userId) User.findById(userId, _next);\n\t\t\telse _next(null, null);\n\t\t},\n\t\tuser: [\"raw_user\", ({raw_user}, _next) => {\n\t\t\tif (!raw_user) {\n\t\t\t\tres.status(400).send(\"<h1>You must be logged in to perform this action</h1>\");\n\t\t\t} else if (raw_user.patreon && raw_user.patreon.user_id && !raw_user.patreon.currently_entitled_tier) {\n\t\t\t\tlog.trace(\"Patreon already linked\", raw_user.consoleText, raw_user.patreon);\n\t\t\t\tres.redirect(config.patreon.login_redirect);\n\t\t\t} else {\n\t\t\t\t_next(null, raw_user);\n\t\t\t}\n\t\t}],\n\t\tenp: [\"user\", ({}, _next) => {\n\t\t\t// Don't perform further work until we determine the user exists\n\t\t\tenp.createToken(req.session.id, _next);\n\t\t}],\n\t\tresolve: [\"enp\", \"user\", ({enp, user}, _next) => {\n\t\t\tlog.trace(\"Patreon phase 1\", user.consoleText, req.session.id, enp);\n\t\t\tconst authorizationUri = oauth2.authorizationCode.authorizeURL({\n\t\t\t\tredirect_uri: config.patreon.redirect_url,\n\t\t\t\tscope,\n\t\t\t\tstate: enp\n\t\t\t});\n\t\t\tres.redirect(authorizationUri);\n\t\t}],\n\t}, (err: any) => {\n\t\tif (err) {\n\t\t\tlog.error(\"PATREON PHASE 1 ERROR\", err, err.options);\n\t\t}\n\t\tnext(err);\n\t});\n}\n\nexport function phase2(req: any, res: any, next: any) {\n\tif (!req.query || !req.query.state || !req.query.code) {\n\t\t// Login failure? Link denied?\n\t\tres.redirect(\"/auth/failure\");\n\t\treturn;\n\t}\n\tAsync.auto<PatreonPhase2AsyncAuto>({\n\t\traw_user: (_next) => {\n\t\t\tconst sess = req.session;\n\t\t\tconst userId = sess && sess.passport && sess.passport.user;\n\n\t\t\tif (userId) User.findById(userId, _next);\n\t\t\telse _next(null, null);\n\t\t},\n\t\tuser: [\"raw_user\", ({raw_user}, _next) => {\n\t\t\tif (!raw_user) {\n\t\t\t\tres.status(400).send(\"<h1>You must be logged in to perform this action</h1>\");\n\t\t\t} else {\n\t\t\t\t_next(null, raw_user);\n\t\t\t}\n\t\t}],\n\t\tenp: [\"user\", ({}, _next) => {\n\t\t\t// Don't perform further work until we determine the user exists\n\t\t\tenp.isValid(req.query.state, req.session.id, _next);\n\t\t}],\n\t\ttokenObject: [\"enp\", ({enp}, _next) => {\n\t\t\tif (!enp) {\n\t\t\t\tlog.warn(\"Patreon callback: invalid state\", req.session.id, JSON.stringify(req.session));\n\t\t\t\tres.redirect(\"/auth/failure\");\n\t\t\t} else {\n\t\t\t\tlog.trace(\"Patreon callback: valid state\");\n\t\t\t\toauth2.authorizationCode.getToken({\n\t\t\t\t\tredirect_uri: config.patreon.redirect_url,\n\t\t\t\t\tcode: req.query.code,\n\t\t\t\t\tscope\n\t\t\t\t}).then((result) => {\n\t\t\t\t\tlog.debug(\"Success getting token!\");\n\t\t\t\t\t_next(null, result);\n\t\t\t\t}).catch((err) => {\n\t\t\t\t\tlog.debug(\"Failure getting token!\", err);\n\t\t\t\t\t_next(err);\n\t\t\t\t});\n\t\t\t}\n\t\t}],\n\t\tpatreonInfo: [\"tokenObject\", ({tokenObject}, _next) => {\n\t\t\tlog.trace(tokenObject);\n\t\t\tconst fullUrl = \"https://www.patreon.com/api/oauth2/v2/identity?\" + new URLSearchParams({\n\t\t\t\t\"include\": \"memberships,memberships.currently_entitled_tiers\",\n\t\t\t\t\"fields[member]\": \"currently_entitled_amount_cents\"\n\t\t\t});\n\t\t\tlog.trace(\"Requesting URL:\", fullUrl);\n\t\t\tfetch(fullUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"Authorization\": \"Bearer \" + tokenObject.access_token\n\t\t\t\t},\n\t\t\t})\n\t\t\t.then((response) => {\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\treturn _next(new Error(\"Not 2xx response\", { cause: response }));\n\t\t\t\t}\n\t\t\t\treturn response.json();\n\t\t\t})\n\t\t\t.then((body) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst user_id = (body.data.id) as string;\n\t\t\t\t\tlog.info(\"Processing Patreon link for user_id:\", user_id);\n\t\t\t\t\tconst memberships = body.data.relationships.memberships.data;\n\t\t\t\t\tconst membership = (memberships.length > 0) ? body.included[0] : null;\n\t\t\t\t\tconst patreonInfo = processMembership(membership, user_id);\n\t\t\t\t\t_next(null, patreonInfo);\n\t\t\t\t} catch(cause) {\n\t\t\t\t\tlet e = Error(\"Invalid identity response\", { cause });\n\t\t\t\t\tlog.error(e);\n\t\t\t\t\t_next(e);\n\t\t\t\t}\n\t\t\t}).catch(_next);\n\t\t}],\n\t\texistingUser: [\"patreonInfo\", ({patreonInfo}, _next) => {\n\t\t\tUser.findOne({ \"patreon.user_id\": patreonInfo.user_id }, _next);\n\t\t}],\n\t\tresolve: [\"user\", \"tokenObject\", \"patreonInfo\", \"existingUser\", ({user, tokenObject, patreonInfo, existingUser}, _next) => {\n\t\t\tlog.info(\"Patreon Login Resolved\", user.consoleText, JSON.stringify(patreonInfo));\n\t\t\tif (existingUser && !existingUser._id.equals(user._id)) {\n\t\t\t\tlog.warn(\"Different existing user. Old user:\", existingUser.consoleText, \"New user:\", user.consoleText);\n\t\t\t\tres.status(400).render(\"patreon_link_error\", {\n\t\t\t\t\tuser_id: patreonInfo.user_id,\n\t\t\t\t\told_email: existingUser.email,\n\t\t\t\t\tnew_email: user.email,\n\t\t\t\t\tconfig\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// All good. Save the Patreon link to MongoDB.\n\t\t\tconst accessToken = oauth2.accessToken.create(tokenObject);\n\t\t\tuser.patreon = { oauth2: accessToken.token, ...patreonInfo };\n\t\t\tuser.save().then(() => {\n\t\t\t\tif (patreonInfo.currently_entitled_tier) {\n\t\t\t\t\tlog.info(\"Linked account already pledged\");\n\t\t\t\t\tres.redirect(\"/\");\n\t\t\t\t} else {\n\t\t\t\t\tlog.info(\"Redirecting to Patreon to pledge\");\n\t\t\t\t\tres.redirect(config.patreon.login_redirect);\n\t\t\t\t}\n\t\t\t}).catch(_next);\n\t\t}]\n\t}, (err: any) => {\n\t\tif (err) {\n\t\t\tlog.error(\"PATREON PHASE 2 ERROR\", err, err.cause, err.options);\n\t\t}\n\t\tnext(err);\n\t});\n}\n\nexport function revoke(req: any, res: any, next: any) {\n\tAsync.auto<PatreonRevokeAsyncAuto>({\n\t\traw_user: (_next) => {\n\t\t\tconst sess = req.session;\n\t\t\tconst userId = sess && sess.passport && sess.passport.user;\n\n\t\t\tif (userId) User.findById(userId, _next);\n\t\t\telse _next(null, null);\n\t\t},\n\t\tuser: [\"raw_user\", ({raw_user}, _next) => {\n\t\t\tif (!raw_user) {\n\t\t\t\tres.status(400).send(\"<h1>You must be logged in to perform this action</h1>\");\n\t\t\t} else if (!raw_user.patreon) {\n\t\t\t\tres.status(400).send(\"<h1>Your Patreon account is not linked</h1>\");\n\t\t\t} else {\n\t\t\t\t_next(null, raw_user);\n\t\t\t}\n\t\t}],\n\t\trevoke: [\"user\", ({user}, _next) => {\n\t\t\tlog.info(\"Patreon Revoke\", user.consoleText);\n\t\t\t// TODO: Figure out how to actually revoke the tokens on demand. They will eventually time out. The following code causes an error on the Patreon response: \"The content-type is not JSON compatible\"\n\t\t\t/*\n\t\t\tconst accessToken = oauth2.accessToken.create(user.patreon.oauth2);\n\t\t\taccessToken.revokeAll().then(() => {\n\t\t\t\t_next(null);\n\t\t\t}).catch(_next);\n\t\t\t*/\n\t\t\t_next(null);\n\t\t}],\n\t\tresolve: [\"user\", \"revoke\", ({user}, _next) => {\n\t\t\tuser.patreon = undefined;\n\t\t\tuser.save().then(() => {\n\t\t\t\tres.redirect(\"/\");\n\t\t\t}).catch(_next);\n\t\t}],\n\t}, (err: any) => {\n\t\tif (err) {\n\t\t\tlog.error(\"PATREON REVOKE ERROR\", err, err.options);\n\t\t}\n\t\tnext(err);\n\t});\n}\n\nexport function webhook(req: any, res: any, next: any) {\n\tconst event = req.headers[\"x-patreon-event\"];\n\tconst data = req.body ? req.body : req.query;\n\n\t// Verify the signature\n\tconst hmac = Crypto.createHmac(\"md5\", config.patreon.webhook_secret);\n\thmac.update(req.rawBody);\n\tconst expectedSignature = hmac.digest(\"hex\");\n\tif (expectedSignature === req.headers[\"x-patreon-signature\"]) {\n\t\tlog.trace(\"Webhook signature matches\");\n\t} else {\n\t\tlog.warn(\"Patreon Webhook: Unexpected Signature\", req.headers[\"x-patreon-signature\"], event, data);\n\t\tres.sendStatus(412);\n\t\treturn;\n\t}\n\n\tlog.info(\"Processing Patreon event:\", event);\n\n\tlet patreonInfo:PatreonInfo;\n\ttry {\n\t\tconst membership = data.data;\n\t\tif (membership && membership.type !== \"member\") {\n\t\t\tlog.error(\"ERROR: Expected webhook data to be the membership\", data);\n\t\t\tres.sendStatus(400);\n\t\t\treturn;\n\t\t}\n\t\tpatreonInfo = processMembership(membership, null);\n\t} catch(e) {\n\t\tlog.error(\"Invalid webhook body:\", JSON.stringify(data));\n\t\treturn next(e);\n\t}\n\n\tUser.findOne({ \"patreon.user_id\": patreonInfo.user_id }).then((user) => {\n\t\tif (!user) {\n\t\t\tlog.info(\"Patreon user does not exist in database:\", JSON.stringify(patreonInfo));\n\t\t\tres.sendStatus(204);\n\t\t\treturn;\n\t\t}\n\t\tlog.info(\"Updating user with Webhook Patreon info\", user.consoleText, JSON.stringify(patreonInfo));\n\t\tObject.assign(user.patreon!, patreonInfo);\n\t\tuser.save().then(() => {\n\t\t\tres.sendStatus(200);\n\t\t}).catch(next);\n\t}).catch(next);\n}\n"
  },
  {
    "path": "front/src/program_model.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Mongoose Program: stores metadata about a program.\n\nimport Mongoose = require(\"mongoose\");\nimport { logger, ILogger } from \"./shared_wrap\";\nimport { IUser } from \"./user_model\";\n\ntype ProgramModel = Mongoose.Model<IProgram, {}, IProgramMethods>;\n\n// Initialize the schema\nconst programSchema = new Mongoose.Schema<IProgram, ProgramModel, IProgramMethods>({\n\tprogram_name: String,\n\n\t// Feature overrides\n\ttier_override: String,\n\tlegal_time_override: Number,\n\tpayload_limit_override: Number,\n\tcountdown_extra_time_override: Number,\n\tcountdown_request_time_override: Number,\n\tads_disabled_override: Boolean,\n});\n\nprogramSchema.index({\n\tprogram_name: 1\n});\n\n// Workaround to make TypeScript apply signatures to the method definitions\ninterface IProgramMethods {\n\tlogf(): ILogger;\n}\n\nexport interface IProgram {\n\t_id: Mongoose.Types.ObjectId;\n\tprogram_name: string;\n\n\t// Feature overrides\n\ttier_override?: string;\n\tlegal_time_override?: number;\n\tpayload_limit_override?: number;\n\tcountdown_extra_time_override?: number;\n\tcountdown_request_time_override?: number;\n\tads_disabled_override?: boolean;\n\n\t// Virtuals\n\tstudents: IUser[] | undefined;\n}\n\n// Virtuals to return results from sub-models\nprogramSchema.virtual(\"students\", {\n\tref: \"User\",\n\tlocalField: \"program_name\",\n\tforeignField: \"program\",\n\tjustOne: false,\n});\n\nprogramSchema.method(\"logf\",\n\tfunction(): ILogger {\n\t\treturn logger(\"program:\" + this.id.valueOf());\n\t}\n);\n\nprogramSchema.set(\"toJSON\", {\n\tvirtuals: true\n});\n\nexport const Program = Mongoose.model<IProgram, ProgramModel>(\"Program\", programSchema);\n\nProgram.on(\"index\", err => {\n\tif (err) logger(\"program-index\").error(err);\n\telse logger(\"program-index\").info(\"Init Success\");\n});\n"
  },
  {
    "path": "front/src/session_middleware.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport MongoStore = require(\"connect-mongo\");\nimport Express = require(\"express\");\nimport ExpressSession = require(\"express-session\");\nimport Mongoose = require(\"mongoose\");\n\nimport { config, logger } from \"./shared_wrap\";\n\nconst log = logger(\"session-middleware\");\n\nexport let middleware: Express.RequestHandler;\nexport let store: ExpressSession.Store;\n\nexport function init() {\n\t// Make the store instance\n\tif (config.mongo.hostname) {\n\t\tlet options = {\n\t\t\tclient: Mongoose.connection.getClient(),\n\t\t};\n\t\t// Can't solve the following TS error:\n\t\t// \"src/session_middleware.ts:39:20 - error TS2589: Type instantiation is excessively deep and possibly infinite\"\n\t\tlet mongoStore = (<any>MongoStore).create(options);\n\t\tstore = mongoStore;\n\t} else {\n\t\tlog.warn(\"mongo disabled; using MemoryStore\");\n\t\tstore = new ExpressSession.MemoryStore() as ExpressSession.Store;\n\t}\n\n\t// Make the middleware instance\n\tmiddleware = ExpressSession({\n\t\tname: config.front.cookie.name,\n\t\tsecret: config.front.cookie.secret,\n\t\tcookie: {\n\t\t\tmaxAge: config.front.cookie.max_age\n\t\t},\n\t\tstore: store,\n\t\tsaveUninitialized: false,\n\t\tresave: false,\n\t});\n\n\tlog.info(\"Initialized Session Store\");\n}\n"
  },
  {
    "path": "front/src/shared_wrap.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport { EventEmitter } from \"events\";\n\nimport * as Shared from \"@oo/shared\";\n\nexport { config, config2, redisUtil } from \"@oo/shared\";\n\n// Workaround for EventEmitter not being added to shared/index.d.ts\n\nexport interface IRedisMessenger extends Shared.RedisMessenger, EventEmitter {}\nexport function newRedisMessenger(...args: any[]): IRedisMessenger {\n\treturn new Shared.RedisMessenger(...args) as IRedisMessenger;\n}\n\nexport interface IRedisQueue extends Shared.RedisQueue, EventEmitter {}\nexport function newRedisQueue(...args: any[]): IRedisQueue {\n\treturn new Shared.RedisQueue(...args) as IRedisQueue;\n}\n\n// This is the interface for @oo/shared/logger, but dts-gen does not generate a full interface for that class.\nexport interface ILogger {\n\ttrace(...args: any): void;\n\tdebug(...args: any): void;\n\tlog(...args: any): void;\n\tinfo(...args: any): void;\n\twarn(...args: any): void;\n\terror(...args: any): void;\n}\n\nexport function logger(id: string): ILogger {\n\treturn Shared.logger(id) as ILogger;\n}\n\nlet gcp: any | undefined;\ntry {\n\tgcp = require(\"../../shared/gcp/index.js\");\n} catch(e) {\n\tlogger(\"shared_wrap\").warn(\"gcp is unavailable:\", e);\n}\nexport { gcp };\n"
  },
  {
    "path": "front/src/socket_connect.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport Async = require(\"async\");\nimport BaseX = require(\"base-x\");\nimport Hjson = require(\"hjson\");\nimport SocketIO = require(\"socket.io\");\nimport Uuid = require(\"uuid\");\n\nimport { BackServerHandler } from \"./back_server_handler\";\nimport { Bucket, IBucket } from \"./bucket_model\";\nimport { logger, ILogger, gcp } from \"./shared_wrap\";\nimport { FlavorRecord } from \"./flavor_record_model\";\nimport { IDestroyable, IWorkspace } from \"./utils\";\nimport { NormalWorkspace } from \"./workspace_normal\";\nimport { SharedWorkspace } from \"./workspace_shared\";\nimport { User, HydratedUser } from \"./user_model\";\nimport { sendZipArchive } from \"./email\";\n\nconst TOKEN_REGEX = /^\\w*$/;\nconst SHORTLINK_REGEX = /^[\\p{L}\\p{Nd}_-]{5,}$/u;\n\nconst Base58 = BaseX(\"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\");\n\ninterface ISocketCustom extends SocketIO.Socket {\n\thandler: SocketHandler;\n\tremoveAllListeners(): this;\n}\n\ninterface InitData {\n\taction: string|null;\n\tinfo: string|null;\n\toldSessCode: string|null;\n\tskipCreate: boolean;\n\tflavor: string|null;\n}\n\ninterface SocketAsyncAuto {\n\tuser: HydratedUser|null;\n\traw_init: any;\n\tinit: InitData;\n\tbucket: IBucket|null;\n\tinit_session: null;\n}\n\ninterface DeleteBucketAsyncAuto {\n\tbucket: IBucket|null;\n\tdeleted: any;\n\trepo: any;\n}\n\nexport class SocketHandler implements IDestroyable {\n\tpublic socket: ISocketCustom;\n\tpublic back: BackServerHandler;\n\tpublic workspace: IWorkspace|null = null;\n\tpublic user: HydratedUser|null = null;\n\tpublic bucket: IBucket|null = null;\n\tpublic flavor: string|null = null;\n\tpublic destroyed = false;\n\n\tprivate _log: ILogger;\n\n\tpublic static onConnection(socket: SocketIO.Socket) {\n\t\tconst handler = new SocketHandler(socket);\n\t\thandler.socket.handler = handler;\n\t}\n\n\tconstructor(socket: SocketIO.Socket) {\n\t\t// Set up the socket\n\t\tthis.socket = socket as ISocketCustom;\n\t\tthis._log = logger(\"socker-handler:\" + socket.id);\n\t\tthis._log.info(\"New Connection\", this.socket.handshake.address);\n\t\tthis.socket.emit(\"init\");\n\n\t\t// Set up Redis connection to back server\n\t\tthis.back = new BackServerHandler();\n\n\t\t// Add event listeners\n\t\tthis.listen();\n\n\t\t// Startup tasks\n\t\tAsync.auto<SocketAsyncAuto>({\n\n\t\t\t// 1. Load user from database\n\t\t\tuser: (next) => {\n\t\t\t\tconst sess = (this.socket.request as any).session;\n\t\t\t\tconst userId = sess?.passport?.user;\n\n\t\t\t\tif (userId) User.findById(userId)\n\t\t\t\t\t.populate(\"programModel\")\n\t\t\t\t\t.exec(next);\n\t\t\t\telse next(null, null);\n\t\t\t},\n\n\t\t\t// 2. Process init data from client and load bucket info\n\t\t\traw_init: (next) => {\n\t\t\t\tthis.socket.once(\"init\", (data) => {\n\t\t\t\t\tnext(null, data);\n\t\t\t\t});\n\t\t\t},\n\t\t\tinit: [\"user\", \"raw_init\", ({user, raw_init}, next) => {\n\t\t\t\traw_init = raw_init || {};\n\n\t\t\t\t// Send back auth user info\n\t\t\t\tthis.socket.emit(\"oo.authuser\", { user });\n\n\t\t\t\t// Process the user's requested action\n\t\t\t\tlet action = raw_init.action;\n\t\t\t\tlet info = raw_init.info;\n\t\t\t\tlet oldSessCode = raw_init.sessCode;\n\t\t\t\tlet skipCreate = raw_init.skipCreate;\n\t\t\t\tconst flavor = raw_init.flavor;\n\t\t\t\tif (action === \"session\" && !oldSessCode) {\n\t\t\t\t\toldSessCode = info; // backwards compat.\n\t\t\t\t}\n\n\t\t\t\t// Sanitize the inputs (don't add flavor yet)\n\t\t\t\taction = TOKEN_REGEX.test(action) ? action : null;\n\t\t\t\tinfo = TOKEN_REGEX.test(info) ? info : null;\n\t\t\t\toldSessCode = TOKEN_REGEX.test(oldSessCode) ? oldSessCode : null;\n\t\t\t\tskipCreate = !!skipCreate;\n\t\t\t\tconst init = { action, info, oldSessCode, skipCreate, flavor: null };\n\n\t\t\t\tif (flavor && user) {\n\t\t\t\t\tlet flavorOK = user.isFlavorOK(flavor);\n\t\t\t\t\tif (flavorOK) {\n\t\t\t\t\t\tthis._log.info(\"User connected with flavor:\", flavor);\n\t\t\t\t\t\tinit.flavor = flavor;\n\t\t\t\t\t}\n\t\t\t\t\tnext(null, init);\n\t\t\t\t} else {\n\t\t\t\t\tnext(null, init);\n\t\t\t\t}\n\t\t\t}],\n\t\t\tbucket: [\"init\", ({init}, next) => {\n\t\t\t\tconst { action, info } = init;\n\t\t\t\tif (action !== \"bucket\" && action !== \"project\") {\n\t\t\t\t\tnext(null, null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tBucket.findOne({ bucket_id: info as string })\n\t\t\t\t\t.populate(\"baseModel\")\n\t\t\t\t\t.exec(next);\n\t\t\t}],\n\n\t\t\t// Callback (depends on 1 and 2)\n\t\t\tinit_session: [\"user\", \"init\", \"bucket\", ({user, init, bucket}, next) => {\n\t\t\t\tif (this.destroyed) return;\n\n\t\t\t\t// Unpack and save init settings\n\t\t\t\tconst { action, info, oldSessCode, skipCreate, flavor } = init;\n\t\t\t\tthis.user = user;\n\t\t\t\tthis.flavor = flavor;\n\t\t\t\tthis.bucket = bucket;\n\n\t\t\t\t// Check for a valid bucket\n\t\t\t\tif (action === \"bucket\" || action === \"project\") {\n\t\t\t\t\tif (!bucket || !bucket.isValidAction(action)) {\n\t\t\t\t\t\tthis._log.info(\"Invalid bucket/project:\", action, (bucket?.consoleText));\n\t\t\t\t\t\tthis.sendMessage(\"Unable to find bucket or project: \" + (info as string));\n\t\t\t\t\t\tthis.socket.emit(\"destroy-u\", \"Invalid Bucket or Project\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (!bucket.checkAccessPermissions(user)) {\n\t\t\t\t\t\tthis._log.info(\"Permission denied:\", bucket.consoleText);\n\t\t\t\t\t\tthis.sendMessage(\"Permission denied: \" + bucket.bucket_id);\n\t\t\t\t\t\tthis.socket.emit(\"destroy-u\", \"Permission Denied\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.socket.emit(\"bucket-info\", bucket);\n\t\t\t\t}\n\n\t\t\t\t// Fork to load instructor data and buckets\n\t\t\t\tthis.loadInstructor();\n\t\t\t\tthis.loadUserBuckets();\n\t\t\t\tthis.touchUser();\n\t\t\t\tthis.touchBucket();\n\n\t\t\t\tif (this.user) {\n\t\t\t\t\tthis._log.info(\"Tier:\", this.user.tier);\n\t\t\t\t}\n\n\t\t\t\tswitch (action) {\n\t\t\t\t\tcase \"workspace\":\n\t\t\t\t\t\tthis._log.warn(\"Attempted to create no-context workspace:\", info);\n\t\t\t\t\t\treturn;\n\n\t\t\t\t\tcase \"student\":\n\t\t\t\t\t\tif (!info) return;\n\t\t\t\t\t\t// Note: this is not necesarilly a student.  It can be any user.\n\t\t\t\t\t\tthis._log.info(\"Attaching to a student's workspace:\", info);\n\t\t\t\t\t\tthis.workspace = new SharedWorkspace(info as string, null, null, socket.id);\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase \"bucket\":\n\t\t\t\t\tcase \"project\":\n\t\t\t\t\t\tif (!bucket) return;\n\t\t\t\t\t\tif (bucket.butype === \"collab\") {\n\t\t\t\t\t\t\t// TODO(#41): Initialize the shared workspace with the project owner, not the auth user. Maybe pass null as the user here and then load the project owner inside SharedWorkspace.\n\t\t\t\t\t\t\tthis._log.info(\"Attaching to a collaborative project:\", bucket.consoleText);\n\t\t\t\t\t\t\tthis.workspace = new SharedWorkspace(null, user, bucket, socket.id);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis._log.info(\"Attaching to a bucket/project:\", bucket.consoleText);\n\t\t\t\t\t\t\tthis.workspace = new NormalWorkspace(oldSessCode, user, bucket);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase \"session\":\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tif (user && user.share_key) {\n\t\t\t\t\t\t\tthis._log.info(\"Attaching as host to student's workspace:\", user.share_key);\n\t\t\t\t\t\t\tthis.workspace = new SharedWorkspace(null, user, null, socket.id);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis._log.info(\"Attaching to default workspace with sessCode\", oldSessCode);\n\t\t\t\t\t\t\tthis.workspace = new NormalWorkspace(oldSessCode, user, null);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tthis.listen();\n\t\t\t\tif (!skipCreate) {\n\t\t\t\t\tthis.workspace.beginOctaveRequest(this.flavor);\n\t\t\t\t}\n\n\t\t\t\t// Continue down the chain (does not do anything currently)\n\t\t\t\tnext(null, null);\n\t\t\t}]\n\n\t\t}, (err) => {\n\t\t\t// Error Handler\n\t\t\tif (err) {\n\t\t\t\tthis._log.error(\"ASYNC ERROR\", err);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate listen(): void {\n\t\t// Prevent duplicate listeners\n\t\tthis.unlisten();\n\n\t\t// Make listeners on the socket\n\t\tthis.socket.on(\"*\", this.onDataD.bind(this));\n\t\tthis.socket.on(\"disconnect\", this.onDestroyD.bind(this));\n\n\t\t// Make listeners on Redis\n\t\tthis.back.on(\"data\", this.onDataU.bind(this));\n\t\tthis.back.on(\"destroy-u\", this.onDestroyU.bind(this));\n\n\t\t// Make listeners on Workspace\n\t\tif (this.workspace) {\n\t\t\tthis.workspace.on(\"data\", this.onDataW.bind(this));\n\t\t\tthis.workspace.on(\"message\", this.sendMessage.bind(this));\n\t\t\tthis.workspace.on(\"sesscode\", this.setSessCode.bind(this));\n\t\t\tthis.workspace.on(\"back\", this.onDataWtoU.bind(this));\n\t\t\tthis.workspace.subscribe();\n\t\t}\n\n\t\t// Let Redis have listeners too\n\t\tthis.back.subscribe();\n\t}\n\n\tprivate unlisten(): void {\n\t\tthis.socket.removeAllListeners();\n\t\tthis.back.removeAllListeners();\n\t\tthis.back.unsubscribe();\n\t\tif (this.workspace) {\n\t\t\tthis.workspace.removeAllListeners();\n\t\t\tthis.workspace.unsubscribe();\n\t\t}\n\t}\n\n\t// Convenience function to post a message in the client's console window\n\tprivate sendMessage(message: string): void {\n\t\t// Log to console for backwards compatibility with older clients.\n\t\t// TODO: Remove this and send the alert box only\n\t\tthis.socket.emit(\"data\", {\n\t\t\ttype: \"stdout\",\n\t\t\tdata: message+\"\\n\"\n\t\t});\n\t\tthis.socket.emit(\"alert\", message);\n\t}\n\n\t//// MAIN LISTENER FUNCTIONS ////\n\n\t// When the client disconnects (destroyed from downstream)\n\tprivate onDestroyD(): void {\n\t\tthis._log.info(\"Client Disconnect\");\n\t\tif (this.workspace) this.workspace.destroyD(\"Client Disconnect\");\n\t\tthis.unlisten();\n\t}\n\n\t// When the back server exits (destroyed from upstream)\n\tprivate onDestroyU(message: string): void {\n\t\tthis._log.info(\"Upstream Destroyed:\", message);\n\t\tthis.socket.emit(\"destroy-u\", message);\n\t\tthis.back.setSessCode(null);\n\t\tif (this.workspace) this.workspace.destroyU(message);\n\t}\n\n\t// When the client sends a message (data from downstream)\n\tprivate onDataD(obj: any): void {\n\t\tconst name: string = obj.data[0] || \"\";\n\t\tconst data: any|null = obj.data[1] || null;\n\n\t\t// Check for name matches\n\t\tswitch(name){\n\t\t\tcase \"init\":\n\t\t\t\treturn;\n\t\t\tcase \"enroll\":\n\t\t\t\tthis.onEnroll(data);\n\t\t\t\treturn;\n\t\t\tcase \"update_students\":\n\t\t\t\tthis.onUpdateStudents(data);\n\t\t\t\treturn;\n\t\t\tcase \"oo.unenroll_student\":\n\t\t\t\tthis.onUnenrollStudent(data);\n\t\t\t\treturn;\n\t\t\tcase \"oo.reenroll_student\":\n\t\t\t\tthis.onReenrollStudent(data);\n\t\t\t\treturn;\n\t\t\tcase \"oo.ping\":\n\t\t\t\tthis.onPing(data);\n\t\t\t\treturn;\n\t\t\tcase \"oo.toggle_sharing\":\n\t\t\t\tthis.onToggleSharing(data);\n\t\t\t\treturn;\n\t\t\tcase \"oo.reconnect\":\n\t\t\t\tthis.onOoReconnect();\n\t\t\t\treturn;\n\t\t\tcase \"oo.set_password\":\n\t\t\t\tthis.onSetPassword(data);\n\t\t\t\treturn;\n\t\t\tcase \"oo.delete_bucket\":\n\t\t\t\tthis.onDeleteBucket(data);\n\t\t\t\treturn;\n\t\t\tcase \"oo.flavor_upgrade\":\n\t\t\t\tthis.onFlavorUpgrade(data);\n\t\t\t\treturn;\n\t\t\tcase \"oo.generate_zip\":\n\t\t\t\tthis.onGenerateZip(data);\n\t\t\t\treturn;\n\t\t\tcase \"oo.create_bucket\":\n\t\t\t\tthis.onCreateBucket(data);\n\t\t\t\treturn;\n\t\t\tcase \"oo.change_bucket_shortlink\":\n\t\t\t\tthis.onChangeBucketShortlink(data);\n\t\t\t\treturn;\n\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// Check for prefix matches\n\t\tswitch(name.substr(0,3)){\n\t\t\tcase \"ot.\":\n\t\t\tcase \"ws.\":\n\t\t\t\tif (this.workspace) this.workspace.dataD(name, data);\n\t\t\t\treturn;\n\t\t}\n\n\t\t// Intercept some commands and fork them into the workspace\n\t\tif (name === \"data\" && this.workspace) this.workspace.dataD(name, data);\n\t\tif (name === \"save\" && this.workspace) this.workspace.dataD(name, data);\n\n\t\t// Send everything else upstream to the back server\n\t\tthis.back.dataD(name, data);\n\t}\n\n\t// When the back server sends a message (data from upstream)\n\t// Let (almost) everything continue downstream to the client\n\tprivate onDataU(name: string, data: any): void {\n\t\tif (this.workspace) this.workspace.dataU(name, data);\n\n\t\tswitch(name){\n\t\t\tcase \"bucket-repo-created\":\n\t\t\t\tthis.onBucketRepoCreated(data);\n\t\t\t\treturn;\n\n\t\t\tcase \"oo.touch-flavor\":\n\t\t\t\tthis.onTouchFlavor(data);\n\t\t\t\tbreak;\n\t\t}\n\n\t\tthis.socket.emit(name, data);\n\t}\n\n\t// When the workspace sends a message (data from workspace)\n\t// Let everything continue downstream to the client\n\tprivate onDataW(name: string, data: any): void {\n\t\tthis.socket.emit(name, data);\n\t}\n\n\t//// OTHER UTILITY FUNCTIONS ////\n\n\tprivate async loadInstructor(): Promise<void> {\n\t\tif (!this.user || !this.user.instructor || !this.user.instructor.length)\n\t\t\treturn;\n\n\t\tlet user = await this.user.loadInstructorModels();\n\t\tuser.instructorModels?.forEach((program) => {\n\t\t\tthis.socket.emit(\"instructor\", {\n\t\t\t\tprogram: program.program_name,\n\t\t\t\tusers: program.students,\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate async loadUserBuckets(): Promise<void> {\n\t\tif (!this.user) return;\n\t\tlet buckets = await Bucket.find({ user_id: this.user._id });\n\t\tthis._log.trace(\"Loaded\", buckets.length, \"buckets for user\", this.user!.consoleText);\n\t\tthis.socket.emit(\"all-buckets\", { buckets });\n\t}\n\n\tprivate touchUser(): void {\n\t\tif (!this.user) return;\n\t\tthis.user.touchLastActivity((err) => {\n\t\t\tif (err) {\n\t\t\t\tthis._log.error(\"TOUCH USER ACTIVITY ERROR\", err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate touchBucket(): void {\n\t\tif (!this.bucket) return;\n\t\tthis.bucket.touchLastActivity((err) => {\n\t\t\tif (err) {\n\t\t\t\tthis._log.error(\"TOUCH BUCKET ACTIVITY ERROR\", err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate onSetPassword(obj: any): void {\n\t\tif (!obj) return;\n\t\tif (!this.user) return;\n\t\tthis.user.setPassword(obj.new_pwd, (err) => {\n\t\t\tif (err) return this._log.error(\"SET PASSWORD ERROR\", err);\n\t\t\tthis.sendMessage(\"Your password has been changed.\");\n\t\t});\n\t}\n\n\tprivate parseDuplicateKeyError(err: Error): object | void {\n\t\tif (err.message.indexOf(\"duplicate key error\") !== -1) {\n\t\t\tlet dup;\n\t\t\ttry {\n\t\t\t\tthis._log.trace(\"Caught duplicate key error:\", err.message);\n\t\t\t\tdup = Hjson.parse(err.message.substr(err.message.indexOf(\"dup key:\") + 9));\n\t\t\t} catch(e) {\n\t\t\t\tthis._log.error(\"ERROR: Could not parse duplicate key error:\", e, err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn dup;\n\t\t}\n\t}\n\n\tprivate onCreateBucket(obj: any): void {\n\t\tif (!obj) return;\n\t\tif (!this.user) return;\n\t\tif (!obj.shortlink) return;\n\n\t\t// Validate the shortlink\n\t\tif (!SHORTLINK_REGEX.test(obj.shortlink)) {\n\t\t\tthis.socket.emit(\"oo.create-bucket-error\", {\n\t\t\t\ttype: \"invalid-shortlink\"\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// Generate the Bucket ID\n\t\tconst bucketIdBuffer = new Buffer(16);\n\t\tUuid.v4(null, bucketIdBuffer, 0);\n\t\tconst bucketId = Base58.encode(bucketIdBuffer);\n\n\t\tconst bucket = new Bucket();\n\t\tbucket.bucket_id = bucketId;\n\t\tbucket.user_id = this.user._id;\n\t\tbucket.butype = obj.butype;\n\t\tbucket.base_bucket_id = obj.base_bucket_id;\n\t\tbucket.shortlink = obj.shortlink;\n\t\tif (obj.butype === \"readonly\") {\n\t\t\tbucket.main = obj.main;\n\t\t}\n\t\tbucket.save((err) => {\n\t\t\tif (err) {\n\t\t\t\tlet dup = this.parseDuplicateKeyError(err);\n\t\t\t\tif (dup) {\n\t\t\t\t\tthis._log.info(\"Failed to create bucket with duplicate key:\", dup, bucket.consoleText, this.user!.consoleText);\n\t\t\t\t\tthis.socket.emit(\"oo.create-bucket-error\", {\n\t\t\t\t\t\ttype: \"duplicate-key\",\n\t\t\t\t\t\tdata: dup,\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t} else {\n\t\t\t\t\tthis._log.error(\"ERROR from Mongo:\", err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Success creating the Mongo entry. Now create the repo.\n\t\t\tthis._log.info(\"Created bucket:\", bucket.consoleText, this.user!.consoleText);\n\t\t\tconst backData = bucket.toJSON();\n\t\t\tbackData.filenames = obj.filenames;\n\t\t\tthis.back.dataD(\"oo.create_bucket\", backData);\n\t\t});\n\t}\n\n\tprivate onBucketRepoCreated(bucket: any): void {\n\t\tthis.socket.emit(\"bucket-created\", { bucket });\n\t}\n\n\tprivate onChangeBucketShortlink(obj: any): void {\n\t\tconst bucket = this.bucket;\n\t\tif (!bucket) return;\n\t\tif (!this.user) return;\n\t\tif (!this.user._id.equals(bucket.user_id)) {\n\t\t\tthis._log.warn(\"Attempt to change shortlink for another user's bucket\");\n\t\t\treturn;\n\t\t}\n\t\tif (bucket.shortlink !== obj.old_shortlink) {\n\t\t\tthis._log.warn(\"Attempt to change stale shortlink:\", bucket.consoleText, obj);\n\t\t\treturn;\n\t\t}\n\n\t\t// Validate the shortlink\n\t\tif (!SHORTLINK_REGEX.test(obj.new_shortlink)) {\n\t\t\tthis.socket.emit(\"oo.change-bucket-shortlink-response\", {\n\t\t\t\tsuccess: false,\n\t\t\t\ttype: \"invalid-shortlink\"\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// Attempt to save the new shortlink\n\t\tthis._log.info(\"Changing shortlink:\", bucket.consoleText, obj.new_shortlink);\n\t\tbucket.shortlink = obj.new_shortlink;\n\t\tbucket.save((err) => {\n\t\t\tif (err) {\n\t\t\t\tlet dup = this.parseDuplicateKeyError(err);\n\t\t\t\tif (dup) {\n\t\t\t\t\tthis._log.info(\"Failed to update bucket with duplicate key:\", dup, bucket.consoleText, this.user!.consoleText);\n\t\t\t\t\tthis.socket.emit(\"oo.change-bucket-shortlink-response\", {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\ttype: \"duplicate-key\",\n\t\t\t\t\t\tdata: dup,\n\t\t\t\t\t});\n\t\t\t\t\t// Reset the shortlink for future calls\n\t\t\t\t\tbucket.shortlink = obj.old_shortlink;\n\t\t\t\t\treturn;\n\t\t\t\t} else {\n\t\t\t\t\tthis._log.error(\"ERROR from Mongo:\", err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.socket.emit(\"oo.change-bucket-shortlink-response\", {\n\t\t\t\tsuccess: true,\n\t\t\t\tbucket\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate onDeleteBucket(obj: any): void {\n\t\tif (!obj) return;\n\t\tif (!obj.bucket_id) return;\n\t\tif (!this.user) return;\n\t\tthis._log.info(\"Deleting bucket:\", obj.bucket_id);\n\n\t\tAsync.auto<DeleteBucketAsyncAuto>({\n\t\t\tbucket: (next) => {\n\t\t\t\tBucket.findOne({ bucket_id: obj.bucket_id }, next);\n\t\t\t},\n\t\t\tdeleted: [\"bucket\", ({bucket}, next) => {\n\t\t\t\tif (!bucket) {\n\t\t\t\t\tnext(new Error(\"Unable to find bucket\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!bucket.isOwnedBy(this.user)) {\n\t\t\t\t\tnext(new Error(\"Bad owner: \" + bucket.consoleText + \" \" + this.user?.consoleText));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbucket.remove(next);\n\t\t\t}],\n\t\t\trepo: [\"bucket\", \"deleted\", ({bucket}, next) => {\n\t\t\t\t// Note: It would be nice to request repo removal here, but there may be back server instances running that still require it. Instead, there should be a job that periodically scans for and removes orphaned bucket repos.\n\t\t\t\tif (bucket) {\n\t\t\t\t\t// bucket.removeRepo(next);\n\t\t\t\t\tnext(null);\n\t\t\t\t} else {\n\t\t\t\t\tnext(null);\n\t\t\t\t}\n\t\t\t}],\n\t\t}, (err) => {\n\t\t\tif (err) {\n\t\t\t\tthis._log.warn(\"DELETE BUCKET\", err);\n\t\t\t\tthis.sendMessage(\"Encountered error while deleting bucket\");\n\t\t\t} else {\n\t\t\t\tthis.socket.emit(\"bucket-deleted\", {\n\t\t\t\t\tbucket_id: obj.bucket_id\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate onTouchFlavor(obj: any): void {\n\t\tif (!obj) return;\n\t\tif (!this.user) {\n\t\t\tthis._log.error(\"ERROR: No user on a flavor session\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Step 1: insert a new FlavorRecord\n\t\tconst flavorRecord = new FlavorRecord();\n\t\tflavorRecord.user_id = this.user._id;\n\t\tflavorRecord.sesscode = this.back.sessCode || \"null\";\n\t\tflavorRecord.start = new Date(obj.start);\n\t\tflavorRecord.current = new Date(obj.current);\n\t\tflavorRecord.flavor = obj.flavor;\n\t\tflavorRecord.save((err) => {\n\t\t\tif (err) return this._log.error(\"ERROR creating FlavorRecord:\", err);\n\n\t\t\t// Step 2: delete older FlavorRecords from the same sessCode\n\t\t\tFlavorRecord.deleteMany({\n\t\t\t\tsesscode: flavorRecord.sesscode,\n\t\t\t\tcurrent: { $lt: flavorRecord.current }\n\t\t\t}, (err /* , writeOpResult */) => {\n\t\t\t\tif (err) return this._log.error(\"ERROR deleting old FlavorRecords:\", err);\n\t\t\t\t// this._log.trace(\"Added new FlavorRecord and deleted \" + (writeOpResult && writeOpResult.result && writeOpResult.result.n) + \" old ones\");\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate onOoReconnect(): void {\n\t\tif (this.workspace) {\n\t\t\tthis.workspace.beginOctaveRequest(this.flavor);\n\t\t} else {\n\t\t\tthis.socket.emit(\"destroy-u\", \"Invalid Session\");\n\t\t}\n\t}\n\n\tprivate setSessCode(sessCode: string): void {\n\t\t// We have our sessCode.\n\t\tthis._log.info(\"SessCode\", sessCode);\n\t\tthis.back.setSessCode(sessCode);\n\t\tthis.socket.emit(\"sesscode\", {\n\t\t\tsessCode: sessCode\n\t\t});\n\t\tif (this.workspace) this.workspace.sessCode = sessCode;\n\t}\n\n\tprivate onDataWtoU(name: string, value: any): void {\n\t\tthis.back.dataD(name, value);\n\t}\n\n\t//// ENROLLING AND STUDENTS LISTENER FUNCTIONS ////\n\n\tprivate onEnroll(obj: any): void {\n\t\tif (!this.user || !obj) return;\n\t\tconst program = obj.program;\n\t\tif (!program) return;\n\t\tthis._log.info(\"Enrolling\", this.user.consoleText, \"in program\", program);\n\t\tthis.user.program = program;\n\t\tthis.user.save((err)=> {\n\t\t\tif (err) return this._log.error(\"MONGO ERROR\", err);\n\t\t\tthis.sendMessage(\"Successfully enrolled\");\n\t\t});\n\t}\n\n\tprivate onUpdateStudents(obj: any): void {\n\t\tif (!obj) return;\n\t\treturn this.sendMessage(\"The update_students command has been replaced.\\nOpen a support ticket for more information.\");\n\t}\n\n\tprivate async onUnenrollStudent(obj: any): Promise<void> {\n\t\tif (!obj) return;\n\t\tif (!obj.userId) return;\n\t\tif (!this.user) return;\n\t\tlet student = await User.findById(obj.userId);\n\t\tif (!student) return this._log.warn(\"Warning: student not found\", obj.userId);\n\t\tif (this.user!.instructor.indexOf(student.program) === -1) return this._log.warn(\"Warning: illegal call to unenroll student\");\n\t\tthis._log.info(\"Un-enrolling\", this.user!.consoleText, \"from program\", student.program);\n\t\tstudent.program = \"default\";\n\t\tawait student.save();\n\t\tthis.sendMessage(\"Student successfully unenrolled: \" + student.displayName);\n\t}\n\n\tprivate async onReenrollStudent(obj: any): Promise<void> {\n\t\tif (!obj) return;\n\t\tif (!obj.userId) return;\n\t\tif (!obj.program) return;\n\t\tif (!this.user) return;\n\t\tif (this.user.instructor.indexOf(obj.program) === -1) {\n\t\t\tthis.sendMessage(\"Student not re-enrolled: Cannot use the course code \" + obj.program);\n\t\t\treturn this._log.warn(\"Warning: illegal call to re-enroll student\");\n\t\t}\n\t\tlet student = await User.findById(obj.userId);\n\t\tif (!student) return this._log.warn(\"Warning: student not found\", obj.userId);\n\t\tif (this.user!.instructor.indexOf(student.program) === -1) return this._log.warn(\"Warning: illegal call to reenroll student\");\n\t\tthis._log.info(\"Re-enrolling\", this.user!.consoleText, \"from program\", student.program, \"to program\", obj.program);\n\t\tstudent.program = obj.program;\n\t\tawait student.save();\n\t\tthis.sendMessage(\"Student successfully re-enrolled: \" + student.displayName);\n\t}\n\n\tprivate onPing(obj: any): void {\n\t\tif (!obj) return;\n\t\tthis.socket.emit(\"oo.pong\", {\n\t\t\tstartTime: parseInt(obj.startTime)\n\t\t});\n\t}\n\n\tprivate onToggleSharing(obj: any): void {\n\t\tconst enabled: boolean = obj?.enabled;\n\t\tconst warning = new Error(\"warning\");\n\n\t\tAsync.series([\n\t\t\t(next) => {\n\t\t\t\tif (this.bucket) {\n\t\t\t\t\tif (!this.bucket.isOwnedBy(this.user)) {\n\t\t\t\t\t\tthis.sendMessage(\"Permission denied\");\n\t\t\t\t\t\tthis._log.warn(\"Could not toggle sharing: permission denied:\", this.bucket.consoleText, this.user?.consoleText);\n\t\t\t\t\t\tnext(warning);\n\t\t\t\t\t} else if (this.bucket.butype === \"editable\" && enabled) {\n\t\t\t\t\t\tthis.bucket.butype = \"collab\";\n\t\t\t\t\t\tthis.bucket.save(next);\n\t\t\t\t\t} else if (this.bucket.butype === \"collab\" && !enabled) {\n\t\t\t\t\t\tif (this.workspace) this.workspace.destroyD(\"Sharing Disabled\");\n\t\t\t\t\t\tthis.bucket.butype = \"editable\";\n\t\t\t\t\t\tthis.bucket.save(next);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.sendMessage(\"Could not toggle sharing\");\n\t\t\t\t\t\tthis._log.warn(\"Could not toggle sharing:\", this.bucket.consoleText, obj);\n\t\t\t\t\t\tnext(warning);\n\t\t\t\t\t}\n\t\t\t\t} else if (this.user) {\n\t\t\t\t\tif (this.user.program && this.user.program !== \"default\") {\n\t\t\t\t\t\tthis.sendMessage(\"You must unenroll before changing sharing settings.\\nTo unenroll, run the command \\\"enroll('default')\\\".\");\n\t\t\t\t\t\tnext(warning);\n\t\t\t\t\t} else if (!this.user.share_key && enabled) {\n\t\t\t\t\t\tthis.user.createShareKey(next);\n\t\t\t\t\t} else if (this.user.share_key && !enabled) {\n\t\t\t\t\t\tif (this.workspace) this.workspace.destroyD(\"Sharing Disabled\");\n\t\t\t\t\t\tthis.user.removeShareKey(next);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.sendMessage(\"Could not toggle sharing\");\n\t\t\t\t\t\tthis._log.warn(\"Could not toggle sharing:\", this.user.consoleText, obj);\n\t\t\t\t\t\tnext(warning);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t], (err) => {\n\t\t\tif (err === warning) {\n\t\t\t\t// no-op\n\t\t\t} else if (err) {\n\t\t\t\tthis._log.error(\"TOGGLE SHARING\", err);\n\t\t\t\tthis.sendMessage(\"Could not toggle sharing\");\n\t\t\t} else {\n\t\t\t\tthis.socket.emit(\"reload\", {});\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate onFlavorUpgrade(obj: any): void {\n\t\tif (!this.user || !obj) return;\n\t\tconst flavor = obj.flavor;\n\n\t\tconst flavorOK = this.user.isFlavorOK(flavor);\n\t\tif (!flavorOK) {\n\t\t\tthis._log.warn(\"Failed to upgrade user to flavor:\", flavor);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.workspace) {\n\t\t\tthis._log.warn(\"No workspace on flavor upgrade attempt\");\n\t\t\treturn;\n\t\t}\n\t\tthis._log.info(\"User upgraded to flavor:\", flavor);\n\t\tthis.flavor = flavor;\n\t\tthis.workspace.destroyD(\"Flavor Upgrade\");\n\t\tthis.workspace.beginOctaveRequest(this.flavor);\n\t}\n\n\tprivate onGenerateZip(obj: any): void {\n\t\tif (!this.user && !this.bucket) {\n\t\t\tthis._log.error(\"Nothing to archive:\", obj);\n\t\t\treturn;\n\t\t}\n\t\tif (!gcp) {\n\t\t\tthis._log.warn(\"Cannot generate zip: gcp unavailable\");\n\t\t\treturn;\n\t\t}\n\t\tconst log = logger(\"create-repo-snapshot:\" + this.socket.id);\n\n\t\tlet [tld, name, desc] = (this.bucket) ? [\"buckets\", this.bucket.bucket_id, this.bucket.displayName] : [\"repos\", this.user!.parametrized, this.user!.displayName];\n\n\t\tthis.sendMessage(\"Your zip archive is being generated…\");\n\t\tgcp.uploadRepoSnapshot(log.log, tld, name).then((url: any) => {\n\t\t\tthis.socket.emit(\"data\", {\n\t\t\t\ttype: \"url\",\n\t\t\t\turl: url,\n\t\t\t\tlinkText: \"Zip Archive Ready\",\n\t\t\t});\n\t\t\tif (this.user) {\n\t\t\t\tsendZipArchive(this.user.email, desc, url).catch((err) => {\n\t\t\t\t\tlog.error(\"Error sending email:\", err);\n\t\t\t\t});\n\t\t\t}\n\t\t}).catch((err: any) => {\n\t\t\tthis._log.error(\"onGenerateZip:\", err);\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "front/src/socketio.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport SocketIO = require(\"socket.io\");\nimport SocketIOWildcard = require(\"socketio-wildcard\");\n\nimport * as ExpressApp from \"./express_setup\";\nimport * as Middleware from \"./session_middleware\";\nimport { SocketHandler } from \"./socket_connect\";\nimport { config, logger } from \"./shared_wrap\";\n\nconst log = logger(\"oo-socketio\");\n\n// TODO: Consider using proper TypeScript types:\n// https://socket.io/docs/v4/typescript/\n\nexport function init(){\n\t/* const io = */ new SocketIO.Server(ExpressApp.server, {\n\t\t\tpath: config.front.socket_io_path,\n\t\t\tallowEIO3: true\n\t\t})\n\t\t.use(SocketIOWildcard())\n\t\t.use((socket, next)=>{\n\t\t\t// Parse the session using middleware\n\t\t\t// Bypass TypeScript since this isn't legal\n\t\t\t(<any>Middleware.middleware)(socket.request, {}, next);\n\t\t})\n\t\t.on(\"connection\", SocketHandler.onConnection);\n\n\t/*\n\tif (config.rackspace.username !== \"xxxxxxxxx\") {\n\t\t// eslint-disable-next-line @typescript-eslint/no-use-before-define\n\t\twatchFlavorServers(io);\n\t}\n\t*/\n\n\tlog.info(\"Initialized Socket.IO Server\", config.front.socket_io_path);\n}\n\n/*\nconst ALL_FLAVORS = Object.keys(config.flavors);\nexport function watchFlavorServers(io: SocketIO.Server) {\n\tAsync.forever(\n\t\t(next: () => void) => {\n\t\t\tAsync.map(ALL_FLAVORS, (flavor, _next) => {\n\t\t\t\t// TODO: Move this call somewhere it could be configurable.\n\t\t\t\track.getFlavorServers(flavor, _next);\n\t\t\t}, (err, results) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tlog.error(\"RACKSPACE ERROR\", err);\n\t\t\t\t} else {\n\t\t\t\t\tresults = results as any[];\n\t\t\t\t\tconst rawServers = Array.prototype.concat.apply([], results.map((data) => { return (data as any).servers; }));\n\t\t\t\t\tconst servers = rawServers.map((server) => {\n\t\t\t\t\t\tconst { name, created, status } = server;\n\t\t\t\t\t\treturn { name, created, status };\n\t\t\t\t\t});\n\t\t\t\t\tio.emit(\"oo.flavor-list\", { servers });\n\n\t\t\t\t\tlog.debug(\"Flavor Servers:\");\n\t\t\t\t\tservers.forEach(({ name, created, status }) => {\n\t\t\t\t\t\tlog.debug(name + \" \" + status + \" \" + created);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tsetTimeout(next, config.front.flavor_log_interval);\n\t\t\t});\n\t\t},\n\t\t(err: Error|null|undefined) => {\n\t\t\tlog.error(\"FOREVER ERROR\", err);\n\t\t}\n\t);\n}\n*/\n"
  },
  {
    "path": "front/src/user_model.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Mongoose User: stores OpenID information for a user.\n\nimport Bcrypt = require(\"bcrypt\");\nimport Crypto = require(\"crypto\");\nimport Mongoose = require(\"mongoose\");\n\nimport * as Utils from \"./utils\";\nimport { config, logger, ILogger } from \"./shared_wrap\";\nimport { Program, IProgram } from \"./program_model\";\n\ntype Err = Error | null;\ntype UserModel = Mongoose.Model<IUser, {}, IUserMethods, IUserVirtuals>;\ntype UserSchema = Mongoose.Schema<IUser, UserModel, IUserMethods, {}, IUserVirtuals>;\nexport type HydratedUser = Mongoose.HydratedDocument<IUser, IUserMethods, IUserVirtuals>;\n\n// Initialize the schema\nconst userSchema: UserSchema = new Mongoose.Schema({\n\temail: String,\n\tparametrized: String,\n\tprofile: Mongoose.Schema.Types.Mixed,\n\topenid: {\n\t\tidentifier: String,\n\t\tprofile: Mongoose.Schema.Types.Mixed\n\t},\n\trepo_key: String,\n\tshare_key: String,\n\tpassword_hash: String,\n\tflavors_enabled: Boolean,\n\tpatreon: {\n\t\tuser_id: String,\n\t\tcurrently_entitled_amount_cents: Number,\n\t\tcurrently_entitled_tier: String,\n\t\toauth2: Mongoose.Schema.Types.Mixed\n\t},\n\tlast_activity: {\n\t\ttype: Date,\n\t\tdefault: Date.now\n\t},\n\tprogram: String,\n\tinstructor: [String],\n\n\t// Feature overrides\n\ttier_override: String,\n\tlegal_time_override: Number,\n\tpayload_limit_override: Number,\n\tcountdown_extra_time_override: Number,\n\tcountdown_request_time_override: Number,\n\tads_disabled_override: Boolean,\n});\n\nuserSchema.index({\n\tshare_key: 1\n});\n\ninterface IUserMethods {\n\tcreateShareKey(next?: (err: Err) => void): void;\n\tremoveShareKey(next?: (err: Err) => void): void;\n\tsetPassword(password: string, next?: (err: Err) => void): void;\n\tcheckPassword(password: string): Promise<boolean>;\n\ttouchLastActivity(next: (err: Err) => void): void;\n\tloadInstructorModels(): Promise<HydratedUser>;\n\tisFlavorOK(flavor: string): boolean;\n\tlogf(): ILogger;\n}\n\ninterface IUserVirtuals {\n\tdisplayName: string;\n\tconsoleText: string;\n\ttier: string;\n\tprogramModel: IProgram | null | undefined;\n\tinstructorModels: IProgram[] | undefined;\n}\n\nexport interface IUser {\n\t_id: Mongoose.Types.ObjectId;\n\temail: string;\n\tparametrized: string;\n\tprofile: any;\n\topenid: {\n\t\tidentifier: string;\n\t\tprofile: any;\n\t};\n\trepo_key: string;\n\tshare_key?: string;\n\tpassword_hash: string;\n\tflavors_enabled: boolean;\n\tpatreon?: {\n\t\tuser_id: string;\n\t\tcurrently_entitled_amount_cents: number;\n\t\tcurrently_entitled_tier: string|null;\n\t\toauth2: any;\n\t\ttier_name?: string; // virtual\n\t};\n\tlast_activity: Date;\n\tprogram: string;\n\tinstructor: string[];\n\n\t// Feature overrides\n\ttier_override?: string;\n\tlegal_time_override?: number;\n\tpayload_limit_override?: number;\n\tcountdown_extra_time_override?: number;\n\tcountdown_request_time_override?: number;\n\tads_disabled_override?: boolean;\n\n\t// Cached sub-models\n\t_instructorModels?: IProgram[];\n}\n\n// Parametrization function used by Octave Online,\n// c. January 2015 - ?\nfunction v2Parametrize(id: Mongoose.Types.ObjectId, email: string) {\n\t// Represent a name such as \"a.b@c.org\" like \"a_b_c_org\".\n\t// Note that this method may have funny results with non-english\n\t// email addresses, if such a thing exists.\n\t// \n\t// Adds a human-readable characteristic to the filename.\n\t// \n\tconst param_email = email\n\t\t.trim()\n\t\t.replace(/[-\\s@.]+/g, \"_\") // replace certain chars with underscores\n\t\t.replace(/[^\\w]/g, \"\") // remove all other non-ASCII characters\n\t\t.replace(/([A-Z]+)/g, \"_$1\") // add underscores before caps\n\t\t.replace(/_+/g, \"_\") // remove duplicate underscores\n\t\t.replace(/^_/, \"\") // remove leading underscore if it exists\n\t\t.toLowerCase(); // convert to lower case\n\n\t// Add an arbitrary, but deterministic, eight characters to the\n\t// begining of the filename.\n\t// \n\t// Helps with indexing of filenames, and mostly prevents filename\n\t// collisions arising from similar email addresses.\n\t// \n\tlet param_md5 = Crypto\n\t\t.createHash(\"md5\")\n\t\t.update(id.toString())\n\t\t.digest(\"base64\")\n\t\t.replace(/[^a-zA-Z]/g, \"\")\n\t\t.toLowerCase();\n\n\t// In the rare case when this returns a string less than eight,\n\t// characters, add arbitrary characters to the end.\n\t// \n\tparam_md5 = (param_md5+\"00000000\").substr(0, 8);\n\n\t// Concatenate it together.\n\t// \n\treturn param_md5 + \"_\" + param_email;\n}\n\n// Returns the user's display name\nuserSchema.virtual(\"displayName\").get(function() {\n\tif (this.profile && this.profile.displayName) return this.profile.displayName;\n\tif (this.openid && this.openid.profile && this.openid.profile.displayName)\n\t\treturn this.openid.profile.displayName;\n\tif (this.email) return this.email;\n\treturn \"Signed In\";\n});\n\n// Returns a string containing information about this user\n// May 2018: Do not log email in consoleText\nuserSchema.virtual(\"consoleText\").get(function() {\n\tconst safeEmail = Utils.emailHash(this.email);\n\tconst safeParameterized = this.parametrized && this.parametrized.substr(0, 8);\n\treturn \"[User \" + this.id + \"; \" + safeEmail + \"; param:\" + safeParameterized + \"_…]\";\n});\n\n// Return the tier for this user, including resource-specific overrides.  These items usually fall back to the default unless a value is explicitly set in the database.  The camel-case name of these fields is for backwards compatibility.\nconst validTiers = Object.keys(config.tiers);\n// TODO: Remove the unnecessary `this: HydratedUser`:\n// https://github.com/Automattic/mongoose/pull/12874\nuserSchema.virtual(\"tier\").get(function(this: HydratedUser) {\n\t// First try: tier_override\n\tlet candidate: string|undefined = this.tier_override;\n\tif (candidate && validTiers.indexOf(candidate) !== -1) {\n\t\treturn candidate;\n\t}\n\n\t// Second try: Patreon\n\tconst patreonTier = this.patreon?.currently_entitled_tier;\n\tif (patreonTier) {\n\t\t// <any> cast: https://stackoverflow.com/a/35209016/1407170\n\t\tcandidate = (config.patreon.tiers as any)[patreonTier].oo_tier;\n\t\tif (candidate && validTiers.indexOf(candidate) !== -1) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\n\t// Third try: program\n\tcandidate = (this as HydratedUser).programModel?.tier_override;\n\tif (candidate && validTiers.indexOf(candidate) !== -1) {\n\t\treturn candidate;\n\t}\n\n\t// Default value:\n\treturn validTiers[0];\n});\n\n// Returns the Patreon tier name for the user\nuserSchema.virtual(\"patreon.tier_name\").get(function() {\n\tconst patreonTier = this.patreon?.currently_entitled_tier;\n\tif (patreonTier) {\n\t\t// <any> cast: https://stackoverflow.com/a/35209016/1407170\n\t\treturn (config.patreon.tiers as any)[patreonTier].name;\n\t}\n});\n\n// Virtuals to return results from sub-models\nuserSchema.virtual(\"programModel\", {\n\tref: \"Program\",\n\tlocalField: \"program\",\n\tforeignField: \"program_name\",\n\tjustOne: true,\n});\nuserSchema.virtual(\"instructorModels\").get(function() {\n\treturn this._instructorModels;\n});\n\n// Add all of the resource-specific overrides to work in the same way.\n[\n\t{\n\t\tfield: \"legalTime\",\n\t\toverrideKey: \"legal_time_override\",\n\t\ttierKey: \"session.legalTime.user\",\n\t\tdefaultValue: config.session.legalTime.user\n\t},\n\t{\n\t\tfield: \"payloadLimit\",\n\t\toverrideKey: \"payload_limit_override\",\n\t\ttierKey: \"session.payloadLimit.user\",\n\t\tdefaultValue: config.session.payloadLimit.user\n\t},\n\t{\n\t\tfield: \"countdownExtraTime\",\n\t\toverrideKey: \"countdown_extra_time_override\",\n\t\ttierKey: \"session.countdownExtraTime\",\n\t\tdefaultValue: config.session.countdownExtraTime\n\t},\n\t{\n\t\tfield: \"countdownRequestTime\",\n\t\toverrideKey: \"countdown_request_time_override\",\n\t\ttierKey: \"session.countdownRequestTime\",\n\t\tdefaultValue: config.session.countdownRequestTime\n\t},\n\t{\n\t\tfield: \"adsDisabled\",\n\t\toverrideKey: \"ads_disabled_override\",\n\t\ttierKey: \"ads.disabled\",\n\t\tdefaultValue: config.ads.disabled\n\t}\n].forEach(({field, overrideKey, tierKey, defaultValue})=>{\n\t// TODO: Remove the unnecessary `this: HydratedUser`:\n\t// https://github.com/Automattic/mongoose/pull/12874\n\tuserSchema.virtual(field).get(function(this: HydratedUser) {\n\t\t// <any> cast: https://stackoverflow.com/a/35209016/1407170\n\t\tlet candidate: any = (this as any)[overrideKey];\n\t\tif (candidate) {\n\t\t\treturn candidate;\n\t\t}\n\t\t// <any> cast: https://stackoverflow.com/a/35209016/1407170\n\t\tcandidate = (this.programModel as any|null)?.[overrideKey];\n\t\tif (candidate) {\n\t\t\treturn candidate;\n\t\t}\n\t\t// <any> cast: https://stackoverflow.com/a/35209016/1407170\n\t\tcandidate = (config.tiers as any)[this.tier]?.[tierKey];\n\t\tif (candidate) {\n\t\t\treturn candidate;\n\t\t}\n\t\treturn defaultValue;\n\t});\n});\n\nfunction randomAlphaString(length: number): string {\n\tlet str = \"\";\n\twhile (str.length < length) {\n\t\tstr += Crypto\n\t\t\t.createHash(\"md5\")\n\t\t\t.update(Math.random().toString())\n\t\t\t.digest(\"base64\")\n\t\t\t.replace(/[^a-zA-Z]/g, \"\");\n\t}\n\treturn str.substr(0, length);\n}\n\n// Auto-fill static fields once, upon creation (or update for old users)\nuserSchema.pre(\"save\", function(next){\n\tif (!this.parametrized) {\n\t\tthis.parametrized = v2Parametrize(this.id, this.email);\n\t}\n\tif (!this.repo_key) {\n\t\tthis.repo_key = randomAlphaString(8);\n\t}\n\n\tnext();\n});\n\nuserSchema.method(\"createShareKey\",\n\t// Instance methods for shared workspace keys\n\tfunction(next?: (err: Err) => void): void {\n\t\tthis.share_key = randomAlphaString(48);\n\t\tthis.logf().trace(\"Creating share key\", this.consoleText, this.share_key);\n\t\tthis.save(next);\n\t}\n);\n\nuserSchema.method(\"removeShareKey\",\n\tfunction(next?: (err: Err) => void): void {\n\t\tthis.share_key = undefined;\n\t\tthis.logf().trace(\"Removing share key\", this.consoleText);\n\t\tthis.save(next);\n\t}\n);\n\nuserSchema.method(\"setPassword\",\n\t// Instance methods for password hashes\n\tfunction(password: string, next?: (err: Err) => void): void {\n\t\tthis.logf().trace(\"Setting password\", this.consoleText);\n\t\tif (!password) {\n\t\t\tprocess.nextTick(() => {\n\t\t\t\tthis.password_hash = \"\";\n\t\t\t\tthis.save(next);\n\t\t\t});\n\t\t} else {\n\t\t\t// To create a new password manually, run:\n\t\t\t// $ node -e \"require('bcrypt').hash('foo', 10, console.log)\"\n\t\t\tBcrypt.hash(password, config.auth.password.salt_rounds, (err, hash) => {\n\t\t\t\tthis.password_hash = hash;\n\t\t\t\tthis.save(next);\n\t\t\t});\n\t\t}\n\t}\n);\n\nuserSchema.method(\"checkPassword\",\n\tasync function(password: string): Promise<boolean> {\n\t\tthis.logf().trace(\"Checking password\", this.consoleText);\n\t\tif (!this.password_hash || !password) {\n\t\t\t// Fail if no password is set on user\n\t\t\treturn false;\n\t\t} else {\n\t\t\treturn Bcrypt.compare(password, this.password_hash);\n\t\t}\n\t}\n);\n\nuserSchema.method(\"touchLastActivity\",\n\t// Other instance methods\n\tfunction(next: (err: Err) => void): void {\n\t\tthis.logf().trace(\"Touching last activity\", this.consoleText);\n\t\tthis.last_activity = new Date();\n\t\tthis.save(next);\n\t}\n);\n\nuserSchema.method(\"loadInstructorModels\",\n\tasync function(): Promise<HydratedUser> {\n\t\tlet programs = await Promise.all(\n\t\t\tthis.instructor.map(async (program_name: string) => {\n\t\t\t\tlet program = await Program.findOne({ program_name });\n\t\t\t\tif (!program) {\n\t\t\t\t\tprogram = new Program();\n\t\t\t\t\tprogram.program_name = program_name;\n\t\t\t\t}\n\t\t\t\tawait program.populate(\"students\");\n\t\t\t\treturn program;\n\t\t\t})\n\t\t);\n\t\tthis._instructorModels = programs;\n\t\treturn this;\n\t}\n);\n\nuserSchema.method(\"isFlavorOK\",\n\tfunction(flavor: string): boolean {\n\t\t// Note: This function must at least validate that the flavor is valid; to this point, the flavor is unsanitized user input.\n\t\tconst availableFlavors = Object.keys(config.flavors);\n\t\tif (availableFlavors.indexOf(flavor) !== -1) {\n\t\t\t// TODO: Implement this when more interesting logic is available.\n\t\t\t// return !!this.flavors_enabled;\n\t\t\treturn true;\n\t\t} else if (flavor) {\n\t\t\tthis.logf().trace(\"WARNING: User requested illegal flavor\", flavor);\n\t\t\treturn false;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n);\n\nuserSchema.method(\"logf\",\n\tfunction(): ILogger {\n\t\treturn logger(\"user:\" + this.id.valueOf());\n\t}\n);\n\n// Make sure the fields are initialized\nuserSchema.post(\"init\", function(){\n\tif (this.program && this.program !== \"default\" && !this.share_key) {\n\t\tthis.createShareKey();\n\t}\n});\n\n// JSON representation: include the virtuals (this object will be transmitted\n// to the actual Octave server)\n// Leave out the password_hash field to avoid leaking it to the front end.\n// Also leave out the *_override fields since the information in those fields is available via the corresponding camel-case virtuals.\nuserSchema.set(\"toJSON\", {\n\tvirtuals: true,\n\ttransform: function(doc: IUser, ret /* , options */) {\n\t\tdelete ret.password_hash;\n\t\tdelete ret.tier_override;\n\t\tif (ret.patreon) {\n\t\t\tdelete ret.patreon.oauth2;\n\t\t}\n\t\tdelete ret.legal_time_override;\n\t\tdelete ret.payload_limit_override;\n\t\tdelete ret.countdown_extra_time_override;\n\t\tdelete ret.countdown_request_time_override;\n\t\treturn ret;\n\t}\n});\n\nexport const User = Mongoose.model<IUser, UserModel>(\"User\", userSchema);\n\nUser.on(\"index\", err => {\n\tif (err) logger(\"user-index\").error(err);\n\telse logger(\"user-index\").info(\"Init Success\");\n});\n"
  },
  {
    "path": "front/src/utils.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport Crypto = require(\"crypto\");\n\n\nexport function emailHash(email: string): string {\n\treturn \"email:\" + Crypto.createHash(\"md5\").update(email).digest(\"hex\").substr(0, 12);\n}\n\nexport interface IDestroyable {\n\tdestroyed: boolean;\n}\n\nexport interface IWorkspace {\n\tsessCode: string|null;\n\n\tdestroyD(message: string): void;\n\tdestroyU(message: string): void;\n\tdataD(name: string, val: any): void;\n\tdataU(name: string, val: any): void;\n\tbeginOctaveRequest(flavor: string|null): void;\n\n\ton(event: string, callback: (...args: any[]) => void): void;\n\tremoveAllListeners(): void;\n\tsubscribe(): void;\n\tunsubscribe(): void;\n}\n"
  },
  {
    "path": "front/src/views/captcha_error.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or (at your\noption) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\nFITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License\nfor more details.\n\nYou should have received a copy of the GNU Affero General Public License along\nwith Octave Online Server.  If not, see <https://www.gnu.org/licenses/>.\n%>\n<%- include(\"partials/head\") %>\n\n<p>\n\t<img src=\"/images/logos/icon-black.svg\" width=\"120px\" />\n</p>\n<h1>ReCAPTCHA Failure</h1>\n<p>\n\tYour request could not be processed due to a ReCAPTCHA Failure. Please click your back button and try again.\n</p>\n<hr/>\n<p>\n\tIf you need help, contact support:\n</p>\n<iframe src=\"<%= config.email.supportUrl %>\" id=\"support\"></iframe>\n\n<%- include(\"partials/foot\") %>\n"
  },
  {
    "path": "front/src/views/incorrect_page.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or (at your\noption) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\nFITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License\nfor more details.\n\nYou should have received a copy of the GNU Affero General Public License along\nwith Octave Online Server.  If not, see <https://www.gnu.org/licenses/>.\n%>\n<%- include(\"partials/head\") %>\n\n<p>\n\t<img src=\"/images/logos/icon-black.svg\" width=\"120px\" />\n</p>\n<h1>Authentication Failure</h1>\n<p>\n\tYou could not be logged in with the email and password that you provided.  If you want to create a new account or if you forgot your password, click the button below to be emailed an 11-digit code to <strong><%= query ? query.s : \"(empty)\" %></strong>.\n</p>\n<form action=\"./tok\" method=\"post\">\n\t<input type=\"hidden\" name=\"s\" value=\"<%= query ? query.s : \"(empty)\" %>\" />\n\t<div class=\"g-recaptcha\" data-sitekey=\"<%= config.recaptcha.siteKey %>\" style=\"display: inline-block;\"></div>\n\t<p>\n\t\t<input type=\"submit\" value=\"Send Code\" />\n\t</p>\n</form>\n<p>\n\tOnce you are signed in, use the \"Change Password\" option in the menu to set a new password.\n</p>\n<hr/>\n<p>\n\tIf you need help, contact support:\n</p>\n<iframe src=\"<%= config.email.supportUrl %>\" id=\"support\"></iframe>\n\n<script type=\"text/javascript\" src=\"https://www.recaptcha.net/recaptcha/api.js\" async>\n</script>\n\n<%- include(\"partials/foot\") %>\n"
  },
  {
    "path": "front/src/views/index.ejs",
    "content": "<!DOCTYPE html>\n<!--\nCopyright © 2018, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or (at your\noption) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\nFITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License\nfor more details.\n\nYou should have received a copy of the GNU Affero General Public License along\nwith Octave Online Server.  If not, see <https://www.gnu.org/licenses/>.\n-->\n<html>\n<head>\n\n\t<title><%= t(config.client.title_key, { config }) %></title>\n\n\t<!-- Standard Meta Tags -->\n\t<meta name=\"description\" content=\"<%= t(config.client.desc_key, { config }) %>\"/>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n\t<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"images/logos/favicon_package/favicon-32x32.png\"/>\n\t<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"images/logos/favicon_package/favicon-16x16.png\"/>\n\t<link rel=\"shortcut icon\" href=\"images/logos/favicon_package/favicon.ico\"/>\n\t<meta name=\"viewport\" content=\"width=device-width\"/>\n\t<meta name=\"theme-color\" content=\"<%= config.client.theme_color %>\"/>\n\n\t<!-- Mobile Config -->\n\t<link rel=\"manifest\" href=\"images/logos/favicon_package/site.webmanifest\"/>\n\t<link rel=\"mask-icon\" href=\"images/logos/favicon_package/safari-pinned-tab.svg\" color=\"<%= config.client.theme_color %>\"/>\n\t<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"images/logos/favicon_package/apple-touch-icon.png\"/>\n\t<meta name=\"mobile-web-app-capable\" content=\"yes\"/>\n\t<meta name=\"apple-mobile-web-app-capable\" content=\"yes\"/>\n\t<meta name=\"apple-mobile-web-app-title\" content=\"<%= config.client.app_name %>\"/>\n\t<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\"/>\n\t<meta name=\"application-name\" content=\"<%= config.client.app_name %>\"/>\n\t<meta name=\"msapplication-TileColor\" content=\"#da532c\"/>\n\t<meta name=\"msapplication-config\" content=\"images/logos/favicon_package/browserconfig.xml\"/>\n\n\t<!-- Others -->\n\t<link href=\"css/themes/fire.css?<%= buildData.cssTimestamp %>\" rel=\"stylesheet\" type=\"text/css\" id=\"theme\"/>\n\t<style type=\"text/css\">\n\t[aria-hidden=\"true\"] { display: none !important; }\n\t</style>\n\t<%- config.ads.head_html %>\n\n</head>\n<body>\n<div id=\"abox\">\n\t<div id=\"abox_default\" class=\"abox_content\" data-bind=\"click: showUpgradeTier\">\n\t\t<%= t(\"header.site.adfallback#body\") %>\n\t</div>\n\t<%- config.ads.abox_html %>\n</div>\n<div id=\"main\">\n<div id=\"flexbox\">\n\t<header>\n\t\t<h1>\n\t\t\t<img alt=\"<%= config.client.app_name %>\" src=\"images/logos/banner-black.svg\" data-bind=\"attr: { src: logoSrc }\" />\n\t\t</h1>\n\t\t<!-- ko if: extraHeaderText -->\n\t\t<small>\n\t\t\t<span id=\"extra_header_text\" data-bind=\"text: extraHeaderText, class: purpose, click: extraHeaderTextClick\"></span>\n\t\t</small>\n\t\t<!-- /ko -->\n\t\t<div class=\"hamburger\" id=\"hamburger\" aria-label=\"Menu\" role=\"button\" aria-controls=\"navigation\">\n\t\t\t<div class=\"hamburger-box\">\n\t\t\t\t<span class=\"hamburger-memo\"><%= t(\"header.site.menu#btn\") %></span>\n\t\t\t\t<div class=\"hamburger-inner\"></div>\n\t\t\t</div>\n\t\t</div>\n\t</header>\n\t<section data-hover=\"flex\">\n\t\t<div id=\"files_container\" aria-hidden=\"true\" data-bind=\"resizeFlex:{ group: flex.sizes, index: 0 }\">\n\t\t\t<div id=\"files_toolbar\" class=\"theme-header\">\n\t\t\t\t<%= t(\"editor.toolbar.files#title\") %>\n\t\t\t\t<!-- ko if: purpose() !== \"bucket\" -->\n\t\t\t\t<span id=\"files_toolbar_create\" title=\"<%= t(\"editor.toolbar.create#btn\") %>\"></span>\n\t\t\t\t<!-- /ko -->\n\t\t\t</div>\n\t\t\t<!-- ko if: purpose() !== \"bucket\" -->\n\t\t\t<div id=\"files_toolbar_lower\">\n\t\t\t\t<span id=\"files_toolbar_upload\" title=\"<%= t(\"editor.toolbar.upload#btn\") %>\"></span>\n\t\t\t\t<span id=\"files_toolbar_refresh\" title=\"<%= t(\"editor.toolbar.refresh#btn\") %>\"></span>\n\t\t\t\t<span id=\"files_toolbar_info\" title=\"<%= t(\"editor.toolbar.history#btn\") %>\"></span>\n\t\t\t</div>\n\t\t\t<!-- /ko -->\n\t\t\t<div id=\"files_list_container\" data-bind=\"css: { 'is_bucket': purpose() === 'bucket' }\">\n\t\t\t\t<ul data-bind=\"foreach: files\">\n\t\t\t\t\t<li data-bind=\"click: open, css: { 'file_active': isActive, 'modified': isModified }\">\n\t\t\t\t\t\t<!-- The ko if statements are for backwards compatibility between an old main.js and a new index.html -->\n\t\t\t\t\t\t<!-- ko if: dirpart -->\n\t\t\t\t\t\t<span class=\"dirpart\" data-bind=\"text: dirpart, visible: !!dirpart\"></span>\n\t\t\t\t\t\t<span class=\"filepart\" data-bind=\"text: filepart\"></span>\n\t\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t\t<!-- ko ifnot: dirpart -->\n\t\t\t\t\t\t<span data-bind=\"text: filename\"></span>\n\t\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<!-- ko if: purpose() !== \"bucket\" -->\n\t\t\t\t<span id=\"files_drop_notification\">\n\t\t\t\t\t<%= t(\"editor.toolbar.drop#ui\") %>\n\t\t\t\t</span>\n\t\t\t\t<!-- /ko -->\n\t\t\t</div>\n\t\t</div>\n\t\t<div id=\"open_container\" aria-hidden=\"true\" data-bind=\"resizeFlex:{ group: flex.sizes, index: 1 }\">\n\t\t\t<!-- ko with: openFile -->\n\t\t\t\t<div id=\"editor_toolbar\" class=\"theme-header\">\n\t\t\t\t\t<span id=\"editor_hamburger\" data-bind=\"click: buttonsShown.toggle()\" title=\"<%= t(\"editor.toolbar.toolbar#btn\") %>\"></span>\n\t\t\t\t\t<span id=\"editor_filename\" data-bind=\"text: filename, css: { 'modified': isModified }\"></span>\n\t\t\t\t\t<!-- ko if: runnable -->\n\t\t\t\t\t<span id=\"editor_runit\" title=\"<%= t(\"editor.toolbar.run#btn\") %>\" data-bind=\"click: runit\"><%= t(\"editor.toolbar.run#ui\") %> &#x25b6;</span>\n\t\t\t\t\t<!-- /ko -->\n\t\t\t\t</div>\n\t\t\t\t<div id=\"editor_btn_container\" data-bind=\"vizSafe: buttonsShown\" aria-hidden=\"true\">\n\t\t\t\t\t<span id=\"editor_download\" title=\"<%= t(\"editor.toolbar.download#btn\") %>\" data-bind=\"click: download\"></span>\n\t\t\t\t\t<!-- ko if: $root.purpose() !== \"bucket\" -->\n\t\t\t\t\t<span id=\"editor_rename\" title=\"<%= t(\"editor.toolbar.rename#btn\") %>\" data-bind=\"click: rename\"></span>\n\t\t\t\t\t<span id=\"editor_delete\" title=\"<%= t(\"editor.toolbar.delete#btn\") %>\" data-bind=\"click: deleteit\"></span>\n\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t<!-- ko if: editable -->\n\t\t\t\t\t<span id=\"editor_print\" title=\"<%= t(\"editor.toolbar.print#btn\") %>\" data-bind=\"click: print\"></span>\n\t\t\t\t\t<label id=\"wrap_checkbox\" title=\"<%= t(\"editor.toolbar.wrap#btn\") %>\"><input type=\"checkbox\" data-bind=\"checked: wrap\" /></label>\n\t\t\t\t\t<!-- ko if: $root.purpose() !== \"bucket\" && $root.authUser -->\n\t\t\t\t\t<span id=\"editor_share\" title=\"<%= t(\"editor.toolbar.share#btn\") %>\" data-bind=\"click: share\"></span>\n\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t<span id=\"editor_save\" title=\"<%= t(\"editor.toolbar.save#btn\") %>\" data-bind=\"click: save\"></span>\n\t\t\t\t\t<!-- /ko -->\n\t\t\t\t</div>\n\n\t\t\t\t<!-- ko if: editable -->\n\t\t\t\t\t<div id=\"editor\" data-bind=\"ace: { text: content, skin: $root.selectedSkin, wrap: wrap, octfile: $data }, css: { taller: !buttonsShown(), shorter: buttonsShown() }\"></div>\n\t\t\t\t<!-- /ko -->\n\t\t\t\t<!-- ko ifnot: editable -->\n\t\t\t\t\t<div class=\"editor_nofile\" data-bind=\"css: { taller: !buttonsShown(), shorter: buttonsShown() }\">\n\t\t\t\t\t\t<h2>\n\t\t\t\t\t\t\t<%= t(\"editor.unsupported.title\", { config }) %>\n\t\t\t\t\t\t</h2>\n\n\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\t<%= t(\"editor.unsupported.p1\") %>\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<ol>\n\t\t\t\t\t\t\t<li><%= t(\"editor.unsupported.li1\") %></li>\n\t\t\t\t\t\t\t<li><%= t(\"editor.unsupported.li2\") %></li>\n\t\t\t\t\t\t\t<li><%= t(\"editor.unsupported.li3\") %></li>\n\t\t\t\t\t\t</ol>\n\t\t\t\t\t</div>\n\t\t\t\t<!-- /ko -->\n\t\t\t<!-- /ko -->\n\t\t\t<!-- ko ifnot: openFile -->\n\t\t\t\t<div class=\"editor_nofile fullheight\">\n\t\t\t\t\t<h2>\n\t\t\t\t\t\t<%= t(\"editor.tips.title\") %>\n\t\t\t\t\t</h2>\n\t\t\t\t\t<!-- ko if: purpose() !== \"bucket\" -->\n\t\t\t\t\t<p>\n\t\t\t\t\t\t<%= t(\"editor.tips.p1\", { config }) %>\n\t\t\t\t\t</p>\n\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t<h3>\n\t\t\t\t\t\t<%= t(\"editor.tips.subhead1\") %>\n\t\t\t\t\t</h3>\n\t\t\t\t\t<p>\n\t\t\t\t\t\t<%= t(\"editor.tips.p2\") %>\n\t\t\t\t\t</p>\n\t\t\t\t\t<dl>\n\t\t\t\t\t\t<dt>Ctrl + Space</dt>\n\t\t\t\t\t\t<dd><%= t(\"editor.tips.dd1\") %></dd>\n\t\t\t\t\t\t<dt>Cmd/Ctrl + S</dt>\n\t\t\t\t\t\t<dd><%= t(\"editor.tips.dd2\") %></dd>\n\t\t\t\t\t\t<dt>Cmd/Ctrl/Win + R</dt>\n\t\t\t\t\t\t<dd><%= t(\"editor.tips.dd3\") %></dd>\n\t\t\t\t\t\t<dt>Cmd/Ctrl/Win + E</dt>\n\t\t\t\t\t\t<dd><%= t(\"editor.tips.dd4\") %></dd>\n\t\t\t\t\t</dl>\n\t\t\t\t\t<p>\n\t\t\t\t\t\t<a href=\"https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts#default-keyboard-shortcuts\" target=\"_blank\"><strong><%= t(\"editor.tips.a1\") %></strong></a>\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t<!-- /ko -->\n\t\t</div>\n\t\t<div id=\"output_panel\" data-bind=\"attr:{ style: flex.outputCss }\">\n\t\t\t<div id=\"plot_container\" aria-hidden=\"true\" data-bind=\"vizSafe: showPlot, with: currentPlot\">\n\t\t\t\t<div id=\"plot_figure_container\" title=\"<%= t(\"panel.plot.zoom#btn\") %>\" data-bind=\"click: $root.zoomPlot, css: { fullscreen: $root.plotZoomed }\">\n\t\t\t\t\t<div id=\"plot_svg_container\" data-bind=\"visible: complete, html: completeData\"></div>\n\t\t\t\t\t<div id=\"plot_loading\" data-bind=\"visible: !complete()\">\n\t\t\t\t\t\t<img src=\"images/spinner.svg\" alt=\"<%= t(\"common.loading#ui\") %>\"/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div id=\"plot_canvas_container\">\n\t\t\t\t\t\t<canvas id=\"plot_canvas\" width=\"600\" height=\"400\"></canvas>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div id=\"plot_controls_container\">\n\t\t\t\t\t<p>\n\t\t\t\t\t\t<span id=\"plot_prev_btn\" title=\"<%= t(\"panel.plot.prev#btn\") %>\" class=\"plot-nav-btn\" data-bind=\"click: $root.showPrevPlot, css: { disabled: $root.firstPlotShown }\">&laquo;</span>\n\t\t\t\t\t\t<span id=\"plot_next_btn\" title=\"<%= t(\"panel.plot.next#btn\") %>\" class=\"plot-nav-btn\" data-bind=\"click: $root.showNextPlot, css: { disabled: $root.lastPlotShown }\">&raquo;</span>\n\t\t\t\t\t</p>\n\t\t\t\t\t<p id=\"plot_info\">\n\t\t\t\t\t\t<%- t(\"panel.plot.description#ui\", { lineNumberHTML: '<span data-bind=\"text: lineNumber\"></span>' }) %>\n\t\t\t\t\t</p>\n\t\t\t\t\t<span id=\"plot_png_download_btn\" title=\"<%= t(\"panel.plot.download#btn\") %>\" data-bind=\"click: downloadPng\"></span>\n\t\t\t\t\t<span id=\"plot_svg_download_btn\" title=\"<%= t(\"panel.plot.download#btn\") %>\" data-bind=\"click: downloadSvg\"></span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div id=\"vars_console_container\"  data-hover=\"flex\">\n\t\t\t\t<div id=\"vars_panel\" aria-hidden=\"true\" data-bind=\"resizeFlex:{ group: flex.sizes, index: 2 }\">\n\t\t\t\t\t<div class=\"theme-header\">\n\t\t\t\t\t\t<%= t(\"panel.vars.title\") %>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div id=\"vars_content\">\n\t\t\t\t\t\t<ul data-bind=\"foreach: vars\">\n\t\t\t\t\t\t\t<li data-bind=\"click: showDetails, attr: { title: typeExplanation }, flash: value\">\n\t\t\t\t\t\t\t\t<span class=\"vars_type\" data-bind=\"text: typeString\"></span>\n\t\t\t\t\t\t\t\t<span class=\"vars_symb\" data-bind=\"text: symbol\"></span>\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div id=\"console_container\" data-bind=\"resizeFlex:{ group: flex.sizes, index: 3 }\">\n\t\t\t\t\t<div id=\"console_output_container\">\n\t\t\t\t\t\t<pre id=\"console\" data-bind=\"css: { 'console-wrap': consoleWhiteSpaceWrap }\"></pre>\n\t\t\t\t\t\t<!-- ko if: patreonValue() > 0 -->\n\t\t\t\t\t\t<div id=\"tier_background\"><div data-bind=\"class: authUser().patreon.tier_name, click: showUpgradeTier\" title=\"<%= t(\"panel.output.upgraded#ui\") %>\"></div></div>\n\t\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t\t<% if (config.client.onboarding) { %>\n\t\t\t\t\t\t<div id=\"instructor-promo\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t<span class=\"l1\"><%= t(\"promos.instructor.p1\", { config }) %></span>\n\t\t\t\t\t\t\t<span class=\"l2\"><%= t(\"promos.instructor.p2\") %></span>\n\t\t\t\t\t\t\t<span class=\"l3\" data-purpose=\"close\"><%= t(\"common.dismiss#btn\") %></span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<% } %>\n\t\t\t\t\t\t<div id=\"cwd_box\" style=\"display: none\" data-bind=\"visible: !!cwd()\">cwd: <span data-bind=\"text: cwd\"></span></div>\n\t\t\t\t\t\t<div id=\"type_here\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t<span class=\"l1\"><%= t(\"promos.prompt.title\") %></span>\n\t\t\t\t\t\t\t<span class=\"l2\"><%= t(\"promos.prompt.p1\") %></span>\n\t\t\t\t\t\t\t<!-- ko ifnot: authUser() -->\n\t\t\t\t\t\t\t<span class=\"l3\"><%= t(\"promos.prompt.p2\") %> <a href=\"javascript:void(0)\" id=\"sign_in_shortcut\"><%= t(\"promos.prompt.btn1\") %></a></span>\n\t\t\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<span id=\"plot_opener\" aria-hidden=\"true\" title=\"<%= t(\"panel.plot.expand#btn\") %>\" data-bind=\"click: togglePlot\"></span>\n\t\t\t\t\t\t<a id=\"agpl_icon\" title=\"<%= t(\"panel.output.github#btn\", { projectName: \"Octave Online Server\" }) %>\" href=\"https://github.com/octave-online/octave-online-server\" target=\"_blank\"><img src=\"images/agpl-logo.svg\" alt=\"AGPL Free Software\" /></a>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div id=\"console_prompt_container\">\n\n\t\t\t\t\t\t<div id=\"prompt_sign\" aria-hidden=\"true\">&#x300B;</div>\n\t\t\t\t\t\t<!--\n\t\t\t\t\t\tNOTE: Removing this inline height style and moving it to CSS\n\t\t\t\t\t\twill result in a race condition between Ace and the CSS file\n\t\t\t\t\t\tbeing loaded.\n\t\t\t\t\t\t-->\n\t\t\t\t\t\t<div id=\"prompt\" style=\"height: 28px\" aria-hidden=\"true\"></div>\n\n\t\t\t\t\t\t<div id=\"runtime_controls_container\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t<span id=\"signal\" title=\"<%= t(\"panel.prompt.sigint#btn\") %>\"></span>\n\t\t\t\t\t\t\t<span id=\"seconds_remaining_container\">\n\t\t\t\t\t\t\t\t<%= t(\"panel.prompt.seconds#ui\") %>\n\t\t\t\t\t\t\t\t<span id=\"seconds_remaining\">0</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span id=\"add_time_container\">\n\t\t\t\t\t\t\t\t<span class=\"clickable\" data-bind=\"click: addTime\"><%- t(\"panel.prompt.add#btn\", { numSecondsHTML: '<span data-bind=\"text: countdownExtraTimeSeconds\"></span>' }) %></span>\n\t\t\t\t\t\t\t\t<!-- ko if: (authUser() && (!authUser().patreon || !authUser().patreon.currently_entitled_tier)) -->\n\t\t\t\t\t\t\t\t/ <span class=\"clickable\" data-bind=\"click: showUpgradeTier\"><%= t(\"panel.prompt.upgrade#btn\") %></span>\n\t\t\t\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span id=\"payload_acknowledge_container\" class=\"clickable\" data-bind=\"click: acknowledgePayload\"><%= t(\"panel.prompt.resume#btn\") %></span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<div id=\"main_menu\" aria-hidden=\"true\"><div id=\"main_menu_content\">\n\t\t\t<% if (buildData.locales.length > 1) { %>\n\t\t\t\t<div class=\"i18n-language-selector\">\n\t\t\t\t\t<span class=\"flaticon-i18n\"></span>\n\t\t\t\t\t<select data-bind=\"options: availableLanguages,\n\t\t\t\t\t\toptionsText: function(lng) {\n\t\t\t\t\t\t\tif (Intl.DisplayNames) {\n\t\t\t\t\t\t\t\treturn new Intl.DisplayNames(lng, { type: 'language' }).of(lng);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn lng;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, value: currentLanguage\"></select>\n\t\t\t\t</div>\n\t\t\t<% } %>\n\t\t\t<!-- ko if: (!authUser() && purpose() !== \"student\") -->\n\t\t\t\t<svg width=\"200\" height=\"40\" class=\"login_btn\" id=\"sign_in_with_google\">\n\t\t\t\t\t<image xlink:href=\"images/sign-in-with-google.svg\" src=\"images/sign-in-with-google.png\" width=\"200\" height=\"40\" alt=\"<%= t(\"header.sidebar.signingoogle#btn\") %>\" />\n\t\t\t\t</svg>\n\t\t\t\t<svg width=\"200\" height=\"40\" class=\"login_btn\" id=\"sign_in_with_password\">\n\t\t\t\t\t<image xlink:href=\"images/sign-in-with-password.svg\" src=\"images/sign-in-with-password.png\" width=\"200\" height=\"40\" alt=\"<%= t(\"header.sidebar.signingoogle#btn\") %>\" />\n\t\t\t\t</svg>\n\t\t\t\t<svg width=\"200\" height=\"40\" class=\"login_btn\" id=\"sign_in_with_email\">\n\t\t\t\t\t<image xlink:href=\"images/sign-in-with-email.svg\" src=\"images/sign-in-with-email.png\" width=\"200\" height=\"40\" alt=\"<%= t(\"header.sidebar.signinemail#btn\") %>\" />\n\t\t\t\t</svg>\n\t\t\t<!-- /ko -->\n\t\t\t<!-- ko if: (authUser() && purpose() !== \"student\") -->\n\t\t\t\t<div id=\"userbox\">\n\t\t\t\t\t<span data-bind=\"text: authUser().name\"></span>\n\t\t\t\t\t<a id=\"logout_icon\" href=\"javascript:void(0)\" title=\"Sign Out\" data-bind=\"click: doLogout\"><%= t(\"header.sidebar.signout#btn\") %></a>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"site-control-item\" data-bind=\"click: showUpgradeTier\">\n\t\t\t\t\t<a href=\"javascript:void(0)\"><%= t(\"header.sidebar.patreon#btn\") %></a>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"share-url-box\">\n\t\t\t\t\t<!-- ko if: sharingEnabled -->\n\t\t\t\t\t\t<!-- ko if: purpose() !== \"project\" -->\n\t\t\t\t\t\t<em><%= t(\"header.sidebar.sharedesc#body\") %></em>\n\t\t\t\t\t\t<input data-bind=\"value: shareLink\" onclick=\"this.select()\" readonly=\"readonly\" />\n\t\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t\t<div class=\"site-control-item\" data-bind=\"click: toggleSharing\">\n\t\t\t\t\t\t\t<a href=\"javascript:void(0)\"><%= t(\"header.sidebar.disablesharing#btn@2\") %></a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t<!-- ko ifnot: sharingEnabled -->\n\t\t\t\t\t\t<div class=\"site-control-item\" data-bind=\"click: toggleSharing\">\n\t\t\t\t\t\t\t<!-- ko if: purpose() === \"project\" -->\n\t\t\t\t\t\t\t<a href=\"javascript:void(0)\" title=\"<%= t(\"header.sidebar.enablesharingp#tooltip\") %>\"><%= t(\"header.sidebar.enablesharing#btn@2\") %></a>\n\t\t\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t\t\t<!-- ko if: purpose() !== \"project\" -->\n\t\t\t\t\t\t\t<a href=\"javascript:void(0)\" title=\"<%= t(\"header.sidebar.enablesharing#tooltip\") %>\"><%= t(\"header.sidebar.enablesharing#btn@2\") %></a>\n\t\t\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t\t</div>\n\t\t\t\t\t<!-- /ko -->\n\t\t\t\t</div>\n\t\t\t\t<div class=\"site-control-item\" data-bind=\"click: showCreateNewProject\">\n\t\t\t\t\t<a href=\"javascript:void(0)\"><%= t(\"header.sidebar.createnewproject#btn\") %></a>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"site-control-item\" id=\"create-password-btn\" data-bind=\"click: showChangePassword\">\n\t\t\t\t\t<a href=\"javascript:void(0)\"><%= t(\"header.sidebar.changepassword#btn\") %></a>\n\t\t\t\t</div>\n\t\t\t<!-- /ko -->\n\t\t\t<!-- ko foreach: instructorPrograms -->\n\t\t\t\t<div class=\"instructor-programs\">\n\t\t\t\t\t<strong><%= t(\"header.students.title\") %></strong><br/>\n\t\t\t\t\t<em><%= t(\"header.students.subhead1\") %> <span data-bind=\"text: program\"></span></em>\n\t\t\t\t\t<ul data-bind=\"foreach: users\">\n\t\t\t\t\t\t<li><a data-bind=\"attr: { href: shareUrl }, text: displayName\" target=\"_blank\"></a> (<a data-bind=\"click: $root.unenrollStudent\" title=\"<%= t(\"header.students.unenroll#btn\") %>\">&#x232B;</a> <a data-bind=\"click: $root.reenrollStudent\" title=\"<%= t(\"header.students.reenroll#btn\") %>\">&#x2794;</a>)</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t<!-- /ko -->\n\t\t\t<div class=\"site-control-item\" id=\"feedback-btn\" data-bind=\"click: openUserVoice\">\n\t\t\t\t<a href=\"javascript:void(0)\"><%= t(\"header.sidebar.support#btn\") %></a>\n\t\t\t</div>\n\t\t\t<div class=\"site-control-item\">\n\t\t\t\t<a id=\"showprivacy\"><%= t(\"header.sidebar.legal#btn\") %></a>\n\t\t\t</div>\n\t\t\t<div class=\"site-control-item\">\n\t\t\t\t<a href=\"https://github.com/octave-online/octave-online-server\" target=\"_blank\"><%= t(\"header.sidebar.github#btn\") %></a>\n\t\t\t</div>\n\t\t\t<div class=\"site-control-item\" id=\"change-skin\">\n\t\t\t\t<a href=\"javascript:void(0)\"><%= t(\"header.sidebar.changetheme#btn\") %></a>\n\t\t\t</div>\n\t\t\t<div class=\"site-control-item\" id=\"reset-layout\">\n\t\t\t\t<a href=\"javascript:void(0)\"><%= t(\"header.sidebar.changelayout#btn\") %></a>\n\t\t\t</div>\n\t\t\t<div id=\"twitter-follow-holder\" class=\"site-control-item\">\n\t\t\t\t<a href=\"https://twitter.com/OctaveOnline\" class=\"twitter-follow-button\" data-show-count=\"false\"\n\t\t\t\tdata-lang=\"en\" data-size=\"large\" data-show-screen-name=\"false\" target=\"_blank\"><%= t(\"header.sidebar.twitter#btn\") %></a>\n\t\t\t</div>\n\t\t\t<div class=\"preference-inline-item\">\n\t\t\t\t<label>\n\t\t\t\t\t<input type=\"checkbox\" data-bind=\"checked: inlinePlots\" />\n\t\t\t\t\t<%= t(\"header.sidebar.inlineplots#btn\") %>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t\t<div class=\"preference-inline-item\">\n\t\t\t\t<label>\n\t\t\t\t\t<input type=\"checkbox\" data-bind=\"checked: consoleWhiteSpaceWrap\" />\n\t\t\t\t\t<%= t(\"header.sidebar.textwrap#btn\") %>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t\t<!-- ko if: allBuckets().length > 0 -->\n\t\t\t\t<div class=\"all-buckets\">\n\t\t\t\t\t<strong><%= t(\"header.buckets.title@2\") %></strong>\n\t\t\t\t\t<ul data-bind=\"foreach: allBuckets\">\n\t\t\t\t\t\t<li>\n\t\t\t\t\t\t\t<a data-bind=\"click: deleteit\" title=\"<%= t(\"header.buckets.delete#btn@2\") %>\" class=\"bucket-delete\">&#x232B;</a>\n\t\t\t\t\t\t\t<!-- ko if: butype() === \"readonly\" -->\n\t\t\t\t\t\t\t<span class=\"bucket-icon-bucket\" title=\"<%= t(\"common.bucket\") %>\"></span>\n\t\t\t\t\t\t\t<!-- /ko -->\n\t\t\t\t\t\t\t<!-- ko ifnot: butype() === \"readonly\" -->\n\t\t\t\t\t\t\t<span class=\"bucket-icon-project\" title=\"<%= t(\"common.project\") %>\"></span>\n\t\t\t\t\t\t\t<!-- /ko -->\n\n\t\t\t\t\t\t\t<a data-bind=\"attr: { href: url }\" target=\"_blank\" class=\"bucket-name\"><span class=\"bucket-shortlink-prefix\"><%= config.redirect.hostname %>/</span><span class=\"bucket-shortlink-suffix\" data-bind=\"text: shortlink\"></span></a>\n\t\t\t\t\t\t\t<br/>\n\t\t\t\t\t\t\t<span data-bind=\"text: createdTimeString\" class=\"bucket-time\"></span>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t<!-- /ko -->\n\t\t\t<div id=\"footnotes\">\n\t\t\t\tIcons made by <a href=\"http://www.flaticon.com/authors/bogdan-rosu\" title=\"Bogdan Rosu\" target=\"_blank\">Bogdan Rosu</a>, <a href=\"http://www.flaticon.com/authors/daniel-bruce\" title=\"Daniel Bruce\" target=\"_blank\">Daniel Bruce</a>, <a href=\"http://www.flaticon.com/authors/sarfraz-shoukat\" title=\"Sarfraz Shoukat\" target=\"_blank\">Sarfraz Shoukat</a>, <a href=\"http://www.flaticon.com/authors/icomoon\" title=\"Icomoon\" target=\"_blank\">Icomoon</a>, <a href=\"http://www.flaticon.com/authors/freepik\" title=\"Freepik\" target=\"_blank\">Freepik</a>, <a href=\"http://www.flaticon.com/authors/icon-works\" title=\"Icon Works\" target=\"_blank\">Icon Works</a>, <a href=\"http://www.flaticon.com/authors/plainicon\" title=\"Plainicon\" target=\"_blank\">Plainicon</a>, and <a href=\"http://www.flaticon.com/authors/simpleicon\" title=\"SimpleIcon\" target=\"_blank\">SimpleIcon</a> are from <a href=\"http://www.flaticon.com\" title=\"Flaticon\" target=\"_blank\">www.flaticon.com</a> and licensed by <a href=\"http://creativecommons.org/licenses/by/3.0/\" target=\"_blank\" title=\"Creative Commons BY 3.0\">CC BY 3.0</a>.\n\t\t\t</div>\n\t\t</div></div>\n\t</section>\n</div>\n\n<% if (config.client.onboarding) { %>\n<div id=\"onboarding\" data-purpose=\"popover\"><div><div>\n\t<h2><%= t(\"promos.welcome.title\", { config }) %></h2>\n\n\t<p>\n\t\t<%= t(\"promos.welcome.p1\", { config }) %>\n\t</p>\n\t<figure>\n\t\t<figcaption>\n\t\t\t<%= t(\"promos.welcome.p2\") %>\n\t\t</figcaption>\n\t\t<img src=\"images/demo/fminbnd-prompt.png\" alt=\"fminbnd(@(x) x .* log(x), 0, 1)\" style=\"max-width: 362px;\"/>\n\t\t<img src=\"images/demo/fminbnd-result.png\" alt=\"0.36788\" style=\"max-width: 366px;\"/>\n\t</figure>\n\t<figure>\n\t\t<figcaption>\n\t\t\t<%= t(\"promos.welcome.p3\") %>\n\t\t</figcaption>\n\t\t<img src=\"images/demo/sombrero-prompt.png\" alt=\"sombrero()\" style=\"max-width: 150px;\"/>\n\t\t<img src=\"images/demo/sombrero-result.jpg\" alt=\"plot showing a sombrero\" style=\"max-width: 531px;\"/>\n\t</figure>\n\t<figure>\n\t\t<figcaption>\n\t\t\t<%= t(\"promos.welcome.p4\") %>\n\t\t</figcaption>\n\t\t<img src=\"images/demo/editor.png\" alt=\"cstr_reactor.m\"/>\n\t</figure>\n\t<button id=\"onboarding-start\" data-purpose=\"close\"><%= t(\"promos.welcome.start#btn\", { config }) %></button>\n\t<span class=\"closebtn\" data-purpose=\"close\"></span>\n</div></div></div>\n<% } %>\n\n<div id=\"announcement-box\" data-purpose=\"popover\" data-announcement-display=\"<%= config.client.announcement_display %>\" aria-hidden=\"true\"><div><div>\n\t<%- config.client.announcement_html %>\n\t<span class=\"closebtn\" data-purpose=\"close\"></span>\n</div></div></div>\n\n<div id=\"welcome_back\" data-purpose=\"popover\" aria-hidden=\"true\"><div><div>\n\t<h2 class=\"popover-title-bar\">\n\t\t<%= t(\"modals.welcome_back.title\") %>\n\t</h2>\n\t<p><%= t(\"modals.welcome_back.p1\", { config }) %></p>\n\t<button data-purpose=\"close\"><%= t(\"modals.welcome_back.btn1\") %></button>\n\t<span class=\"closebtn\" data-purpose=\"close\"></span>\n</div></div></div>\n\n<div id=\"privacy\" data-purpose=\"popover\" aria-hidden=\"true\"><div><div>\n\t<article><%- buildData.privacy_html %></article>\n\t<span class=\"closebtn\" data-purpose=\"close\"></span>\n</div></div></div>\n\n<div id=\"email_token\" data-purpose=\"popover\" aria-hidden=\"true\"><div><div>\n\t<label for=\"emailField\"><%= t(\"modals.email.label1\") %></label>\n\t<form action=\"./auth/tok\" method=\"post\">\n\t\t<input type=\"email\" id=\"emailField1\" name=\"s\" placeholder=\"<%= t(\"common.email#ph\") %>\" />\n\t\t<input type=\"password\" id=\"passwordField1\" name=\"p\" />\n\t\t<div class=\"g-recaptcha\" data-sitekey=\"<%= config.recaptcha.siteKey %>\"></div>\n\t\t<input type=\"submit\" value=\"<%= t(\"common.submit#btn\") %>\" />\n\t</form>\n\t<p><%= t(\"modals.email.p1\") %></p>\n\t<span class=\"closebtn\" data-purpose=\"close\"></span>\n</div></div></div>\n\n<div id=\"email_password\" data-purpose=\"popover\" aria-hidden=\"true\"><div><div>\n\t<p><strong><%= t(\"modals.password.subhead1\") %></strong> <%= t(\"modals.password.p1\") %></p>\n\t<form action=\"./auth/pwd\" method=\"post\">\n\t\t<input type=\"email\" id=\"emailField2\" name=\"s\" placeholder=\"<%= t(\"common.email#ph\") %>\" />\n\t\t<input type=\"password\" name=\"p\" placeholder=\"<%= t(\"common.password#ph\") %>\" />\n\t\t<!-- <div class=\"g-recaptcha\" data-sitekey=\"<%= config.recaptcha.siteKey %>\"></div> -->\n\t\t<!-- ko if: window.location.protocol === \"http:\" -->\n\t\t<p><strong><%= t(\"common.caution#label\") %></strong> <%= t(\"common.https#p\") %> <a data-bind=\"attr: { href: 'https:' + window.location.href.substr(5) }\"><%= t(\"common.https#btn\") %></a></p>\n\t\t<!-- /ko -->\n\t\t<input type=\"submit\" value=\"<%= t(\"common.submit#btn\") %>\" />\n\t</form>\n\t<span class=\"closebtn\" data-purpose=\"close\"></span>\n</div></div></div>\n\n<div id=\"change_password\" data-purpose=\"popover\" aria-hidden=\"true\"><div><div>\n\t<h2 class=\"popover-title-bar\">\n\t\t<%= t(\"modals.changepwd.title\") %>\n\t</h2>\n\t<p><%= t(\"modals.changepwd.p1\") %></p>\n\t<input type=\"password\" id=\"new_pwd\" placeholder=\"<%= t(\"common.password#ph\") %>\" />\n\t<!-- ko if: window.location.protocol === \"http:\" -->\n\t<p><strong><%= t(\"common.caution#label\") %></strong> <%= t(\"common.https#p\") %> <a data-bind=\"attr: { href: 'https:' + window.location.href.substr(5) }\"><%= t(\"common.https#btn\") %></a></p>\n\t<!-- /ko -->\n\t<button id=\"save-password-btn\"><%= t(\"modals.changepwd.submit#btn\") %></button>\n\t<span class=\"closebtn\" data-purpose=\"close\"></span>\n</div></div></div>\n\n<div id=\"bucket_info\" data-purpose=\"popover\" aria-hidden=\"true\"><div><div>\n\t<!-- ko with: currentBucket -->\n\t<h2 class=\"popover-title-bar\">\n\t\t<!-- ko if: butype() === \"readonly\" -->\n\t\t<%= t(\"common.bucket\") %>\n\t\t<!-- /ko -->\n\t\t<!-- ko ifnot: butype() === \"readonly\" -->\n\t\t<%= t(\"common.project\") %>\n\t\t<!-- /ko -->\n\t\t<span data-bind=\"text: shortlink\"></span>\n\t</h2>\n\t<div class=\"shortlink\">\n\t\t<span class=\"shortlink-url\" data-bind=\"text: shortUrl\"></span>\n\t\t<!-- ko if: isOwnedByCurrentUser -->\n\t\t<span class=\"edit-shortlink\" title=\"<%= t('modals.bucketinfo.btn2') %>\" data-bind=\"click: editShortlink\"></span>\n\t\t<!-- /ko -->\n\t</div>\n\t<p>\n\t\t<%= t(\"modals.bucketinfo.p1\") %>\n\t</p>\n\t<p>\n\t\t<a class=\"action-link\" data-bind=\"click: $root.showCloneAsProject\">\n\t\t\t<!-- ko if: butype() === \"readonly\" -->\n\t\t\t<%= t(\"modals.bucketinfo.btn1\") %>\n\t\t\t<!-- /ko -->\n\t\t\t<!-- ko ifnot: butype() === \"readonly\" -->\n\t\t\t<%= t(\"modals.bucketinfo.btn1p\") %>\n\t\t\t<!-- /ko -->\n\t\t</a>\n\t</p>\n\t<!-- ko with: baseModel -->\n\t<p>\n\t\t<%= t(\"modals.bucketinfo.label2\") %>\n\t\t<a data-bind=\"attr: { href: url }\" target=\"_blank\">\n\t\t\t<!-- ko if: butype() === \"readonly\" -->\n\t\t\t<%= t(\"common.bucket\") %>\n\t\t\t<!-- /ko -->\n\t\t\t<!-- ko ifnot: butype() === \"readonly\" -->\n\t\t\t<%= t(\"common.project\") %>\n\t\t\t<!-- /ko -->\n\t\t\t<span data-bind=\"text: shortlink\"></span>\n\t\t</a>\n\t</p>\n\t<!-- /ko -->\n\t<p>\n\t\t<%= t(\"modals.bucketinfo.label1\") %>\n\t\t<span data-bind=\"text: createdTimeString\"></span>\n\t</p>\n\t<!-- /ko -->\n\t<span class=\"closebtn\" data-purpose=\"close\"></span>\n</div></div></div>\n\n<div id=\"create_bucket\" data-purpose=\"popover\" aria-hidden=\"true\"><div><div>\n\t<!-- ko with: newBucket -->\n\t<h2 class=\"popover-title-bar\">\n\t\t<!-- ko if: butype() === \"readonly\" -->\n\t\t<%= t(\"modals.createbucket.title\") %>\n\t\t<!-- /ko -->\n\t\t<!-- ko ifnot: butype() === \"readonly\" -->\n\t\t<%= t(\"modals.createbucket.titlep\") %>\n\t\t<!-- /ko -->\n\t</h2>\n\t<div id=\"create-bucket-content\">\n\t\t<p><%= t(\"modals.createbucket.p1@2\") %></p>\n\t\t<div class=\"bucket-butype\">\n\t\t\t<label><input type=\"radio\" value=\"readonly\" data-bind=\"checked: butype\" /><span><%= t(\"common.bucket\") %></span></label>\n\t\t\t<label><input type=\"radio\" value=\"editable\" data-bind=\"checked: butype\" /><span><%= t(\"common.project\") %></span></label>\n\t\t</div>\n\t\t<h3>\n\t\t\t<!-- ko if: butype() === \"readonly\" -->\n\t\t\t<%= t(\"modals.createbucket.subhead1@2\") %>\n\t\t\t<!-- /ko -->\n\t\t\t<!-- ko ifnot: butype() === \"readonly\" -->\n\t\t\t<%= t(\"modals.createbucket.subhead1p\") %>\n\t\t\t<!-- /ko -->\n\t\t</h3>\n\t\t<p><%= t(\"modals.createbucket.p2\") %></p>\n\t\t<fieldset class=\"bucket-files-fieldset left\">\n\t\t\t<legend><%= t(\"modals.createbucket.subhead2\") %></legend>\n\t\t\t<select data-bind=\"options: filesNotIncluded, optionsText: 'filename', selectedOptions: selectedLeft\" multiple></select>\n\t\t</fieldset>\n\t\t<fieldset class=\"bucket-files-fieldset right\">\n\t\t\t<legend>\n\t\t\t\t<!-- ko if: butype() === \"readonly\" -->\n\t\t\t\t<%= t(\"modals.createbucket.subhead3\") %>\n\t\t\t\t<!-- /ko -->\n\t\t\t\t<!-- ko ifnot: butype() === \"readonly\" -->\n\t\t\t\t<%= t(\"modals.createbucket.subhead3p\") %>\n\t\t\t\t<!-- /ko -->\n\t\t\t</legend>\n\t\t\t<select data-bind=\"options: files, optionsText: 'filename', selectedOptions: selectedRight\" multiple></select>\n\t\t</fieldset>\n\t\t<p class=\"bucket-file-move-btns\">\n\t\t\t<button data-bind=\"click: moveRightToLeft\">⇦</button>\n\t\t\t<button data-bind=\"click: moveLeftToRight\">⇨</button>\n\t\t</p>\n\t\t<h3><%= t(\"modals.createbucket.subhead5\") %></h3>\n\t\t<p><%= t(\"modals.createbucket.p5\", { config }) %></p>\n\t\t<p><label>\n\t\t\t<tt><%= t(\"constants.shortlink_prefix\", { config }) %></tt>\n\t\t\t<input type=\"text\" data-bind=\"value: shortlink\" />\n\t\t</label></p>\n\t\t<!-- ko if: butype() === \"readonly\" -->\n\t\t<h3><%= t(\"modals.createbucket.subhead4\") %></h3>\n\t\t<p><%= t(\"modals.createbucket.p3\") %></p>\n\t\t<p><label>\n\t\t\t<strong><%= t(\"modals.createbucket.label1\") %></strong>\n\t\t\t<select data-bind=\"options: textFiles, optionsText: 'filename', optionsCaption: 'none', value: main\"></select>\n\t\t</label></p>\n\t\t<!-- /ko -->\n\t\t<p class=\"last\">\n\t\t\t<!-- ko if: butype() === \"readonly\" -->\n\t\t\t<%= t(\"modals.createbucket.p4\") %>\n\t\t\t<!-- /ko -->\n\t\t\t<!-- ko ifnot: butype() === \"readonly\" -->\n\t\t\t<%= t(\"modals.createbucket.p4p\") %>\n\t\t\t<!-- /ko -->\n\t\t</p>\n\t</div>\n\t<div id=\"create-bucket-bar\">\n\t\t<button id=\"create-bucket-btn\" data-bind=\"click: createOnServer, visible: showCreateButton\">\n\t\t\t<!-- ko if: butype() === \"readonly\" -->\n\t\t\t<%= t(\"modals.createbucket.submit#btn\") %> ➡\n\t\t\t<!-- /ko -->\n\t\t\t<!-- ko ifnot: butype() === \"readonly\" -->\n\t\t\t<%= t(\"modals.createbucket.submitp#btn\") %> ➡\n\t\t\t<!-- /ko -->\n\t\t</button>\n\t</div>\n\t<img id=\"create-bucket-spinner\" src=\"images/spinner.svg\" width=\"40\" data-bind=\"visible: !showCreateButton()\" />\n\t<!-- /ko -->\n\t<span class=\"closebtn\" data-purpose=\"close\" data-bind=\"click: clearBucket\"></span>\n</div></div></div>\n\n<div id=\"upgrade_to_tier\" data-purpose=\"popover\" aria-hidden=\"true\"><div><div>\n\t<h2 class=\"popover-title-bar\">\n\t\t<%= t(\"modals.upgrade.title\") %>\n\t</h2>\n\t<!-- ko ifnot: authUser -->\n\t<p class=\"action-links\">\n\t\t<a href=\"/auth/patreon/plans\" target=\"_blank\"><%= t(\"modals.upgrade.btn1\") %></a> / <a href=\"/auth/patreon\"><%= t(\"modals.upgrade.btn2\") %></a>\n\t</p>\n\t<!-- /ko -->\n\t<!-- ko with: authUser -->\n\t<!-- ko ifnot: $data.patreon -->\n\t<p class=\"action-links\">\n\t\t<a href=\"/auth/patreon/plans\" target=\"_blank\"><%= t(\"modals.upgrade.btn1\") %></a> / <a href=\"/auth/patreon\"><%= t(\"modals.upgrade.btn2\") %></a>\n\t</p>\n\t<!-- /ko -->\n\t<!-- ko if: $data.patreon -->\n\t<!-- ko if: patreon.currently_entitled_amount_cents -->\n\t<p class=\"bigger\">\n\t\t<%- t(\"modals.upgrade.p1\", { pledgeAmountHTML: `<span data-bind=\"text: (patreon.currently_entitled_amount_cents/100).toLocaleString(undefined, { style: 'currency', currency: 'usd' })\"></span>` }) %>\n\t</p>\n\t<!-- /ko -->\n\t<p class=\"action-links\">\n\t\t<a href=\"/auth/patreon/plans\" target=\"_blank\"><%= t(\"modals.upgrade.btn3\") %></a>\n\t</p>\n\t<p class=\"minor-action-links\">\n\t\t<%= t(\"modals.upgrade.p2\") %> <a href=\"/auth/patreon/revoke\"><%= t(\"modals.upgrade.btn4\") %></a>\n\t</p>\n\t<!-- /ko -->\n\t<!-- /ko -->\n\t<p>\n\t\t<%= t(\"modals.upgrade.p3\", { config }) %>\n\t</p>\n\t<span class=\"closebtn\" data-purpose=\"close\"></span>\n</div></div></div>\n\n<div id=\"file_history_box\" data-purpose=\"popover\" aria-hidden=\"true\"><div><div>\n\t<h2 class=\"popover-title-bar\">\n\t\t<%= t(\"modals.history.title\") %>\n\t</h2>\n\t<!-- ko if: purpose() !== \"project\" -->\n\t<h3><%= t(\"modals.history.subtitle1\") %></h3>\n\t<p>\n\t\t<%= t(\"modals.history.p1\") %>\n\t</p>\n\t<button data-bind=\"click: openGit\"><%= t(\"modals.history.btn1\") %></button>\n\t<!-- /ko -->\n\t<h3><%= t(\"modals.history.subtitle2\") %></h3>\n\t<p>\n\t\t<%= t(\"modals.history.p2\") %>\n\t</p>\n\t<button data-bind=\"click: generateZip\"><%= t(\"modals.history.btn2\") %></button>\n\t<span class=\"closebtn\" data-purpose=\"close\"></span>\n</div></div></div>\n\n<div id=\"site_loading\">\n\t<div id=\"cosine-pulse\"></div>\n\t<div id=\"site_loading_patience\" class=\"patience\" aria-hidden=\"true\"><%= t(\"modals.patience.p1\") %></div>\n\t<div id=\"site_loading_more_patience\" class=\"patience\" aria-hidden=\"true\"><%= t(\"modals.patience.p2\") %></div>\n</div>\n\n<% if (config.client.onboarding) { %>\n<div id=\"sync-promo\" aria-hidden=\"true\">\n\t<span class=\"l1\"><%= t(\"promos.sync.p1\") %></span>\n\t<span class=\"l2\"><%= t(\"promos.sync.p2\") %></span>\n\t<span class=\"l3\" data-purpose=\"close\"><%= t(\"common.dismiss#btn\") %></span>\n</div>\n<div id=\"create-bucket-promo\" aria-hidden=\"true\">\n\t<span class=\"l1\"><%= t(\"promos.bucketcreate.p1\") %></span>\n\t<span class=\"l2\"><%= t(\"promos.bucketcreate.p2\") %></span>\n\t<span class=\"l3\" data-purpose=\"close\"><%= t(\"common.dismiss#btn\") %></span>\n</div>\n<div id=\"share-promo\" aria-hidden=\"true\">\n\t<span class=\"l0\"><%= t(\"promos.sharing.subhead1\") %></span>\n\t<span class=\"l1\"><%= t(\"promos.sharing.p1@2\") %></span>\n\t<span class=\"l3\" data-purpose=\"close\"><%= t(\"common.dismiss#btn\") %></span>\n</div>\n<div id=\"login-promo\" aria-hidden=\"true\">\n\t<span class=\"l1\"><%= t(\"promos.login.p1\") %></span>\n\t<span class=\"l3\"><%= t(\"promos.login.p2\") %></span>\n\t<span class=\"l4\" data-purpose=\"close\"><%= t(\"common.dismiss#btn\") %></span>\n</div>\n<div id=\"bucket-promo\" aria-hidden=\"true\">\n\t<span class=\"l1\"><%= t(\"promos.bucketsbacklink.p1\", { config }) %></span>\n\t<span class=\"l2\" data-purpose=\"close\"><%= t(\"common.dismiss#btn\") %></span>\n</div>\n<% } %>\n\n<script type=\"text/javascript\">\nwindow.oo_translations = <%- JSON.stringify(oo_translations) %>;\nwindow.oo_currentLanguage = <%- JSON.stringify(currentLanguage) %>;\nwindow.oo_availableLanguages = <%- JSON.stringify(buildData.locales) %>;\n</script>\n\n<% if (buildData.useDistPaths) { %>\n<script type=\"text/javascript\" src=\"js/require.js\"></script>\n<% } else { %>\n<script type=\"text/javascript\" src=\"vendor/requirejs/require.js\"></script>\n<script type=\"text/javascript\" src=\"main.js\"></script>\n<% } %>\n\n<script type=\"text/javascript\">\nrequire.config({\n\turlArgs: \"<%= buildData.jsTimestamp %>\",\n\twaitSeconds: 0\n});\nrequire([\"js/app\"], function(){\n\trequire([\"js/runtime\"]);\n//var OctMethods = require(\"js/client\"); OctMethods.console.writeError(\"NOTICE: Announcement Here\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\");\n// var OctMethods = require(\"js/client\"); OctMethods.console.writeUrl(\"/auth/patreon\"); OctMethods.console.write(\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\");\n});\n</script>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "front/src/views/login_error.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or (at your\noption) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\nFITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License\nfor more details.\n\nYou should have received a copy of the GNU Affero General Public License along\nwith Octave Online Server.  If not, see <https://www.gnu.org/licenses/>.\n%>\n<%- include(\"partials/head\") %>\n\n<p>\n\t<img src=\"/images/logos/icon-black.svg\" width=\"120px\" />\n</p>\n<h1>Login Failure</h1>\n<p>\n\tYou could not be logged in.  This could be because you did not agree to the terms of Google or because you entered an incorrect email login token.  Please go back and try again.\n</p>\n<hr/>\n<p>\n\tIf you need help, contact support:\n</p>\n<iframe src=\"<%= config.email.supportUrl %>\" id=\"support\"></iframe>\n\n<%- include(\"partials/foot\") %>\n"
  },
  {
    "path": "front/src/views/partials/foot.ejs",
    "content": "</div>\n</body>\n</html>\n"
  },
  {
    "path": "front/src/views/partials/head.ejs",
    "content": "<!DOCTYPE html>\n\n<%\nif (config.client.theme_collection === \"official\") {\n\tcolor = \"#FF5C4A\"; // OO Fire Red\n} else {\n\tcolor = \"#AD928E\"; // OO Server Brown\n}\n%>\n\n<html>\n<head>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<style type=\"text/css\">\nbody {\n\twidth: 75%;\n\tbackground-color: <%= color %>;\n\tmargin: 0 auto;\n\tpadding: 0;\n\tfont-family: sans-serif;\n}\n@media (max-width: 480px) {\n\tbody {\n\t\twidth: 100%;\n\t}\n}\n#main {\n\tbackground-color: #fff;\n\tmargin: 0;\n\tpadding: 10px;\n\ttext-align: center;\n}\n#support {\n\tdisplay: block;\n\twidth: 90%;\n\theight: 400px;\n\tmargin: 0 auto;\n}\ninput[type=text] {\n\tfont-size: 2em;\n\ttext-align: center;\n\tborder: 2px solid <%= color %>; /* OO Fire Red */\n\n\tdisplay: block;\n\twidth: 90%;\n\tmargin: 1ex auto;\n}\ninput[type=submit] {\n\tfont-size: 1.5em;\n}\n</style>\n</head>\n<body>\n<div id=\"main\">\n"
  },
  {
    "path": "front/src/views/patreon_link_error.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or (at your\noption) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\nFITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License\nfor more details.\n\nYou should have received a copy of the GNU Affero General Public License along\nwith Octave Online Server.  If not, see <https://www.gnu.org/licenses/>.\n%>\n<%- include(\"partials/head\") %>\n\n<p>\n\t<img src=\"/images/logos/icon-black.svg\" width=\"120px\" />\n</p>\n<h1>Patreon Already Linked</h1>\n<p>\n\tYour Patreon account with ID <%= user_id %> cannot be linked to your Octave Online account \"<%= new_email %>\" because it is already linked with the one for \"<%= old_email %>\".\n</p>\n<hr/>\n<p>\n\tIf you need help, contact support:\n</p>\n<iframe src=\"<%= config.email.supportUrl %>\" id=\"support\"></iframe>\n\n<%- include(\"partials/foot\") %>\n"
  },
  {
    "path": "front/src/views/token_page.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or (at your\noption) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\nFITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License\nfor more details.\n\nYou should have received a copy of the GNU Affero General Public License along\nwith Octave Online Server.  If not, see <https://www.gnu.org/licenses/>.\n%>\n<%- include(\"partials/head\") %>\n\n<p>\n\t<img src=\"/images/logos/icon-black.svg\" width=\"120px\" />\n</p>\n<h1>Email Sign-In</h1>\n<form action=\"./tok\" method=\"get\">\n\t<p>\n\t\t<label for=\"token\">An email has been sent to you with your login token.  When you receive your token, enter it here:</label>\n\t</p>\n\t<input type=\"hidden\" name=\"u\" value=\"<%= query ? query.s : '' %>\" />\n\t<p>\n\t\t<input type=\"text\" name=\"t\" id=\"t\" value=\"\" autofocus=\"autofocus\" placeholder=\"Enter Token\" />\n\t</p>\n\t<p>\n\t\t<input type=\"submit\" value=\"Submit\" />\n\t</p>\n</form>\n<p>\n\tAfter you sign in, you can use the \"Change Password\" option in the menu to set a new password.  If you don't set a password, you can have Octave Online email you a token whenever you sign in.  This prevents you from having to remember a unique password for Octave Online.\n</p>\n<p>\n\tThe email address you entered was: <strong><%= query ? query.s : \"(empty)\" %></strong>.  If you entered an invalid email address, please click the back button and try again.\n</p>\n<hr/>\n<p>\n\tIf you did not receive your code or need help, contact support:\n</p>\n<iframe src=\"<%= config.email.supportUrl %>\" id=\"support\"></iframe>\n\n<%- include(\"partials/foot\") %>\n"
  },
  {
    "path": "front/src/workspace_normal.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport { EventEmitter } from \"events\";\n\nimport Async = require(\"async\");\n\nimport { IDestroyable, IWorkspace } from \"./utils\";\nimport { IBucket } from \"./bucket_model\";\nimport { HydratedUser } from \"./user_model\";\nimport { config, newRedisMessenger, logger, ILogger } from \"./shared_wrap\";\nimport { octaveHelper, SessionState } from \"./octave_session_helper\";\n\ntype Err = Error|null;\n\n\nconst redisMessenger = newRedisMessenger();\n\nexport class NormalWorkspace\n\textends EventEmitter\n\timplements IWorkspace, IDestroyable {\n\tpublic sessCode: string|null;\n\tpublic destroyed = false;\n\tprivate user: HydratedUser|null;\n\tprivate bucket: IBucket|null;\n\tprivate _log: ILogger;\n\n\tconstructor(sessCode: string|null, user: HydratedUser|null, bucket: IBucket|null){\n\t\tsuper();\n\t\tthis.sessCode = sessCode;\n\t\tthis.user = user;\n\t\tthis.bucket = bucket;\n\t\tthis._log = logger(\"workspace-nrm:uninitialized\");\n\n\t\tprocess.nextTick(()=>{\n\t\t\tthis.emit(\"data\", \"oo.wsuser\", { user });\n\t\t});\n\t}\n\n\tpublic destroyD(message: string){\n\t\tthis.destroyed = true;\n\t\tif (!this.sessCode) {\n\t\t\treturn;\n\t\t}\n\t\t// TODO: It's poor style to do a string comparison here\n\t\tif (message !== \"Client Disconnect\" || config.worker.onDisconnect === \"destroy\") {\n\t\t\tthis._log.trace(\"destroyD: Destroy Now:\", message);\n\t\t\toctaveHelper.sendDestroyD(this.sessCode, message);\n\t\t} else if (config.worker.onDisconnect === \"ignore\") {\n\t\t\tthis._log.trace(\"destroyD: Ignore:\", message);\n\t\t\t// Ensure that the files are committed immediately, so that if a user reloads the page, they get their data immediately synced\n\t\t\tthis.emit(\"back\", \"commit\", { comment: \"Scripted Commit on Disconnect\" });\n\t\t} else if (config.worker.onDisconnect === \"expireShort\") {\n\t\t\tthis._log.trace(\"destroyD: Expire Short:\", message);\n\t\t\tredisMessenger.touchInput(this.sessCode, true);\n\t\t\t// Ensure that the files are committed immediately, so that if a user reloads the page, they get their data immediately synced\n\t\t\tthis.emit(\"back\", \"commit\", { comment: \"Scripted Commit on Disconnect\" });\n\t\t}\n\t}\n\n\tpublic beginOctaveRequest(flavor: string) {\n\t\tAsync.waterfall([\n\t\t\t(next: (err: Err, sessCode: string, state: SessionState) => void) => {\n\t\t\t\t// Check with Redis about the status of the desired sessCode\n\t\t\t\toctaveHelper.getNewSessCode(this.sessCode, next);\n\t\t\t},\n\t\t\t(sessCode: string, state: SessionState, next: (err: Err) => void) => {\n\t\t\t\tif (this.destroyed) {\n\t\t\t\t\tif (state !== SessionState.Needed)\n\t\t\t\t\t\toctaveHelper.sendDestroyD(sessCode, \"Client Gone 1\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.sessCode = sessCode;\n\t\t\t\tthis._log = logger(`workspace-nrm:${sessCode}`) as ILogger;\n\t\t\t\tif (this.user) {\n\t\t\t\t\tthis._log.info(\"User\", this.user.consoleText);\n\t\t\t\t}\n\t\t\t\tif (this.bucket) {\n\t\t\t\t\tthis._log.info(\"Bucket\", this.bucket.consoleText);\n\t\t\t\t}\n\n\t\t\t\t// Ask for an Octave session if we need one.\n\t\t\t\t// Otherwise, inform the client.\n\t\t\t\tif (state === SessionState.Needed) {\n\t\t\t\t\toctaveHelper.askForOctave(sessCode, {\n\t\t\t\t\t\tuser: this.user,\n\t\t\t\t\t\tbucket: this.bucket,\n\t\t\t\t\t\tflavor\n\t\t\t\t\t}, next);\n\t\t\t\t} else {\n\t\t\t\t\tthis.emit(\"sesscode\", sessCode);\n\t\t\t\t\tthis.emit(\"data\", \"prompt\", {});\n\t\t\t\t\tthis.emit(\"data\", \"files-ready\", {});\n\t\t\t\t}\n\t\t\t},\n\t\t\t(next: (err: Err) => void) => {\n\t\t\t\tif (this.destroyed) {\n\t\t\t\t\toctaveHelper.sendDestroyD(this.sessCode!, \"Client Gone 2\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.emit(\"sesscode\", this.sessCode!);\n\n\t\t\t\tnext(null);\n\t\t\t}\n\t\t], (err) => {\n\t\t\tif (err) this._log.error(\"REDIS ERROR\", err);\n\t\t});\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars\n\tpublic destroyU(message: string){\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars\n\tpublic dataD(name: string, val: any){\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars\n\tpublic dataU(name: string, val: any){\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-empty-function\n\tpublic subscribe() {\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-empty-function\n\tpublic unsubscribe() {\n\t}\n}\n"
  },
  {
    "path": "front/src/workspace_shared.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nimport { EventEmitter } from \"events\";\nimport Crypto = require(\"crypto\");\n\nimport Async = require(\"async\");\nimport Uuid = require(\"uuid\");\n\nimport { config, newRedisMessenger, logger, ILogger } from \"./shared_wrap\";\nimport { IWorkspace } from \"./utils\";\nimport { octaveHelper, SessionState } from \"./octave_session_helper\";\nimport { OtDocument } from \"./ot_document\";\nimport { User, HydratedUser } from \"./user_model\";\nimport { IBucket } from \"./bucket_model\";\n\ninterface BeginOctaveRequestAsyncAuto {\n\tuser: HydratedUser|null;\n\tready: void;\n}\n\ntype Err = Error|null;\n\n\n// Make Redis connections for Shared Workspace\nconst redisMessenger = newRedisMessenger();\nconst wsSessClient = newRedisMessenger();\nwsSessClient.subscribeToWorkspaceMsgs();\n\nwsSessClient.setMaxListeners(30);\n\nexport class SharedWorkspace\n\textends EventEmitter\n\timplements IWorkspace {\n\tpublic wsId: string|null = null;\n\tpublic safeWsId: string|null = null;  // ID with no username\n\tpublic sessCode: string|null = null;\n\tpublic destroyed = false;\n\tprivate shareKey: string|null = null;\n\tprivate user: HydratedUser|null = null;\n\tprivate bucket: IBucket|null = null;\n\tprivate docs: { [key: string]: OtDocument } = {};\n\tprivate msgIds: string[] = [];\n\tprivate otEventCounter = 0;\n\tprivate wsMessageCounter = 0;\n\tprivate statsInterval: any;\n\tprivate touchInterval: any;\n\tprivate _log: ILogger;\n\tprivate logId: string;\n\n\tconstructor(shareKey: string|null, user: HydratedUser|null, bucket: IBucket|null, logId: string) {\n\t\tsuper();\n\n\t\tthis._log = logger(`workspace-shr:${logId}:uninitialized`);\n\t\tthis.logId = logId;\n\n\t\tthis.shareKey = shareKey;\n\t\tthis.user = user;\n\t\tthis.bucket = bucket;\n\n\t\tif (bucket) {\n\t\t\tthis.setWsId(`bucket_${bucket.bucket_id}`, false);\n\t\t} else if (user) {\n\t\t\tthis.setWsId(user.parametrized, true);\n\t\t}\n\n\t\tthis.subscribe();\n\t}\n\n\tprivate setWsId(wsId: string, truncate: boolean) {\n\t\tif (this.wsId) {\n\t\t\tif (this.wsId !== wsId)\n\t\t\t\tthis._log.error(\"SHARED WORKSPACE ERROR: Trying to set wsId to\", wsId, \"when it was already set to\", this.wsId);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.wsId = wsId;\n\n\t\t// May 2018: remove email-based IDs from log\n\t\tthis.safeWsId = truncate ? wsId.substr(0, 8) : wsId;\n\t\tthis._log = logger(`workspace-shr:${this.logId}:${this.safeWsId}`);\n\n\t\t// Create the prompt's OtDocument (every session)\n\t\t// Never emit from constructors since there are no listeners yet;\n\t\t// use process.nextTick() instead\n\t\tconst promptId = \"prompt.\" + this.wsId;\n\t\tprocess.nextTick(() => {\n\t\t\tthis.emit(\"data\", \"ws.promptid\", promptId);\n\t\t\tthis.docs[promptId] = new OtDocument(promptId, `${this.logId}:prompt.${this.safeWsId}`, \"\");\n\t\t\tthis.subscribe();\n\t\t});\n\t}\n\n\tprivate forEachDoc(fn: (docId: string,doc: OtDocument) => void){\n\t\tObject.getOwnPropertyNames(this.docs).forEach((docId) => {\n\t\t\tfn(docId, this.docs[docId]);\n\t\t});\n\t}\n\n\tpublic destroyD(message: string){\n\t\t// The Octave session will be destroyed by expiring keys once all\n\t\t// users have disconnected.  There is no need to destroy it here.\n\t\t// Special case: when sharing is disabled or flavor upgraded\n\t\t// TODO: It's poor style to do a string comparison here\n\t\tif (!this.sessCode) {\n\t\t\tthis.destroyed = true;\n\t\t} else if (message === \"Sharing Disabled\") {\n\t\t\tthis.destroyed = true;\n\t\t\toctaveHelper.sendDestroyD(this.sessCode, message);\n\t\t} else if (message === \"Flavor Upgrade\") {\n\t\t\t// Don't set this.destroyed here because the workspace will get a new sessCode\n\t\t\toctaveHelper.sendDestroyD(this.sessCode, message);\n\t\t} else {\n\t\t\tthis.destroyed = true;\n\t\t\t// Ensure that the files are committed immediately, so that if a user reloads the page, they get their data immediately synced\n\t\t\tthis.emit(\"back\", \"commit\", { comment: \"Scripted Commit on Disconnect\" });\n\t\t}\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars\n\tpublic destroyU(message: string){\n\t}\n\n\tpublic dataD(name: string, value: any) {\n\t\tif (!name) name = \"\";\n\t\tif (!value) value = {};\n\n\t\t// NOTE: Remember that each downstream event occurs on just one instance\n\t\t// of IWorkspace, but upstream events occur on ALL listening instances.\n\n\t\t// Pass OT events down to the OT instances\n\t\tif (name.substr(0,3) === \"ot.\") {\n\t\t\tthis.forEachDoc(function(docId,doc){ doc.dataD(name, value); });\n\t\t\tthis.otEventCounter++;\n\t\t\treturn;\n\t\t}\n\n\t\t// A few special handlers\n\t\tif (name === \"save\") {\n\t\t\t// happens when the user saves a file OR creates a new file\n\t\t\tthis.resolveFileSave(value);\n\t\t}\n\n\t\t// Pass other events into the onUserAction handler\n\t\tthis.onUserAction(name, value);\n\t}\n\n\tpublic dataU(name: string, value: any) {\n\t\tif (!name) name = \"\";\n\t\tif (!value) value = {};\n\n\t\t// NOTE: Remember that each downstream event occurs on just one instance\n\t\t// of IWorkspace, but upstream events occur on ALL listening instances.\n\n\t\t// A few special handlers\n\t\tif (name === \"user\") {\n\t\t\t// happens when the full list of files is read\n\t\t\tthis.resolveFileList(value.files /* , true, value.refresh */);\n\n\t\t} else if (name === \"fileadd\") {\n\t\t\t// happens when SIOFU uploads a file\n\t\t\tthis.resolveFileAdd(value);\n\n\t\t} else if (name === \"renamed\") {\n\t\t\t// happens when a file is successfully renamed\n\t\t\tthis.resolveFileRename(value.oldname, value.newname);\n\n\t\t} else if (name === \"deleted\") {\n\t\t\t// happens when a file is deleted\n\t\t\tthis.resolveFileDelete(value.filename);\n\t\t}\n\t}\n\n\tprivate resolveFileList(files: any){\n\t\tfiles = files || {};\n\t\tfor(const filename in files){\n\t\t\tif (!files.hasOwnProperty(filename)) continue;\n\t\t\tconst file = files[filename];\n\t\t\tif (!file.isText) continue;\n\t\t\tconst content = new Buffer(file.content, \"base64\").toString();\n\t\t\tthis.resolveFile(filename, content);\n\t\t}\n\t}\n\n\tprivate resolveFileAdd(file: any){\n\t\tif (!file.isText) return;\n\n\t\tthis._log.trace(\"Resolving File Add\", file.filename);\n\n\t\tconst content = new Buffer(file.content, \"base64\").toString();\n\t\tthis.resolveFile(file.filename, content);\n\t}\n\n\tprivate resolveFileSave(file: any){\n\t\tthis._log.trace(\"Resolving File Save\", file.filename);\n\t\tthis.resolveFile(file.filename, file.content);\n\t}\n\n\tprivate resolveFile(filename: string, content: string) {\n\t\t// Note: hash.copy() was added in Node.js 13; we could use that here instead of converting via Buffer.from()\n\t\tconst hexHash = Crypto.createHash(\"md5\").update(filename).digest(\"hex\");\n\t\tconst shortHash = Buffer.from(hexHash, \"hex\").toString(\"base64\").replace(/=/g, \"\");\n\t\tconst docId = `doc.${this.wsId}.${hexHash}`;\n\n\t\tif (!this.docs[docId]) {\n\t\t\tthis.docs[docId] = new OtDocument(docId, `${this.logId}:doc.${this.safeWsId}.${shortHash}`, content);\n\t\t\tthis.docs[docId].logFilename(filename);\n\t\t\tthis.subscribe();\n\t\t\tprocess.nextTick(() => {\n\t\t\t\tthis.emit(\"data\", \"ws.doc\", {\n\t\t\t\t\tdocId: docId,\n\t\t\t\t\tfilename: filename\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate resolveFileRename(oldname: string, newname: string) {\n\t\tconst oldhash = Crypto.createHash(\"md5\").update(oldname).digest(\"hex\");\n\t\tconst newhash = Crypto.createHash(\"md5\").update(newname).digest(\"hex\");\n\n\t\tconst oldDocId = \"doc.\" + this.wsId + \".\" + oldhash;\n\t\tconst newDocId = \"doc.\" + this.wsId + \".\" + newhash;\n\n\t\tif (!this.docs[oldDocId]) {\n\t\t\tthis._log.trace(\"Attempted to resolve file rename, but couldn't find old file in shared workspace (non-text file?)\", oldname, newname, oldhash);\n\t\t\treturn;\n\t\t}\n\t\tif (this.docs[newDocId]) {\n\t\t\tthis._log.warn(\"WARNING: Attempted to resolve file rename, but the new name already exists in the workspace\", oldname, newname, newhash);\n\t\t\treturn;\n\t\t}\n\n\t\tthis._log.trace(\"Resolving File Remame\", oldname, newname, oldhash, newhash);\n\n\t\tconst doc = this.docs[oldDocId];\n\t\tdelete this.docs[oldDocId];\n\t\tthis.docs[newDocId] = doc;\n\n\t\tdoc.changeDocId(newDocId);\n\n\t\tthis.emit(\"data\", \"ws.rename\", {\n\t\t\toldname: oldname,\n\t\t\tnewname: newname,\n\t\t\toldDocId: oldDocId,\n\t\t\tnewDocId: newDocId\n\t\t});\n\t}\n\n\tprivate resolveFileDelete(filename: string) {\n\t\tconst hash = Crypto.createHash(\"md5\").update(filename).digest(\"hex\");\n\t\tconst docId = \"doc.\" + this.wsId + \".\" + hash;\n\n\t\tif (!this.docs[docId]) {\n\t\t\tthis._log.trace(\"Attempted to resolve file delete, but couldn't find file in shared workspace (non-text file?)\", filename, hash);\n\t\t\treturn;\n\t\t}\n\n\t\tthis._log.trace(\"Resolving File Delete\", filename);\n\n\t\tconst doc = this.docs[docId];\n\t\tdelete this.docs[docId];\n\t\tdoc.destroy();\n\n\t\tthis.emit(\"data\", \"ws.delete\", {\n\t\t\tfilename: filename,\n\t\t\tdocId: docId\n\t\t});\n\t}\n\n\tpublic beginOctaveRequest(flavor: string) {\n\t\t// Before actually performing the Octave request, ensure that\n\t\t// pre-conditions are satisfied.\n\t\tAsync.auto<BeginOctaveRequestAsyncAuto>({\n\t\t\tuser: (next) => {\n\t\t\t\tif (this.shareKey && !this.user) {\n\t\t\t\t\tUser.findOne({ share_key: this.shareKey }, next);\n\t\t\t\t} else {\n\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\tnext(null, this.user);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t\tready: [\"user\", ({user}, next) => {\n\t\t\t\tthis.user = user;\n\t\t\t\tif (user) {\n\t\t\t\t\t// TODO(#41): Make sure this is actually the workspace owner.\n\t\t\t\t\tthis.emit(\"data\", \"oo.wsuser\", { user });\n\t\t\t\t\tif (!this.wsId) {\n\t\t\t\t\t\tthis.setWsId(user.parametrized, true);\n\t\t\t\t\t\tthis._log.info(\"Connecting to student:\", user.consoleText);\n\t\t\t\t\t}\n\t\t\t\t} else if (!this.wsId) {\n\t\t\t\t\tthis._log.warn(\"WARNING: Could not find student with share key\", this.shareKey);\n\t\t\t\t\tthis.emit(\"message\", \"Could not find the specified workspace.  Please check your URL and try again.\");\n\t\t\t\t\tthis.emit(\"data\", \"destroy-u\", \"No Such Workspace\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.doBeginOctaveRequest(flavor);\n\t\t\t\tnext(null);\n\t\t\t}]\n\t\t}, (err) => {\n\t\t\tif (err) this._log.error(\"ASYNC ERROR\", err);\n\t\t});\n\t}\n\n\tprivate doBeginOctaveRequest(flavor: string) {\n\t\tAsync.waterfall([\n\t\t\t(next: (err: Err, sessCode: string) => void) => {\n\t\t\t\t// Check if there is a sessCode in Redis already.\n\t\t\t\tredisMessenger.getWorkspaceSessCode(this.wsId, next);\n\t\t\t},\n\t\t\t(sessCode: string, next: (err: Err, sessCode: string, state: SessionState) => void) => {\n\t\t\t\tif (this.destroyed) return;\n\t\t\t\tthis.sessCode = sessCode;\n\n\t\t\t\t// Make sure that sessCode is still live.\n\t\t\t\toctaveHelper.getNewSessCode(sessCode, next);\n\t\t\t},\n\t\t\t(sessCode: string, state: SessionState, next: (err: Err, result: any) => void) => {\n\t\t\t\tif (this.destroyed) return;\n\t\t\t\tthis._log.trace(\"SessCode State:\", state);\n\n\t\t\t\t// Ask Octave for a session if we need one.\n\t\t\t\tif (state === SessionState.Needed) {\n\t\t\t\t\tredisMessenger.setWorkspaceSessCode(this.wsId, sessCode, this.sessCode, next);\n\n\t\t\t\t// Request a file listing if we need one\n\t\t\t\t} else if (state === SessionState.Live) {\n\t\t\t\t\tthis.emit(\"sesscode\", sessCode);\n\t\t\t\t\tthis.emit(\"data\", \"prompt\", {});\n\t\t\t\t\tthis.emit(\"data\", \"files-ready\", {});\n\t\t\t\t\tthis.emit(\"back\", \"list\", {});\n\n\t\t\t\t// No action necessary\n\t\t\t\t} else {\n\t\t\t\t\tthis.emit(\"sesscode\", sessCode);\n\t\t\t\t}\n\t\t\t},\n\t\t\t(results: any[], next: (err: Err) => void) => {\n\t\t\t\tconst saved: boolean = results[0];\n\t\t\t\tconst sessCode: string = results[1];\n\t\t\t\tif (!saved) return;\n\t\t\t\tthis.sessCode = sessCode;\n\n\t\t\t\t// Our sessCode was accepted.\n\t\t\t\t// Broadcast the new sessCode.\n\t\t\t\tredisMessenger.workspaceMsg(this.wsId, \"sesscode\", sessCode);\n\t\t\t\tthis.touch();\n\n\t\t\t\t// Start the new Octave session.\n\t\t\t\tthis._log.info(\"Sending Octave Request for Shared Workspace\");\n\t\t\t\toctaveHelper.askForOctave(this.sessCode, {\n\t\t\t\t\tuser: this.user,\n\t\t\t\t\tbucket: this.bucket,\n\t\t\t\t\tflavor\n\t\t\t\t}, next);\n\t\t\t}\n\t\t], (err) => {\n\t\t\tif (err) this._log.error(\"REDIS ERROR\", err);\n\t\t});\n\t}\n\n\tpublic subscribe() {\n\t\tthis.unsubscribe();\n\n\t\twsSessClient.on(\"ws-sub\", this.wsMessageListener);\n\t\tthis.touch();\n\t\tthis.touchInterval = setInterval(this.touch, config.redis.expire.interval);\n\t\tthis.statsInterval = setInterval(this.recordStats, config.ot.stats_interval);\n\n\t\tthis.forEachDoc((docId, doc) => {\n\t\t\tdoc.subscribe();\n\t\t\tdoc.on(\"data\", this.onDataO);\n\t\t});\n\t}\n\n\tpublic unsubscribe() {\n\t\twsSessClient.removeListener(\"ws-sub\", this.wsMessageListener);\n\t\tclearInterval(this.touchInterval);\n\t\tclearInterval(this.statsInterval);\n\n\t\tthis.forEachDoc((docId, doc) => {\n\t\t\tdoc.unsubscribe();\n\t\t\tdoc.removeListener(\"data\", this.onDataO);\n\t\t});\n\t}\n\n\tprivate touch = () => {\n\t\tif (!this.wsId) return;\n\t\tredisMessenger.touchWorkspace(this.wsId);\n\t};\n\n\t//// SHARED WORKSPACE HANDLERS ////\n\n\tprivate wsMessageListener = (wsId: string, type: string, data: any) => {\n\t\tif (wsId !== this.wsId) return;\n\t\tif (!data) return;\n\n\t\tthis.wsMessageCounter++;\n\n\t\tlet i;\n\n\t\tswitch(type){\n\t\t\tcase \"sesscode\":\n\t\t\t\tthis.emit(\"sesscode\", data);\n\t\t\t\tbreak;\n\n\t\t\tcase \"user-action\":\n\t\t\t\ti = this.msgIds.indexOf(data.id);\n\t\t\t\tif (i > -1) this.msgIds.splice(i, 1);\n\t\t\t\telse {\n\t\t\t\t\tthis.emit(\"data\", data.name as string, data.data);\n\n\t\t\t\t\t// Special handlers for a few user actions\n\t\t\t\t\tswitch(data.name){\n\t\t\t\t\t\tcase \"ws.save\":\n\t\t\t\t\t\t\tthis.resolveFileSave(data.data);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t};\n\n\t// This function publishes selected actions into the Redis channel.\n\t// Clients on the same channel will resolve those messages in their\n\t// \"wsMessageListener\" function.\n\tprivate onUserAction = (name: string, value: any) => {\n\t\tlet eventName, data;\n\n\t\tswitch(name){\n\t\t\tcase \"data\":\n\t\t\t\teventName = \"ws.command\";\n\t\t\t\tdata = value.data;\n\t\t\t\tbreak;\n\n\t\t\tcase \"save\":\n\t\t\t\teventName = \"ws.save\";\n\t\t\t\tdata = value;\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\treturn;\n\t\t}\n\n\t\tconst msgId = Uuid.v4();\n\t\tthis.msgIds.push(msgId);\n\n\t\tredisMessenger.workspaceMsg(this.wsId, \"user-action\", {\n\t\t\tid: msgId,\n\t\t\tname: eventName,\n\t\t\tdata: data\n\t\t});\n\t};\n\n\tprivate onDataO = (name: string, value: any) => {\n\t\tthis.emit(\"data\", name, value);\n\t};\n\n\tprivate recordStats = () => {\n\t\tlet opsReceivedTotal = 0, setContentTotal = 0;\n\t\tthis.forEachDoc(function(docId,doc){\n\t\t\topsReceivedTotal += doc.opsReceivedCounter;\n\t\t\tsetContentTotal += doc.setContentCounter;\n\t\t});\n\t\tthis._log.debug(\"STATS:\",\n\t\t\tthis.otEventCounter, \"OT events and\",\n\t\t\tthis.wsMessageCounter, \"WS messages and\",\n\t\t\topsReceivedTotal, \"operations received and\",\n\t\t\tsetContentTotal, \"calls to setContent\");\n\t}\n}\n"
  },
  {
    "path": "front/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    /* Basic Options */\n    // \"incremental\": true,                   /* Enable incremental compilation */\n    \"target\": \"ES2022\",                       /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */\n    \"module\": \"commonjs\",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */\n    // \"lib\": [],                             /* Specify library files to be included in the compilation. */\n    // \"allowJs\": true,                       /* Allow javascript files to be compiled. */\n    // \"checkJs\": true,                       /* Report errors in .js files. */\n    // \"jsx\": \"preserve\",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */\n    // \"declaration\": true,                   /* Generates corresponding '.d.ts' file. */\n    // \"declarationMap\": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */\n    // \"sourceMap\": true,                     /* Generates corresponding '.map' file. */\n    // \"outFile\": \"./\",                       /* Concatenate and emit output to single file. */\n    \"outDir\": \"dist\",                         /* Redirect output structure to the directory. */\n    \"rootDir\": \"src\",                         /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */\n    // \"composite\": true,                     /* Enable project compilation */\n    // \"tsBuildInfoFile\": \"./\",               /* Specify file to store incremental compilation information */\n    // \"removeComments\": true,                /* Do not emit comments to output. */\n    // \"noEmit\": true,                        /* Do not emit outputs. */\n    // \"importHelpers\": true,                 /* Import emit helpers from 'tslib'. */\n    // \"downlevelIteration\": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */\n    // \"isolatedModules\": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */\n\n    /* Strict Type-Checking Options */\n    \"strict\": true,                           /* Enable all strict type-checking options. */\n    // \"noImplicitAny\": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */\n    // \"strictNullChecks\": true,              /* Enable strict null checks. */\n    // \"strictFunctionTypes\": true,           /* Enable strict checking of function types. */\n    // \"strictBindCallApply\": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */\n    // \"strictPropertyInitialization\": true,  /* Enable strict checking of property initialization in classes. */\n    // \"noImplicitThis\": true,                /* Raise error on 'this' expressions with an implied 'any' type. */\n    // \"alwaysStrict\": true,                  /* Parse in strict mode and emit \"use strict\" for each source file. */\n\n    /* Additional Checks */\n    \"noUnusedLocals\": true,                   /* Report errors on unused locals. */\n    // \"noUnusedParameters\": true,            /* Report errors on unused parameters. */\n    \"noImplicitReturns\": true,                /* Report error when not all code paths in function return a value. */\n    \"noFallthroughCasesInSwitch\": true,       /* Report errors for fallthrough cases in switch statement. */\n\n    /* Module Resolution Options */\n    // \"moduleResolution\": \"node\",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */\n    // \"baseUrl\": \"./\",                       /* Base directory to resolve non-absolute module names. */\n    // \"paths\": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */\n    // \"rootDirs\": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */\n    // \"typeRoots\": [],                       /* List of folders to include type definitions from. */\n    // \"types\": [],                           /* Type declaration files to be included in compilation. */\n    // \"allowSyntheticDefaultImports\": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */\n    // \"esModuleInterop\": true,               /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */\n    // \"preserveSymlinks\": true,              /* Do not resolve the real path of symlinks. */\n    // \"allowUmdGlobalAccess\": true,          /* Allow accessing UMD globals from modules. */\n\n    /* Source Map Options */\n    // \"sourceRoot\": \"\",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */\n    // \"mapRoot\": \"\",                         /* Specify the location where debugger should locate map files instead of generated locations. */\n    // \"inlineSourceMap\": true,               /* Emit a single file with source maps instead of having a separate file. */\n    // \"inlineSources\": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */\n\n    /* Experimental Options */\n    // \"experimentalDecorators\": true,        /* Enables experimental support for ES7 decorators. */\n    // \"emitDecoratorMetadata\": true,         /* Enables experimental support for emitting type metadata for decorators. */\n\n    /* Advanced Options */\n    \"forceConsistentCasingInFileNames\": true  /* Disallow inconsistently-cased references to the same file. */\n  }\n}\n"
  },
  {
    "path": "front/typings/easy-no-password.d.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndeclare module 'easy-no-password' {\n\n\texport = easy_no_password;\n\n\ttype Err = Error | null;\n\n\tinterface Data {\n\t\tstage: number;\n\t\tusername: string;\n\t\ttoken?: string;\n\t}\n\n\tinterface Options {\n\t\tsecret: string;\n\t\tpassReqToCallback?: boolean;\n\t\tmaxTokenAge?: number;\n\t}\n\n\ttype VerifiedFn = (err: Err, user?: unknown, info?: any) => void;\n\n\ttype VerifyFn = ((username: string, verified: VerifiedFn) => void)\n\t\t| ((req: any, username: string, verified: VerifiedFn) => void);\n\n\n\tclass EasyNoPassword {\n\t\tcreateToken(username: string, next: (err: Err, token?: string) => void): void;\n\t\tisValid(token: string, username: string, next: (err: Err, isValid?: boolean) => void): void;\n\t}\n\n\tfunction easy_no_password(secret: string, maxTokenAge: number): EasyNoPassword;\n\n\tnamespace easy_no_password {\n\t\texport class Strategy {\n\t\t\tconstructor(\n\t\t\t\toptions: Options,\n\t\t\t\tparseRequest: (req: any) => Data | null,\n\t\t\t\tsendToken: (username: string, token: string, next: (err: Err) => void) => void,\n\t\t\t\tverify: VerifyFn);\n\t\t\tauthenticate(req: any): void;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "front/typings/i18next-fs-backend.d.ts",
    "content": "/*\n * Copyright © 2020, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndeclare module 'i18next-fs-backend' {\n\tinterface IHandleOptions {\n\t\tignoreRoutes: string[];\n\t}\n\n\tconst Backend: import(\"i18next\").Module;\n\texport = Backend;\n}\n"
  },
  {
    "path": "front/typings/i18next-http-middleware.d.ts",
    "content": "/*\n * Copyright © 2020, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndeclare module 'i18next-http-middleware' {\n\tinterface IHandleOptions {\n\t\tignoreRoutes: string[];\n\t}\n\n\texport const LanguageDetector: import(\"i18next\").Module;\n\n\texport function handle(i18next: any, options?: IHandleOptions): import(\"express\").RequestHandler;\n}\n"
  },
  {
    "path": "front/typings/ot.d.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndeclare module 'ot' {\n\tinterface ITextOperation {\n\t\ttoJSON(): any;\n\t}\n\n\texport class Server {\n\t\tconstructor(initial_text:string);\n\t\treceiveOperation(rev:number, operation:ITextOperation):ITextOperation;\n\t}\n\n\texport class TextOperation {\n\t\tstatic fromJSON(json: string): ITextOperation;\n\t}\n}\n\n"
  },
  {
    "path": "front/typings/pseudo-localization.d.ts",
    "content": "/*\n * Copyright © 2020, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndeclare module 'pseudo-localization' {\n\texport function localize(s: string): string;\n}\n\n"
  },
  {
    "path": "front/typings/socketio-file-upload.d.ts",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\ndeclare module 'socketio-file-upload' {\n\n\texport = socketio_file_upload;\n\n\tfunction socketio_file_upload(options: any): any;\n\n\tnamespace socketio_file_upload {\n\t    const clientPath: string;\n\n\t    function listen(app: any): void;\n\n\t    function router(req: any, res: any, next: any): void;\n\n\t}\n}\n"
  },
  {
    "path": "front/typings/socketio-wildcard.d.ts",
    "content": "/*\r\n * Copyright © 2019, Octave Online LLC\r\n *\r\n * This file is part of Octave Online Server.\r\n *\r\n * Octave Online Server is free software: you can redistribute it and/or\r\n * modify it under the terms of the GNU Affero General Public License as\r\n * published by the Free Software Foundation, either version 3 of the License,\r\n * or (at your option) any later version.\r\n *\r\n * Octave Online Server is distributed in the hope that it will be useful, but\r\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\r\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\r\n * License for more details.\r\n *\r\n * You should have received a copy of the GNU Affero General Public License\r\n * along with Octave Online Server.  If not, see\r\n * <https://www.gnu.org/licenses/>.\r\n */\r\n\r\ndeclare module 'socketio-wildcard' {\r\n\r\n\texport = socketio_wildcard;\r\n\r\n\tfunction socketio_wildcard(CustomEmitter?: any): any;\r\n\r\n}\r\n\r\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"octave-online-server\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Hosted GNU Octave processes served over the World Wide Web. The infrastructure that powers Octave Online.\",\n  \"scripts\": {\n    \"lint\": \"eslint . --ext .js,.jsx,.ts,.tsx\",\n    \"fix\": \"eslint . --ext .js,.jsx,.ts,.tsx --fix\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:octave-online/octave-online-server.git\"\n  },\n  \"keywords\": [\n    \"matlab\",\n    \"octave\",\n    \"online\"\n  ],\n  \"author\": \"Octave Online LLC\",\n  \"license\": \"AGPL-3.0\",\n  \"devDependencies\": {\n    \"@typescript-eslint/eslint-plugin\": \"^2.13.0\",\n    \"@typescript-eslint/parser\": \"^2.13.0\",\n    \"eslint\": \"^7.32.0\",\n    \"typescript\": \"^3.7.4\"\n  }\n}\n"
  },
  {
    "path": "redirect/README.md",
    "content": "Octave Online Server: Redirect Service\n======================================\n\nThis directory contains a standalone service for redirecting shortlinks to the main server.\n\nThis service is used to power `octav.onl`.\n"
  },
  {
    "path": "redirect/app.js",
    "content": "/*\n * Copyright © 2021, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nconst express = require(\"express\");\nconst asyncify = require(\"express-asyncify\");\nconst path = require(\"path\");\nconst logger = require(\"morgan\");\nconst config = require(\"@oo/shared\").config;\nconst db = require(\"./src/db\");\n\nconst log = require(\"@oo/shared\").logger(\"app\");\n\nconst app = asyncify(express());\n\n// view engine setup\napp.set(\"views\", path.join(__dirname, \"views\"));\napp.set(\"view engine\", \"ejs\");\n\napp.use(logger(\"dev\"));\n\napp.get(\"/\", async (req, res) => {\n\tres.redirect(302, `//${config.front.hostname}:${config.front.port}/`);\n});\n\napp.get(\"/:shortlink\", async (req, res) => {\n\tconst shortlink = req.params.shortlink;\n\tconst bucket = await db.findBucketWithShortlink(shortlink);\n\tif (!bucket) {\n\t\tres.status(404);\n\t\tres.render(\"error\", { config, shortlink });\n\t\treturn;\n\t}\n\tconst token = (bucket.butype === \"readonly\") ? \"bucket\" : \"project\";\n\tlog.info(`Redirecting ${shortlink} to ${bucket.bucket_id}`);\n\tres.redirect(302, `//${config.front.hostname}:${config.front.port}/${token}~${bucket.bucket_id}`);\n});\n\nmodule.exports = app;\n"
  },
  {
    "path": "redirect/bin/server.js",
    "content": "#!/usr/bin/env node\n/*\n * Copyright © 2021, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n/**\n * Module dependencies.\n */\n\nconst app = require(\"../app\");\nconst db = require(\"../src/db\");\nconst debug = require(\"debug\")(\"oo:server\");\nconst http = require(\"http\");\nconst config = require(\"@oo/shared\").config;\n\n(async function() {\n\n\t// Database connection.\n\tconst mongoUrl = `mongodb://${config.mongo.hostname}:${config.mongo.port}`;\n\tconst mongoDb = config.mongo.db;\n\tawait db.connect(mongoUrl, mongoDb);\n\tdebug(\"Connected to MongoDB:\", mongoUrl, mongoDb);\n\n\t// Get port from environment and store in Express.\n\n\tconst port = normalizePort(process.env.PORT || \"3000\");\n\tapp.set(\"port\", port);\n\n\t// Create HTTP server.\n\n\tconst server = http.createServer(app);\n\n\t// Listen on provided port, on all network interfaces.\n\n\tserver.listen(port);\n\tserver.on(\"error\", onError);\n\tserver.on(\"listening\", onListening);\n\n\t// Normalize a port into a number, string, or false.\n\n\tfunction normalizePort(val) {\n\t\tconst port = parseInt(val, 10);\n\n\t\tif (isNaN(port)) {\n\t\t// named pipe\n\t\t\treturn val;\n\t\t}\n\n\t\tif (port >= 0) {\n\t\t// port number\n\t\t\treturn port;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t// Event listener for HTTP server \"error\" event.\n\n\tfunction onError(error) {\n\t\tif (error.syscall !== \"listen\") {\n\t\t\tthrow error;\n\t\t}\n\n\t\tconst bind = typeof port === \"string\"\n\t\t\t? \"Pipe \" + port\n\t\t\t: \"Port \" + port;\n\n\t\t// handle specific listen errors with friendly messages\n\t\tswitch (error.code) {\n\t\t\tcase \"EACCES\":\n\t\t\t\tdebug(bind + \" requires elevated privileges\");\n\t\t\t\tprocess.exit(1);\n\t\t\t\tbreak;\n\t\t\tcase \"EADDRINUSE\":\n\t\t\t\tdebug(bind + \" is already in use\");\n\t\t\t\tprocess.exit(1);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// Event listener for HTTP server \"listening\" event.\n\n\tfunction onListening() {\n\t\tconst addr = server.address();\n\t\tconst bind = typeof addr === \"string\"\n\t\t\t? \"pipe \" + addr\n\t\t\t: \"port \" + addr.port;\n\t\tdebug(\"Listening on \" + bind);\n\t}\n\n// async function\n})().catch((err) => {\n\tthrow err;\n});\n"
  },
  {
    "path": "redirect/package.json",
    "content": "{\n  \"name\": \"@oo/redirect\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"node ./bin/server.js\"\n  },\n  \"author\": \"Octave Online LLC\",\n  \"license\": \"AGPL-3.0\",\n  \"dependencies\": {\n    \"@oo/shared\": \"file:../shared\",\n    \"ejs\": \"^3.1.6\",\n    \"express\": \"^4.17.1\",\n    \"express-asyncify\": \"^1.0.1\",\n    \"mongodb\": \"^3.6.10\",\n    \"morgan\": \"^1.10.0\"\n  }\n}\n"
  },
  {
    "path": "redirect/src/db.js",
    "content": "/*\n * Copyright © 2021, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nconst mongodb = require(\"mongodb\");\n\nlet mongoClient = null;\nlet db = null;\n\nasync function connect(url, dbName) {\n\tmongoClient = new mongodb.MongoClient(url);\n\tawait mongoClient.connect();\n\tdb = mongoClient.db(dbName);\n}\n\nasync function close() {\n\tmongoClient.close();\n}\n\nasync function findBucketWithShortlink(shortlink) {\n\tconst collection = db.collection(\"buckets\");\n\tconst result = await collection.findOne({\n\t\tshortlink: shortlink\n\t});\n\treturn result;\n}\n\nmodule.exports = {\n\tconnect,\n\tclose,\n\tfindBucketWithShortlink,\n};\n"
  },
  {
    "path": "redirect/views/error.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or (at your\noption) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\nFITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License\nfor more details.\n\nYou should have received a copy of the GNU Affero General Public License along\nwith Octave Online Server.  If not, see <https://www.gnu.org/licenses/>.\n%>\n\n<!DOCTYPE html>\n\n<%\nif (config.client.theme_collection === \"official\") {\n\tcolor = \"#FF5C4A\"; // OO Fire Red\n} else {\n\tcolor = \"#AD928E\"; // OO Server Brown\n}\n%>\n\n<html>\n<head>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<style type=\"text/css\">\nbody {\n\twidth: 75%;\n\tbackground-color: <%= color %>;\n\tmargin: 0 auto;\n\tpadding: 0;\n\tfont-family: sans-serif;\n}\n@media (max-width: 480px) {\n\tbody {\n\t\twidth: 100%;\n\t}\n}\n#main {\n\tbackground-color: #fff;\n\tmargin: 0;\n\tpadding: 10px;\n\ttext-align: center;\n}\n</style>\n</head>\n<body>\n<div id=\"main\">\n\n<p>Error: Unknown Shortlink: \"<%= shortlink %>\"</p>\n\n<p><a href=\"http://<%= config.front.hostname %>:<%= config.front.port %>\">Go to <%= config.front.hostname %></a></p>\n\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "shared/.eslintrc.yml",
    "content": ""
  },
  {
    "path": "shared/.npmrc",
    "content": "# Please keep this in sync with all other .npmrc files\n# SEE: https://github.com/npm/feedback/discussions/864\ninstall-links=false\n"
  },
  {
    "path": "shared/README.md",
    "content": "Octave Online Server: Shared Utilities\n======================================\n\nThis directory contains code that is shared among the client, front server, and back server.\n\nWhen changing any exports here, run `npm run dtx-gen` to re-generate *index.d.ts*, which is used by the front server for TypeScript type definitions.\n\nThe logging code can optionally send logs to Google Stackdriver.  If you don't know what this means, you can ignore this feature.  To enable this feature, from this directory, run:\n\n```bash\n$ npm install ./stackdriver\n```\n"
  },
  {
    "path": "shared/async-cache.js",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\n// This is a helper class that caches an async result (aka Promise) with a certain expiration date.\n// The getter should be a function that takes a callback function.  It should pass the callback function three arguments: err, value, and expires, where expires is how long to keep the value before calling the function again.\n// bufferTime is subtracted off the expires value when evaluating whether to bust the cache.\n\nmodule.exports = function(getter, bufferTime) {\n\tbufferTime = bufferTime || 0;\n\tlet cachedPromise = null;\n\tfunction makePromise() {\n\t\tlet promise = new Promise((resolve, reject) => {\n\t\t\tgetter((err, value, expires) => {\n\t\t\t\tif (err) return reject(err);\n\t\t\t\tpromise.expires = expires;\n\t\t\t\tresolve(value);\n\t\t\t});\n\t\t});\n\t\treturn promise;\n\t}\n\tcachedPromise = makePromise();\n\treturn function(next) {\n\t\tif (cachedPromise.expires && cachedPromise.expires - new Date() < bufferTime) {\n\t\t\tcachedPromise = makePromise();\n\t\t}\n\t\tcachedPromise.then((value) => {\n\t\t\tnext(null, value);\n\t\t}).catch(next);\n\t};\n};\n"
  },
  {
    "path": "shared/config-helper.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst config = require(\"./config\");\n\nmodule.exports = {\n\ttier: function(tier) {\n\t\treturn Object.assign(\n\t\t\t{\n\t\t\t\t\"sessionManager.poolSize\": config.sessionManager.poolSize,\n\t\t\t\t\"sessionManager.poolTier\": config.sessionManager.poolTier,\n\t\t\t\t\"sessionManager.queueBoostTime\": config.sessionManager.queueBoostTime,\n\t\t\t\t\"selinux.cgroup.name\": config.selinux.cgroup.name,\n\t\t\t\t\"selinux.prlimit.addressSpace\": config.selinux.prlimit.addressSpace,\n\t\t\t\t\"session.legalTime.user\": config.session.legalTime.user,\n\t\t\t\t\"session.payloadLimit.user\": config.session.payloadLimit.user,\n\t\t\t\t\"session.countdownExtraTime\": config.session.countdownExtraTime,\n\t\t\t\t\"session.countdownRequestTime\": config.session.countdownRequestTime,\n\t\t\t\t\"session.timewarnTime\": config.session.timewarnTime,\n\t\t\t\t\"session.timeoutTime\": config.session.timeoutTime,\n\t\t\t},\n\t\t\tconfig.tiers[tier]\n\t\t);\n\t},\n\tflavor: function(flavor) {\n\t\tif (!config.flavors[flavor]) {\n\t\t\treturn null;\n\t\t}\n\t\treturn Object.assign(\n\t\t\t{},\n\t\t\tconfig.flavorCommon,\n\t\t\tconfig.flavors[flavor]\n\t\t);\n\t}\n};\n"
  },
  {
    "path": "shared/config.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst hjson = require(\"hjson\");\nconst deepmerge = require(\"deepmerge\");\n\nconst log = require(\"./logger\")(\"config\");\n\nconst defaultConfig = hjson.parse(fs.readFileSync(path.join(__dirname, \"..\", \"config_defaults.hjson\")).toString(\"utf-8\"));\n\ntry {\n\tvar configBuffer = fs.readFileSync(path.join(__dirname, \"..\", \"config.hjson\"));\n} catch(e) {\n\tconst message = \"Could not read config.hjson. Octave Online Server will use all default settings.\";\n\tlog.warn(message);\n\t// Allow console to make sure the message always gets printed, even when not in debug mode:\n\t// eslint-disable-next-line no-console\n\tconsole.error(\"Notice: \" + message);\n\tconfigBuffer = Buffer.alloc(0);\n}\n\ntry {\n\tvar config = hjson.parse(configBuffer.toString(\"utf-8\"));\n} catch(e) {\n\t// The process will die here if config.hjson is not found, so console is OK\n\t// eslint-disable-next-line no-console\n\tconsole.error(\"Possible syntax error in config file!\");\n\t// eslint-disable-next-line no-console\n\tconsole.error(e);\n\tprocess.exit(1);\n}\n\nconst resolvedConfig = deepmerge(defaultConfig, config);\n\nmodule.exports = resolvedConfig;\n"
  },
  {
    "path": "shared/gcp/.npmrc",
    "content": "# Please keep this in sync with all other .npmrc files\n# SEE: https://github.com/npm/feedback/discussions/864\ninstall-links=false\n"
  },
  {
    "path": "shared/gcp/fetch_translations.js",
    "content": "#!/usr/bin/env node\n\n/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst fs = require(\"fs\");\nconst os = require(\"os\");\nconst path = require(\"path\");\nconst util = require(\"util\");\nconst execFile = util.promisify(require(\"child_process\").execFile);\n\nconst gcp = require(\"./index\");\nconst { config, logger } = require(\"@oo/shared\");\n\nconst log = logger(\"fetch_translations\");\n\nmodule.exports = async function fetchTranslations(buildData) {\n\tconst tmpdir = await fs.promises.mkdtemp(path.join(os.tmpdir(), \"oo-translations-\"));\n\tlog.trace(\"Made tmpdir:\", tmpdir);\n\tconst targzPath = path.join(tmpdir, \"i18next_locales.tar.gz\");\n\tlog.trace(\"Making request to Google Cloud Storage to download translations…\");\n\tawait gcp.downloadFile(log.log, config.gcp.artifacts_bucket, config.gcp.i18next_locales_tar_gz, targzPath);\n\tlog.trace(\"Translations succesfully downloaded\");\n\tconst { stdout, stderr } = await execFile(\"tar\", [\"zxf\", \"i18next_locales.tar.gz\"], { cwd: tmpdir });\n\tlog.trace(\"Unpacked i18next_locales.tar.gz\", stdout, stderr);\n\tconst i18nextLocalesDir = path.join(tmpdir, \"i18next_locales\");\n\n\tbuildData.locales_path = path.join(i18nextLocalesDir, \"{{lng}}.json\");\n\tbuildData.locales = [];\n\n\tconst re = new RegExp(\"^(\\\\w+)\\\\.json\");\n\tconst filenames = await fs.promises.readdir(i18nextLocalesDir);\n\tlog.trace(\"filenames present in unpacked tar.gz:\", filenames);\n\tfor (let filename of filenames) {\n\t\tlet match = re.exec(filename);\n\t\tif (match) {\n\t\t\tbuildData.locales.push(match[1]);\n\t\t}\n\t}\n};\n"
  },
  {
    "path": "shared/gcp/index.js",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst path = require(\"path\");\n\nconst Compute = require(\"@google-cloud/compute\");\nconst { Storage } = require(\"@google-cloud/storage\");\nconst gcpMetadata = require(\"gcp-metadata\");\n\nconst { config, gitarchive } = require(\"@oo/shared\");\n\n///// Compute /////\n\nlet _computeClient = null;\n\nfunction getComputeClient() {\n\tif (!_computeClient) {\n\t\tif (config.gcp.credentials) {\n\t\t\t_computeClient = new Compute({\n\t\t\t\tprojectId: config.gcp.credentials.project_id,\n\t\t\t\tcredentials: config.gcp.credentials\n\t\t\t});\n\t\t} else {\n\t\t\t_computeClient = new Compute();\n\t\t}\n\t}\n\treturn _computeClient;\n}\n\nasync function getRecommendedSize(log) {\n\tconst client = getComputeClient()\n\t\t.zone(config.gcp.zone)\n\t\t.autoscaler(config.gcp.instance_group_name);\n\tconst [, result ] = await client.get();\n\tlog(\"Recommended size:\", result.recommendedSize);\n\treturn result.recommendedSize;\n}\n\nasync function getTargetSize(log) {\n\tconst client = getComputeClient()\n\t\t.zone(config.gcp.zone)\n\t\t.instanceGroupManager(config.gcp.instance_group_name);\n\tconst [, result ] = await client.get();\n\tlog(\"Target size:\", result.targetSize);\n\treturn result.targetSize;\n}\n\nasync function getAutoscalerInfo(log) {\n\tconst [ recommendedSize, targetSize ] = await Promise.all([\n\t\tgetRecommendedSize(log),\n\t\tgetTargetSize(log)\n\t]);\n\treturn {\n\t\trecommendedSize,\n\t\ttargetSize\n\t};\n}\n\nasync function getSelfName(log) {\n\tconst name = await gcpMetadata.instance(\"name\");\n\tlog(\"Self name:\", name);\n\treturn name;\n}\n\nasync function removeSelfFromGroup(log) {\n\tconst selfName = await getSelfName(log);\n\tconst vm = getComputeClient()\n\t\t.zone(config.gcp.zone)\n\t\t.vm(selfName);\n\tconst client = getComputeClient()\n\t\t.zone(config.gcp.zone)\n\t\t.instanceGroupManager(config.gcp.instance_group_name);\n\n\tlet operation;\n\tif (config.gcp.instance_group_removal_method === \"abandon\") {\n\t\tlog(\"Abandoning self\");\n\t\t[ operation ] = await client.abandonInstances(vm);\n\t} else if (config.gcp.instance_group_removal_method === \"delete\") {\n\t\tlog(\"Deleting self\");\n\t\t[ operation ] = await client.deleteInstances(vm);\n\t} else {\n\t\tthrow new Error(\"Unknown removal method\");\n\t}\n\treturn operation;\n}\n\n///// Storage /////\n\nlet _storageClient = null;\n\nfunction getStorageClient() {\n\tif (!_storageClient) {\n\t\tif (config.gcp.credentials) {\n\t\t\t_storageClient = new Storage({\n\t\t\t\tprojectId: config.gcp.credentials.project_id,\n\t\t\t\tcredentials: config.gcp.credentials\n\t\t\t});\n\t\t} else {\n\t\t\t_storageClient = new Storage();\n\t\t}\n\t}\n\treturn _storageClient;\n}\n\nasync function downloadFile(log, bucketName, srcPath, destination) {\n\tconst client = getStorageClient()\n\t\t.bucket(bucketName);\n\tawait client.file(srcPath).download({ destination });\n\tlog(`gs://${bucketName}/${srcPath} downloaded to ${destination}.`);\n}\n\nasync function uploadRepoArchive(log, tld, name) {\n\tconst needsArchive = await gitarchive.repoContainsRefs(tld, name);\n\tif (!needsArchive) {\n\t\tlog(`Empty or nonexistant repo: ${tld}/${name}`);\n\t\treturn;\n\t}\n\tconst bucketName = config.gcp.archive_bucket;\n\tawait doUploadRepo(log, tld, name, bucketName);\n}\n\nasync function uploadRepoSnapshot(log, tld, name) {\n\tconst bucketName = config.gcp.snapshots_bucket;\n\tconst file = await doUploadRepo(log, tld, name, bucketName);\n\treturn (await file.getSignedUrl({\n\t\tversion: \"v4\",\n\t\taction: \"read\",\n\t\texpires: Date.now() + config.gcp.snapshots_duration,\n\t}))[0];\n}\n\nasync function doUploadRepo(log, tld, name, bucketName) {\n\tconst filename = gitarchive.generateFilename(name);\n\tconst bucketPath = `zips/${tld}/${filename}`;\n\tlog(`Streaming to gs://${bucketName}/${bucketPath}`);\n\n\tconst file = getStorageClient()\n\t\t.bucket(bucketName)\n\t\t.file(bucketPath);\n\tconst stream = file.createWriteStream({\n\t\tresumable: false,\n\t});\n\tawait gitarchive.createRepoSnapshot(tld, name, stream);\n\treturn file;\n}\n\nasync function restoreRepoFromCloudStorage(log, tld, name, bucketName, bucketPath) {\n\tlog(`Fetching gs://${bucketName}/${bucketPath}`);\n\n\tconst file = getStorageClient()\n\t\t.bucket(bucketName)\n\t\t.file(bucketPath);\n\tconst fileContents = (await file.download())[0];\n\tlog(\"Zip file downloaded: # of bytes:\", fileContents.length);\n\tconst branchName = path.basename(bucketPath).replace(/:/g, \"\");\n\tawait gitarchive.restoreRepoFromZipFile(log, tld, name, branchName, fileContents);\n}\n\n\nmodule.exports = {\n\tgetAutoscalerInfo,\n\tremoveSelfFromGroup,\n\tdownloadFile,\n\tuploadRepoArchive,\n\tuploadRepoSnapshot,\n\trestoreRepoFromCloudStorage,\n};\n"
  },
  {
    "path": "shared/gcp/package.json",
    "content": "{\n  \"name\": \"@oo/shared_gcp\",\n  \"version\": \"0.0.0\",\n  \"description\": \"GCP bindings for Octave Online\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"author\": \"Shane F. Carr\",\n  \"license\": \"AGPL-3.0\",\n  \"dependencies\": {\n    \"@google-cloud/compute\": \"^2.1.0\",\n    \"@google-cloud/storage\": \"^5.3.0\",\n    \"@oo/shared\": \"file:..\",\n    \"gcp-metadata\": \"^4.3.0\"\n  }\n}\n"
  },
  {
    "path": "shared/gcp/reboot_or_remove_self.js",
    "content": "#!/usr/bin/env node\n\n/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\n/* eslint-disable no-console */\n\nconst child_process = require(\"child_process\");\nconst util = require(\"util\");\n\nconst gcp = require(\"./index\");\n\nconst log = console.log;\n\nconst execFile = util.promisify(child_process.execFile);\n\nasync function reboot() {\n\treturn execFile(\"sudo\", [\"/usr/sbin/reboot\"], { stdio: \"inherit\" });\n}\n\nasync function main() {\n\tconst { recommendedSize, targetSize } = await gcp.getAutoscalerInfo(log);\n\tlog(\"Recommended/Target Size:\", recommendedSize, targetSize);\n\tif (targetSize > recommendedSize) {\n\t\tlog(\"Removing self from group\");\n\t\treturn await gcp.removeSelfFromGroup(log);\n\t} else {\n\t\tlog(\"Requesting reboot\");\n\t\treturn await reboot();\n\t}\n}\n\nmodule.exports = function() {\n\tmain().then((results) => {\n\t\tlog(results);\n\t\tprocess.exit(0);\n\t}).catch((err) => {\n\t\tconsole.error(err);\n\t\tprocess.exit(1);\n\t});\n};\n"
  },
  {
    "path": "shared/gitarchive.js",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst child_process = require(\"child_process\");\nconst fsPromises = require(\"fs\").promises;\nconst jszip = require(\"jszip\");\nconst os = require(\"os\");\nconst path = require(\"path\");\nconst util = require(\"util\");\n\nconst execFilePromise = util.promisify(child_process.execFile);\n\nconst config = require(\"./config\");\nconst log = require(\"./logger\")(\"gitarchive\");\n\nasync function repoContainsRefs(tld, name) {\n\tconst remote = `git://${config.git.hostname}:${config.git.gitDaemonPort}/${tld}/${name}.git`;\n\n\tlet stdoutLen = 0;\n\tconst p = child_process.spawn(\"git\", [\"ls-remote\", remote, \"master\"]);\n\tp.stdout.on(\"data\", (data) => {\n\t\tstdoutLen += data.length;\n\t});\n\tp.stderr.on(\"data\", (data) => {\n\t\tlog.log(data);\n\t});\n\n\treturn new Promise(function(resolve, reject) {\n\t\tp.on(\"close\", (code) => {\n\t\t\tif (code) {\n\t\t\t\tlog.trace(\"Git exited with code \" + code);\n\t\t\t\tresolve(false);\n\t\t\t} else {\n\t\t\t\tlog.trace(\"Number of bytes from stdout:\", stdoutLen);\n\t\t\t\tresolve(!!stdoutLen);\n\t\t\t}\n\t\t});\n\t\tp.on(\"error\", reject);\n\t});\n}\n\nasync function createRepoSnapshot(tld, name, outStream) {\n\tconst remote = `git://${config.git.hostname}:${config.git.gitDaemonPort}/${tld}/${name}.git`;\n\tlog.info(`Archiving ${remote}`);\n\n\tconst p = child_process.spawn(\"git\", [\"archive\", \"--format=zip\", \"--remote=\"+remote, \"master\"]);\n\tp.stdout.pipe(outStream);\n\tp.stderr.on(\"data\", (data) => {\n\t\tlog.log(data);\n\t});\n\n\treturn new Promise(function(resolve, reject) {\n\t\tp.on(\"close\", (code) => {\n\t\t\tif (code) {\n\t\t\t\tlog.error(\"Git exited with code \" + code);\n\t\t\t}\n\t\t\tresolve();\n\t\t});\n\t\tp.on(\"error\", reject);\n\t});\n}\n\nasync function restoreRepoFromZipFile(log, tld, name, branchName, zipFileBlob) {\n\tconst remote = `git://${config.git.hostname}:${config.git.gitDaemonPort}/${tld}/${name}.git`;\n\tconst zip = await jszip.loadAsync(zipFileBlob, { createFolders: true });\n\tconst tmpdir = await fsPromises.mkdtemp(path.join(os.tmpdir(), \"oo-reporestore-\"));\n\tconst gitdir = path.join(tmpdir, \"work\");\n\tconst gitoptbase = [\"-c\", \"user.email='webmaster@octave-online.net'\", \"-c\", \"user.name='Octave Online'\"];\n\tlog(\"tmpdir:\", tmpdir);\n\ttry {\n\t\tlog(\"A-clone\",\n\t\t\tawait execFilePromise(\"git\", [\"clone\", remote, \"work\"],\n\t\t\t\t{ cwd: tmpdir }));\n\t\tlog(\"A-commit-0\",\n\t\t\tawait execFilePromise(\"git\", gitoptbase.concat([\"commit\", \"--allow-empty\", \"-m\", \"Prep for restoration\"]),\n\t\t\t\t{ cwd: gitdir }));\n\t\tlog(\"A-co-orphan\",\n\t\t\tawait execFilePromise(\"git\", [\"checkout\", \"--no-guess\", \"--orphan\", branchName],\n\t\t\t\t{ cwd: gitdir }));\n\t\tlog(\"A-git-rm\", await new Promise((resolve, reject) => {\n\t\t\tchild_process.execFile(\"git\", [\"rm\", \"-rf\", \".\"],\n\t\t\t\t{ cwd: gitdir },\n\t\t\t\tfunction(err, stdout, stderr) {\n\t\t\t\t\t// Errors are expected here if the repo is empty\n\t\t\t\t\tresolve({ stdout, stderr });\n\t\t\t\t});\n\t\t}));\n\t\tfor (let [relativePath, file] of Object.entries(zip.files)) {\n\t\t\tconst fullPath = path.join(gitdir, relativePath);\n\t\t\tif (file.dir) {\n\t\t\t\tlog(\"Creating directory:\", relativePath);\n\t\t\t\tawait fsPromises.mkdir(fullPath);\n\t\t\t} else {\n\t\t\t\tlog(\"Writing file:\", relativePath);\n\t\t\t\tconst fileData = await zip.file(relativePath).async(\"nodebuffer\");\n\t\t\t\t// TODO: Should we restore \"date\", \"unixPermissions\", ... ?\n\t\t\t\tawait fsPromises.writeFile(fullPath, fileData);\n\t\t\t}\n\t\t}\n\t\tlog(\"A-add-1\",\n\t\t\tawait execFilePromise(\"git\", [\"add\", \"-A\"],\n\t\t\t\t{ cwd: gitdir }));\n\t\tlog(\"A-commit-1\",\n\t\t\tawait execFilePromise(\"git\", gitoptbase.concat([\"commit\", \"-m\", \"Snapshot: \" + branchName]),\n\t\t\t\t{ cwd: gitdir }));\n\t\tlog(\"A-checkout-master\",\n\t\t\tawait execFilePromise(\"git\", [\"checkout\", \"master\"],\n\t\t\t\t{ cwd: gitdir }));\n\t\tconst mergeOutput = await new Promise((resolve, reject) => {\n\t\t\tchild_process.execFile(\"git\", gitoptbase.concat([\"merge\", \"--allow-unrelated-histories\", \"--squash\", \"--no-commit\", branchName]),\n\t\t\t\t{ cwd: gitdir },\n\t\t\t\tfunction(err, stdout, stderr) {\n\t\t\t\t\t// Errors are expected here if there was a merge conflict; ignore them gracefully.\n\t\t\t\t\tresolve({ stdout, stderr });\n\t\t\t\t});\n\t\t});\n\t\tlog(\"A-merge\", mergeOutput);\n\t\tlog(\"A-add-2\",\n\t\t\tawait execFilePromise(\"git\", [\"add\", \"-A\"],\n\t\t\t\t{ cwd: gitdir }));\n\t\tlog(\"A-commit-2\",\n\t\t\tawait execFilePromise(\"git\", gitoptbase.concat([\"commit\", \"-m\", \"Restoring: \" + branchName + \"\\n\\nGit Merge Output:\\n-----\\n\" + mergeOutput.stdout]),\n\t\t\t\t{ cwd: gitdir }));\n\t\tlog(\"A-push\",\n\t\t\tawait execFilePromise(\"git\", [\"push\", \"origin\", \"master\"],\n\t\t\t\t{ cwd: gitdir }));\n\t} catch (e) {\n\t\tthrow e;\n\t} finally {\n\t\tlog(\"C1\", await execFilePromise(\"rm\", [\"-rf\", tmpdir]));\n\t}\n}\n\nfunction generateFilename(name) {\n\treturn `oo_${new Date().toISOString()}_${name}.zip`;\n}\n\nmodule.exports = {\n\trepoContainsRefs,\n\tcreateRepoSnapshot,\n\tgenerateFilename,\n\trestoreRepoFromZipFile,\n};\n"
  },
  {
    "path": "shared/hostname.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst child_process = require(\"child_process\");\n\nlet hostnameCache = null;\n\nmodule.exports = function getHostname() {\n\tif (!hostnameCache) {\n\t\thostnameCache = child_process.execSync(\"hostname\").toString(\"utf8\").trim();\n\t}\n\treturn hostnameCache;\n};\n"
  },
  {
    "path": "shared/index.d.ts",
    "content": "/** Declaration file generated by dts-gen */\r\n\r\nexport class JSONStreamSafe {\r\n    constructor(...args: any[]);\r\n\r\n    static captureRejectionSymbol: any;\r\n\r\n    static captureRejections: boolean;\r\n\r\n    static defaultMaxListeners: number;\r\n\r\n    static errorMonitor: any;\r\n\r\n    static getEventListeners(emitterOrTarget: any, type: any): any;\r\n\r\n    static init(opts: any): void;\r\n\r\n    static kMaxEventTargetListeners: any;\r\n\r\n    static kMaxEventTargetListenersWarned: any;\r\n\r\n    static listenerCount(emitter: any, type: any): any;\r\n\r\n    static on(emitter: any, event: any, options: any): any;\r\n\r\n    static once(emitter: any, name: any, options: any): any;\r\n\r\n    static setMaxListeners(n: any, eventTargets: any): void;\r\n\r\n    static usingDomains: boolean;\r\n\r\n}\r\n\r\nexport class OnlineOffline {\r\n    constructor(...args: any[]);\r\n\r\n    create(...args: any[]): void;\r\n\r\n    destroy(...args: any[]): void;\r\n\r\n    isOnline(...args: any[]): void;\r\n\r\n    static captureRejectionSymbol: any;\r\n\r\n    static captureRejections: boolean;\r\n\r\n    static defaultMaxListeners: number;\r\n\r\n    static errorMonitor: any;\r\n\r\n    static getEventListeners(emitterOrTarget: any, type: any): any;\r\n\r\n    static init(opts: any): void;\r\n\r\n    static kMaxEventTargetListeners: any;\r\n\r\n    static kMaxEventTargetListenersWarned: any;\r\n\r\n    static listenerCount(emitter: any, type: any): any;\r\n\r\n    static on(emitter: any, event: any, options: any): any;\r\n\r\n    static once(emitter: any, name: any, options: any): any;\r\n\r\n    static setMaxListeners(n: any, eventTargets: any): void;\r\n\r\n    static usingDomains: boolean;\r\n\r\n}\r\n\r\nexport class Queue {\r\n    constructor(...args: any[]);\r\n\r\n    dequeue(...args: any[]): void;\r\n\r\n    enqueue(...args: any[]): void;\r\n\r\n    isEmpty(...args: any[]): void;\r\n\r\n    peek(...args: any[]): void;\r\n\r\n    removeAll(...args: any[]): void;\r\n\r\n    size(...args: any[]): void;\r\n\r\n    static captureRejectionSymbol: any;\r\n\r\n    static captureRejections: boolean;\r\n\r\n    static defaultMaxListeners: number;\r\n\r\n    static errorMonitor: any;\r\n\r\n    static getEventListeners(emitterOrTarget: any, type: any): any;\r\n\r\n    static init(opts: any): void;\r\n\r\n    static kMaxEventTargetListeners: any;\r\n\r\n    static kMaxEventTargetListenersWarned: any;\r\n\r\n    static listenerCount(emitter: any, type: any): any;\r\n\r\n    static on(emitter: any, event: any, options: any): any;\r\n\r\n    static once(emitter: any, name: any, options: any): any;\r\n\r\n    static setMaxListeners(n: any, eventTargets: any): void;\r\n\r\n    static usingDomains: boolean;\r\n\r\n}\r\n\r\nexport class RedisMessenger {\r\n    constructor(...args: any[]);\r\n\r\n    applyOtOperation(...args: any[]): void;\r\n\r\n    changeOtDocId(...args: any[]): void;\r\n\r\n    close(...args: any[]): void;\r\n\r\n    destroyD(...args: any[]): void;\r\n\r\n    destroyOtDoc(...args: any[]): void;\r\n\r\n    destroyU(...args: any[]): void;\r\n\r\n    enableOtScriptsSync(...args: any[]): void;\r\n\r\n    enableSessCodeScriptsSync(...args: any[]): void;\r\n\r\n    getSessCode(...args: any[]): void;\r\n\r\n    getSessCodeFlavor(...args: any[]): void;\r\n\r\n    getWorkspaceSessCode(...args: any[]): void;\r\n\r\n    input(...args: any[]): void;\r\n\r\n    isValid(...args: any[]): void;\r\n\r\n    loadOtDoc(...args: any[]): void;\r\n\r\n    otMsg(...args: any[]): void;\r\n\r\n    output(...args: any[]): void;\r\n\r\n    putSessCode(...args: any[]): void;\r\n\r\n    putSessCodeFlavor(...args: any[]): void;\r\n\r\n    replyToFlavorStatus(...args: any[]): void;\r\n\r\n    replyToRebootRequest(...args: any[]): void;\r\n\r\n    requestFlavorStatus(...args: any[]): void;\r\n\r\n    requestReboot(...args: any[]): void;\r\n\r\n    setLive(...args: any[]): void;\r\n\r\n    setOtDocContent(...args: any[]): void;\r\n\r\n    setWorkspaceSessCode(...args: any[]): void;\r\n\r\n    subscribeToDestroyD(...args: any[]): void;\r\n\r\n    subscribeToDestroyU(...args: any[]): void;\r\n\r\n    subscribeToExpired(...args: any[]): void;\r\n\r\n    subscribeToFlavorStatus(...args: any[]): void;\r\n\r\n    subscribeToInput(...args: any[]): void;\r\n\r\n    subscribeToOtMsgs(...args: any[]): void;\r\n\r\n    subscribeToOutput(...args: any[]): void;\r\n\r\n    subscribeToRebootRequests(...args: any[]): void;\r\n\r\n    subscribeToWorkspaceMsgs(...args: any[]): void;\r\n\r\n    touchInput(...args: any[]): void;\r\n\r\n    touchOtDoc(...args: any[]): void;\r\n\r\n    touchOutput(...args: any[]): void;\r\n\r\n    touchWorkspace(...args: any[]): void;\r\n\r\n    workspaceMsg(...args: any[]): void;\r\n\r\n    static captureRejectionSymbol: any;\r\n\r\n    static captureRejections: boolean;\r\n\r\n    static defaultMaxListeners: number;\r\n\r\n    static errorMonitor: any;\r\n\r\n    static getEventListeners(emitterOrTarget: any, type: any): any;\r\n\r\n    static init(opts: any): void;\r\n\r\n    static kMaxEventTargetListeners: any;\r\n\r\n    static kMaxEventTargetListenersWarned: any;\r\n\r\n    static listenerCount(emitter: any, type: any): any;\r\n\r\n    static on(emitter: any, event: any, options: any): any;\r\n\r\n    static once(emitter: any, name: any, options: any): any;\r\n\r\n    static setMaxListeners(n: any, eventTargets: any): void;\r\n\r\n    static usingDomains: boolean;\r\n\r\n}\r\n\r\nexport class RedisQueue {\r\n    constructor(...args: any[]);\r\n\r\n    enqueueMessage(...args: any[]): void;\r\n\r\n    reset(...args: any[]): void;\r\n\r\n    static captureRejectionSymbol: any;\r\n\r\n    static captureRejections: boolean;\r\n\r\n    static defaultMaxListeners: number;\r\n\r\n    static errorMonitor: any;\r\n\r\n    static getEventListeners(emitterOrTarget: any, type: any): any;\r\n\r\n    static init(opts: any): void;\r\n\r\n    static kMaxEventTargetListeners: any;\r\n\r\n    static kMaxEventTargetListenersWarned: any;\r\n\r\n    static listenerCount(emitter: any, type: any): any;\r\n\r\n    static on(emitter: any, event: any, options: any): any;\r\n\r\n    static once(emitter: any, name: any, options: any): any;\r\n\r\n    static setMaxListeners(n: any, eventTargets: any): void;\r\n\r\n    static usingDomains: boolean;\r\n\r\n}\r\n\r\nexport class StdioMessenger {\r\n    constructor(...args: any[]);\r\n\r\n    sendMessage(...args: any[]): void;\r\n\r\n    setReadStream(...args: any[]): void;\r\n\r\n    setWriteStream(...args: any[]): void;\r\n\r\n    static captureRejectionSymbol: any;\r\n\r\n    static captureRejections: boolean;\r\n\r\n    static defaultMaxListeners: number;\r\n\r\n    static errorMonitor: any;\r\n\r\n    static getEventListeners(emitterOrTarget: any, type: any): any;\r\n\r\n    static init(opts: any): void;\r\n\r\n    static kMaxEventTargetListeners: any;\r\n\r\n    static kMaxEventTargetListenersWarned: any;\r\n\r\n    static listenerCount(emitter: any, type: any): any;\r\n\r\n    static on(emitter: any, event: any, options: any): any;\r\n\r\n    static once(emitter: any, name: any, options: any): any;\r\n\r\n    static setMaxListeners(n: any, eventTargets: any): void;\r\n\r\n    static usingDomains: boolean;\r\n\r\n}\r\n\r\nexport const config: {\r\n    ads: {\r\n        abox_html: string;\r\n        disabled: boolean;\r\n        head_html: string;\r\n    };\r\n    auth: {\r\n        easy: {\r\n            max_token_age: number;\r\n            secret: string;\r\n        };\r\n        google: {\r\n            oauth_key: string;\r\n            oauth_secret: string;\r\n        };\r\n        password: {\r\n            delay: number;\r\n            salt_rounds: number;\r\n        };\r\n        utils_admin: {\r\n            users: {\r\n                webmaster: string;\r\n            };\r\n        };\r\n    };\r\n    client: {\r\n        announcement_display: string;\r\n        announcement_html: string;\r\n        app_name: string;\r\n        description_key: string;\r\n        gacode: string;\r\n        gtagid: string;\r\n        onboarding: boolean;\r\n        theme_collection: string;\r\n        theme_color: string;\r\n        title_key: string;\r\n        uservoice: string;\r\n        welcome_back_ms: number;\r\n    };\r\n    docker: {\r\n        cpuShares: number;\r\n        cwd: string;\r\n        diskQuotaKiB: number;\r\n        gitdir: string;\r\n        images: {\r\n            filesystemSuffix: string;\r\n            octaveSuffix: string;\r\n        };\r\n        memoryShares: string;\r\n    };\r\n    email: {\r\n        from: string;\r\n        productName: string;\r\n        provider: string;\r\n        supportUrl: string;\r\n    };\r\n    flavorCommon: {\r\n        blockVolume: boolean;\r\n        defaultClusterSize: number;\r\n        idleTime: number;\r\n        image_uuid: string;\r\n        network_uuid: string;\r\n        statusInterval: number;\r\n    };\r\n    flavors: {\r\n        basic: {\r\n            blockVolume: boolean;\r\n            rackspaceFlavor: string;\r\n        };\r\n        memory: {\r\n            rackspaceFlavor: string;\r\n        };\r\n    };\r\n    front: {\r\n        cookie: {\r\n            max_age: number;\r\n            name: string;\r\n            secret: string;\r\n        };\r\n        flavor_log_interval: number;\r\n        hostname: string;\r\n        listen_port: number;\r\n        locales: string[];\r\n        locales_path: string;\r\n        port: number;\r\n        protocol: string;\r\n        require_https: boolean;\r\n        socket_io_path: string;\r\n        static_path: string;\r\n        view_cache_clear_interval: number;\r\n    };\r\n    gcp: {\r\n        archive_bucket: string;\r\n        artifacts_bucket: string;\r\n        credentials: {\r\n            auth_provider_x509_cert_url: string;\r\n            auth_uri: string;\r\n            client_email: string;\r\n            client_id: string;\r\n            client_x509_cert_url: string;\r\n            private_key: string;\r\n            private_key_id: string;\r\n            project_id: string;\r\n            token_uri: string;\r\n            type: string;\r\n        };\r\n        health_check_port: number;\r\n        i18next_locales_tar_gz: string;\r\n        instance_group_name: string;\r\n        instance_group_removal_method: string;\r\n        snapshots_bucket: string;\r\n        snapshots_duration: number;\r\n        zone: string;\r\n    };\r\n    git: {\r\n        author: {\r\n            email: string;\r\n            name: string;\r\n        };\r\n        autoCommitInterval: number;\r\n        commitTimeLimit: number;\r\n        createRepoPort: number;\r\n        gitDaemonPort: number;\r\n        hostname: string;\r\n        httpUrl: string;\r\n        supportsAllowUnrelatedHistories: boolean;\r\n    };\r\n    gith: {\r\n        hostname: string;\r\n    };\r\n    mailgun: {\r\n        api_key: string;\r\n        domain: string;\r\n    };\r\n    maintenance: {\r\n        interval: number;\r\n        maxNodesInMaintenance: number;\r\n        minNodesInCluster: number;\r\n        pauseDuration: number;\r\n        requestInterval: number;\r\n        responseWaitTime: number;\r\n    };\r\n    mongo: {\r\n        db: string;\r\n        hostname: string;\r\n        port: number;\r\n    };\r\n    ot: {\r\n        document_expire: {\r\n            interval: number;\r\n            timeout: number;\r\n        };\r\n        operation_expire: number;\r\n        stats_interval: number;\r\n    };\r\n    patreon: {\r\n        client_id: string;\r\n        client_secret: string;\r\n        login_redirect: string;\r\n        redirect_url: string;\r\n        state_max_token_age: number;\r\n        state_secret: string;\r\n        tiers: {\r\n            \"4941716\": {\r\n                name: string;\r\n                oo_tier: string;\r\n            };\r\n            \"4941717\": {\r\n                name: string;\r\n                oo_tier: string;\r\n            };\r\n            \"4941718\": {\r\n                name: string;\r\n                oo_tier: string;\r\n            };\r\n            \"4994534\": {\r\n                name: string;\r\n                oo_tier: string;\r\n            };\r\n        };\r\n        webhook_secret: string;\r\n    };\r\n    postmark: {\r\n        onDemandSnapshots: {\r\n            stream: string;\r\n            template: string;\r\n        };\r\n        serverToken: string;\r\n        templateAlias: string;\r\n    };\r\n    rackspace: {\r\n        api_key: string;\r\n        identity_base_url: string;\r\n        personality_filename: string;\r\n        servers_base_url: string;\r\n        username: string;\r\n    };\r\n    recaptcha: {\r\n        secretKey: string;\r\n        siteKey: string;\r\n    };\r\n    redirect: {\r\n        hostname: string;\r\n    };\r\n    redis: {\r\n        expire: {\r\n            interval: number;\r\n            timeout: number;\r\n            timeoutShort: number;\r\n        };\r\n        hostname: string;\r\n        maxPayload: number;\r\n        options: {\r\n            auth_pass: string;\r\n        };\r\n        port: number;\r\n    };\r\n    selinux: {\r\n        cgroup: {\r\n            conf: string;\r\n            cpuPeriod: number;\r\n            cpuQuota: number;\r\n            gid: string;\r\n            name: string;\r\n            uid: string;\r\n        };\r\n        prlimit: {\r\n            addressSpace: number;\r\n        };\r\n    };\r\n    session: {\r\n        countdownExtraTime: number;\r\n        countdownRequestTime: number;\r\n        countdownRequestTimeBuffer: number;\r\n        implementation: string;\r\n        jsonMaxMessageLength: number;\r\n        legalTime: {\r\n            guest: number;\r\n            user: number;\r\n        };\r\n        payloadAcknowledgeDelay: number;\r\n        payloadLimit: {\r\n            guest: number;\r\n            user: number;\r\n        };\r\n        payloadMessageDelay: number;\r\n        textFileSizeLimit: number;\r\n        timeoutTime: number;\r\n        timewarnMessage: string;\r\n        timewarnTime: number;\r\n        urlreadMaxBytes: number;\r\n        urlreadPatterns: string[];\r\n    };\r\n    sessionManager: {\r\n        logInterval: number;\r\n        poolInterval: number;\r\n        poolSize: number;\r\n        poolTier: boolean;\r\n        queueBoostTime: number;\r\n        startupTimeLimit: number;\r\n    };\r\n    statsd: {\r\n        hostname: string;\r\n        port: number;\r\n    };\r\n    tiers: {\r\n        root: {\r\n            \"ads.disabled\": boolean;\r\n            \"sessionManager.poolTier\": string;\r\n            \"sessionManager.queueBoostTime\": number;\r\n        };\r\n        vip: {\r\n            \"ads.disabled\": boolean;\r\n            \"selinux.cgroup.name\": string;\r\n            \"selinux.prlimit.addressSpace\": number;\r\n            \"session.countdownExtraTime\": number;\r\n            \"session.countdownRequestTime\": number;\r\n            \"session.legalTime.user\": number;\r\n            \"session.payloadLimit.user\": number;\r\n            \"session.timeoutTime\": number;\r\n            \"session.timewarnTime\": number;\r\n            \"sessionManager.poolSize\": number;\r\n            \"sessionManager.queueBoostTime\": number;\r\n        };\r\n    };\r\n    worker: {\r\n        clockInterval: {\r\n            max: number;\r\n            min: number;\r\n        };\r\n        clockStrategy: string;\r\n        logDir: string;\r\n        maxSessions: number;\r\n        monitorLogs: {\r\n            subdir: string;\r\n        };\r\n        onDisconnect: string;\r\n        sessionLogs: {\r\n            depth: number;\r\n            subdir: string;\r\n        };\r\n        token: string;\r\n        uid: number;\r\n    };\r\n};\r\n\r\nexport function asyncCache(getter: any, bufferTime: any): any;\r\n\r\nexport function hostname(): any;\r\n\r\nexport function logger(id: any): any;\r\n\r\nexport function onceMessage(emitter: any, messageName: any, next: any): void;\r\n\r\nexport function silent(messageRegex: any, next: any, ...args: any[]): any;\r\n\r\nexport function timeLimit(milliseconds: any, defaults: any, callback: any, ...args: any[]): any;\r\n\r\nexport namespace JSONStreamSafe {\r\n    class EventEmitter {\r\n        constructor(opts: any);\r\n\r\n        addListener(type: any, listener: any): any;\r\n\r\n        emit(type: any, args: any): any;\r\n\r\n        eventNames(): any;\r\n\r\n        getMaxListeners(): any;\r\n\r\n        listenerCount(type: any): any;\r\n\r\n        listeners(type: any): any;\r\n\r\n        off(type: any, listener: any): any;\r\n\r\n        on(type: any, listener: any): any;\r\n\r\n        once(type: any, listener: any): any;\r\n\r\n        prependListener(type: any, listener: any): any;\r\n\r\n        prependOnceListener(type: any, listener: any): any;\r\n\r\n        rawListeners(type: any): any;\r\n\r\n        removeAllListeners(type: any, ...args: any[]): any;\r\n\r\n        removeListener(type: any, listener: any): any;\r\n\r\n        setMaxListeners(n: any): any;\r\n\r\n        static EventEmitter: any;\r\n\r\n        static captureRejectionSymbol: any;\r\n\r\n        static captureRejections: boolean;\r\n\r\n        static defaultMaxListeners: number;\r\n\r\n        static errorMonitor: any;\r\n\r\n        static getEventListeners(emitterOrTarget: any, type: any): any;\r\n\r\n        static init(opts: any): void;\r\n\r\n        static kMaxEventTargetListeners: any;\r\n\r\n        static kMaxEventTargetListenersWarned: any;\r\n\r\n        static listenerCount(emitter: any, type: any): any;\r\n\r\n        static on(emitter: any, event: any, options: any): any;\r\n\r\n        static once(emitter: any, name: any, options: any): any;\r\n\r\n        static setMaxListeners(n: any, eventTargets: any): void;\r\n\r\n        static usingDomains: boolean;\r\n\r\n    }\r\n\r\n}\r\n\r\nexport namespace OnlineOffline {\r\n    class EventEmitter {\r\n        constructor(opts: any);\r\n\r\n        addListener(type: any, listener: any): any;\r\n\r\n        emit(type: any, args: any): any;\r\n\r\n        eventNames(): any;\r\n\r\n        getMaxListeners(): any;\r\n\r\n        listenerCount(type: any): any;\r\n\r\n        listeners(type: any): any;\r\n\r\n        off(type: any, listener: any): any;\r\n\r\n        on(type: any, listener: any): any;\r\n\r\n        once(type: any, listener: any): any;\r\n\r\n        prependListener(type: any, listener: any): any;\r\n\r\n        prependOnceListener(type: any, listener: any): any;\r\n\r\n        rawListeners(type: any): any;\r\n\r\n        removeAllListeners(type: any, ...args: any[]): any;\r\n\r\n        removeListener(type: any, listener: any): any;\r\n\r\n        setMaxListeners(n: any): any;\r\n\r\n        static EventEmitter: any;\r\n\r\n        static captureRejectionSymbol: any;\r\n\r\n        static captureRejections: boolean;\r\n\r\n        static defaultMaxListeners: number;\r\n\r\n        static errorMonitor: any;\r\n\r\n        static getEventListeners(emitterOrTarget: any, type: any): any;\r\n\r\n        static init(opts: any): void;\r\n\r\n        static kMaxEventTargetListeners: any;\r\n\r\n        static kMaxEventTargetListenersWarned: any;\r\n\r\n        static listenerCount(emitter: any, type: any): any;\r\n\r\n        static on(emitter: any, event: any, options: any): any;\r\n\r\n        static once(emitter: any, name: any, options: any): any;\r\n\r\n        static setMaxListeners(n: any, eventTargets: any): void;\r\n\r\n        static usingDomains: boolean;\r\n\r\n    }\r\n\r\n}\r\n\r\nexport namespace Queue {\r\n    class EventEmitter {\r\n        constructor(opts: any);\r\n\r\n        addListener(type: any, listener: any): any;\r\n\r\n        emit(type: any, args: any): any;\r\n\r\n        eventNames(): any;\r\n\r\n        getMaxListeners(): any;\r\n\r\n        listenerCount(type: any): any;\r\n\r\n        listeners(type: any): any;\r\n\r\n        off(type: any, listener: any): any;\r\n\r\n        on(type: any, listener: any): any;\r\n\r\n        once(type: any, listener: any): any;\r\n\r\n        prependListener(type: any, listener: any): any;\r\n\r\n        prependOnceListener(type: any, listener: any): any;\r\n\r\n        rawListeners(type: any): any;\r\n\r\n        removeAllListeners(type: any, ...args: any[]): any;\r\n\r\n        removeListener(type: any, listener: any): any;\r\n\r\n        setMaxListeners(n: any): any;\r\n\r\n        static EventEmitter: any;\r\n\r\n        static captureRejectionSymbol: any;\r\n\r\n        static captureRejections: boolean;\r\n\r\n        static defaultMaxListeners: number;\r\n\r\n        static errorMonitor: any;\r\n\r\n        static getEventListeners(emitterOrTarget: any, type: any): any;\r\n\r\n        static init(opts: any): void;\r\n\r\n        static kMaxEventTargetListeners: any;\r\n\r\n        static kMaxEventTargetListenersWarned: any;\r\n\r\n        static listenerCount(emitter: any, type: any): any;\r\n\r\n        static on(emitter: any, event: any, options: any): any;\r\n\r\n        static once(emitter: any, name: any, options: any): any;\r\n\r\n        static setMaxListeners(n: any, eventTargets: any): void;\r\n\r\n        static usingDomains: boolean;\r\n\r\n    }\r\n\r\n}\r\n\r\nexport namespace RedisMessenger {\r\n    class EventEmitter {\r\n        constructor(opts: any);\r\n\r\n        addListener(type: any, listener: any): any;\r\n\r\n        emit(type: any, args: any): any;\r\n\r\n        eventNames(): any;\r\n\r\n        getMaxListeners(): any;\r\n\r\n        listenerCount(type: any): any;\r\n\r\n        listeners(type: any): any;\r\n\r\n        off(type: any, listener: any): any;\r\n\r\n        on(type: any, listener: any): any;\r\n\r\n        once(type: any, listener: any): any;\r\n\r\n        prependListener(type: any, listener: any): any;\r\n\r\n        prependOnceListener(type: any, listener: any): any;\r\n\r\n        rawListeners(type: any): any;\r\n\r\n        removeAllListeners(type: any, ...args: any[]): any;\r\n\r\n        removeListener(type: any, listener: any): any;\r\n\r\n        setMaxListeners(n: any): any;\r\n\r\n        static EventEmitter: any;\r\n\r\n        static captureRejectionSymbol: any;\r\n\r\n        static captureRejections: boolean;\r\n\r\n        static defaultMaxListeners: number;\r\n\r\n        static errorMonitor: any;\r\n\r\n        static getEventListeners(emitterOrTarget: any, type: any): any;\r\n\r\n        static init(opts: any): void;\r\n\r\n        static kMaxEventTargetListeners: any;\r\n\r\n        static kMaxEventTargetListenersWarned: any;\r\n\r\n        static listenerCount(emitter: any, type: any): any;\r\n\r\n        static on(emitter: any, event: any, options: any): any;\r\n\r\n        static once(emitter: any, name: any, options: any): any;\r\n\r\n        static setMaxListeners(n: any, eventTargets: any): void;\r\n\r\n        static usingDomains: boolean;\r\n\r\n    }\r\n\r\n}\r\n\r\nexport namespace RedisQueue {\r\n    class EventEmitter {\r\n        constructor(opts: any);\r\n\r\n        addListener(type: any, listener: any): any;\r\n\r\n        emit(type: any, args: any): any;\r\n\r\n        eventNames(): any;\r\n\r\n        getMaxListeners(): any;\r\n\r\n        listenerCount(type: any): any;\r\n\r\n        listeners(type: any): any;\r\n\r\n        off(type: any, listener: any): any;\r\n\r\n        on(type: any, listener: any): any;\r\n\r\n        once(type: any, listener: any): any;\r\n\r\n        prependListener(type: any, listener: any): any;\r\n\r\n        prependOnceListener(type: any, listener: any): any;\r\n\r\n        rawListeners(type: any): any;\r\n\r\n        removeAllListeners(type: any, ...args: any[]): any;\r\n\r\n        removeListener(type: any, listener: any): any;\r\n\r\n        setMaxListeners(n: any): any;\r\n\r\n        static EventEmitter: any;\r\n\r\n        static captureRejectionSymbol: any;\r\n\r\n        static captureRejections: boolean;\r\n\r\n        static defaultMaxListeners: number;\r\n\r\n        static errorMonitor: any;\r\n\r\n        static getEventListeners(emitterOrTarget: any, type: any): any;\r\n\r\n        static init(opts: any): void;\r\n\r\n        static kMaxEventTargetListeners: any;\r\n\r\n        static kMaxEventTargetListenersWarned: any;\r\n\r\n        static listenerCount(emitter: any, type: any): any;\r\n\r\n        static on(emitter: any, event: any, options: any): any;\r\n\r\n        static once(emitter: any, name: any, options: any): any;\r\n\r\n        static setMaxListeners(n: any, eventTargets: any): void;\r\n\r\n        static usingDomains: boolean;\r\n\r\n    }\r\n\r\n}\r\n\r\nexport namespace StdioMessenger {\r\n    class EventEmitter {\r\n        constructor(opts: any);\r\n\r\n        addListener(type: any, listener: any): any;\r\n\r\n        emit(type: any, args: any): any;\r\n\r\n        eventNames(): any;\r\n\r\n        getMaxListeners(): any;\r\n\r\n        listenerCount(type: any): any;\r\n\r\n        listeners(type: any): any;\r\n\r\n        off(type: any, listener: any): any;\r\n\r\n        on(type: any, listener: any): any;\r\n\r\n        once(type: any, listener: any): any;\r\n\r\n        prependListener(type: any, listener: any): any;\r\n\r\n        prependOnceListener(type: any, listener: any): any;\r\n\r\n        rawListeners(type: any): any;\r\n\r\n        removeAllListeners(type: any, ...args: any[]): any;\r\n\r\n        removeListener(type: any, listener: any): any;\r\n\r\n        setMaxListeners(n: any): any;\r\n\r\n        static EventEmitter: any;\r\n\r\n        static captureRejectionSymbol: any;\r\n\r\n        static captureRejections: boolean;\r\n\r\n        static defaultMaxListeners: number;\r\n\r\n        static errorMonitor: any;\r\n\r\n        static getEventListeners(emitterOrTarget: any, type: any): any;\r\n\r\n        static init(opts: any): void;\r\n\r\n        static kMaxEventTargetListeners: any;\r\n\r\n        static kMaxEventTargetListenersWarned: any;\r\n\r\n        static listenerCount(emitter: any, type: any): any;\r\n\r\n        static on(emitter: any, event: any, options: any): any;\r\n\r\n        static once(emitter: any, name: any, options: any): any;\r\n\r\n        static setMaxListeners(n: any, eventTargets: any): void;\r\n\r\n        static usingDomains: boolean;\r\n\r\n    }\r\n\r\n}\r\n\r\nexport namespace config2 {\r\n    function flavor(flavor: any): any;\r\n\r\n    function tier(tier: any): any;\r\n\r\n}\r\n\r\nexport namespace gitarchive {\r\n    function createRepoSnapshot(tld: any, name: any, outStream: any): any;\r\n\r\n    function generateFilename(name: any): any;\r\n\r\n    function repoContainsRefs(tld: any, name: any): any;\r\n\r\n    function restoreRepoFromZipFile(log: any, tld: any, name: any, branchName: any, zipFileBlob: any): void;\r\n\r\n}\r\n\r\nexport namespace metrics {\r\n    function gauge(id: any, value: any): any;\r\n\r\n}\r\n\r\nexport namespace redisUtil {\r\n    function createClient(): any;\r\n\r\n    function getSessCodeFromChannel(channel: any): any;\r\n\r\n    function isValidSessCode(sessCode: any): any;\r\n\r\n    namespace chan {\r\n        const destroyD: string;\r\n\r\n        const destroyU: string;\r\n\r\n        const needsOctave: string;\r\n\r\n        const rebootRequest: string;\r\n\r\n        function attachment(id: any): any;\r\n\r\n        function flavorStatus(flavor: any): any;\r\n\r\n        function input(sessCode: any): any;\r\n\r\n        function needsOctaveFlavor(flavor: any): any;\r\n\r\n        function otCnt(docId: any): any;\r\n\r\n        function otDoc(docId: any): any;\r\n\r\n        function otOps(docId: any): any;\r\n\r\n        function otSub(docId: any): any;\r\n\r\n        function output(sessCode: any): any;\r\n\r\n        function session(sessCode: any): any;\r\n\r\n        function wsSess(wsId: any): any;\r\n\r\n        function wsSub(wsId: any): any;\r\n\r\n    }\r\n\r\n}\r\n\r\n"
  },
  {
    "path": "shared/index.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nmodule.exports = {\n\tasyncCache: require(\"./async-cache\"),\n\tconfig: require(\"./config\"),\n\tconfig2: require(\"./config-helper\"),\n\tgitarchive: require(\"./gitarchive\"),\n\thostname: require(\"./hostname\"),\n\tJSONStreamSafe: require(\"./json-stream-safe\"),\n\tlogger: require(\"./logger\"),\n\tmetrics: require(\"./metrics\"),\n\tonceMessage: require(\"./once-message\"),\n\tOnlineOffline: require(\"./online-offline\"),\n\tQueue: require(\"./queue\"),\n\tRedisMessenger: require(\"./redis-messenger\"),\n\tRedisQueue: require(\"./redis-queue\"),\n\tredisUtil: require(\"./redis-util\"),\n\tsilent: require(\"./silent\"),\n\tStdioMessenger: require(\"./stdio-messenger\"),\n\ttimeLimit: require(\"./time-limit\"),\n};\n"
  },
  {
    "path": "shared/json-stream-safe.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst parserFactory = require(\"stream-json\").parser;\nconst streamValues = require(\"stream-json/streamers/StreamValues\").streamValues;\nconst EventEmitter = require(\"events\");\nconst log = require(\"./logger\")(\"json-stream-safe\");\n\n// This is a thin wrapper around JSONStream that recovers from parse errors.\n\nclass JSONStreamSafe extends EventEmitter {\n\tconstructor(stream) {\n\t\tsuper();\n\t\tthis.stream = stream;\n\t\tthis._run();\n\t}\n\n\t_run() {\n\t\t// Note: upon error events, we must restart the parser, because Node closes streams upon any error event.\n\t\tthis.stream\n\t\t\t.pipe(parserFactory({\n\t\t\t\tjsonStreaming: true\n\t\t\t}))\n\t\t\t.on(\"error\", (err) => {\n\t\t\t\tlog.error(\"JSON syntax error\", err);\n\t\t\t\tthis._run();\n\t\t\t})\n\t\t\t.pipe(streamValues())\n\t\t\t.on(\"error\", (err) => {\n\t\t\t\tlog.error(\"Could not unpack value\", err);\n\t\t\t\tthis._run();\n\t\t\t})\n\t\t\t.on(\"data\", (d) => {\n\t\t\t\tthis.emit(\"data\", d.value);\n\t\t\t})\n\t\t\t.on(\"end\", () => {\n\t\t\t\t// TODO: Does this method get run? Is it important?\n\t\t\t\tthis.emit(\"end\");\n\t\t\t});\n\t}\n}\n\nmodule.exports = JSONStreamSafe;\n"
  },
  {
    "path": "shared/logger.js",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\n// Centralized logger definition for most OO projects.\n\nlet writeStackdriverLog = function() {};\n\n// Use debug-logger with all logs going to stderr\nconst debugLogger = require(\"debug-logger\").config({\n\tlevels: {\n\t\ttrace: { fd: 2 },\n\t\tdebug: { fd: 2 },\n\t\tlog: { fd: 2 },\n\t\tinfo: { fd: 2 },\n\t\twarn: { fd: 2 },\n\t\terror: { fd: 2 }\n\t}\n});\n\ntry {\n\tconst stackdriver = require(\"./stackdriver\");\n\twriteStackdriverLog = stackdriver.writeLog;\n\tdebugLogger(\"oo:logger\").info(\"Note: Stackdriver logging enabled\");\n} catch(err) {\n\t// Don't log to stackdriver\n\tdebugLogger(\"oo:logger\").info(\"Note: Stackdriver logging disabled\");\n}\n\nprocess.on(\"unhandledRejection\", (reason, promise) => {\n\tdebugLogger(\"oo:unhandledRejection\").error(\"Unhandled Rejection at:\", promise, \"reason:\", reason);\n\twriteStackdriverLog(\"error\", \"unhandledRejection\", [promise, reason]);\n});\n\nmodule.exports = function(id) {\n\tconst impl = debugLogger(\"oo:\" + id);\n\treturn {\n\t\t/** Trace: low-level operational details */\n\t\ttrace: (...args) => {\n\t\t\timpl.trace(...args);\n\t\t\t// don't log to stackdriver\n\t\t},\n\n\t\t/** Debug: information related to app health */\n\t\tdebug: (...args) => {\n\t\t\timpl.debug(...args);\n\t\t\twriteStackdriverLog(\"debug\", id, args);\n\t\t},\n\n\t\t/** Log: uncategorized messages from another source */\n\t\tlog: (...args) => {\n\t\t\timpl.log(...args);\n\t\t\t// don't log to stackdriver\n\t\t},\n\n\t\t/** Info: changes to an application state */\n\t\tinfo: (...args) => {\n\t\t\timpl.info(...args);\n\t\t\twriteStackdriverLog(\"info\", id, args);\n\t\t},\n\n\t\t/** Warn: unusual state, but not an error */\n\t\twarn: (...args) => {\n\t\t\timpl.warn(...args);\n\t\t\twriteStackdriverLog(\"warning\", id, args);\n\t\t},\n\n\t\t/** Error: unexpected state */\n\t\terror: (...args) => {\n\t\t\timpl.error(...args);\n\t\t\twriteStackdriverLog(\"error\", id, args);\n\t\t},\n\t};\n};\n"
  },
  {
    "path": "shared/lua/get-sesscode.lua",
    "content": "-- Copyright © 2018, Octave Online LLC\n--\n-- This file is part of Octave Online Server.\n--\n-- Octave Online Server is free software: you can redistribute it and/or\n-- modify it under the terms of the GNU Affero General Public License as\n-- published by the Free Software Foundation, either version 3 of the License,\n-- or (at your option) any later version.\n--\n-- Octave Online Server is distributed in the hope that it will be useful, but\n-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n-- License for more details.\n--\n-- You should have received a copy of the GNU Affero General Public License\n-- along with Octave Online Server.  If not, see\n-- <https://www.gnu.org/licenses/>.\n\nlocal needs_octave_key = KEYS[1]\nlocal token = ARGV[1]\n\n-- TODO: An additional key is used in this script beyond the keys passed in from the arguments.  Could cause issue with clusters.\n\nlocal sesscodes = redis.call(\"ZRANGE\", needs_octave_key, 0, 0)\n\nwhile table.getn(sesscodes) == 1\ndo\n\tlocal sesscode = sesscodes[1]\n\tredis.call(\"ZREM\", needs_octave_key, sesscode)\n\n\t-- Check that the sesscode is still valid; if its hash was deleted, then we should discard this sesscode.\n\tlocal sess_info_key = \"oo:session:\" .. sesscode\n\tlocal exists = redis.call(\"EXISTS\", sess_info_key)\n\n\tif exists == 1 then\n\t\tlocal user = redis.call(\"HGET\", sess_info_key, \"user\")\n\t\tredis.call(\"HSET\", sess_info_key, \"worker\", token)\n\t\treturn {sesscode, user}\n\n\telse\n\t\t-- Try again; loop back to the top\n\t\tsesscodes = redis.call(\"ZRANGE\", needs_octave_key, 0, 0)\n\tend\nend\n\nreturn -1\n"
  },
  {
    "path": "shared/lua/ot.lua",
    "content": "-- Copyright © 2018, Octave Online LLC\n--\n-- This file is part of Octave Online Server.\n--\n-- Octave Online Server is free software: you can redistribute it and/or\n-- modify it under the terms of the GNU Affero General Public License as\n-- published by the Free Software Foundation, either version 3 of the License,\n-- or (at your option) any later version.\n--\n-- Octave Online Server is distributed in the hope that it will be useful, but\n-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n-- License for more details.\n--\n-- You should have received a copy of the GNU Affero General Public License\n-- along with Octave Online Server.  If not, see\n-- <https://www.gnu.org/licenses/>.\n\n-------------------------------------------------------------------------------\n\n-- This code is heavily based on the JavaScript implementation ot.js:\n-- https://github.com/Operational-Transformation/ot.js/\n--\n-- Copyright © 2012-2014 Tim Baumann, http://timbaumann.info\n--\n-- Permission is hereby granted, free of charge, to any person obtaining a copy\n-- of this software and associated documentation files (the “Software”), to deal\n-- in the Software without restriction, including without limitation the rights\n-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n-- copies of the Software, and to permit persons to whom the Software is\n-- furnished to do so, subject to the following conditions:\n--\n-- The above copyright notice and this permission notice shall be included in\n-- all copies or substantial portions of the Software.\n--\n-- THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n-- THE SOFTWARE.\n\n-------------------------------------------------------------------------------\n\n-- Perform Operational Transormation.\n\n-- Lua has poor support for UTF-8, so we need to have custom functions.\n-- These are based on those originally written by Cosmin Apreutesei:\n-- https://github.com/luapower/utf8\n-- Use the prefix \"lutf8\" in case at some point in the future Redis adds the Lua 5.3 built-in \"utf8\" library.\n\n-- Byte index of the next char after the char at byte index i, followed by a valid flag for the char at byte index i.\n-- nil if not found. invalid characters are iterated as 1-byte chars.\nfunction lutf8_next(s, i)\n\tif not i then\n\t\tif #s == 0 then return nil end\n\t\treturn 1, true --fake flag (doesn't matter since this flag is not to be taken as full validation)\n\tend\n\tif i > #s then return end\n\tlocal c = s:byte(i)\n\tif c >= 0x00 and c <= 0x7F then\n\t\ti = i + 1\n\telseif c >= 0xC2 and c <= 0xDF then\n\t\ti = i + 2\n\telseif c >= 0xE0 and c <= 0xEF then\n\t\ti = i + 3\n\telseif c >= 0xF0 and c <= 0xF4 then\n\t\ti = i + 4\n\telse --invalid\n\t\treturn i + 1, false\n\tend\n\tif i > #s then return end\n\treturn i, true\nend\n\n-- Iterator over byte indices in the string.\nfunction lutf8_byte_indices(s, previ)\n\treturn lutf8_next, s, previ\nend\n\n-- Returns the number of UTF-8 characters in the string, like string.len.\nfunction lutf8_len(s)\n\tlocal len = 0\n\tfor _ in lutf8_byte_indices(s) do\n\t\tlen = len + 1\n\tend\n\treturn len\nend\n\n-- Performs a substring operation, like string.sub.\n-- TODO: This is an O(N) operation, which makes merging changes together O(N^2) in the worst case.  See if there's a way to make this operation more efficient.\nfunction lutf8_sub(s, start_ci, end_ci)\n\tlocal ci = 0\n\tlocal start_i = 1\n\tlocal end_i = s:len()\n\tfor i in lutf8_byte_indices(s) do\n\t\tci = ci + 1\n\t\tif ci == start_ci then\n\t\t\tstart_i = i\n\t\tend\n\t\tif ci == end_ci+1 then\n\t\t\tend_i = i-1\n\t\t\tbreak\n\t\tend\n\tend\n\treturn string.sub(s, start_i, end_i)\nend\n\n-- Remove redundant ops from an operations table\nfunction condense(ops)\n\tlocal i = 2\n\n\twhile ops[i] ~= nil do\n\t\t-- insert/insert\n\t\tif type(ops[i]) == \"string\" and type(ops[i-1]) == \"string\" then\n\t\t\tops[i-1] = ops[i-1] .. ops[i]\n\t\t\ttable.remove(ops, i)\n\n\t\t-- delete/insert\n\t\t-- The order of these operations does not matter with\n\t\t-- respect to the \"apply\" function, but for consistency\n\t\t-- we always put \"insert\" first.\n\t\telseif type(ops[i]) == \"string\" and ops[i-1]<0 then\n\t\t\tlocal tmp = ops[i]\n\t\t\tops[i] = ops[i-1]\n\t\t\tops[i-1] = tmp\n\n\t\t\t-- go backwards in case the new insert can be condensed\n\t\t\ti = i-1\n\n\t\t-- other/insert (do nothing)\n\t\telseif type(ops[i]) == \"string\" or type(ops[i-1]) == \"string\" then\n\t\t\ti = i+1\n\n\t\t-- delete/delete\n\t\telseif ops[i]<0 and ops[i-1]<0 then\n\t\t\tops[i-1] = ops[i-1] + ops[i]\n\t\t\ttable.remove(ops, i)\n\n\t\t-- retain/retain\n\t\telseif ops[i]>0 and ops[i-1]>0 then\n\t\t\tops[i-1] = ops[i-1] + ops[i]\n\t\t\ttable.remove(ops, i)\n\n\t\t-- something else that we can't condense\n\t\telse\n\t\t\ti = i+1\n\n\t\tend\n\n\t\tif i<2 then\n\t\t\ti = 2\n\t\tend\n\tend\nend\n\n-- Transform takes two operations A and B that happened\n-- concurrently and produces two operations A' and B'\n-- such that apply(apply(S,A),B') == apply(apply(S,B),A')\n-- This function is the heart of OT.\nfunction transform(ops1, ops2)\n\tlocal ops1p = {}\n\tlocal ops2p = {}\n\tlocal i1 = 1\n\tlocal i2 = 1\n\tlocal op1 = ops1[i1]\n\tlocal op2 = ops2[i2]\n\tlocal minl = 0\n\n\twhile op1 ~= nil or op2 ~= nil do\n\t\t-- insert by player 1\n\t\t-- break tie by prefering player 1\n\t\tif type(op1) == \"string\" then\n\t\t\ttable.insert(ops1p, op1) -- insert\n\t\t\ttable.insert(ops2p, lutf8_len(op1)) -- retain\n\t\t\ti1 = i1+1; op1 = ops1[i1]\n\n\t\t-- insert by player 2\n\t\telseif type(op2) == \"string\" then\n\t\t\ttable.insert(ops1p, lutf8_len(op2)) -- retain\n\t\t\ttable.insert(ops2p, op2) -- insert\n\t\t\ti2 = i2+1; op2 = ops2[i2]\n\n\t\t-- retain/retain\n\t\telseif op1>0 and op2>0 then\n\t\t\tif op1>op2 then\n\t\t\t\tminl = op2\n\t\t\t\top1 = op1 - op2\n\t\t\t\ti2 = i2+1; op2 = ops2[i2]\n\t\t\telseif op1 == op2 then\n\t\t\t\tminl = op2\n\t\t\t\ti1 = i1+1; op1 = ops1[i1]\n\t\t\t\ti2 = i2+1; op2 = ops2[i2]\n\t\t\telse\n\t\t\t\tminl = op1\n\t\t\t\top2 = op2 - op1\n\t\t\t\ti1 = i1+1; op1 = ops1[i1]\n\t\t\tend\n\t\t\ttable.insert(ops1p, minl) -- retain\n\t\t\ttable.insert(ops2p, minl) -- retain\n\n\t\t-- delete/delete\n\t\telseif op1<0 and op2<0 then\n\t\t\tif op1 < op2 then\n\t\t\t\top1 = op1 - op2\n\t\t\t\ti2 = i2+1; op2 = ops2[i2]\n\t\t\telseif op1 == op2 then\n\t\t\t\ti1 = i1+1; op1 = ops1[i1]\n\t\t\t\ti2 = i2+1; op2 = ops2[i2]\n\t\t\telse\n\t\t\t\top2 = op2 - op1\n\t\t\t\ti1 = i1+1; op1 = ops1[i1]\n\t\t\tend\n\n\t\t-- delete/retain\n\t\telseif op1<0 and op2>0 then\n\t\t\tif -op1 > op2 then\n\t\t\t\tminl = op2\n\t\t\t\top1 = op1 + op2\n\t\t\t\ti2 = i2+1; op2 = ops2[i2]\n\t\t\telseif -op1 == op2 then\n\t\t\t\tminl = op2\n\t\t\t\ti1 = i1+1; op1 = ops1[i1]\n\t\t\t\ti2 = i2+1; op2 = ops2[i2]\n\t\t\telse\n\t\t\t\tminl = -op1\n\t\t\t\top2 = op2 + op1\n\t\t\t\ti1 = i1+1; op1 = ops1[i1]\n\t\t\tend\n\t\t\ttable.insert(ops1p, -minl) -- delete\n\n\t\t-- retain/delete\n\t\telseif op1>0 and op2<0 then\n\t\t\tif op1 > -op2 then\n\t\t\t\tminl = -op2\n\t\t\t\top1 = op1 + op2\n\t\t\t\ti2 = i2+1; op2 = ops2[i2]\n\t\t\telseif op1 == -op2 then\n\t\t\t\tminl = op1\n\t\t\t\ti1 = i1+1; op1 = ops1[i1]\n\t\t\t\ti2 = i2+1; op2 = ops2[i2]\n\t\t\telse\n\t\t\t\tminl = op1\n\t\t\t\top2 = op2 + op1\n\t\t\t\ti1 = i1+1; op1 = ops1[i1]\n\t\t\tend\n\t\t\ttable.insert(ops2p, -minl) -- delete\n\n\t\t-- noop\n\t\telseif op1==0 then\n\t\t\ti1 = i1+1; op1 = ops1[i1]\n\t\telseif op2==0 then\n\t\t\ti2 = i2+1; op2 = ops2[i2]\n\n\t\t-- unknown\n\t\telse\n\t\t\terror()\n\t\tend\n\tend\n\n\tcondense(ops1p)\n\tcondense(ops2p)\n\treturn ops1p, ops2p\nend\n\n-- Apply an operation to a text\nfunction apply(str, ops)\n\tlocal j = 1\n\tlocal res = \"\"\n\n\tfor i,op in pairs(ops) do\n\n\t\t-- insert\n\t\tif type(op)==\"string\" then\n\t\t\tres = res .. op\n\n\t\t-- delete\n\t\telseif op<0 then\n\t\t\tj = j - op\n\n\t\t-- retain\n\t\telse\n\t\t\tres = res .. lutf8_sub(str, j, j+op-1)\n\t\t\tj = j + op\n\t\tend\n\tend\n\n\treturn res\nend\n\n"
  },
  {
    "path": "shared/lua/ot_apply.lua",
    "content": "-- Copyright © 2018, Octave Online LLC\n--\n-- This file is part of Octave Online Server.\n--\n-- Octave Online Server is free software: you can redistribute it and/or\n-- modify it under the terms of the GNU Affero General Public License as\n-- published by the Free Software Foundation, either version 3 of the License,\n-- or (at your option) any later version.\n--\n-- Octave Online Server is distributed in the hope that it will be useful, but\n-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n-- License for more details.\n--\n-- You should have received a copy of the GNU Affero General Public License\n-- along with Octave Online Server.  If not, see\n-- <https://www.gnu.org/licenses/>.\n\n-- Perform operations on server.\n-- Read arguments\nlocal rev = tonumber(ARGV[1])\nlocal message = cjson.decode(ARGV[2])\nlocal op_expiretime = tonumber(ARGV[3])\nlocal doc_expiretime = tonumber(ARGV[4])\nlocal ops_key = KEYS[1]\nlocal doc_key = KEYS[2]\nlocal sub_key = KEYS[3]\nlocal cnt_key = KEYS[4]\n\n-- Load concurrent operations.  The operations store is truncated according\n-- to the call to \"EXPIRE\" later in this file, so we need to compute the index\n-- into the operations store relative to the current length of the store.  If\n-- that index is out of range of the list, then some of the concurrent\n-- operations required for transforming the new operation have been expired\n-- out of the cache, and we need to raise an error.\nlocal nrev = redis.call(\"GET\", cnt_key)\nif not nrev then nrev = 0 end\nlocal nstore = redis.call(\"LLEN\", ops_key)\nif not nstore then nstore = 0 end\nlocal idx = nstore-nrev+rev\nif idx < 0 then error(\"Operation history is too shallow\") end\nlocal concurrent = redis.call(\"LRANGE\", ops_key, idx, -1)\n\n-- Transform the new operation against all the concurrent operations\nif concurrent then\n\tfor i,cops in pairs(concurrent) do\n\t\tmessage.ops = transform(message.ops, cjson.decode(cops))\n\tend\nend\n\n-- Save the operation\nredis.call(\"RPUSH\", ops_key, cjson.encode(message.ops))\nredis.call(\"INCR\", cnt_key)\n\n-- Load and apply to the document\nlocal doc = redis.call(\"GET\", doc_key)\nif type(doc)==\"boolean\" then doc=\"\" end\ndoc = apply(doc, message.ops)\nredis.call(\"SET\", doc_key, doc)\n\n-- Touch the operation keys' expire value\nredis.call(\"EXPIRE\", ops_key, op_expiretime)\nredis.call(\"EXPIRE\", doc_key, doc_expiretime)\nredis.call(\"EXPIRE\", cnt_key, doc_expiretime)\n\n-- Publish to the subscribe channel\nredis.call(\"PUBLISH\", sub_key, cjson.encode(message))\n"
  },
  {
    "path": "shared/lua/ot_set.lua",
    "content": "-- Copyright © 2018, Octave Online LLC\n--\n-- This file is part of Octave Online Server.\n--\n-- Octave Online Server is free software: you can redistribute it and/or\n-- modify it under the terms of the GNU Affero General Public License as\n-- published by the Free Software Foundation, either version 3 of the License,\n-- or (at your option) any later version.\n--\n-- Octave Online Server is distributed in the hope that it will be useful, but\n-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n-- License for more details.\n--\n-- You should have received a copy of the GNU Affero General Public License\n-- along with Octave Online Server.  If not, see\n-- <https://www.gnu.org/licenses/>.\n\n-- Set an OT document to the specified value using transformations.\n-- Read arguments\nlocal content = ARGV[1]\nlocal message = cjson.decode(ARGV[2])\nlocal op_expiretime = tonumber(ARGV[3])\nlocal doc_expiretime = tonumber(ARGV[4])\nlocal ops_key = KEYS[1]\nlocal doc_key = KEYS[2]\nlocal sub_key = KEYS[3]\nlocal cnt_key = KEYS[4]\n\nlocal old_content = redis.call(\"GET\", doc_key)\n\nif type(old_content)==\"boolean\" then\n\t-- Document doesn't exist.  Make an operation to set its content.\n\tmessage.ops = {content}\n\nelse\n\t-- No action necessary\n\treturn 0\n\nend\n\n-- Update Redis\nredis.call(\"SET\", doc_key, content)\nredis.call(\"SET\", cnt_key, 0)\n\n-- Touch the operation keys' expire value\nredis.call(\"EXPIRE\", ops_key, op_expiretime)\nredis.call(\"EXPIRE\", doc_key, doc_expiretime)\nredis.call(\"EXPIRE\", cnt_key, doc_expiretime)\n\nreturn 1"
  },
  {
    "path": "shared/lua/ot_test.lua",
    "content": "-- Copyright © 2018, Octave Online LLC\n--\n-- This file is part of Octave Online Server.\n--\n-- Octave Online Server is free software: you can redistribute it and/or\n-- modify it under the terms of the GNU Affero General Public License as\n-- published by the Free Software Foundation, either version 3 of the License,\n-- or (at your option) any later version.\n--\n-- Octave Online Server is distributed in the hope that it will be useful, but\n-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n-- License for more details.\n--\n-- You should have received a copy of the GNU Affero General Public License\n-- along with Octave Online Server.  If not, see\n-- <https://www.gnu.org/licenses/>.\n\n-- Tests for the OT implementation.\n\nrequire \"ot\"\n\nfunction table_equals(a,b)\n\ti = 1\n\twhile a[i] ~= nil do\n\t\tif a[i] ~= b[i] then return false end\n\t\ti = i+1\n\tend\n\tif a[i]~=nil or b[i]~=nil then return false end\n\treturn true\nend\n\n-- Test a few condense operations\nops = {5, \"hello\", -3, 6}\ncondense(ops)\nassert(table_equals(ops, {5, \"hello\", -3, 6}))\n\nops = {5, -3, \"hello\", 6}\ncondense(ops)\nassert(table_equals(ops, {5, \"hello\", -3, 6}))\n\nops = {2, 3, \"he\", -2, \"llo\", -1, 4, 2}\ncondense(ops)\nassert(table_equals(ops, {5, \"hello\", -3, 6}))\n\n-- Test some apply operations\nstr = apply(\"hello world\", {6, \"earth\", -5})\nassert(str == \"hello earth\")\n\nops1 = {6, \"world\", -5} -- lorem world\nops2 = {\"hello\", -5, 6} -- hello ipsum\nstr = apply(apply(\"lorem ipsum\", ops1), ops2)\nassert(str == \"hello world\")\n\n-- Test a transform operation\nstr = \"lorem ipsum\"\nops1 = {6, \"world\", -5} -- lorem world\nops2 = {\"hello\", -5, 6} -- hello ipsum\nops1p, ops2p = transform(ops1, ops2) -- hello world\nstr1 = apply(apply(str, ops1), ops2p)\nstr2 = apply(apply(str, ops2), ops1p)\nassert(str1 == str2)\n\n-- Test some UTF-8 commands\nstr = \"abçd\"\nassert(lutf8_len(str) == 4)\nassert(lutf8_sub(str, 2, 3) == \"bç\")\n"
  },
  {
    "path": "shared/metrics.js",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst lynx = require(\"lynx\");\n\nconst config = require(\"./config\");\n\nlet _client = null;\n\nfunction getClient() {\n\tif (!_client) {\n\t\t_client = new lynx(config.statsd.hostname, config.statsd.port);\n\t}\n\treturn _client;\n}\n\nmodule.exports = {\n\tgauge: function(id, value) {\n\t\treturn getClient().gauge(id, value);\n\t}\n};\n"
  },
  {
    "path": "shared/once-message.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\n// This is a simple utility function that waits for a specific message name before continuing.\n\nmodule.exports = function onceMessage(emitter, messageName, next) {\n\tconst messageCallback = (name, content) => {\n\t\tif (name === messageName) {\n\t\t\tnext(null, content);\n\t\t\temitter.removeListener(\"message\", messageCallback);\n\t\t}\n\t};\n\temitter.on(\"message\", messageCallback);\n};\n"
  },
  {
    "path": "shared/online-offline.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst EventEmitter = require(\"events\");\nconst Queue = require(\"./queue\");\n// const log = require(\"./logger\")(\"online-offline\");\n\n/**\n * This is a class that handles safely creating and destroying something asynchronously.\n * Possible states: INIT, CREATING, ONLINE, PENDING-DESTROY, DESTROYING, and DESTROYED.\n * Required methods to implement in derived classes: _doCreate, _doDestroy.\n * Derived classes may read the state but should never change it.\n */\n\nclass OnlineOffline extends EventEmitter {\n\tconstructor() {\n\t\tsuper();\n\n\t\tthis._state = \"INIT\";\n\t\tthis._createCBs = new Queue();\n\t\tthis._destroyCBs = new Queue();\n\t}\n\n\tisOnline() {\n\t\treturn this._state === \"ONLINE\";\n\t}\n\n\tcreate(next) {\n\t\tnext = next || function(){};\n\t\tswitch (this._state) {\n\t\t\tcase \"INIT\": {\n\t\t\t\tthis._state = \"CREATING\";\n\t\t\t\tthis._createCBs.enqueue(next);\n\t\t\t\tlet args = Array.prototype.slice.call(arguments, 1);\n\t\t\t\targs.unshift(this._afterCreate.bind(this));\n\t\t\t\tthis._doCreate.apply(this, args);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"CREATING\":\n\t\t\tcase \"PENDING-DESTROY\":\n\t\t\t\tthis._createCBs.enqueue(next);\n\t\t\t\tbreak;\n\n\t\t\tcase \"ONLINE\":\n\t\t\t\treturn process.nextTick(() => {\n\t\t\t\t\tnext(new Error(\"Already created\"));\n\t\t\t\t});\n\n\t\t\tcase \"DESTROYING\":\n\t\t\tcase \"DESTROYED\":\n\t\t\t\treturn process.nextTick(() => {\n\t\t\t\t\tnext(new Error(\"Already destroyed\"));\n\t\t\t\t});\n\n\t\t\tdefault:\n\t\t\t\tthis._log.error(\"Unknown state:\", this._state);\n\t\t\t\treturn process.nextTick(() => {\n\t\t\t\t\tnext(new Error(\"Internal online-offline error\"));\n\t\t\t\t});\n\t\t}\n\t}\n\n\t_afterCreate(err) {\n\t\tswitch (this._state) {\n\t\t\tcase \"CREATING\":\n\t\t\t\tthis._state = \"ONLINE\";\n\t\t\t\tif (err) {\n\t\t\t\t\tthis.destroy();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"PENDING-DESTROY\":\n\t\t\t\tthis._state = \"ONLINE\";\n\t\t\t\tthis.destroy();\n\t\t\t\tif (!err) {\n\t\t\t\t\terr = new Error(\"Pending destroy\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"DESTROYED\":\n\t\t\t\tif (!err) {\n\t\t\t\t\terr = new Error(\"Already destroyed\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"INIT\":\n\t\t\tcase \"ONLINE\":\n\t\t\tcase \"DESTROYING\":\n\t\t\t\tthis._log.error(\"Unexpected state:\", this._state);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tthis._log.error(\"Unknown state:\", this._state);\n\t\t\t\tbreak;\n\t\t}\n\n\t\tlet args = Array.prototype.slice.call(arguments, 1);\n\t\targs.unshift(err);\n\n\t\twhile (!this._createCBs.isEmpty()) {\n\t\t\tlet cb = this._createCBs.dequeue();\n\t\t\tprocess.nextTick(() => {\n\t\t\t\tcb.apply(this, args);\n\t\t\t});\n\t\t}\n\t}\n\n\tdestroy(next) {\n\t\tnext = next || function(){};\n\t\tswitch (this._state) {\n\t\t\tcase \"ONLINE\": {\n\t\t\t\tthis._state = \"DESTROYING\";\n\t\t\t\tthis._destroyCBs.enqueue(next);\n\t\t\t\tlet args = Array.prototype.slice.call(arguments, 1);\n\t\t\t\targs.unshift(this._afterDestroy.bind(this));\n\t\t\t\tthis._doDestroy.apply(this, args);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"CREATING\":\n\t\t\tcase \"PENDING-DESTROY\":\n\t\t\t\tthis._state = \"PENDING-DESTROY\";\n\t\t\t\tthis._destroyCBs.enqueue(next);\n\t\t\t\tbreak;\n\n\t\t\tcase \"DESTROYING\":\n\t\t\t\tthis._destroyCBs.enqueue(next);\n\t\t\t\tbreak;\n\n\t\t\tcase \"INIT\":\n\t\t\tcase \"DESTROYED\":\n\t\t\t\tthis._state = \"DESTROYED\";\n\t\t\t\treturn process.nextTick(() => {\n\t\t\t\t\tnext(null);\n\t\t\t\t});\n\n\t\t\tdefault:\n\t\t\t\tthis._log.error(\"Unknown state:\", this._state);\n\t\t\t\treturn process.nextTick(() => {\n\t\t\t\t\tnext(new Error(\"Internal online-offline error\"));\n\t\t\t\t});\n\t\t}\n\t}\n\n\t_afterDestroy(err) {\n\t\tswitch (this._state) {\n\t\t\tcase \"DESTROYING\":\n\t\t\t\tthis._state = \"DESTROYED\";\n\t\t\t\tbreak;\n\n\t\t\tcase \"INIT\":\n\t\t\tcase \"CREATING\":\n\t\t\tcase \"ONLINE\":\n\t\t\tcase \"PENDING-DESTROY\":\n\t\t\tcase \"DESTROYED\":\n\t\t\t\tthis._log.error(\"Unexpected state:\", this._state);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tthis._log.error(\"Unknown state:\", this._state);\n\t\t\t\tbreak;\n\t\t}\n\n\t\twhile (!this._destroyCBs.isEmpty()) {\n\t\t\tlet cb = this._destroyCBs.dequeue();\n\t\t\tprocess.nextTick(() => {\n\t\t\t\tcb.call(this, err);\n\t\t\t});\n\t\t}\n\t}\n\n\t_internalDestroyed(err) {\n\t\tswitch (this._state) {\n\t\t\tcase \"DESTROYING\":\n\t\t\tcase \"CREATING\":\n\t\t\tcase \"ONLINE\":\n\t\t\tcase \"PENDING-DESTROY\":\n\t\t\t\tthis._state = \"DESTROYING\";\n\t\t\t\tthis._afterDestroy(err);\n\t\t\t\tbreak;\n\n\t\t\tcase \"DESTROYED\":\n\t\t\t\tbreak;\n\n\t\t\tcase \"INIT\":\n\t\t\t\tthis._log.error(\"Unexpected state:\", this._state);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tthis._log.error(\"Unknown state:\", this._state);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nmodule.exports = OnlineOffline;\n"
  },
  {
    "path": "shared/package.json",
    "content": "{\n  \"name\": \"@oo/shared\",\n  \"version\": \"0.0.0\",\n  \"description\": \"Utility routines shared by one or more projects\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dts-gen\": \"dts-gen -m '@oo/shared' -f index.d.ts --overwrite\"\n  },\n  \"author\": \"Octave Online LLC\",\n  \"license\": \"AGPL-3.0\",\n  \"private\": true,\n  \"engines\": {\n    \"node\": \"18.x\"\n  },\n  \"dependencies\": {\n    \"async\": \"^1.5.2\",\n    \"debug-logger\": \"^0.4.1\",\n    \"deepmerge\": \"^2.1.1\",\n    \"hjson\": \"^3.2.2\",\n    \"jszip\": \"^3.7.1\",\n    \"lynx\": \"^0.2.0\",\n    \"redis\": \"^3.1.2\",\n    \"redis-scripto2\": \"^0.2\",\n    \"stream-json\": \"^1.3.1\",\n    \"uuid\": \"^3.3.2\"\n  },\n  \"devDependencies\": {\n    \"@oo/shared\": \"file:../shared\",\n    \"dts-gen\": \"^0.6.1\"\n  }\n}\n"
  },
  {
    "path": "shared/queue.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst EventEmitter = require(\"events\");\n\n// This is a simple queue implementation that emits an event whenever an item is enqueued.\n\nclass Queue extends EventEmitter {\n\tconstructor() {\n\t\tsuper();\n\t\tthis.enabled = true;\n\t\tthis._items = [];\n\t}\n\n\tsize() {\n\t\treturn this._items.length;\n\t}\n\n\tenqueue(item) {\n\t\tif (this.enabled) {\n\t\t\tthis._items.push(item);\n\t\t\tthis.emit(\"enqueue\");\n\t\t}\n\t}\n\n\tdequeue() {\n\t\tif (this._items.length > 0) {\n\t\t\treturn this._items.shift();\n\t\t} else {\n\t\t\tthrow new RangeError(\"Can't dequeue from an empty queue\");\n\t\t}\n\t}\n\n\tpeek() {\n\t\tif (this._items.length > 0) {\n\t\t\treturn this._items[0];\n\t\t} else {\n\t\t\tthrow new RangeError(\"Can't peek into an empty queue\");\n\t\t}\n\t}\n\n\tremoveAll() {\n\t\tthis._items = [];\n\t}\n\n\tisEmpty() {\n\t\treturn this._items.length === 0;\n\t}\n}\n\nmodule.exports = Queue;\n"
  },
  {
    "path": "shared/redis-messenger.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst EventEmitter = require(\"events\");\nconst logger = require(\"./logger\");\nconst redisUtil = require(\"./redis-util\");\nconst Scripto = require(\"redis-scripto2\");\nconst path = require(\"path\");\nconst uuid = require(\"uuid\");\nconst fs = require(\"fs\");\nconst config = require(\"./config\");\nconst hostname = require(\"./hostname\")();\n\nclass RedisMessenger extends EventEmitter {\n\tconstructor() {\n\t\tsuper();\n\t\tthis._client = redisUtil.createClient();\n\t\tthis._subscribed = false;\n\t\tthis._scriptManager = null;\n\t\tthis.id = uuid.v4().substr(0, 8);  // For logging\n\t\tthis._log = logger(\"redis-messenger:\" + this.id);\n\t\tthis._mlog = logger(\"redis-messenger:\" + this.id + \":minor\");\n\n\t\tthis._client.on(\"error\", (err) => {\n\t\t\tthis._log.trace(\"REDIS CLIENT\", err);\n\t\t});\n\t\tthis._client.on(\"end\", () => {\n\t\t\tthis._log.trace(\"Redis connection ended\");\n\t\t});\n\t\tthis._client.on(\"reconnecting\", (info) => {\n\t\t\tthis._log.trace(\"Redis reconnecting:\", info);\n\t\t\tthis._log.debug(\"FYI: Subscription set:\", this._client.subscription_set);\n\t\t});\n\t\tthis._client.on(\"ready\", (info) => {\n\t\t\tthis._log.trace(\"Redis ready:\", info);\n\t\t});\n\t}\n\n\t// PUBLIC METHODS\n\n\tenableSessCodeScriptsSync() {\n\t\tthis._makeScriptManager();\n\t\tthis._scriptManager.loadFromFile(\"get-sesscode\", path.join(__dirname, \"lua/get-sesscode.lua\"));\n\t\treturn this;\n\t}\n\n\tenableOtScriptsSync() {\n\t\tthis._makeScriptManager();\n\n\t\tlet otApplyScript = fs.readFileSync(path.join(__dirname, \"lua/ot.lua\"), \"utf8\");\n\t\totApplyScript += fs.readFileSync(path.join(__dirname, \"lua/ot_apply.lua\"), \"utf8\");\n\t\totApplyScript = otApplyScript.replace(/function/g, \"local function\");\n\n\t\tlet otSetScript = fs.readFileSync(path.join(__dirname, \"lua/ot_set.lua\"), \"utf8\");\n\n\t\tthis._scriptManager.load({\n\t\t\t\"ot-apply\": otApplyScript,\n\t\t\t\"ot-set\": otSetScript\n\t\t});\n\n\t\treturn this;\n\t}\n\n\t_makeScriptManager() {\n\t\tif (!this._scriptManager) {\n\t\t\tthis._scriptManager = new Scripto(this._client);\n\t\t}\n\t}\n\n\tinput(sessCode, name, content) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tlet channel = redisUtil.chan.input(sessCode);\n\t\tlet messageString = this._serializeMessage(name, content);\n\n\t\tthis._client.publish(channel, messageString);\n\t}\n\n\tsubscribeToInput() {\n\t\tthis._psubscribe(redisUtil.chan.input);\n\t\tthis.on(\"_message\", this._emitMessage.bind(this));\n\t\treturn this;\n\t}\n\n\toutput(sessCode, name, content) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tlet channel = redisUtil.chan.output(sessCode);\n\t\tlet messageString = this._serializeMessage(name, content);\n\n\t\tthis._client.publish(channel, messageString);\n\t}\n\n\tsubscribeToOutput() {\n\t\tthis._psubscribe(redisUtil.chan.output);\n\t\tthis.on(\"_message\", this._emitMessage.bind(this));\n\t\treturn this;\n\t}\n\n\tputSessCode(sessCode, millisecondBoost, content) {\n\t\treturn this._putSessCode(sessCode, millisecondBoost, redisUtil.chan.needsOctave, content);\n\t}\n\n\tputSessCodeFlavor(sessCode, millisecondBoost, flavor, content) {\n\t\treturn this._putSessCode(sessCode, millisecondBoost, redisUtil.chan.needsOctaveFlavor(flavor), content);\n\t}\n\n\t_putSessCode(sessCode, millisecondBoost, channel, content) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tlet time = new Date().valueOf() - millisecondBoost/1000;\n\n\t\tlet multi = this._client.multi();\n\t\tmulti.zadd(channel, time, sessCode);\n\t\t// NOTE: For backwards compatibilty, this field is called \"user\" instead of \"content\"\n\t\tmulti.hset(redisUtil.chan.session(sessCode), \"user\", JSON.stringify(content));\n\t\tmulti.hset(redisUtil.chan.session(sessCode), \"live\", \"false\");\n\t\tmulti.set(redisUtil.chan.input(sessCode), time);\n\t\tmulti.set(redisUtil.chan.output(sessCode), time);\n\t\tmulti.exec(this._handleError.bind(this));\n\t}\n\n\tgetSessCode(next) {\n\t\treturn this._getSessCode(redisUtil.chan.needsOctave, next);\n\t}\n\n\tgetSessCodeFlavor(flavor, next) {\n\t\treturn this._getSessCode(redisUtil.chan.needsOctaveFlavor(flavor), next);\n\t}\n\n\t_getSessCode(channel, next) {\n\t\tthis._runScript(\"get-sesscode\", [channel], [config.worker.token], (err, result) => {\n\t\t\tif (err) this._handleError(err);\n\t\t\tif (result === -1) return next(null, null, null);\n\t\t\ttry {\n\t\t\t\tlet content = JSON.parse(result[1]);\n\t\t\t\tthis.touchOutput(result[0]);\n\t\t\t\tnext(null, result[0], content);\n\t\t\t} catch (err) {\n\t\t\t\tnext(err, null, null);\n\t\t\t}\n\t\t});\n\t}\n\n\tdestroyD(sessCode, reason) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tlet channel = redisUtil.chan.destroyD;\n\t\tlet message = { sessCode, message: reason };\n\n\t\tlet multi = this._client.multi();\n\t\tmulti.del(redisUtil.chan.session(sessCode));\n\t\tmulti.del(redisUtil.chan.input(sessCode));\n\t\tmulti.del(redisUtil.chan.output(sessCode));\n\t\t// For efficiency, zrem the key from needsOctave. However, the key could be in a needs-flavor channel. That case is handled in get-sesscode.lua.\n\t\tmulti.zrem(redisUtil.chan.needsOctave, sessCode);\n\t\tmulti.publish(channel, JSON.stringify(message));\n\t\tmulti.exec(this._handleError.bind(this));\n\t}\n\n\tsubscribeToDestroyD() {\n\t\tthis._subscribe(redisUtil.chan.destroyD);\n\t\tthis.on(\"_message\", (message) => {\n\t\t\tthis.emit(\"destroy-d\", message.sessCode, message.message);\n\t\t});\n\t\treturn this;\n\t}\n\n\tdestroyU(sessCode, reason) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tlet channel = redisUtil.chan.destroyU;\n\t\tlet message = { sessCode, message: reason };\n\n\t\tlet multi = this._client.multi();\n\t\tmulti.del(redisUtil.chan.session(sessCode));\n\t\tmulti.del(redisUtil.chan.input(sessCode));\n\t\tmulti.del(redisUtil.chan.output(sessCode));\n\t\tmulti.publish(channel, JSON.stringify(message));\n\t\tmulti.exec(this._handleError.bind(this));\n\t}\n\n\tsubscribeToDestroyU() {\n\t\tthis._subscribe(redisUtil.chan.destroyU);\n\t\tthis.on(\"_message\", (message) => {\n\t\t\tthis.emit(\"destroy-u\", message.sessCode, message.message);\n\t\t});\n\t\treturn this;\n\t}\n\n\tsetLive(sessCode) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tthis._client.hset(redisUtil.chan.session(sessCode), \"live\", \"true\");\n\t\tthis.touchOutput(sessCode);\n\t}\n\n\tisValid(sessCode, next) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tthis._client.hget(redisUtil.chan.session(sessCode), \"live\", next);\n\t}\n\n\ttouchInput(sessCode, short) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tconst timeout = short ? config.redis.expire.timeoutShort : config.redis.expire.timeout;\n\n\t\tconst multi = this._client.multi();\n\t\tmulti.expire(redisUtil.chan.session(sessCode), timeout/1000);\n\t\tmulti.expire(redisUtil.chan.input(sessCode), timeout/1000);\n\t\tmulti.exec(this._handleError.bind(this));\n\t}\n\n\ttouchOutput(sessCode) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tconst multi = this._client.multi();\n\t\tmulti.expire(redisUtil.chan.session(sessCode), config.redis.expire.timeout/1000);\n\t\tmulti.expire(redisUtil.chan.output(sessCode), config.redis.expire.timeout/1000);\n\t\tmulti.exec(this._handleError.bind(this));\n\t}\n\n\ttouchWorkspace(wsId) {\n\t\tthis._ensureNotSubscribed();\n\n\t\t// TODO: Should these \"expire\" calls on intervals be buffered and sent to the server in batches, both here and above?\n\t\tthis._client.expire(redisUtil.chan.wsSess(wsId), config.redis.expire.timeout/1000, this._handleError.bind(this));\n\t}\n\n\ttouchOtDoc(docId) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tconst multi = this._client.multi();\n\t\tmulti.expire(redisUtil.chan.otCnt(docId), config.ot.document_expire.timeout/1000);\n\t\tmulti.expire(redisUtil.chan.otDoc(docId), config.ot.document_expire.timeout/1000);\n\t\tmulti.exec(this._handleError.bind(this));\n\t}\n\n\tsubscribeToExpired() {\n\t\tthis._epsubscribe();\n\t\tthis.on(\"_message\", (sessCode, channel) => {\n\t\t\tthis.emit(\"expired\", sessCode, channel);\n\t\t});\n\t\treturn this;\n\t}\n\n\trequestReboot(id, priority) {\n\t\treturn this._requestReboot(redisUtil.chan.rebootRequest, id, priority);\n\t}\n\n\trequestFlavorStatus(flavor, id, priority) {\n\t\treturn this._requestReboot(redisUtil.chan.flavorStatus(flavor), id, priority);\n\t}\n\n\t_requestReboot(channel, id, priority) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tlet message = {\n\t\t\tid,\n\t\t\tisRequest: true,\n\t\t\ttoken: config.worker.token,\n\t\t\thostname,\n\t\t\tpriority\n\t\t};\n\n\t\tthis._client.publish(channel, JSON.stringify(message), this._handleError.bind(this));\n\t}\n\n\treplyToRebootRequest(id, response) {\n\t\treturn this._replyToRebootRequest(redisUtil.chan.rebootRequest, id, response);\n\t}\n\n\treplyToFlavorStatus(flavor, id, response) {\n\t\treturn this._replyToRebootRequest(redisUtil.chan.flavorStatus(flavor), id, response);\n\t}\n\n\t_replyToRebootRequest(channel, id, response) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tlet message = {\n\t\t\tid,\n\t\t\tisRequest: false,\n\t\t\ttoken: config.worker.token,\n\t\t\thostname,\n\t\t\tresponse\n\t\t};\n\n\t\tthis._client.publish(channel, JSON.stringify(message), this._handleError.bind(this));\n\t}\n\n\tsubscribeToRebootRequests() {\n\t\treturn this._subscribeToRebootRequests(redisUtil.chan.rebootRequest, \"reboot-request\");\n\t}\n\n\tsubscribeToFlavorStatus(flavor) {\n\t\treturn this._subscribeToRebootRequests(redisUtil.chan.flavorStatus(flavor), \"flavor-status\");\n\t}\n\n\t_subscribeToRebootRequests(channel, eventName) {\n\t\tthis._subscribe(channel);\n\t\tthis.on(\"_message\", (message) => {\n\t\t\tthis.emit(eventName, message.id, message.isRequest, message);\n\t\t});\n\t\treturn this;\n\t}\n\n\tworkspaceMsg(wsId, type, data) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tlet channel = redisUtil.chan.wsSub(wsId);\n\t\tlet messageString = JSON.stringify({\n\t\t\ttype,\n\t\t\tdata\n\t\t});\n\n\t\tthis._client.publish(channel, messageString, this._handleError.bind(this));\n\t}\n\n\tsubscribeToWorkspaceMsgs() {\n\t\tthis._psubscribe(redisUtil.chan.wsSub);\n\t\tthis.on(\"_message\", (wsId, message) => {\n\t\t\tthis.emit(\"ws-sub\", wsId, message.type, message.data);\n\t\t});\n\t\treturn this;\n\t}\n\n\tgetWorkspaceSessCode(wsId, next) {\n\t\tthis._ensureNotSubscribed();\n\t\tthis._client.get(redisUtil.chan.wsSess(wsId), next);\n\t}\n\n\tsetWorkspaceSessCode(wsId, newSessCode, oldSessCode, next) {\n\t\tthis._ensureNotSubscribed();\n\n\t\t// Perform a Compare-And-Swap operation (this is oddly not\n\t\t// in core Redis, so a Lua script is required)\n\t\tconst casScript = \"local k=redis.call(\\\"GET\\\",KEYS[1]); print(k); if k==false or k==ARGV[2] then redis.call(\\\"SET\\\",KEYS[1],ARGV[1]); return {true,ARGV[1]}; end; return {false,k};\";\n\t\tthis._client.eval(casScript, 1, redisUtil.chan.wsSess(wsId), newSessCode, oldSessCode || \"-\", next);\n\t}\n\n\totMsg(docId, obj) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tconst channel = redisUtil.chan.otSub(docId);\n\t\tconst messageString = JSON.stringify(obj);\n\n\t\tthis._client.publish(channel, messageString, this._handleError.bind(this));\n\t}\n\n\tsubscribeToOtMsgs() {\n\t\tthis._psubscribe(redisUtil.chan.otSub);\n\t\tthis.on(\"_message\", (docId, obj) => {\n\t\t\tthis.emit(\"ot-sub\", docId, obj);\n\t\t});\n\t\treturn this;\n\t}\n\n\tchangeOtDocId(oldDocId, newDocId) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tconst multi = this._client.multi();\n\t\tmulti.rename(redisUtil.chan.otOps(oldDocId), redisUtil.chan.otOps(newDocId));\n\t\tmulti.rename(redisUtil.chan.otDoc(oldDocId), redisUtil.chan.otDoc(newDocId));\n\t\tmulti.rename(redisUtil.chan.otSub(oldDocId), redisUtil.chan.otSub(newDocId));\n\t\tmulti.rename(redisUtil.chan.otCnt(oldDocId), redisUtil.chan.otCnt(newDocId));\n\t\tmulti.exec(this._handleError.bind(this));\n\t}\n\n\tdestroyOtDoc(docId) {\n\t\tthis._ensureNotSubscribed();\n\n\t\tconst multi = this._client.multi();\n\t\tmulti.del(redisUtil.chan.otOps(docId));\n\t\tmulti.del(redisUtil.chan.otDoc(docId));\n\t\tmulti.del(redisUtil.chan.otSub(docId));\n\t\tmulti.del(redisUtil.chan.otCnt(docId));\n\t\tmulti.exec(this._handleError.bind(this));\n\t}\n\n\tloadOtDoc(docId, next) {\n\t\tconst multi = this._client.multi();\n\t\tmulti.get(redisUtil.chan.otCnt(docId));\n\t\tmulti.get(redisUtil.chan.otDoc(docId));\n\t\tmulti.exec((err, res) => {\n\t\t\tif (err) return next(err);\n\t\t\tconst rev = Number(res[0]) || -1;\n\t\t\tconst content = res[1] || \"\";\n\t\t\tnext(err, rev, content);\n\t\t});\n\t}\n\n\tapplyOtOperation(docId, rev, message) {\n\t\tconst ops_key = redisUtil.chan.otOps(docId);\n\t\tconst doc_key = redisUtil.chan.otDoc(docId);\n\t\tconst sub_key = redisUtil.chan.otSub(docId);\n\t\tconst cnt_key = redisUtil.chan.otCnt(docId);\n\n\t\tthis._runScript(\n\t\t\t\"ot-apply\",\n\t\t\t[ops_key, doc_key, sub_key, cnt_key],\n\t\t\t[\n\t\t\t\trev,\n\t\t\t\tJSON.stringify(message),\n\t\t\t\tconfig.ot.operation_expire/1000,\n\t\t\t\tconfig.ot.document_expire.timeout/1000\n\t\t\t],\n\t\t\tthis._handleError.bind(this));\n\t}\n\n\tsetOtDocContent(docId, content, message) {\n\t\tconst ops_key = redisUtil.chan.otOps(docId);\n\t\tconst doc_key = redisUtil.chan.otDoc(docId);\n\t\tconst sub_key = redisUtil.chan.otSub(docId);\n\t\tconst cnt_key = redisUtil.chan.otCnt(docId);\n\n\t\tthis._runScript(\n\t\t\t\"ot-set\",\n\t\t\t[ops_key, doc_key, sub_key, cnt_key],\n\t\t\t[\n\t\t\t\tcontent,\n\t\t\t\tJSON.stringify(message),\n\t\t\t\tconfig.ot.operation_expire/1000,\n\t\t\t\tconfig.ot.document_expire.timeout/1000,\n\t\t\t],\n\t\t\tthis._handleError.bind(this));\n\t}\n\n\tclose() {\n\t\tthis._client.end(true);\n\t}\n\n\t// PRIVATE METHODS\n\n\t_subscribe(channel) {\n\t\tthis._ensureNotSubscribed();\n\t\tthis._subscribed = true;\n\n\t\tthis._log.trace(\"Subscribing to channel:\", channel);\n\n\t\tthis._client.subscribe(channel);\n\t\tthis._client.on(\"message\", (channel, message) => {\n\t\t\ttry {\n\t\t\t\tlet obj = JSON.parse(message);\n\t\t\t\tthis.emit(\"_message\", obj);\n\t\t\t} catch (err) {\n\t\t\t\tthis._handleError(err);\n\t\t\t}\n\t\t});\n\t\treturn this;\n\t}\n\n\t_psubscribe(chanFn) {\n\t\tthis._ensureNotSubscribed();\n\t\tthis._subscribed = true;\n\n\t\tconst pattern = chanFn(\"*\");\n\t\tthis._log.trace(\"Subscribing to pattern:\", pattern);\n\n\t\tconst regex = new RegExp(`^${chanFn(\"([^:]+)\")}$`);\n\n\t\tthis._client.psubscribe(pattern);\n\t\tthis._client.on(\"pmessage\", (pattern, channel, message) => {\n\t\t\tconst match = regex.exec(channel);\n\t\t\tif (!match) {\n\t\t\t\tthis._log.error(\"pmessage result does not match regex\", pattern, channel);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst sessCode = match[1];\n\t\t\ttry {\n\t\t\t\tlet obj = JSON.parse(message);\n\t\t\t\tthis.emit(\"_message\", sessCode, obj);\n\t\t\t} catch (err) {\n\t\t\t\tthis._handleError(err);\n\t\t\t}\n\t\t});\n\t\treturn this;\n\t}\n\n\t_epsubscribe() {\n\t\tthis._ensureNotSubscribed();\n\t\tthis._subscribed = true;\n\n\t\tthis._log.trace(\"Subscribing to expiring keys\");\n\n\t\tthis._client.subscribe(\"__keyevent@0__:expired\");\n\t\tthis._client.on(\"message\", (channel, message) => {\n\t\t\tthis._mlog.trace(\"Received expire message\", channel, message);\n\t\t\tconst match = /^oo:\\w+:(\\w+)$/.exec(message);\n\t\t\tif (match) {\n\t\t\t\tthis._log.trace(\"Matched sesscode in expire message\");\n\t\t\t\tthis.emit(\"_message\", match[1], message);\n\t\t\t}\n\t\t});\n\t}\n\n\t_runScript(memo, keys, args, next) {\n\t\tthis._ensureNotSubscribed();\n\t\tif (!this._scriptManager) throw new Error(\"Need to enable scripts first\");\n\n\t\tthis._mlog.trace(\"Running script:\", memo);\n\n\t\tthis._scriptManager.run(memo, keys, args, next);\n\t}\n\n\t_serializeMessage(name, content) {\n\t\t// Protect against name length\n\t\tif (name.length > config.redis.maxPayload) {\n\t\t\tthis._log.error(new Error(\"Name length exceeds max redis payload length!\"));\n\t\t\treturn null;\n\t\t}\n\n\t\t// If data is too long, save it as an \"attachment\"\n\t\tlet contentString = JSON.stringify(content);\n\t\tif (contentString.length > config.redis.maxPayload) {\n\t\t\tlet id = uuid.v4();\n\t\t\tthis._mlog.trace(\"Sending content as attachment:\", name, id, contentString.length);\n\t\t\tthis._uploadAttachment(id, contentString, this._handleError.bind(this));\n\t\t\treturn JSON.stringify({ name, attachment: id });\n\t\t}\n\n\t\t// The message is short enough to send as one chunk!\n\t\tthis._mlog.trace(\"Sending content on channel:\", name);\n\t\treturn JSON.stringify({ name, data: content });\n\t}\n\n\t_emitMessage(sessCode, message) {\n\t\tlet getData = (next) => {\n\t\t\tif (message.data) return process.nextTick(() => {\n\t\t\t\tnext(null, message.data);\n\t\t\t});\n\t\t\telse {\n\t\t\t\treturn this._downloadAttachment(message.attachment, (err, contentString) => {\n\t\t\t\t\tthis._mlog.trace(\"Received content as attachment:\", message.name, message.attachment, contentString.length);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tnext(err, JSON.parse(contentString));\n\t\t\t\t\t} catch (_err) {\n\t\t\t\t\t\tnext(_err);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\tthis.emit(\"message\", sessCode, message.name, getData);\n\t}\n\n\t_uploadAttachment(id, contentString, next) {\n\t\tlet channel = redisUtil.chan.attachment(id);\n\n\t\t// Create a new client to offload bandwidth from the main artery channel\n\t\tlet client = redisUtil.createClient();\n\t\tclient.on(\"error\", this._handleError.bind(this));\n\n\t\t// Upload the attachment along with an expire time\n\t\tlet multi = client.multi();\n\t\tmulti.lpush(channel, contentString);\n\t\tmulti.expire(channel, config.redis.expire.timeout/1000);\n\t\tmulti.exec((err) => {\n\t\t\tclient.quit();\n\t\t\tnext(err);\n\t\t});\n\t}\n\n\t_downloadAttachment(id, next) {\n\t\tlet channel = redisUtil.chan.attachment(id);\n\n\t\t// Create a new client to offload bandwidth from the main artery channel\n\t\tlet client = redisUtil.createClient();\n\t\tclient.on(\"error\", this._handleError.bind(this));\n\n\t\t// Download the attachment\n\t\tclient.brpoplpush(channel, channel, config.redis.expire.timeout/1000, (err, response) => {\n\t\t\tclient.quit();\n\t\t\tif (response) {\n\t\t\t\tnext(err, response);\n\t\t\t} else {\n\t\t\t\tnext(err, JSON.stringify(null));\n\t\t\t}\n\t\t});\n\t}\n\n\t_handleError() {\n\t\tif (arguments[0]) this._log.warn.apply(this, arguments);\n\t}\n\n\t_ensureNotSubscribed() {\n\t\tif (this._subscribed) throw new Error(\"Can't call this method on a client that is subscribed to a channel\");\n\t}\n}\n\nmodule.exports = RedisMessenger;\n"
  },
  {
    "path": "shared/redis-queue.js",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst EventEmitter = require(\"events\");\nconst logger = require(\"./logger\");\nconst Queue = require(\"./queue\");\n\n// For streams of messages with attachments, use a queue to handle incoming messages to ensure that messages are processed in order.  The loading of data via attachments is asynchronous and may cause messages to change order.\nclass RedisQueue extends EventEmitter {\n\tconstructor(logId) {\n\t\tsuper();\n\t\tthis._queue = new Queue();\n\t\tthis._log = logger(\"rq:\" + logId);\n\t\tthis._mlog = logger(\"rq:\" + logId + \":minor\");\n\t}\n\n\tenqueueMessage(name, getData) {\n\t\tlet message = { name, ready: false };\n\t\tthis._queue.enqueue(message);\n\t\tgetData((err, content) => {\n\t\t\tif (err) this._log.error(err);\n\t\t\tmessage.content = content;\n\t\t\tmessage.ready = true;\n\t\t\tthis._flushMessageQueue();\n\t\t});\n\t}\n\n\treset() {\n\t\tthis._queue.removeAll();\n\t}\n\n\t_flushMessageQueue() {\n\t\twhile (!this._queue.isEmpty() && this._queue.peek().ready) {\n\t\t\tlet message = this._queue.dequeue();\n\t\t\tthis._mlog.trace(\"Emitting message:\", message.name);\n\t\t\tthis.emit(\"message\", message.name, message.content);\n\t\t}\n\t\tthis._mlog.debug(\"Items remaining in queue:\", this._queue.size());\n\t}\n}\n\nmodule.exports = RedisQueue;\n"
  },
  {
    "path": "shared/redis-util.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst EventEmitter = require(\"events\");\nconst redis = require(\"redis\");\nconst log = require(\"./logger\")(\"redis-util\");\nconst mlog = require(\"./logger\")(\"redis-util:minor\");\nconst config = require(\"./config\");\n// const log = require(\"./logger\")(\"redis-util\");\n\nconst PORT = config.redis.port;\nconst HOSTNAME = config.redis.hostname;\nconst OPTIONS = config.redis.options;\n\nclass MockRedisClient extends EventEmitter {\n\tpsubscribe(name, ...args) {\n\t\tmlog.trace(\"Ignoring psubscribe:\", name);\n\t}\n\tsubscribe(name, ...args) {\n\t\tmlog.trace(\"Ignoring subscribe:\", name);\n\t}\n\tmulti() {\n\t\tmlog.trace(\"Ignoring multi\");\n\t\treturn new MockRedisClient();\n\t}\n\tsend_command(name, ...args) {\n\t\tmlog.trace(\"Ignoring send_command:\", name);\n\t}\n\tdel(name, ...args) {\n\t\tmlog.trace(\"Ignoring del:\", name);\n\t}\n\tpublish(name, ...args) {\n\t\tmlog.trace(\"Ignoring publish:\", name);\n\t}\n\tzadd(name, ...args) {\n\t\tmlog.trace(\"Ignoring zadd:\", name);\n\t}\n\tzrem(name, ...args) {\n\t\tmlog.trace(\"Ignoring zrem:\", name);\n\t}\n\thget(name, ...args) {\n\t\tmlog.trace(\"Ignoring hget:\", name);\n\t}\n\thset(name, ...args) {\n\t\tmlog.trace(\"Ignoring hset:\", name);\n\t}\n\tset(name, ...args) {\n\t\tmlog.trace(\"Ignoring set:\", name);\n\t}\n\texpire(name, ...args) {\n\t\tmlog.trace(\"Ignoring expire:\", name);\n\t}\n\texec() {\n\t\tmlog.trace(\"Ignoring exec\");\n\t}\n}\n\nlet createClient;\nif (config.redis.hostname) {\n\tcreateClient = () => {\n\t\tmlog.debug(\"Connecting to Redis\");\n\t\treturn redis.createClient(PORT, HOSTNAME, OPTIONS);\n\t};\n} else {\n\tlog.warn(\"Redis is disabled; using mock\");\n\tcreateClient = () => {\n\t\tmlog.debug(\"Creating mock Redis client\");\n\t\treturn new MockRedisClient();\n\t}\n}\n\nmodule.exports = {\n\tcreateClient,\n\n\tchan: {\n\t\tneedsOctave: \"oo:needs-octave\",\n\t\tdestroyD: \"oo:destroy-d\",\n\t\tdestroyU: \"oo:destroy-u\",\n\t\trebootRequest: \"oo:reboot-request\",\n\t\tsession: (sessCode) => {\n\t\t\treturn \"oo:session:\" + sessCode;\n\t\t},\n\t\tinput: (sessCode) => {\n\t\t\treturn \"oo:input:\" + sessCode;\n\t\t},\n\t\toutput: (sessCode) => {\n\t\t\treturn \"oo:output:\" + sessCode;\n\t\t},\n\t\tattachment: (id) => {\n\t\t\treturn \"attachment:\" + id;\n\t\t},\n\t\tneedsOctaveFlavor: (flavor) => {\n\t\t\treturn \"oo:needs-flavor:\" + flavor;\n\t\t},\n\t\tflavorStatus: (flavor) => {\n\t\t\treturn \"oo:flavor-status-\" + flavor;\n\t\t},\n\t\totOps: (docId) => {\n\t\t\treturn \"ot:\" + docId + \":ops\";\n\t\t},\n\t\totDoc: (docId) => {\n\t\t\treturn \"ot:\" + docId + \":doc\";\n\t\t},\n\t\totSub: (docId) => {\n\t\t\treturn \"ot:\" + docId + \":sub\";\n\t\t},\n\t\totCnt: (docId) => {\n\t\t\treturn \"ot:\" + docId + \":cnt\";\n\t\t},\n\t\twsSess: (wsId) => {\n\t\t\treturn \"oo:workspace:\" + wsId + \":sess\";\n\t\t},\n\t\twsSub: (wsId) => {\n\t\t\treturn \"oo:workspace:\" + wsId + \":sub\";\n\t\t},\n\t},\n\n\tgetSessCodeFromChannel: (channel) => {\n\t\tconst match = /^oo:(\\w+):(\\w+)$/.exec(channel);\n\t\tif (!match) throw new Error(\"Can't extract sessCode from channel name\");\n\t\treturn match[2];\n\t},\n\n\tisValidSessCode: (sessCode) => {\n\t\treturn /^\\w{24}$/.test(sessCode);\n\t}\n};\n"
  },
  {
    "path": "shared/silent.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nvar mlog = require(\"./logger\")(\"silent:minor\");\n\n// Callback wrapper that catches errors and prevents them from propagating.\nfunction silent(messageRegex, next) {\n\n\tfunction pass() {\n\t\tvar args = Array.prototype.slice.call(arguments, 1);\n\t\tmlog.log(\"Suppressed additional output (regex: \" + messageRegex + \"): \", JSON.stringify(args));\n\t\targs.unshift(null);\n\t\tnext.apply(this, args);\n\t}\n\n\tfunction logNext() {\n\t\tvar args = Array.prototype.slice.call(arguments, 1);\n\t\tmlog.log(\"Pass-through additional output (regex: \" + messageRegex + \"): \", JSON.stringify(args));\n\t\tnext.apply(this, arguments);\n\t}\n\n\t// The following function needs to be an ES5-style function in order for \"arguments\" to work.  Note: At the time of writing, the ES6 spread operator is not supported in Node.JS.\n\tfunction checkError() {\n\t\tvar err = arguments[0];\n\t\tif (err && messageRegex.test(err.message)) {\n\t\t\tmlog.trace(\"Message suppressed (regex: \" + messageRegex + \")\");\n\t\t\treturn pass.apply(this, arguments);\n\t\t} else {\n\t\t\treturn logNext.apply(this, arguments);\n\t\t}\n\t}\n\n\tfunction checkStdout(err, stdout /*, stderr*/) {\n\t\tif (stdout && messageRegex.test(stdout)) {\n\t\t\tmlog.trace(\"Message suppressed from stdout (regex: \" + messageRegex + \")\");\n\t\t\treturn pass.apply(this, arguments);\n\t\t} else {\n\t\t\treturn checkError.apply(this, arguments);\n\t\t}\n\t}\n\n\tcheckError.stdout = checkStdout;\n\treturn checkError;\n}\n\nmodule.exports = silent;\n"
  },
  {
    "path": "shared/stackdriver/index.js",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst util = require(\"util\");\n\nconst { ErrorReporting } = require(\"@google-cloud/error-reporting\");\nconst { Logging } = require(\"@google-cloud/logging\");\n\nconst errors = new ErrorReporting();\n\n// TODO: Allow each project to customize this?\nlet gcpLog = new Logging().log(\"oo-projects\");\n\nfunction reportError(label, message) {\n\tconst errorEvent = errors.event()\n\t\t.setMessage(message)\n\t\t.setUser(label);\n\terrors.report(errorEvent);\n}\n\nfunction writeLog(level, label, args) {\n\tif (label.indexOf(\":minor\") !== -1) {\n\t\treturn;\n\t}\n\n\tconst message = util.format(...args);\n\tconst data = { label, message, objects: args };\n\tconst entry = gcpLog.entry(data);\n\tgcpLog[level](entry);\n\n\tif (level === \"error\") {\n\t\treportError(label, label + \" \" + message);\n\t}\n}\n\nmodule.exports = {\n\treportError,\n\twriteLog\n};\n"
  },
  {
    "path": "shared/stackdriver/package.json",
    "content": "{\n  \"name\": \"@oo/shared_stackdriver\",\n  \"version\": \"0.0.0\",\n  \"description\": \"GCP Stackdriver bindings for Node.js\",\n  \"main\": \"index.js\",\n  \"scripts\": {},\n  \"author\": \"Shane F. Carr\",\n  \"license\": \"AGPL-3.0\",\n  \"dependencies\": {\n    \"@google-cloud/error-reporting\": \"^2\",\n    \"@google-cloud/logging\": \"^9\"\n  }\n}\n"
  },
  {
    "path": "shared/stdio-messenger.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst OnlineOffline = require(\"./online-offline\");\nconst Queue = require(\"./queue\");\nconst JSONStreamSafe = require(\"./json-stream-safe\");\n\nclass StdioMessenger extends OnlineOffline {\n\tconstructor() {\n\t\tsuper();\n\n\t\t// Message queue (\"buffer\") for outgoing messages\n\t\tthis._messageQueue = new Queue();\n\t\tthis._messageQueue.on(\"enqueue\", this._flush.bind(this));\n\n\t\tthis._readStream = null;\n\t\tthis._writeStream = null;\n\t}\n\n\tsetReadStream(stream) {\n\t\tif (this._readStream) throw new Error(\"Can set only one read stream\");\n\n\t\t// TODO: Will the JSONStreamSafe ever be garbage collected?  The underlying stream will be closed when the session dies; is that sufficient?  Is it dangerous that the callback references \"this\"?\n\t\tthis._readStream = new JSONStreamSafe(stream);\n\t\tthis._readStream.on(\"data\", this._handleData.bind(this));\n\t\tthis._readStream.on(\"error\", (err) => { this.emit(\"error\", err); });\n\t}\n\n\tsetWriteStream(stream) {\n\t\tif (this._writeStream) throw new Error(\"Can set only one write stream\");\n\n\t\tthis._writeStream = stream;\n\t\tthis._flush();\n\t}\n\n\tsendMessage(name, content) {\n\t\tconst message = JSON.stringify([name, content]);\n\t\tthis._messageQueue.enqueue(message);\n\t}\n\n\t_flush() {\n\t\tif (!this._writeStream) return this._log.warn(\"Message stream unavailable\");\n\t\tif (!this._writeStream.writable) return this._log.warn(\"Message stream not writable\");\n\n\t\twhile (!this._messageQueue.isEmpty()) {\n\t\t\tthis._writeStream.write(this._messageQueue.dequeue());\n\t\t}\n\t}\n\n\t_handleData(data) {\n\t\tif (!Array.isArray(data) || data.length !== 2 || typeof data[0] !== \"string\") {\n\t\t\treturn this.emit(\"error\", new Error(`Malformed message: '${data}'`));\n\t\t}\n\n\t\tthis.emit(\"message\", data[0], data[1]);\n\t\tthis.emit(`msg:${data[0]}`, data[1]);\n\t}\n}\n\nmodule.exports = StdioMessenger;\n"
  },
  {
    "path": "shared/time-limit.js",
    "content": "/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// From https://github.com/caolan/async/issues/1007\n// (my contribution)\n\nfunction timeLimit(milliseconds, defaults, callback) {\n\tvar timer, normalCallbackRef;\n\n\tvar normalCallback = function() {\n\t\tcallback.apply(null, arguments);\n\t\tclearTimeout(timer);\n\t};\n\tvar timeoutCallback = function() {\n\t\tcallback.apply(null, defaults);\n\t\tnormalCallbackRef = function(){}; // noop\n\t};\n\n\ttimer = setTimeout(timeoutCallback, milliseconds);\n\tnormalCallbackRef = normalCallback;\n\n\treturn function() {\n\t\tnormalCallbackRef.apply(null, arguments);\n\t};\n}\n\nmodule.exports = timeLimit;\n"
  },
  {
    "path": "test/package.json",
    "content": "{\n  \"name\": \"@oo/test\",\n  \"version\": \"0.0.0\",\n  \"description\": \"Unit tests for Octave Online (incomplete)\",\n  \"scripts\": {\n    \"test\": \"ava\"\n  },\n  \"ava\": {\n    \"files\": [\n      \"*.js\"\n    ]\n  },\n  \"author\": \"Shane F. Carr\",\n  \"license\": \"AGPL-3.0\",\n  \"devDependencies\": {\n    \"@oo/shared\": \"file:../shared\",\n    \"ava\": \"^3.9.0\"\n  }\n}\n"
  },
  {
    "path": "test/small-unit.js",
    "content": "/*\n * Copyright © 2019, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// Small unit tests that are fast and don't require external services\n\n\"use strict\";\n\nconst stream = require(\"stream\");\n\nconst test = require(\"ava\");\n\nconst shared = require(\"@oo/shared\");\n\ntest(\"json byte stream\", (t) => {\n\t{\n\t\tconst pt = new stream.PassThrough();\n\t\tconst jss = new shared.JSONStreamSafe(pt);\n\n\t\t// Objects that are expected out of the stream:\n\t\tconst expectedObjects = [\n\t\t\t[\"a\", \"b\"],\n\t\t\t[\"c\"],\n\t\t\t\"�\",\n\t\t\t{ key: \"�Ȗ��\" },\n\t\t\t[]\n\t\t];\n\n\t\tlet i = 0;\n\t\t// let closed = false;\n\t\tjss.on(\"data\", (data) => {\n\t\t\tt.deepEqual(data, expectedObjects[i++], `Index ${i}`);\n\t\t});\n\t\tjss.on(\"end\", () => {\n\t\t\t// closed = true;\n\t\t});\n\n\t\t// Write data to the stream:\n\t\tpt.write(\"[\\\"a\\\",\\\"b\\\"][\\\"c\\\"]\");\n\t\tpt.write(Buffer.from([34, 128, 34]));\n\t\tpt.write(\"{\\\"key\\\":\\\"\");\n\t\tpt.write(Buffer.from([\n\t\t\t237, 137, // 3-byte character missing third byte\n\t\t\t200, 150, // valid 2-byte char\n\t\t\t150, // dangling continuation char\n\t\t\t250 // lead char with nothing following\n\t\t]));\n\t\tpt.write(\"\\\"}\");\n\t\tpt.write(\"[]\");\n\t\tpt.end();\n\n\t\t// All objects should have been read:\n\t\tt.is(i, expectedObjects.length);\n\n\t\t// TODO: It seems the end event does not bubble through.\n\t\t// t.true(closed);\n\t}\n});\n"
  },
  {
    "path": "utils-admin/.npmrc",
    "content": "# Please keep this in sync with all other .npmrc files\n# SEE: https://github.com/npm/feedback/discussions/864\ninstall-links=false\n"
  },
  {
    "path": "utils-admin/README.md",
    "content": "Octave Online Server: Admin Panel\n=================================\n\nThis directory contains a standalone administration panel service for Octave Online Server. It interacts with the MongoDB database to help create and set up user accounts.\n"
  },
  {
    "path": "utils-admin/app.js",
    "content": "/*\n * Copyright © 2020, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nconst createError = require(\"http-errors\");\nconst express = require(\"express\");\nconst path = require(\"path\");\nconst cookieParser = require(\"cookie-parser\");\nconst logger = require(\"morgan\");\nconst basicAuth = require(\"express-basic-auth\");\nconst config = require(\"@oo/shared\").config;\n\nconst indexRouter = require(\"./routes/index\");\nconst usersRouter = require(\"./routes/users\");\n\nconst app = express();\n\n// view engine setup\napp.set(\"views\", path.join(__dirname, \"views\"));\napp.set(\"view engine\", \"ejs\");\n\napp.use(logger(\"dev\"));\napp.use(basicAuth({ users: config.auth.utils_admin.users, challenge: true }));\napp.use(express.json());\napp.use(express.urlencoded({ extended: false }));\napp.use(cookieParser());\napp.use(express.static(path.join(__dirname, \"public\")));\n\napp.use(\"/\", indexRouter);\napp.use(\"/users\", usersRouter);\n\n// catch 404 and forward to error handler\napp.use(function(req, res, next) {\n\tnext(createError(404));\n});\n\n// error handler\napp.use(function(err, req, res) {\n\t// set locals, only providing error in development\n\tres.locals.message = err.message;\n\tres.locals.error = req.app.get(\"env\") === \"development\" ? err : {};\n\n\t// render the error page\n\tres.status(err.status || 500);\n\tres.render(\"error\");\n});\n\nmodule.exports = app;\n"
  },
  {
    "path": "utils-admin/bin/repo-cleanup.js",
    "content": "#!/usr/bin/env node\n/*\n * Copyright © 2020, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n// This tool deletes repos from inactive users.\n\n/* eslint-disable no-console */\n\nconst config = require(\"@oo/shared\").config;\nconst db = require(\"../src/db\");\nconst debug = require(\"debug\")(\"oo:repo-cleanup\");\nconst gcp = require(\"../../shared/gcp/index.js\");\nconst repo = require(\"../src/repo\");\n\nconst command = process.argv[2];\nif (command !== \"run\" && command !== \"dryrun\") {\n\tconsole.log(\"Usage: DEBUG=oo:* bin/repo-cleanup.js [dry]run [30 [200]]\");\n\treturn;\n}\n\n(async function() {\n\n\t// Database connection.\n\tconst mongoUrl = `mongodb://${config.mongo.hostname}:${config.mongo.port}`;\n\tconst mongoDb = config.mongo.db;\n\tawait db.connect(mongoUrl, mongoDb);\n\tdebug(\"Connected to MongoDB:\", mongoUrl, mongoDb);\n\n\tlet numDays = process.argv[3] ? parseInt(process.argv[3]) : 30;\n\tdebug(\"Number of days to clean:\", numDays);\n\n\tlet skipDays = process.argv[4] ? parseInt(process.argv[4]) : 200;\n\tdebug(\"End at this many days in the past:\", skipDays);\n\n\tlet startTime = new Date();\n\tstartTime.setDate(startTime.getDate() - skipDays - numDays);\n\tdebug(\"Start time:\", startTime);\n\n\tlet endTime = new Date();\n\tendTime.setDate(endTime.getDate() - skipDays);\n\tdebug(\"End time:\", endTime);\n\n\tconst query = {\n\t\t\"last_activity\": {\n\t\t\t\"$gt\": startTime,\n\t\t\t\"$lt\": endTime,\n\t\t},\n\t\t\"patreon.currently_entitled_amount_cents\": {\n\t\t\t\"$in\": [null, 0],\n\t\t},\n\t};\n\tfor await (let user of db.findAll(\"users\", query, { parametrized: 1, last_activity: 1 })) {\n\t\tdebug(\"Processing:\", JSON.stringify(user));\n\t\tif (command === \"run\") {\n\t\t\tawait gcp.uploadRepoArchive(debug, \"repos\", user.parametrized);\n\t\t\tawait repo.deleteRepo(user);\n\t\t}\n\t}\n\n\tdb.close();\n\n// async function\n})().catch((err) => {\n\tthrow err;\n});\n"
  },
  {
    "path": "utils-admin/bin/server.js",
    "content": "#!/usr/bin/env node\n/*\n * Copyright © 2020, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n/**\n * Module dependencies.\n */\n\nconst app = require(\"../app\");\nconst db = require(\"../src/db\");\nconst debug = require(\"debug\")(\"oo:server\");\nconst http = require(\"http\");\nconst config = require(\"@oo/shared\").config;\n\n(async function() {\n\n\t// Database connection.\n\tconst mongoUrl = `mongodb://${config.mongo.hostname}:${config.mongo.port}`;\n\tconst mongoDb = config.mongo.db;\n\tawait db.connect(mongoUrl, mongoDb);\n\tdebug(\"Connected to MongoDB:\", mongoUrl, mongoDb);\n\n\t// Get port from environment and store in Express.\n\n\tconst port = normalizePort(process.env.PORT || \"3000\");\n\tapp.set(\"port\", port);\n\n\t// Create HTTP server.\n\n\tconst server = http.createServer(app);\n\n\t// Listen on provided port, on all network interfaces.\n\n\tserver.listen(port);\n\tserver.on(\"error\", onError);\n\tserver.on(\"listening\", onListening);\n\n\t// Normalize a port into a number, string, or false.\n\n\tfunction normalizePort(val) {\n\t\tconst port = parseInt(val, 10);\n\n\t\tif (isNaN(port)) {\n\t\t// named pipe\n\t\t\treturn val;\n\t\t}\n\n\t\tif (port >= 0) {\n\t\t// port number\n\t\t\treturn port;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t// Event listener for HTTP server \"error\" event.\n\n\tfunction onError(error) {\n\t\tif (error.syscall !== \"listen\") {\n\t\t\tthrow error;\n\t\t}\n\n\t\tconst bind = typeof port === \"string\"\n\t\t\t? \"Pipe \" + port\n\t\t\t: \"Port \" + port;\n\n\t\t// handle specific listen errors with friendly messages\n\t\tswitch (error.code) {\n\t\t\tcase \"EACCES\":\n\t\t\t\tdebug(bind + \" requires elevated privileges\");\n\t\t\t\tprocess.exit(1);\n\t\t\t\tbreak;\n\t\t\tcase \"EADDRINUSE\":\n\t\t\t\tdebug(bind + \" is already in use\");\n\t\t\t\tprocess.exit(1);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// Event listener for HTTP server \"listening\" event.\n\n\tfunction onListening() {\n\t\tconst addr = server.address();\n\t\tconst bind = typeof addr === \"string\"\n\t\t\t? \"pipe \" + addr\n\t\t\t: \"port \" + addr.port;\n\t\tdebug(\"Listening on \" + bind);\n\t}\n\n// async function\n})().catch((err) => {\n\tthrow err;\n});\n"
  },
  {
    "path": "utils-admin/package.json",
    "content": "{\n  \"name\": \"@oo/utilsadmin\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"node ./bin/server.js\"\n  },\n  \"author\": \"Octave Online LLC\",\n  \"license\": \"AGPL-3.0\",\n  \"dependencies\": {\n    \"@awaitjs/express\": \"^0.4.0\",\n    \"@oo/shared\": \"file:../shared\",\n    \"@oo/shared_gcp\": \"file:../shared/gcp\",\n    \"bcryptjs\": \"^2.4.3\",\n    \"cookie-parser\": \"~1.4.4\",\n    \"debug\": \"~2.6.9\",\n    \"ejs\": \"~2.6.1\",\n    \"express\": \"~4.16.1\",\n    \"express-basic-auth\": \"^1.2.0\",\n    \"got\": \"^11.8.2\",\n    \"http-errors\": \"~1.6.3\",\n    \"mongodb\": \"^3.6.10\",\n    \"morgan\": \"~1.9.1\"\n  }\n}\n"
  },
  {
    "path": "utils-admin/public/stylesheets/style.css",
    "content": "/*\n * Copyright © 2020, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nbody {\n\tmax-width: 640px;\n\tbackground-color: #AD928E;\n\tmargin: 0 auto;\n\tpadding: 0;\n\t/*font:12px/16px Verdana, sans-serif;*/\n}\n\ndiv#main {\n\tbackground-color: #FFF;\n\tmargin: 0;\n\tpadding: 10px;\n}\n"
  },
  {
    "path": "utils-admin/routes/index.js",
    "content": "/*\n * Copyright © 2020, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nconst express = require(\"express\");\nconst { addAsync } = require(\"@awaitjs/express\");\n\nconst db = require(\"../src/db\");\n\nconst router = addAsync(express.Router());\n\nrouter.get(\"/\", function(req, res) {\n\tres.render(\"index\", {\n\t\ttitle: \"OO Admin Portal\",\n\t\ttop: true\n\t});\n});\n\nrouter.getAsync(\"/find/\", async function(req, res) {\n\tconst queryString = req.query.mq || \"{}\";\n\tlet queryObject;\n\ttry {\n\t\tqueryObject = JSON.parse(queryString);\n\t} catch(e) {\n\t\tres.status(400).type(\"txt\").send(`JSON parse error: ${e.message}\\n\\n${queryString}`);\n\t\treturn;\n\t}\n\tconst docs = await db.find(req.query.collection, queryObject);\n\tres.render(\"find\", {\n\t\ttitle: \"OO Find\",\n\t\tdocs,\n\t\tquery: req.query\n\t});\n});\n\nrouter.postAsync(\"/create.do\", async function(req, res) {\n\tconst newDoc = JSON.parse(req.body.document);\n\tconst result = await db.createDocument(\"users\", newDoc);\n\tres.redirect(`users/${result.ops[0]._id}`);\n});\n\nmodule.exports = router;\n"
  },
  {
    "path": "utils-admin/routes/users.js",
    "content": "/*\n * Copyright © 2020, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nconst crypto = require(\"crypto\");\nconst util = require(\"util\");\n\nconst bcryptjs = require(\"bcryptjs\");\nconst express = require(\"express\");\nconst { addAsync } = require(\"@awaitjs/express\");\n\nconst { config, logger } = require(\"@oo/shared\");\nconst db = require(\"../src/db\");\nconst gcp = require(\"@oo/shared_gcp\");\nconst repo = require(\"../src/repo\");\n\nconst router = addAsync(express.Router());\n\nfunction randomString(length) {\n\tconst base = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\".split(\"\");\n\tconst bytes = crypto.randomBytes(length);\n\tlet str = \"\";\n\tfor (var i = 0; i < length; i++) {\n\t\tstr += base[bytes[i] % base.length];\n\t}\n\treturn str;\n}\n\nrouter.getAsync(\"/\", async function(req, res) {\n\tconst queryString = req.query.mq || \"{}\";\n\tlet queryObject;\n\ttry {\n\t\tqueryObject = JSON.parse(queryString);\n\t} catch(e) {\n\t\tres.status(400).type(\"txt\").send(`JSON parse error: ${e.message}\\n\\n${queryString}`);\n\t\treturn;\n\t}\n\tconst users = await db.find(\"users\", queryObject);\n\tres.render(\"user-list\", {\n\t\ttitle: \"OO User Search\",\n\t\tusers,\n\t\tqueryString\n\t});\n});\n\nrouter.getAsync(\"/:userId\", async function(req, res) {\n\tconst userId = req.params.userId || \"\";\n\tconst user = await db.findById(\"users\", userId);\n\tconst buckets = await db.find(\"buckets\", {\n\t\t\"user_id\": user._id,\n\t});\n\tres.render(\"user\", {\n\t\ttitle: `OO: ${user.email || `Deleted User ${user.deleted_email}`}`,\n\t\tuser,\n\t\tbuckets,\n\t\trandomString: randomString(12),\n\t\tconfig\n\t});\n});\n\nrouter.postAsync(\"/:userId/add-code.do\", async function(req, res) {\n\tconst userId = req.params.userId || \"\";\n\tawait db.updateById(\"users\", userId, {\n\t\t$addToSet: {\n\t\t\tinstructor: req.body.courseCode\n\t\t}\n\t});\n\tres.redirect(\".\");\n});\n\nrouter.postAsync(\"/:userId/set-password.do\", async function(req, res) {\n\tconst userId = req.params.userId || \"\";\n\tconst rawPassword = req.body.string;\n\tconst salt = await util.promisify(bcryptjs.genSalt)(config.auth.password.salt_rounds);\n\tconst hash = await util.promisify(bcryptjs.hash)(rawPassword, salt);\n\tawait db.updateById(\"users\", userId, {\n\t\t$set: {\n\t\t\tpassword_hash: hash\n\t\t}\n\t});\n\tres.redirect(\".\");\n});\n\nrouter.postAsync(\"/:userId/overwrite.do\", async function(req, res) {\n\tconst userId = req.params.userId || \"\";\n\tlet newDoc;\n\ttry {\n\t\tnewDoc = JSON.parse(req.body.document);\n\t} catch(e) {\n\t\tres.status(400).type(\"txt\").send(`JSON parse error: ${e.message}\\n\\n${req.body.document}`);\n\t\treturn;\n\t}\n\tawait db.replaceById(\"users\", userId, newDoc);\n\tres.redirect(\".\");\n});\n\nrouter.postAsync(\"/:userId/delete-data.do\", async function(req, res) {\n\tconst userId = req.params.userId || \"\";\n\tconst user = await db.findById(\"users\", userId);\n\tif (req.body.deleteRepo) {\n\t\tawait repo.deleteRepo(user);\n\t}\n\tif (req.body.deleteMongo) {\n\t\tconst newDoc = {\n\t\t\tdeleted_email: user.email,\n\t\t\tparametrized: user.parametrized,\n\t\t};\n\t\tawait db.replaceById(\"users\", userId, newDoc);\n\t}\n\tres.redirect(\".\");\n});\n\nrouter.postAsync(\"/:userId/restore-from-storage.do\", async function(req, res) {\n\tconst userId = req.params.userId || \"\";\n\tconst user = await db.findById(\"users\", userId);\n\tconst gsUri = req.body.gsUri;\n\tconst matches = /^gs:\\/\\/([^\\/]+)\\/(.+)$/.exec(gsUri);\n\tif (!matches) {\n\t\tres.status(400).type(\"txt\").send(`gsUri is not valid: ${gsUri}`);\n\t\treturn;\n\t}\n\tconst log = logger(`restore:${userId}`);\n\tlog.info(\"Beginning restoration process:\", gsUri);\n\ttry {\n\t\tawait gcp.restoreRepoFromCloudStorage(log.log, \"repos\", user.parametrized, matches[1], matches[2]);\n\t} catch(e) {\n\t\tres.status(500).type(\"txt\").send(`Error while restoring files:\\n\\n${e}`);\n\t\tlog.error(e);\n\t\treturn;\n\t}\n\tres.redirect(\".\");\n});\n\nmodule.exports = router;\n"
  },
  {
    "path": "utils-admin/src/db.js",
    "content": "/*\n * Copyright © 2020, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nconst mongodb = require(\"mongodb\");\n\nlet mongoClient = null;\nlet db = null;\n\nasync function connect(url, dbName) {\n\tmongoClient = new mongodb.MongoClient(url);\n\tawait mongoClient.connect();\n\tdb = mongoClient.db(dbName);\n}\n\nasync function close() {\n\tmongoClient.close();\n}\n\nasync function find(collectionName, query) {\n\tconst collection = db.collection(collectionName);\n\treturn await collection.find(query).limit(10).toArray();\n}\n\nasync function findById(collectionName, id) {\n\tconst collection = db.collection(collectionName);\n\tconst result = await collection.findOne({\n\t\t_id: new mongodb.ObjectId(id)\n\t});\n\tif (!result) {\n\t\tthrow new Error(\"Could not find user with id: \" + id);\n\t}\n\treturn result;\n}\n\nfunction findAll(collectionName, query, projection) {\n\tconst collection = db.collection(collectionName);\n\tconst cursor = collection.find(query).project(projection).batchSize(50);\n\treturn cursor;\n}\n\nasync function updateById(collectionName, id, update) {\n\tconst collection = db.collection(collectionName);\n\treturn await collection.updateOne({\n\t\t_id: new mongodb.ObjectId(id)\n\t}, update);\n}\n\nasync function replaceById(collectionName, id, newDoc) {\n\tconst collection = db.collection(collectionName);\n\treturn await collection.replaceOne({\n\t\t_id: new mongodb.ObjectId(id)\n\t}, newDoc);\n}\n\nasync function createDocument(collectionName, newDoc) {\n\tconst collection = db.collection(collectionName);\n\treturn await collection.insertOne(newDoc);\n}\n\nmodule.exports = {\n\tconnect,\n\tclose,\n\tfind,\n\tfindById,\n\tfindAll,\n\tupdateById,\n\treplaceById,\n\tcreateDocument\n};\n"
  },
  {
    "path": "utils-admin/src/repo.js",
    "content": "/*\n * Copyright © 2020, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\nconst got = require(\"got\");\n\nconst config = require(\"@oo/shared\").config;\n\nasync function deleteRepo(user) {\n\treturn await got(`http://${config.git.hostname}:${config.git.createRepoPort}`, {\n\t\tsearchParams: {\n\t\t\ttype: \"repos\",\n\t\t\tname: user.parametrized,\n\t\t\taction: \"delete\",\n\t\t},\n\t\tretry: 0,\n\t});\n}\n\nmodule.exports = {\n\tdeleteRepo,\n};\n"
  },
  {
    "path": "utils-admin/views/error.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or\nmodify it under the terms of the GNU Affero General Public License as\npublished by the Free Software Foundation, either version 3 of the License,\nor (at your option) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\nor FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\nLicense for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with Octave Online Server.  If not, see\n<https://www.gnu.org/licenses/>.\n%>\n\n<h1><%= message %></h1>\n<h2><%= error.status %></h2>\n<pre><%= error.stack %></pre>\n"
  },
  {
    "path": "utils-admin/views/find.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or\nmodify it under the terms of the GNU Affero General Public License as\npublished by the Free Software Foundation, either version 3 of the License,\nor (at your option) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\nor FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\nLicense for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with Octave Online Server.  If not, see\n<https://www.gnu.org/licenses/>.\n%>\n\n<%- include(\"partials/header\"); -%>\n\n<% if (docs.length === 0) { %>\n<p><em>No documents found!</em></p>\n<% } else { %>\n<h2>Query Results</h2>\n<% for (let doc of docs) { %>\n<hr/>\n<pre style=\"white-space: pre-wrap;\"><%= JSON.stringify(doc, null, 2) %></pre>\n<% } %>\n<hr/>\n<% } %>\n\n<form action=\"/find/\" class=\"pure-form\">\n\t<p>\n\t\t<input type=\"text\" name=\"collection\" placeholder=\"collection name\" value=\"<%= query.collection %>\" />\n\t</p>\n\t<p>\n\t\t<textarea name=\"mq\" class=\"pure-input-1\" placeholder=\"query\"><%= query.mq %></textarea>\n\t</p>\n\t<p>\n\t\t<input type=\"submit\" class=\"pure-button\" value=\"Search Again\" />\n\t</p>\n</form>\n\n<%- include(\"partials/footer\"); -%>\n"
  },
  {
    "path": "utils-admin/views/index.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or\nmodify it under the terms of the GNU Affero General Public License as\npublished by the Free Software Foundation, either version 3 of the License,\nor (at your option) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\nor FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\nLicense for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with Octave Online Server.  If not, see\n<https://www.gnu.org/licenses/>.\n%>\n\n<%- include(\"partials/header\"); -%>\n\n<h2>Search for User</h2>\n\n<form action=\"/users/\" class=\"pure-form\">\n\t<p>\n\t\t<textarea name=\"mq\" class=\"pure-input-1\">{ \"email\": { \"$regex\": \"___\" } }</textarea>\n\t</p>\n\t<p>\n\t\t<input type=\"submit\" class=\"pure-button\" value=\"Search\" />\n\t</p>\n</form>\n\n<hr/>\n\n<form action=\"/create.do\" method=\"post\" class=\"pure-form\">\n\t<p>\n\t\t<textarea name=\"document\" class=\"pure-input-1\" rows=\"7\">{\n\t\"email\": \"___\",\n\t\"profile\": {\n\t\t\"method\": \"administrative\"\n\t}\n}</textarea>\n\t</p>\n\t<p>\n\t\t<input type=\"submit\" class=\"pure-button\" value=\"Create New User\" />\n\t</p>\n</form>\n\n<hr/>\n\n<form action=\"/find/\" class=\"pure-form\">\n\t<p>\n\t\t<input type=\"text\" name=\"collection\" placeholder=\"collection name\" />\n\t</p>\n\t<p>\n\t\t<textarea name=\"mq\" class=\"pure-input-1\" placeholder=\"query\"></textarea>\n\t</p>\n\t<p>\n\t\t<input type=\"submit\" class=\"pure-button\" value=\"Find Documents\" />\n\t</p>\n</form>\n\n<%- include(\"partials/footer\"); -%>\n"
  },
  {
    "path": "utils-admin/views/partials/footer.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or\nmodify it under the terms of the GNU Affero General Public License as\npublished by the Free Software Foundation, either version 3 of the License,\nor (at your option) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\nor FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\nLicense for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with Octave Online Server.  If not, see\n<https://www.gnu.org/licenses/>.\n%>\n\n</div></body>\n</html>\n"
  },
  {
    "path": "utils-admin/views/partials/header.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or\nmodify it under the terms of the GNU Affero General Public License as\npublished by the Free Software Foundation, either version 3 of the License,\nor (at your option) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\nor FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\nLicense for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with Octave Online Server.  If not, see\n<https://www.gnu.org/licenses/>.\n%>\n\n<!DOCTYPE html>\n<head>\n<meta charset=\"UTF-8\" />\n<title><%= title %></title>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n<link rel=\"stylesheet\" href=\"https://unpkg.com/purecss@1.0.1/build/pure-min.css\" integrity=\"sha384-oAOxQR6DkCoMliIh8yFnu25d7Eq/PHS21PClpwjOTeU2jRSq11vu66rf90/cZr47\" crossorigin=\"anonymous\" />\n<link rel=\"stylesheet\" type=\"text/css\" href=\"/stylesheets/style.css\" />\n</head>\n<body><div id=\"main\">\n\n<header>\n\t<% if (!locals.top) { %>\n\t\t<a href=\"/\">&laquo; Home</a>\n\t<% } %>\n\t<h1><%= title %></h1>\n</header>\n"
  },
  {
    "path": "utils-admin/views/user-list.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or\nmodify it under the terms of the GNU Affero General Public License as\npublished by the Free Software Foundation, either version 3 of the License,\nor (at your option) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\nor FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\nLicense for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with Octave Online Server.  If not, see\n<https://www.gnu.org/licenses/>.\n%>\n\n<%- include(\"partials/header\"); -%>\n\n<% if (users.length === 0) { %>\n\t<p><em>No users found!</em></p>\n<% } else { %>\n\t<ul>\n\t<% for (let user of users) { %>\n\t\t<li>\n\t\t\t<strong><a href=\"<%= `/users/${user._id}/` %>\"><%= user.email %> (<%= user._id %>)</a></strong>\n\t\t</li>\n\t<% } %>\n\t</ul>\n<% } %>\n\n<form action=\"/users/\" class=\"pure-form\">\n\t<p>\n\t\t<textarea name=\"mq\" class=\"pure-input-1\"><%= queryString %></textarea>\n\t</p>\n\t<p>\n\t\t<input type=\"submit\" class=\"pure-button\" value=\"Search Again\" />\n\t</p>\n</form>\n\n<%- include(\"partials/footer\"); -%>\n"
  },
  {
    "path": "utils-admin/views/user.ejs",
    "content": "<%#\nCopyright © 2020, Octave Online LLC\n\nThis file is part of Octave Online Server.\n\nOctave Online Server is free software: you can redistribute it and/or\nmodify it under the terms of the GNU Affero General Public License as\npublished by the Free Software Foundation, either version 3 of the License,\nor (at your option) any later version.\n\nOctave Online Server is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\nor FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\nLicense for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with Octave Online Server.  If not, see\n<https://www.gnu.org/licenses/>.\n%>\n\n<%- include(\"partials/header\"); -%>\n\n<h2>Raw MongoDB Document</h2>\n\n<pre style=\"white-space: pre-wrap;\"><%= JSON.stringify(user, null, 2) %></pre>\n\n<h2>Add Instructor Course Code</h2>\n\n<form action=\"<%= `/users/${user._id}/add-code.do` %>\" method=\"post\" class=\"pure-form\">\n\t<input type=\"hidden\" name=\"id\" value=\"<%= user._id %>\" />\n\t<p>\n\t\t<input type=\"text\" name=\"courseCode\" value=\"XXX_YYY_ZZZ\" />\n\t</p>\n\t<p>\n\t\t<input type=\"submit\" class=\"pure-button\" value=\"Add Course Code\" />\n\t</p>\n</form>\n\n<h2>Set Password</h2>\n\n<form action=\"<%= `/users/${user._id}/set-password.do` %>\" method=\"post\" class=\"pure-form\">\n\t<input type=\"hidden\" name=\"id\" value=\"<%= user._id %>\" />\n\t<p>\n\t\t<input type=\"text\" name=\"string\" value=\"<%= randomString %>\" />\n\t</p>\n\t<p>\n\t\t<input type=\"submit\" class=\"pure-button\" value=\"Set Password\" />\n\t</p>\n</form>\n\n<h2>Buckets</h2>\n\n<% if (buckets.length) { %>\n<ul>\n<% for (let bucket of buckets) { %>\n<li><tt><%= bucket.bucket_id %></tt> (<%= bucket._id.getTimestamp().toISOString() %>)</li>\n<% } %>\n</ul>\n<% } else { %>\n<p><em>No buckets found</em></p>\n<% } %>\n\n<h2>Repo Operations</h2>\n\n<p>\n\t<a href=\"<%= `${config.git.httpUrl}${user.parametrized}.git/` %>\">Open Repo Viewer</a>\n</p>\n\n<form action=\"<%= `/users/${user._id}/restore-from-storage.do` %>\" method=\"post\" class=\"pure-form\">\n\t<input type=\"hidden\" name=\"id\" value=\"<%= user._id %>\" />\n\t<p>\n\t\t<input type=\"text\" name=\"gsUri\" placeholder=\"gs://...\" />\n\t</p>\n\t<p>\n\t\t<input type=\"submit\" class=\"pure-button\" value=\"Restore Data from Google Cloud Storage URI\" />\n\t</p>\n</form>\n\n<h2>Delete User Data</h2>\n\n<form action=\"<%= `/users/${user._id}/delete-data.do` %>\" method=\"post\" class=\"pure-form\">\n\t<input type=\"hidden\" name=\"id\" value=\"<%= user._id %>\" />\n\t<p>\n\t\t<label><input type=\"checkbox\" name=\"deleteRepo\" /> Delete User Repo</label><br/>\n\t\t<label><input type=\"checkbox\" name=\"deleteMongo\" /> Delete Mongo Data</label><br/>\n\t\t<em>Note: Bucket deletion not supported yet</em>\n\t</p>\n\t<p>\n\t\t<input type=\"submit\" class=\"pure-button\" value=\"Delete User Data\" />\n\t</p>\n</form>\n\n<h2>Overwrite Whole Document</h2>\n\n<form action=\"<%= `/users/${user._id}/overwrite.do` %>\" method=\"post\" class=\"pure-form\">\n\t<input type=\"hidden\" name=\"id\" value=\"<%= user._id %>\" />\n\t<p>\n\t\t<%\n\t\t\tuserFiltered = Object.assign({}, user);\n\t\t\tdelete userFiltered._id;\n\t\t%>\n\t\t<textarea name=\"document\" class=\"pure-input-1\" rows=\"20\"><%= JSON.stringify(userFiltered, null, 2) %></textarea>\n\t</p>\n\t<p>\n\t\t<input type=\"submit\" class=\"pure-button\" value=\"Overwrite\" />\n\t</p>\n</form>\n\n<%- include(\"partials/footer\"); -%>\n"
  },
  {
    "path": "utils-auth/.eslintrc.yml",
    "content": "rules:\n  # Allow console.log\n  no-console:\n    - off\n"
  },
  {
    "path": "utils-auth/.npmrc",
    "content": "# Please keep this in sync with all other .npmrc files\n# SEE: https://github.com/npm/feedback/discussions/864\ninstall-links=false\n"
  },
  {
    "path": "utils-auth/README.md",
    "content": "Octave Online Server: Authentication Service\n============================================\n\nThis directory contains a standalone service for authenticating user accounts stored in MongoDB.  It is a very basic HTTP server that uses HTTP Basic Auth to authenticate against the MongoDB user database.\n\nThis service was designed for plugging into Nginx to allow for authenticated https access to Git repositories, a feature on octave-online.net.\n\n**Note: Most users do not need to worry about the code in this directory.**\n"
  },
  {
    "path": "utils-auth/app.js",
    "content": "#!/usr/bin/env node\n/*\n * Copyright © 2018, Octave Online LLC\n *\n * This file is part of Octave Online Server.\n *\n * Octave Online Server is free software: you can redistribute it and/or\n * modify it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the License,\n * or (at your option) any later version.\n *\n * Octave Online Server is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with Octave Online Server.  If not, see\n * <https://www.gnu.org/licenses/>.\n */\n\n\"use strict\";\n\nconst http = require(\"http\");\nconst mongoose = require(\"mongoose\");\nconst bcrypt = require(\"bcrypt\");\nconst config = require(\"@oo/shared\").config;\nconst path = require(\"path\");\nconst basicAuth = require(\"basic-auth\");\nconst fs = require(\"fs\");\n\n//const PORT = 5123;\nconst SOCKET_PATH = \"/var/run/oosocks/auth.sock\";\ntry {\n\tfs.unlinkSync(SOCKET_PATH);\n} catch(err) {\n\t// ignored; socket file might not exist\n}\nconsole.log(\"Listening on UNIX socket \" + SOCKET_PATH);\n\nconst mongoUrl = `mongodb://${config.mongo.hostname}:${config.mongo.port}/${config.mongo.db}`;\nmongoose.connect(mongoUrl, { useNewUrlParser: true }).then(() => {\n\tconsole.log(\"Connected to MongoDB at\", mongoUrl);\n}).catch((err) => {\n\tconsole.error(\"FAILED TO CONNECT TO MONGODB\", mongoUrl, err);\n\tprocess.exit(1);\n});\nconst User = mongoose.model(\"User\", {\n\temail: String,\n\tparametrized: String,\n\tpassword_hash: String\n});\n\nvar benchmarkMongoAvg = 0.0;\nvar benchmarkMongoCnt = 0;\nvar benchmarkBcryptAvg = 0.0;\nvar benchmarkBcryptCnt = 0;\n\nconst server = http.createServer((req, res) => {\n\tconst originalUri = path.normalize(req.headers[\"x-original-uri\"] || \"\");\n\tconst match1 = /^\\/(\\w+)\\.git\\/.*$/.exec(originalUri);\n\tconst match2 = /^\\/(src|themes|vendor)\\/.*$/.exec(originalUri);\n\tif (match2 !== null) {\n\t\tres.writeHead(204);\n\t\treturn res.end();\n\t}\n\tif (match1 === null) {\n\t\tres.writeHead(403);\n\t\treturn res.end();\n\t}\n\tconst parametrized = match1[1];\n\tif (req.headers[\"authorization\"]) {\n\t\t// Check username/password\n\t\tconst creds = basicAuth.parse(req.headers[\"authorization\"]);\n\t\tif (!creds) {\n\t\t\tres.writeHead(400);\n\t\t\treturn res.end(\"Invalid credentials\");\n\t\t}\n\t\tconst adminPass = config.auth.utils_admin.users[creds.name];\n\t\tif (adminPass && creds.pass === adminPass) {\n\t\t\tres.writeHead(204);\n\t\t\treturn res.end();\n\t\t}\n\t\tconst mongoStart = new Date().valueOf();\n\t\tUser.findOne({ email: creds.name }, (err, user) => {\n\t\t\tconst mongoEnd = new Date().valueOf();\n\t\t\tbenchmarkMongoAvg = benchmarkMongoAvg + (mongoEnd-mongoStart-benchmarkMongoAvg)/(benchmarkMongoCnt+1);\n\t\t\tbenchmarkMongoCnt++;\n\t\t\tif (err || !user) {\n\t\t\t\tconsole.error(err || \"User not found\");\n\t\t\t\treturn prompt(res, \"Make sure you entered the correct email/password\");\n\t\t\t}\n\t\t\tif (!user.password_hash) {\n\t\t\t\treturn prompt(res, \"Make sure you have set a password\");\n\t\t\t}\n\t\t\tif (user.parametrized !== parametrized) {\n\t\t\t\treturn prompt(res, \"Make sure your repository path is correct\");\n\t\t\t}\n\t\t\tconst bcryptStart = new Date().valueOf();\n\t\t\tbcrypt.compare(creds.pass, user.password_hash, (err, valid) => {\n\t\t\t\tconst bcryptEnd = new Date().valueOf();\n\t\t\t\tbenchmarkBcryptAvg = benchmarkBcryptAvg + (bcryptEnd-bcryptStart-benchmarkBcryptAvg)/(benchmarkBcryptCnt+1);\n\t\t\t\tbenchmarkBcryptCnt++;\n\t\t\t\tif (err) {\n\t\t\t\t\tconsole.error(err);\n\t\t\t\t\tres.writeHead(400);\n\t\t\t\t\treturn res.end(\"Problem validating password\");\n\t\t\t\t}\n\t\t\t\tif (!valid) {\n\t\t\t\t\treturn prompt(res, \"Make sure you entered the correct email/password\");\n\t\t\t\t}\n\t\t\t\tres.writeHead(204);\n\t\t\t\treturn res.end();\n\t\t\t});\n\t\t});\n\t} else {\n\t\t// Prompt for username/password\n\t\tprompt(res, \"Please enter your email and password\");\n\t}\n});\nserver.listen(SOCKET_PATH);\n\nfunction prompt(res, message) {\n\tres.writeHead(401, {\n\t\t\"WWW-Authenticate\": \"Basic realm=\\\"\"+message+\"\\\"\"\n\t});\n\treturn res.end();\n}\n\nsetInterval(() => {\n\tconsole.log(\"Mongo: \" + benchmarkMongoAvg + \" (\" + benchmarkMongoCnt + \" reqs); \" + \"Bcrypt: \"+ benchmarkBcryptAvg+ \" (\" + benchmarkBcryptCnt+ \" reqs)\");\n}, 86400000);\n"
  },
  {
    "path": "utils-auth/configs/custom_4xx.html",
    "content": "<!DOCTYPE html>\n\n<head>\n\t<title>Octave Online</title>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t<style type=\"text/css\">\n\tbody {\n\t\twidth: 75%;\n\t\tbackground-color: #FF5C4A; /* OO Fire Red */\n\t\tmargin: 0 auto;\n\t\tpadding: 0;\n\t\tfont-family: sans-serif;\n\t}\n\t@media (max-width: 480px) {\n\t\tbody {\n\t\t\twidth: 100%;\n\t\t}\n\t}\n\t#main {\n\t\tbackground-color: #fff;\n\t\tmargin: 0;\n\t\tpadding: 10px;\n\t\ttext-align: center;\n\t}\n</style>\n</head>\n\n<script type=\"text/javascript\" src=\"https://cdnjs.cloudflare.com/ajax/libs/js-cookie/2.1.4/js.cookie.min.js\"></script>\n<script type=\"text/javascript\" src=\"https://cdnjs.cloudflare.com/ajax/libs/jsurl/2.1.7/url.min.js\"></script>\n<script type=\"text/javascript\">\nvar u = new Url();\nif (u.query.next) {\n  Cookies.set(\"oo.repo_next\", u.query.next);\n}\nif (u.query.user) {\n  Cookies.set(\"oo.repo_user\", u.query.user);\n}\nvar repoNext = Cookies.get(\"oo.repo_next\") || \"unknown\";\nvar repoUser = Cookies.get(\"oo.repo_user\") || \"\";\n</script>\n\n<body>\n\t<div id=\"main\">\n\t\t<p><img src=\"https://octave-online.net/images/logos/icon-black.svg\" width=\"120\" height=\"120\" /></p>\n\t\t<div style=\"font-size:1.2em;\">\n\t\t<p><a id=\"repoNext\">&laquo; Enter Octave Online File History Viewer &raquo;</a></p>\n\t\t</div>\n\t\t<p>When prompted for a username and password, enter <strong>your email address<span id=\"repoUser\"></span></strong> and <strong>your Octave Online password</strong>.  Create a password for your Octave Online account by clicking \"Change Password\" in the main menu on the home page.</p>\n\t\t</p>\n\t</div>\n</body>\n\n<script>\ndocument.getElementById(\"repoNext\").setAttribute(\"href\", \"/\"+repoNext+\"/\");\nif (repoUser) document.getElementById(\"repoUser\").textContent = \" (\" + repoUser + \")\";\n</script>\n"
  },
  {
    "path": "utils-auth/configs/gitlist.ini",
    "content": "; Copyright © 2019, Octave Online LLC\n;\n; This file is part of Octave Online Server.\n;\n; Octave Online Server is free software: you can redistribute it and/or modify\n; it under the terms of the GNU Affero General Public License as published by\n; the Free Software Foundation, either version 3 of the License, or (at your\n; option) any later version.\n;\n; Octave Online Server is distributed in the hope that it will be useful, but\n; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n; License for more details.\n;\n; You should have received a copy of the GNU Affero General Public License\n; along with Octave Online Server.  If not, see\n; <https://www.gnu.org/licenses/>.\n\n; This is the Octave Online config for the GitList instance\n\n[git]\nclient = '/usr/bin/git' ; Your git executable path\ndefault_branch = 'master' ; Default branch when HEAD is detached\n\nrepositories[] = '/srv/oo/git/repos'\n\n[app]\ndebug = false\ncache = true\ntheme = 'default'\ntitle = 'Octave Online File History'\n\n[clone_button]\n; ssh remote\nshow_ssh_remote = false ; display remote URL for SSH\n\n; http remote\nshow_http_remote = true ; display remote URL for HTTP\n;http_host = '' ; host to use for cloning via HTTP (default: none => uses gitlist web host)\nuse_https = \"auto\" ; generate URL with https://\n;http_url_subdir = 'git/' ; if cloning via HTTP is triggered using virtual dir (e.g. https://example.com/git/repo.git)\n                    ; has to end with trailing slash\n;http_user = '' ; user to use for cloning via HTTP (default: none)\nhttp_user_dynamic = true ; when enabled, http_user is set to $_SERVER['PHP_AUTH_USER']\n\n; If you need to specify custom filetypes for certain extensions, do this here\n[filetypes]\n; extension = type\n; dist = xml\n\n; If you need to set file types as binary or not, do this here\n[binary_filetypes]\n; extension = true\n; svh = false\n; map = true\n\n; set the timezone\n[date]\n; timezone = UTC\n; format = 'd/m/Y H:i:s'\n\n; custom avatar service\n[avatar]\n; url = '//gravatar.com/avatar/'\n; query[] = 'd=identicon'\n"
  },
  {
    "path": "utils-auth/configs/oo-utils-auth.service",
    "content": "# Copyright © 2018, Octave Online LLC\n#\n# This file is part of Octave Online Server.\n#\n# Octave Online Server is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or (at your\n# option) any later version.\n#\n# Octave Online Server is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public\n# License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with Octave Online Server.  If not, see\n# <https://www.gnu.org/licenses/>.\n\n###############################################################\n# NOTE: This systemd service file is here for reference only; #\n# it is not currently being used in Octave Online Server.     #\n###############################################################\n\n[Unit]\nDescription=\"Authenticates users against the Mongo database\"\n\n[Service]\nExecStart=/srv/oo/projects/utils-auth/app.js\nStandardOutput=syslog\nStandardError=syslog\nSyslogIdentifier=oo-utils-auth\nUser=nginx\nGroup=nginx\nRuntimeDirectory=oosocks\nEnvironment=NODE_ENV=production\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "utils-auth/package.json",
    "content": "{\n  \"name\": \"@oo/utilsauth\",\n  \"version\": \"0.0.0\",\n  \"description\": \"Authenticates users accessing git repos on the back server\",\n  \"main\": \"app.js\",\n  \"scripts\": {},\n  \"author\": \"Octave Online LLC\",\n  \"license\": \"AGPL-3.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@oo/shared\": \"file:../shared\",\n    \"basic-auth\": \"^1.1.0\",\n    \"bcrypt\": \"^5.0.1\",\n    \"mongoose\": \"^5.10.19\"\n  }\n}\n"
  }
]