From 396caa52cf9899721a864d8ae1ca26b273a73429 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Thu, 7 May 2020 11:59:03 +0200 Subject: [PATCH] New: Replaced launcher on OSX Catalina so that individual permissions can be assigned (note, will ignore permissions previously assigned to sh) --- .gitattributes | 2 +- build.sh | 39 +- distribution/debian.sh | 4 +- distribution/osx/Launcher/LICENSE.LGPL.md | 459 ++++++++++++++ distribution/osx/Launcher/README.md | 28 + distribution/osx/Launcher/dist/Launcher | Bin 0 -> 83860 bytes distribution/osx/Launcher/src/Launcher.m | 32 + .../osx/Launcher/src/PFMoveApplication.h | 32 + .../osx/Launcher/src/PFMoveApplication.m | 565 ++++++++++++++++++ distribution/osx/Launcher/src/compile.sh | 16 + distribution/osx/Launcher/src/run-with-mono.h | 11 + distribution/osx/Launcher/src/run-with-mono.m | 258 ++++++++ .../osx}/Sonarr.app/Contents/Info.plist | 0 .../Sonarr.app/Contents/Resources/sonarr.icns | Bin .../Sonarr => distribution/osx/Sonarr.old.sh | 0 .../windows/setup}/build.bat | 2 +- .../windows/setup}/inno/Default.isl | 0 .../windows/setup}/inno/ISCC.exe | Bin .../windows/setup}/inno/ISCmplr.dll | Bin .../windows/setup}/inno/ISPP.dll | Bin .../windows/setup}/inno/Setup.e32 | Bin .../windows/setup}/inno/SetupLdr.e32 | Bin .../windows/setup}/inno/WizModernImage.bmp | Bin .../setup}/inno/WizModernSmallImage.bmp | Bin .../windows/setup}/inno/islzma.dll | Bin .../windows/setup}/sonarr.iss | 4 +- .../Processes/ProcessProvider.cs | 4 + src/NzbDrone.Host/SpinService.cs | 35 +- .../UpdateEngine/InstallUpdateService.cs | 18 +- .../UpdateEngine/StartNzbDrone.cs | 30 +- 30 files changed, 1510 insertions(+), 29 deletions(-) create mode 100644 distribution/osx/Launcher/LICENSE.LGPL.md create mode 100644 distribution/osx/Launcher/README.md create mode 100755 distribution/osx/Launcher/dist/Launcher create mode 100644 distribution/osx/Launcher/src/Launcher.m create mode 100644 distribution/osx/Launcher/src/PFMoveApplication.h create mode 100644 distribution/osx/Launcher/src/PFMoveApplication.m create mode 100644 distribution/osx/Launcher/src/compile.sh create mode 100644 distribution/osx/Launcher/src/run-with-mono.h create mode 100644 distribution/osx/Launcher/src/run-with-mono.m rename {macOS => distribution/osx}/Sonarr.app/Contents/Info.plist (100%) rename {macOS => distribution/osx}/Sonarr.app/Contents/Resources/sonarr.icns (100%) rename macOS/Sonarr => distribution/osx/Sonarr.old.sh (100%) rename {setup => distribution/windows/setup}/build.bat (70%) rename {setup => distribution/windows/setup}/inno/Default.isl (100%) rename {setup => distribution/windows/setup}/inno/ISCC.exe (100%) rename {setup => distribution/windows/setup}/inno/ISCmplr.dll (100%) rename {setup => distribution/windows/setup}/inno/ISPP.dll (100%) rename {setup => distribution/windows/setup}/inno/Setup.e32 (100%) rename {setup => distribution/windows/setup}/inno/SetupLdr.e32 (100%) rename {setup => distribution/windows/setup}/inno/WizModernImage.bmp (100%) rename {setup => distribution/windows/setup}/inno/WizModernSmallImage.bmp (100%) rename {setup => distribution/windows/setup}/inno/islzma.dll (100%) rename {setup => distribution/windows/setup}/sonarr.iss (95%) diff --git a/.gitattributes b/.gitattributes index 9580abc23..f3e639368 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,7 +5,7 @@ # when checked out on windows *.sh text eol=lf distribution/debian/* text eol=lf -macOS/Sonarr text eol=lf +distribution/osx/Sonarr text eol=lf # Custom for Visual Studio *.cs diff=csharp diff --git a/build.sh b/build.sh index 08b535a8e..6064cdb7e 100755 --- a/build.sh +++ b/build.sh @@ -266,14 +266,24 @@ PackageMacOS() rm -rf $outputFolderMacOS mkdir $outputFolderMacOS - - echo "Adding Startup script" - cp ./macOS/Sonarr $outputFolderMacOS - dos2unix $outputFolderMacOS/Sonarr - + echo "Copying Binaries" cp -r $outputFolderLinux/* $outputFolderMacOS + echo "Adding Sonarr Launcher" + cp ./distribution/osx/Launcher/dist/Launcher $outputFolderMacOS/ + mv $outputFolderMacOS/Sonarr.exe $outputFolderMacOS/Sonarr.exe.bak + mv $outputFolderMacOS/Launcher $outputFolderMacOS/Sonarr + mv $outputFolderMacOS/Sonarr.exe.bak $outputFolderMacOS/Sonarr.exe + chmod +x $outputFolderMacOS/Sonarr + + echo "Adding Sonarr.Update Launcher" + cp ./distribution/osx/Launcher/dist/Launcher $outputFolderMacOS/Sonarr.Update/ + mv $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe.bak + mv $outputFolderMacOS/Sonarr.Update/Launcher $outputFolderMacOS/Sonarr.Update/Sonarr.Update + mv $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe.bak $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe + chmod +x $outputFolderMacOS/Sonarr.Update/Sonarr.Update + echo "Adding sqlite dylibs" cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOS @@ -289,24 +299,27 @@ PackageMacOSApp() rm -rf $outputFolderMacOSApp mkdir $outputFolderMacOSApp - cp -r ./macOS/Sonarr.app $outputFolderMacOSApp + cp -r ./distribution/osx/Sonarr.app $outputFolderMacOSApp mkdir -p $outputFolderMacOSApp/Sonarr.app/Contents/MacOS - echo "Adding Startup script" - cp ./macOS/Sonarr $outputFolderMacOSApp/Sonarr.app/Contents/MacOS - dos2unix $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr + echo "Adding Sonarr Launcher" + cp ./distribution/osx/Launcher/dist/Launcher $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/ + mv $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Launcher $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr + chmod +x $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr echo "Copying Binaries" - cp -r $outputFolderLinux/* $outputFolderMacOSApp/Sonarr.app/Contents/MacOS + mkdir -p $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin + cp -r $outputFolderLinux/* $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin/ echo "Adding sqlite dylibs" - cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS + cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin/ echo "Adding MediaInfo dylib" - cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS + cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin/ echo "Removing Update Folder" - rm -r $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr.Update + rm -r $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin/Sonarr.Update + echo "# Do Not Edit\nPackageVersion=${BUILD_NUMBER}\nPackageAuthor=[Team Sonarr](https://sonarr.tv)\nReleaseVersion=${BUILD_NUMBER}\nUpdateMethod=$PackageUpdater\nBranch=${Branch:-master}" > $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/package_info ProgressEnd 'Creating macOS App Package' } diff --git a/distribution/debian.sh b/distribution/debian.sh index 446e47d20..bcd24c425 100644 --- a/distribution/debian.sh +++ b/distribution/debian.sh @@ -16,7 +16,7 @@ echo Updating changelog for $BuildVersion sed -i "s:{version}:$BuildVersion:g; s:{branch}:$BuildBranch:g;" debian/changelog sed -i "s:{version}:$BuildVersion:g; s:{updater}:$PackageUpdater:g" debian/preinst debian/postinst debian/postrm sed -i '/#BEGIN BUILTIN UPDATER/,/#END BUILTIN UPDATER/d' debian/preinst debian/postinst debian/postrm -echo "# Do Not Edit\nPackageVersion=$BuildVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$PackageUpdater\nBranch=$BuildBranch" > package_info +echo "# Do Not Edit\nPackageVersion=$BuildVersion\nPackageAuthor=[Team Sonarr](https://sonarr.tv)\nReleaseVersion=$BuildVersion\nUpdateMethod=$PackageUpdater\nBranch=$BuildBranch" > package_info echo Running debuild for $BuildVersion if [ -z "${TEST_OUTPUT}" ]; then @@ -33,7 +33,7 @@ echo Updating changelog for $BootstrapVersion sed -i "s:{version}:$BootstrapVersion:g; s:{branch}:$BuildBranch:g;" debian/changelog sed -i "s:{version}:$BuildVersion:g; s:{updater}:$BootstrapUpdater:g" debian/preinst debian/postinst debian/postrm sed -i '/#BEGIN BUILTIN UPDATER/d; /#END BUILTIN UPDATER/d' debian/preinst debian/postinst debian/postrm -echo "# Do Not Edit\nPackageVersion=$BootstrapVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$BootstrapUpdater\nBranch=$BuildBranch" > package_info +echo "# Do Not Edit\nPackageVersion=$BootstrapVersion\nPackageAuthor=[Team Sonarr](https://sonarr.tv)\nReleaseVersion=$BuildVersion\nUpdateMethod=$BootstrapUpdater\nBranch=$BuildBranch" > package_info echo Running debuild for $BootstrapVersion if [ -z "${TEST_OUTPUT}" ]; then diff --git a/distribution/osx/Launcher/LICENSE.LGPL.md b/distribution/osx/Launcher/LICENSE.LGPL.md new file mode 100644 index 000000000..c5bf65e21 --- /dev/null +++ b/distribution/osx/Launcher/LICENSE.LGPL.md @@ -0,0 +1,459 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + \ No newline at end of file diff --git a/distribution/osx/Launcher/README.md b/distribution/osx/Launcher/README.md new file mode 100644 index 000000000..5345e871d --- /dev/null +++ b/distribution/osx/Launcher/README.md @@ -0,0 +1,28 @@ +Code reused from duplicati, licensed under LGPL 2.1 +Modified for Sonarr by Taloth Saldono + +see here for the original source: https://github.com/duplicati/duplicati/tree/679981d29f8a6e445d3c1e6d41e72a673ffaa653/Installer/OSX + +License +------- + +Sonarr as a whole is licensed under GPL 3.0 as specified in the git repository root. + +But to preserve the original intent of the duplicati project, the modified versions of the sources in this folder are dual licensed under LGPL 2.1 and GPL 3.0. +Note: This exception can be freely removed in any copy of Sonarr sources as per LGPL/GPL licensing terms. + +A copy of the LGPL 2.1 license is included in the LICENSE.LGPL.md file. + +Purpose +------- + +The Launcher is a bootstrap/shim application that checks if the appropriate version of mono is installed and subsequently use it to execute Sonarr. +By using a separate application, instead of a shell script, this allows the user to assign certain operating system permissions to Sonarr specifically. + +Compiling the Launcher +---------------------- + +You need an OSX installation with xcode +Then run compile.sh in a terminal + +The generated dist/Launcher can be renamed to Sonarr and Sonarr.Update to serve as shims to run Sonarr.exe and Sonarr.Update.exe respectively. diff --git a/distribution/osx/Launcher/dist/Launcher b/distribution/osx/Launcher/dist/Launcher new file mode 100755 index 0000000000000000000000000000000000000000..be72543ea62264a8c57118bf2b0e861c72f3f1a1 GIT binary patch literal 83860 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 literal 0 HcmV?d00001 diff --git a/distribution/osx/Launcher/src/Launcher.m b/distribution/osx/Launcher/src/Launcher.m new file mode 100644 index 000000000..5af282665 --- /dev/null +++ b/distribution/osx/Launcher/src/Launcher.m @@ -0,0 +1,32 @@ +#import "run-with-mono.h" +#import "PFMoveApplication.h" + +int const MONO_VERSION_MAJOR = 5; +int const MONO_VERSION_MINOR = 20; + +int main() { + @autoreleasepool { + // Use our own executable name so the same compiled binary to be used for forks + NSString * const FileName = NSProcessInfo.processInfo.arguments[0].lastPathComponent; + + // Sonarr.Update.exe + NSString * const ASSEMBLY = [NSString stringWithFormat:@"%@.exe", FileName]; + + // Sonarr Update + NSString * const APP_NAME = [FileName stringByReplacingOccurrencesOfString:@"." withString:@" "]; + + // -sonarrupdate + NSString * const PROCESS_NAME = [NSString stringWithFormat:@"-%@", [FileName stringByReplacingOccurrencesOfString:@"." withString:@""].lowercaseString]; + + @try + { + PFMoveToApplicationsFolderIfNecessary(); + } + @catch (NSException * ex) + { + NSLog(@"Translocation/Quarantine check failed, starting normally. Reason: %@", ex.reason); + } + + return [RunWithMono runAssemblyWithMono:APP_NAME procnamesuffix:PROCESS_NAME assembly:ASSEMBLY major:MONO_VERSION_MAJOR minor:MONO_VERSION_MINOR]; + } +} diff --git a/distribution/osx/Launcher/src/PFMoveApplication.h b/distribution/osx/Launcher/src/PFMoveApplication.h new file mode 100644 index 000000000..c86a21a9b --- /dev/null +++ b/distribution/osx/Launcher/src/PFMoveApplication.h @@ -0,0 +1,32 @@ +// +// PFMoveApplication.h, version 1.24 +// LetsMove +// +// Created by Andy Kim at Potion Factory LLC on 9/17/09 +// +// The contents of this file are dedicated to the public domain. + +#ifdef __cplusplus +extern "C" { +#endif + +#import + +/** + Moves the running application to ~/Applications or /Applications if the former does not exist. + After the move, it relaunches app from the new location. + DOES NOT work for sandboxed applications. + + Call from \c NSApplication's delegate method \c -applicationWillFinishLaunching: method. */ +void PFMoveToApplicationsFolderIfNecessary(void); + +/** + Check whether an app move is currently in progress. + Returns YES if LetsMove is currently in-progress trying to move the app to the Applications folder, or NO otherwise. + This can be used to work around a crash with apps that terminate after last window is closed. + See https://github.com/potionfactory/LetsMove/issues/64 for details. */ +BOOL PFMoveIsInProgress(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/distribution/osx/Launcher/src/PFMoveApplication.m b/distribution/osx/Launcher/src/PFMoveApplication.m new file mode 100644 index 000000000..35effaec4 --- /dev/null +++ b/distribution/osx/Launcher/src/PFMoveApplication.m @@ -0,0 +1,565 @@ +// +// PFMoveApplication.m, version 1.24 +// LetsMove +// +// Created by Andy Kim at Potion Factory LLC on 9/17/09 +// +// The contents of this file are dedicated to the public domain. + +#import "PFMoveApplication.h" + +#import +#import +#import +#import + +@interface LetsMove : NSObject +@end + +@implementation LetsMove ++ (NSBundle *)bundle { + return [NSBundle bundleForClass:self]; +} +@end + +// Strings +// These are macros to be able to use custom i18n tools +#define _I10NS(nsstr) NSLocalizedStringFromTableInBundle(nsstr, @"MoveApplication", [LetsMove bundle], nil) +#define kStrMoveApplicationCouldNotMove _I10NS(@"Could not move to Applications folder") +#define kStrMoveApplicationQuestionTitle _I10NS(@"Move to Applications folder?") +#define kStrMoveApplicationQuestionTitleHome _I10NS(@"Move to Applications folder in your Home folder?") +#define kStrMoveApplicationQuestionMessage _I10NS(@"I can move myself to the Applications folder if you'd like.") +#define kStrMoveApplicationButtonMove _I10NS(@"Move to Applications Folder") +#define kStrMoveApplicationButtonDoNotMove _I10NS(@"Do Not Move") +#define kStrMoveApplicationQuestionInfoWillRequirePasswd _I10NS(@"Note that this will require an administrator password.") +#define kStrMoveApplicationQuestionInfoInDownloadsFolder _I10NS(@"This will keep your Downloads folder uncluttered.") + +// Needs to be defined for compiling under 10.5 SDK +#ifndef NSAppKitVersionNumber10_5 + #define NSAppKitVersionNumber10_5 949 +#endif + +// By default, we use a small control/font for the suppression button. +// If you prefer to use the system default (to match your other alerts), +// set this to 0. +#define PFUseSmallAlertSuppressCheckbox 1 + + +static NSString *AlertSuppressKey = @"moveToApplicationsFolderAlertSuppress"; +static BOOL MoveInProgress = NO; + +// Helper functions +static NSString *PreferredInstallLocation(BOOL *isUserDirectory); +static BOOL IsInApplicationsFolder(NSString *path); +static BOOL IsInDownloadsFolder(NSString *path); +static BOOL IsApplicationAtPathRunning(NSString *path); +static BOOL IsApplicationAtPathNested(NSString *path); +static NSString *ContainingDiskImageDevice(NSString *path); +static BOOL Trash(NSString *path); +static BOOL DeleteOrTrash(NSString *path); +static BOOL AuthorizedInstall(NSString *srcPath, NSString *dstPath, BOOL *canceled); +static BOOL CopyBundle(NSString *srcPath, NSString *dstPath); +static NSString *ShellQuotedString(NSString *string); +static void Relaunch(NSString *destinationPath); + +// Main worker function +void PFMoveToApplicationsFolderIfNecessary(void) { + + // Make sure to do our work on the main thread. + // Apparently Electron apps need this for things to work properly. + if (![NSThread isMainThread]) { + dispatch_async(dispatch_get_main_queue(), ^{ + PFMoveToApplicationsFolderIfNecessary(); + }); + return; + } + + // Skip if user suppressed the alert before + if ([[NSUserDefaults standardUserDefaults] boolForKey:AlertSuppressKey]) return; + + // Path of the bundle + NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; + + // Check if the bundle is embedded in another application + BOOL isNestedApplication = IsApplicationAtPathNested(bundlePath); + + // Skip if the application is already in some Applications folder, + // unless it's inside another app's bundle. + if (IsInApplicationsFolder(bundlePath) && !isNestedApplication) return; + + // OK, looks like we'll need to do a move - set the status variable appropriately + MoveInProgress = YES; + + // File Manager + NSFileManager *fm = [NSFileManager defaultManager]; + + // Are we on a disk image? + NSString *diskImageDevice = ContainingDiskImageDevice(bundlePath); + + // Since we are good to go, get the preferred installation directory. + BOOL installToUserApplications = NO; + NSString *applicationsDirectory = PreferredInstallLocation(&installToUserApplications); + NSString *bundleName = [bundlePath lastPathComponent]; + NSString *destinationPath = [applicationsDirectory stringByAppendingPathComponent:bundleName]; + + // Check if we need admin password to write to the Applications directory + BOOL needAuthorization = ([fm isWritableFileAtPath:applicationsDirectory] == NO); + + // Check if the destination bundle is already there but not writable + needAuthorization |= ([fm fileExistsAtPath:destinationPath] && ![fm isWritableFileAtPath:destinationPath]); + + // Setup the alert + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + { + NSString *informativeText = nil; + + [alert setMessageText:(installToUserApplications ? kStrMoveApplicationQuestionTitleHome : kStrMoveApplicationQuestionTitle)]; + + informativeText = kStrMoveApplicationQuestionMessage; + + if (needAuthorization) { + informativeText = [informativeText stringByAppendingString:@" "]; + informativeText = [informativeText stringByAppendingString:kStrMoveApplicationQuestionInfoWillRequirePasswd]; + } + else if (IsInDownloadsFolder(bundlePath)) { + // Don't mention this stuff if we need authentication. The informative text is long enough as it is in that case. + informativeText = [informativeText stringByAppendingString:@" "]; + informativeText = [informativeText stringByAppendingString:kStrMoveApplicationQuestionInfoInDownloadsFolder]; + } + + [alert setInformativeText:informativeText]; + + // Add accept button + [alert addButtonWithTitle:kStrMoveApplicationButtonMove]; + + // Add deny button + NSButton *cancelButton = [alert addButtonWithTitle:kStrMoveApplicationButtonDoNotMove]; + [cancelButton setKeyEquivalent:[NSString stringWithFormat:@"%C", 0x1b]]; // Escape key + + // Setup suppression button + [alert setShowsSuppressionButton:YES]; + + if (PFUseSmallAlertSuppressCheckbox) { + NSCell *cell = [[alert suppressionButton] cell]; + [cell setControlSize:NSSmallControlSize]; + [cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + } + } + + // Activate app -- work-around for focus issues related to "scary file from internet" OS dialog. + if (![NSApp isActive]) { + [NSApp activateIgnoringOtherApps:YES]; + } + + if ([alert runModal] == NSAlertFirstButtonReturn) { + NSLog(@"INFO -- Moving myself to the Applications folder"); + + // Move + if (needAuthorization) { + BOOL authorizationCanceled; + + if (!AuthorizedInstall(bundlePath, destinationPath, &authorizationCanceled)) { + if (authorizationCanceled) { + NSLog(@"INFO -- Not moving because user canceled authorization"); + MoveInProgress = NO; + return; + } + else { + NSLog(@"ERROR -- Could not copy myself to /Applications with authorization"); + goto fail; + } + } + } + else { + // If a copy already exists in the Applications folder, put it in the Trash + if ([fm fileExistsAtPath:destinationPath]) { + // But first, make sure that it's not running + if (IsApplicationAtPathRunning(destinationPath)) { + // Give the running app focus and terminate myself + NSLog(@"INFO -- Switching to an already running version"); + [[NSTask launchedTaskWithLaunchPath:@"/usr/bin/open" arguments:[NSArray arrayWithObject:destinationPath]] waitUntilExit]; + MoveInProgress = NO; + exit(0); + } + else { + if (!Trash([applicationsDirectory stringByAppendingPathComponent:bundleName])) + goto fail; + } + } + + if (!CopyBundle(bundlePath, destinationPath)) { + NSLog(@"ERROR -- Could not copy myself to %@", destinationPath); + goto fail; + } + } + + // Trash the original app. It's okay if this fails. + // NOTE: This final delete does not work if the source bundle is in a network mounted volume. + // Calling rm or file manager's delete method doesn't work either. It's unlikely to happen + // but it'd be great if someone could fix this. + if (!isNestedApplication && diskImageDevice == nil && !DeleteOrTrash(bundlePath)) { + NSLog(@"WARNING -- Could not delete application after moving it to Applications folder"); + } + + // Relaunch. + Relaunch(destinationPath); + + // Launched from within a disk image? -- unmount (if no files are open after 5 seconds, + // otherwise leave it mounted). + if (diskImageDevice && !isNestedApplication) { + NSString *script = [NSString stringWithFormat:@"(/bin/sleep 5 && /usr/bin/hdiutil detach %@) &", ShellQuotedString(diskImageDevice)]; + [NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]]; + } + + MoveInProgress = NO; + exit(0); + } + // Save the alert suppress preference if checked + else if ([[alert suppressionButton] state] == NSOnState) { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:AlertSuppressKey]; + } + + MoveInProgress = NO; + return; + +fail: + { + // Show failure message + alert = [[[NSAlert alloc] init] autorelease]; + [alert setMessageText:kStrMoveApplicationCouldNotMove]; + [alert runModal]; + MoveInProgress = NO; + } +} + +BOOL PFMoveIsInProgress() { + return MoveInProgress; +} + +#pragma mark - +#pragma mark Helper Functions + +static NSString *PreferredInstallLocation(BOOL *isUserDirectory) { + // Return the preferred install location. + // Assume that if the user has a ~/Applications folder, they'd prefer their + // applications to go there. + + NSFileManager *fm = [NSFileManager defaultManager]; + + /* + NSArray *userApplicationsDirs = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES); + + if ([userApplicationsDirs count] > 0) { + NSString *userApplicationsDir = [userApplicationsDirs objectAtIndex:0]; + BOOL isDirectory; + + if ([fm fileExistsAtPath:userApplicationsDir isDirectory:&isDirectory] && isDirectory) { + // User Applications directory exists. Get the directory contents. + NSArray *contents = [fm contentsOfDirectoryAtPath:userApplicationsDir error:NULL]; + + // Check if there is at least one ".app" inside the directory. + for (NSString *contentsPath in contents) { + if ([[contentsPath pathExtension] isEqualToString:@"app"]) { + if (isUserDirectory) *isUserDirectory = YES; + return [userApplicationsDir stringByResolvingSymlinksInPath]; + } + } + } + } + */ + + // No user Applications directory in use. Return the machine local Applications directory + if (isUserDirectory) *isUserDirectory = NO; + + return [[NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSLocalDomainMask, YES) lastObject] stringByResolvingSymlinksInPath]; +} + +static BOOL IsInApplicationsFolder(NSString *path) { + // Check all the normal Application directories + NSArray *applicationDirs = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSAllDomainsMask, YES); + for (NSString *appDir in applicationDirs) { + if ([path hasPrefix:appDir]) return YES; + } + + // Also, handle the case that the user has some other Application directory (perhaps on a separate data partition). + if ([[path pathComponents] containsObject:@"Applications"]) return YES; + + return NO; +} + +static BOOL IsInDownloadsFolder(NSString *path) { + NSArray *downloadDirs = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSAllDomainsMask, YES); + for (NSString *downloadsDirPath in downloadDirs) { + if ([path hasPrefix:downloadsDirPath]) return YES; + } + + return NO; +} + +static BOOL IsApplicationAtPathRunning(NSString *bundlePath) { + bundlePath = [bundlePath stringByStandardizingPath]; + +#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 + // Use the new API on 10.6 or higher to determine if the app is already running + if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5) { + for (NSRunningApplication *runningApplication in [[NSWorkspace sharedWorkspace] runningApplications]) { + NSString *runningAppBundlePath = [[[runningApplication bundleURL] path] stringByStandardizingPath]; + if ([runningAppBundlePath isEqualToString:bundlePath]) { + return YES; + } + } + return NO; + } +#endif + // Use the shell to determine if the app is already running on systems 10.5 or lower + NSString *script = [NSString stringWithFormat:@"/bin/ps ax -o comm | /usr/bin/grep %@/ | /usr/bin/grep -v grep >/dev/null", ShellQuotedString(bundlePath)]; + NSTask *task = [NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]]; + [task waitUntilExit]; + + // If the task terminated with status 0, it means that the final grep produced 1 or more lines of output. + // Which means that the app is already running + return [task terminationStatus] == 0; +} + +static BOOL IsApplicationAtPathNested(NSString *path) { + NSString *containingPath = [path stringByDeletingLastPathComponent]; + + NSArray *components = [containingPath pathComponents]; + for (NSString *component in components) { + if ([[component pathExtension] isEqualToString:@"app"]) { + return YES; + } + } + + return NO; +} + +static NSString *ContainingDiskImageDevice(NSString *path) { + NSString *containingPath = [path stringByDeletingLastPathComponent]; + + struct statfs fs; + if (statfs([containingPath fileSystemRepresentation], &fs) || (fs.f_flags & MNT_ROOTFS)) + return nil; + + NSString *device = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:fs.f_mntfromname length:strlen(fs.f_mntfromname)]; + + NSTask *hdiutil = [[[NSTask alloc] init] autorelease]; + [hdiutil setLaunchPath:@"/usr/bin/hdiutil"]; + [hdiutil setArguments:[NSArray arrayWithObjects:@"info", @"-plist", nil]]; + [hdiutil setStandardOutput:[NSPipe pipe]]; + [hdiutil launch]; + [hdiutil waitUntilExit]; + + NSData *data = [[[hdiutil standardOutput] fileHandleForReading] readDataToEndOfFile]; + NSDictionary *info = nil; +#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 + if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5) { + info = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:NULL]; + } + else { +#endif +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10 + info = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:NULL]; +#endif +#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 + } +#endif + + if (![info isKindOfClass:[NSDictionary class]]) return nil; + + NSArray *images = (NSArray *)[info objectForKey:@"images"]; + if (![images isKindOfClass:[NSArray class]]) return nil; + + for (NSDictionary *image in images) { + if (![image isKindOfClass:[NSDictionary class]]) return nil; + + id systemEntities = [image objectForKey:@"system-entities"]; + if (![systemEntities isKindOfClass:[NSArray class]]) return nil; + + for (NSDictionary *systemEntity in systemEntities) { + if (![systemEntity isKindOfClass:[NSDictionary class]]) return nil; + + NSString *devEntry = [systemEntity objectForKey:@"dev-entry"]; + if (![devEntry isKindOfClass:[NSString class]]) return nil; + + if ([devEntry isEqualToString:device]) + return device; + } + } + + return nil; +} + +static BOOL Trash(NSString *path) { + BOOL result = NO; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 + if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_8) { + result = [[NSFileManager defaultManager] trashItemAtURL:[NSURL fileURLWithPath:path] resultingItemURL:NULL error:NULL]; + } +#endif +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11 + if (!result) { + result = [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation + source:[path stringByDeletingLastPathComponent] + destination:@"" + files:[NSArray arrayWithObject:[path lastPathComponent]] + tag:NULL]; + } +#endif + + // As a last resort try trashing with AppleScript. + // This allows us to trash the app in macOS Sierra even when the app is running inside + // an app translocation image. + if (!result) { + NSAppleScript *appleScript = [[[NSAppleScript alloc] initWithSource: + [NSString stringWithFormat:@"\ + set theFile to POSIX file \"%@\" \n\ + tell application \"Finder\" \n\ + move theFile to trash \n\ + end tell", path]] autorelease]; + NSDictionary *errorDict = nil; + NSAppleEventDescriptor *scriptResult = [appleScript executeAndReturnError:&errorDict]; + if (scriptResult == nil) { + NSLog(@"Trash AppleScript error: %@", errorDict); + } + result = (scriptResult != nil); + } + + if (!result) { + NSLog(@"ERROR -- Could not trash '%@'", path); + } + + return result; +} + +static BOOL DeleteOrTrash(NSString *path) { + NSError *error; + + if ([[NSFileManager defaultManager] removeItemAtPath:path error:&error]) { + return YES; + } + else { + // Don't log warning if on Sierra and running inside App Translocation path + if ([path rangeOfString:@"/AppTranslocation/"].location == NSNotFound) + NSLog(@"WARNING -- Could not delete '%@': %@", path, [error localizedDescription]); + + return Trash(path); + } +} + +static BOOL AuthorizedInstall(NSString *srcPath, NSString *dstPath, BOOL *canceled) { + if (canceled) *canceled = NO; + + // Make sure that the destination path is an app bundle. We're essentially running 'sudo rm -rf' + // so we really don't want to fuck this up. + if (![[dstPath pathExtension] isEqualToString:@"app"]) return NO; + + // Do some more checks + if ([[dstPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) return NO; + if ([[srcPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) return NO; + + int pid, status; + AuthorizationRef myAuthorizationRef; + + // Get the authorization + OSStatus err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &myAuthorizationRef); + if (err != errAuthorizationSuccess) return NO; + + AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0}; + AuthorizationRights myRights = {1, &myItems}; + AuthorizationFlags myFlags = (AuthorizationFlags)(kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize); + + err = AuthorizationCopyRights(myAuthorizationRef, &myRights, NULL, myFlags, NULL); + if (err != errAuthorizationSuccess) { + if (err == errAuthorizationCanceled && canceled) + *canceled = YES; + goto fail; + } + + static OSStatus (*security_AuthorizationExecuteWithPrivileges)(AuthorizationRef authorization, const char *pathToTool, + AuthorizationFlags options, char * const *arguments, + FILE **communicationsPipe) = NULL; + if (!security_AuthorizationExecuteWithPrivileges) { + // On 10.7, AuthorizationExecuteWithPrivileges is deprecated. We want to still use it since there's no + // good alternative (without requiring code signing). We'll look up the function through dyld and fail + // if it is no longer accessible. If Apple removes the function entirely this will fail gracefully. If + // they keep the function and throw some sort of exception, this won't fail gracefully, but that's a + // risk we'll have to take for now. + security_AuthorizationExecuteWithPrivileges = (OSStatus (*)(AuthorizationRef, const char*, + AuthorizationFlags, char* const*, + FILE **)) dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges"); + } + if (!security_AuthorizationExecuteWithPrivileges) goto fail; + + // Delete the destination + { + char *args[] = {"-rf", (char *)[dstPath fileSystemRepresentation], NULL}; + err = security_AuthorizationExecuteWithPrivileges(myAuthorizationRef, "/bin/rm", kAuthorizationFlagDefaults, args, NULL); + if (err != errAuthorizationSuccess) goto fail; + + // Wait until it's done + pid = wait(&status); + if (pid == -1 || !WIFEXITED(status)) goto fail; // We don't care about exit status as the destination most likely does not exist + } + + // Copy + { + char *args[] = {"-pR", (char *)[srcPath fileSystemRepresentation], (char *)[dstPath fileSystemRepresentation], NULL}; + err = security_AuthorizationExecuteWithPrivileges(myAuthorizationRef, "/bin/cp", kAuthorizationFlagDefaults, args, NULL); + if (err != errAuthorizationSuccess) goto fail; + + // Wait until it's done + pid = wait(&status); + if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status)) goto fail; + } + + AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults); + return YES; + +fail: + AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults); + return NO; +} + +static BOOL CopyBundle(NSString *srcPath, NSString *dstPath) { + NSFileManager *fm = [NSFileManager defaultManager]; + NSError *error = nil; + + if ([fm copyItemAtPath:srcPath toPath:dstPath error:&error]) { + return YES; + } + else { + NSLog(@"ERROR -- Could not copy '%@' to '%@' (%@)", srcPath, dstPath, error); + return NO; + } +} + +static NSString *ShellQuotedString(NSString *string) { + return [NSString stringWithFormat:@"'%@'", [string stringByReplacingOccurrencesOfString:@"'" withString:@"'\\''"]]; +} + +static void Relaunch(NSString *destinationPath) { + // The shell script waits until the original app process terminates. + // This is done so that the relaunched app opens as the front-most app. + int pid = [[NSProcessInfo processInfo] processIdentifier]; + + // Command run just before running open /final/path + NSString *preOpenCmd = @""; + + NSString *quotedDestinationPath = ShellQuotedString(destinationPath); + + // OS X >=10.5: + // Before we launch the new app, clear xattr:com.apple.quarantine to avoid + // duplicate "scary file from the internet" dialog. + if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5) { + // Add the -r flag on 10.6 + preOpenCmd = [NSString stringWithFormat:@"/usr/bin/xattr -d -r com.apple.quarantine %@", quotedDestinationPath]; + } + else { + preOpenCmd = [NSString stringWithFormat:@"/usr/bin/xattr -d com.apple.quarantine %@", quotedDestinationPath]; + } + + NSString *script = [NSString stringWithFormat:@"(while /bin/kill -0 %d >&/dev/null; do /bin/sleep 0.1; done; %@; /usr/bin/open %@) &", pid, preOpenCmd, quotedDestinationPath]; + + [NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]]; +} \ No newline at end of file diff --git a/distribution/osx/Launcher/src/compile.sh b/distribution/osx/Launcher/src/compile.sh new file mode 100644 index 000000000..e8947aff9 --- /dev/null +++ b/distribution/osx/Launcher/src/compile.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# -fobjc-arc: enables ARC +# -fmodules: enables modules so you can import with `@import AppKit;` +# -mmacosx-version-min=10.6: support older OS X versions, this might increase the binary size + +if [ ! -d "../dist" ]; then mkdir ../dist; fi + +clang PFMoveApplication.m -fno-objc-arc -fmodules -mmacosx-version-min=10.6 -c -o PFMoveApplication.o +clang run-with-mono.m Launcher.m PFMoveApplication.o -fobjc-arc -fmodules -mmacosx-version-min=10.6 -o ../dist/Launcher +rm PFMoveApplication.o + +if [ "$1" == "install" ] && [ "$2" != "" ]; then + echo "Installing to $2" + cp ../dist/Launcher $2 + chmod +x $2 +fi diff --git a/distribution/osx/Launcher/src/run-with-mono.h b/distribution/osx/Launcher/src/run-with-mono.h new file mode 100644 index 000000000..c95536103 --- /dev/null +++ b/distribution/osx/Launcher/src/run-with-mono.h @@ -0,0 +1,11 @@ +@import Foundation; +@import AppKit; + +@interface RunWithMono : NSObject { +} + ++ (void) openDownloadLink:(NSButton*)button; ++ (bool) showDownloadMonoDialog:(NSString *)appName major:(int)major minor:(int)minor; ++ (int) runAssemblyWithMono:(NSString *)appName procnamesuffix:(NSString *)procnamesuffix assembly:(NSString *)assembly major:(int) major minor:(int) minor; + +@end \ No newline at end of file diff --git a/distribution/osx/Launcher/src/run-with-mono.m b/distribution/osx/Launcher/src/run-with-mono.m new file mode 100644 index 000000000..2979fcc53 --- /dev/null +++ b/distribution/osx/Launcher/src/run-with-mono.m @@ -0,0 +1,258 @@ +#import "run-with-mono.h" + +@import Foundation; +@import AppKit; + +NSString * const VERSION_TITLE = @"Cannot launch %@"; +NSString * const VERSION_MSG = @"%@ requires the Mono Framework version %d.%d or later."; +NSString * const DOWNLOAD_URL = @"http://www.mono-project.com/download/stable/#download-mac"; + +// Helper method to see if the user has requested debug output +bool D() { + NSString* v = [[[NSProcessInfo processInfo]environment]objectForKey:@"DEBUG"]; + if (v == nil || v.length == 0 || [v isEqual:@"0"] || [v isEqual:@"false"] || [v isEqual:@"f"]) + return false; + return true; +} + +// Wrapper method to invoke commandline operations and return the string output +NSString *runCommand(NSString *program, NSArray *arguments) { + NSPipe *pipe = [NSPipe pipe]; + NSFileHandle *file = pipe.fileHandleForReading; + + NSTask *task = [[NSTask alloc] init]; + task.launchPath = program; + task.arguments = arguments; + task.standardOutput = pipe; + + [task launch]; + + NSData *data = [file readDataToEndOfFile]; + [file closeFile]; + [task waitUntilExit]; + + NSString *cmdOutput = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; + if (cmdOutput == nil || cmdOutput.length == 0) + return nil; + + return [cmdOutput stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; +} + +// Checks if the Mono version is greater than or equal to the desired version +bool isValidMono(NSString *mono, int major, int minor) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if (mono == nil) + return false; + + if (![fileManager fileExistsAtPath:mono] || ![fileManager isExecutableFileAtPath:mono]) + return false; + + NSString *versionInfo = runCommand(mono, @[@"--version"]); + + NSRange rg = [versionInfo rangeOfString:@"Mono JIT compiler version \\d+\\.\\d+" options:NSRegularExpressionSearch]; + if (rg.location != NSNotFound) { + versionInfo = [versionInfo substringWithRange:rg]; + if (D()) NSLog(@"Matched version: %@", versionInfo); + rg = [versionInfo rangeOfString:@"\\d+\\.\\d+" options:NSRegularExpressionSearch]; + if (rg.location != NSNotFound) { + versionInfo = [versionInfo substringWithRange:rg]; + if (D()) NSLog(@"Matched version: %@", versionInfo); + + NSArray *versionComponents = [versionInfo componentsSeparatedByString:@"."]; + if ([versionComponents[0] intValue] < major) + return false; + if ([versionComponents[0] intValue] == major && [versionComponents[1] intValue] < minor) + return false; + + return true; + } + } + + return false; +} + +// Attempts to locate a mono with a valid version +NSString *findMono(int major, int minor) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSString *currentMono = runCommand(@"/usr/bin/which", @[@"mono"]); + if (D()) NSLog(@"which mono: %@", currentMono); + + if (isValidMono(currentMono, major, minor)) { + if (D()) NSLog(@"Found mono with: %@", currentMono); + return currentMono; + } + + NSArray *probepaths = @[@"/usr/local/bin/mono", @"/Library/Frameworks/Mono.framework/Versions/Current/bin/mono", @"/opt/local/bin/mono"]; + for(NSString* probepath in probepaths) { + if (D()) NSLog(@"Trying mono with: %@", probepath); + if (isValidMono(probepath, major, minor)) { + if (D()) NSLog(@"Found mono with: %@", probepath); + return probepath; + } + } + + if (D()) NSLog(@"Failed to find Mono, returning: %@", nil); + return nil; +} + +// Check Bundle for quarantine +void checkBundle() { + + NSString * const bundlePath = [[NSBundle mainBundle] bundlePath]; + NSString * const attributes = runCommand(@"/usr/bin/xattr", @[@"-l", bundlePath]); + if (D()) NSLog(@"Attributes: %@", attributes); + if ([attributes containsString:@"com.apple.quarantine:"]) { + runCommand(@"/usr/bin/xattr", @[@"-dr", @"com.apple.quarantine", bundlePath]); + NSLog(@"Removed quarantine attribute from bundle"); + } +} + + +@implementation RunWithMono + ++ (void) openDownloadLink:(NSButton*)button { + if (D()) NSLog(@"Clicked Download"); + runCommand(@"/usr/bin/open", @[DOWNLOAD_URL]); +} + +// Shows the download dialog, prompting to download Mono ++ (bool) showDownloadMonoDialog:(NSString *)appName major:(int)major minor:(int)minor { + NSAlert *alert = [[NSAlert alloc] init]; + + [alert setInformativeText:[NSString stringWithFormat:VERSION_MSG, appName, major, minor]]; + [alert setMessageText:[NSString stringWithFormat:VERSION_TITLE, appName]]; + [alert addButtonWithTitle:@"Cancel"]; + [alert addButtonWithTitle:@"Retry"]; + [alert addButtonWithTitle:@"Download"]; + + NSButton *downloadButton = [[alert buttons] objectAtIndex:2]; + + [downloadButton setTarget:self]; + [downloadButton setAction:@selector(openDownloadLink:)]; + + NSModalResponse btn = [alert runModal]; + if (btn == NSAlertFirstButtonReturn) { + if (D()) NSLog(@"Clicked Cancel"); + return true; + } + else if (btn == NSAlertSecondButtonReturn) { + if (D()) NSLog(@"Clicked Retry"); + return false; + } + + return true; +} + +// Top-level method, finds Mono with an appropriate version and launches the assembly ++ (int) runAssemblyWithMono: (NSString *)appName procnamesuffix:(NSString *)procnamesuffix assembly:(NSString *)assembly major:(int) major minor:(int) minor { + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSString *assemblyPath; + bool found = false; + + NSString *localPath = NSProcessInfo.processInfo.arguments[0].stringByDeletingLastPathComponent; + NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; + NSArray *paths = @[ + localPath, + [NSString pathWithComponents:@[localPath, @"bin"]], + resourcePath, + [NSString pathWithComponents:@[resourcePath, @"bin"]] + ]; + for (NSString* entryFolder in paths) { + if (D()) NSLog(@"Checking folder: %@", entryFolder); + + assemblyPath = [NSString pathWithComponents:@[entryFolder, assembly]]; + + if ([fileManager fileExistsAtPath:assemblyPath]) { + found = true; + break; + } + } + + if (!found) { + NSLog(@"Assembly file not found"); + return 1; + } + + if (D()) NSLog(@"assemblyPath: %@", assemblyPath); + + checkBundle(); + + NSString *currentMono = findMono(major, minor); + + while (currentMono == nil) { + NSLog(@"No valid mono found!"); + bool close = [self showDownloadMonoDialog:appName major:major minor:minor]; + if (close) + return 1; + currentMono = findMono(major, minor); + } + + // Setup dylib fallback loading + NSMutableArray * dylibPath = [NSMutableArray arrayWithObject:assemblyPath.stringByDeletingLastPathComponent]; + + // Update the PATH to use the specified mono version + if ([currentMono hasPrefix:@"/"]) + { + NSString * curMonoBinDir = currentMono.stringByDeletingLastPathComponent; + NSString * curMonoDir = curMonoBinDir.stringByDeletingLastPathComponent; + NSString * curMonoLibDir = [NSString pathWithComponents:@[curMonoDir, @"lib"]]; + + NSString * curEnvPath = [NSString stringWithUTF8String:getenv("PATH")]; + NSString * newEnvPath = [NSString stringWithFormat:@"%@:%@", curMonoBinDir, curEnvPath]; + setenv("PATH", newEnvPath.UTF8String, 1); + + [dylibPath addObject:curMonoLibDir]; + + NSLog(@"Added %@ to PATH", curMonoBinDir); + } + + // Setup libsqlite? + /* if [[ -f '/opt/local/lib/libsqlite3.0.dylib' ]]; then + export DYLD_FALLBACK_LIBRARY_PATH="/opt/local/lib:$DYLD_FALLBACK_LIBRARY_PATH" + fi + */ + + [dylibPath addObjectsFromArray:@[@"$HOME/lib", @"/usr/local/lib", @"/lib", @"/usr/lib"]]; + + setenv("DYLD_FALLBACK_LIBRARY_PATH", [dylibPath componentsJoinedByString:@":"].UTF8String, 1); + + if (D()) NSLog(@"Running %@ --debug %@", currentMono, assemblyPath); + + // Copy commandline arguments + NSMutableArray* arguments = [[NSMutableArray alloc] init]; + // Disabled suffix for now coz it's confusing and not preserved on in-app restart + [arguments addObject:currentMono]; + //[arguments addObject:[currentMono stringByAppendingString:procnamesuffix]]; + [arguments addObject:@"--debug"]; + [arguments addObjectsFromArray:[[NSProcessInfo processInfo] arguments]]; + + // replace the executable-path with the assembly path + [arguments replaceObjectAtIndex:2 withObject:assemblyPath]; + + // Try switch to mono using execv + char * cPath = strdup([currentMono UTF8String]); + char ** cArgs; + char ** pArgNext = cArgs = malloc(sizeof(*cArgs) * ([arguments count] + 1)); + for (NSString *s in arguments) { + *pArgNext++ = strdup([s UTF8String]); + } + *pArgNext = NULL; + int ret = execv(cPath, cArgs); + if (ret != 0) + NSLog(@"Failed execv with errno @d", errno); + // execv failed, cleanup + pArgNext = cArgs; + for (NSString *s in arguments) { + free(*pArgNext++); + } + free(cArgs); + free(cPath); + + return -1; +} + +@end \ No newline at end of file diff --git a/macOS/Sonarr.app/Contents/Info.plist b/distribution/osx/Sonarr.app/Contents/Info.plist similarity index 100% rename from macOS/Sonarr.app/Contents/Info.plist rename to distribution/osx/Sonarr.app/Contents/Info.plist diff --git a/macOS/Sonarr.app/Contents/Resources/sonarr.icns b/distribution/osx/Sonarr.app/Contents/Resources/sonarr.icns similarity index 100% rename from macOS/Sonarr.app/Contents/Resources/sonarr.icns rename to distribution/osx/Sonarr.app/Contents/Resources/sonarr.icns diff --git a/macOS/Sonarr b/distribution/osx/Sonarr.old.sh similarity index 100% rename from macOS/Sonarr rename to distribution/osx/Sonarr.old.sh diff --git a/setup/build.bat b/distribution/windows/setup/build.bat similarity index 70% rename from setup/build.bat rename to distribution/windows/setup/build.bat index c19eef596..dfeba4491 100644 --- a/setup/build.bat +++ b/distribution/windows/setup/build.bat @@ -4,4 +4,4 @@ echo ##teamcity[progressStart 'Building setup file'] inno\ISCC.exe sonarr.iss echo ##teamcity[progressFinish 'Building setup file'] -echo ##teamcity[publishArtifacts 'setup\output\*.exe'] +echo ##teamcity[publishArtifacts 'distribution\windows\setup\output\*.exe'] diff --git a/setup/inno/Default.isl b/distribution/windows/setup/inno/Default.isl similarity index 100% rename from setup/inno/Default.isl rename to distribution/windows/setup/inno/Default.isl diff --git a/setup/inno/ISCC.exe b/distribution/windows/setup/inno/ISCC.exe similarity index 100% rename from setup/inno/ISCC.exe rename to distribution/windows/setup/inno/ISCC.exe diff --git a/setup/inno/ISCmplr.dll b/distribution/windows/setup/inno/ISCmplr.dll similarity index 100% rename from setup/inno/ISCmplr.dll rename to distribution/windows/setup/inno/ISCmplr.dll diff --git a/setup/inno/ISPP.dll b/distribution/windows/setup/inno/ISPP.dll similarity index 100% rename from setup/inno/ISPP.dll rename to distribution/windows/setup/inno/ISPP.dll diff --git a/setup/inno/Setup.e32 b/distribution/windows/setup/inno/Setup.e32 similarity index 100% rename from setup/inno/Setup.e32 rename to distribution/windows/setup/inno/Setup.e32 diff --git a/setup/inno/SetupLdr.e32 b/distribution/windows/setup/inno/SetupLdr.e32 similarity index 100% rename from setup/inno/SetupLdr.e32 rename to distribution/windows/setup/inno/SetupLdr.e32 diff --git a/setup/inno/WizModernImage.bmp b/distribution/windows/setup/inno/WizModernImage.bmp similarity index 100% rename from setup/inno/WizModernImage.bmp rename to distribution/windows/setup/inno/WizModernImage.bmp diff --git a/setup/inno/WizModernSmallImage.bmp b/distribution/windows/setup/inno/WizModernSmallImage.bmp similarity index 100% rename from setup/inno/WizModernSmallImage.bmp rename to distribution/windows/setup/inno/WizModernSmallImage.bmp diff --git a/setup/inno/islzma.dll b/distribution/windows/setup/inno/islzma.dll similarity index 100% rename from setup/inno/islzma.dll rename to distribution/windows/setup/inno/islzma.dll diff --git a/setup/sonarr.iss b/distribution/windows/setup/sonarr.iss similarity index 95% rename from setup/sonarr.iss rename to distribution/windows/setup/sonarr.iss index 24edd6806..0ca353448 100644 --- a/setup/sonarr.iss +++ b/distribution/windows/setup/sonarr.iss @@ -48,8 +48,8 @@ Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked [Files] -Source: "..\_output_windows\Sonarr.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\_output_windows\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "..\..\..\_output_windows\Sonarr.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\..\..\_output_windows\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] diff --git a/src/NzbDrone.Common/Processes/ProcessProvider.cs b/src/NzbDrone.Common/Processes/ProcessProvider.cs index 665e4970e..75ac9add1 100644 --- a/src/NzbDrone.Common/Processes/ProcessProvider.cs +++ b/src/NzbDrone.Common/Processes/ProcessProvider.cs @@ -359,6 +359,10 @@ namespace NzbDrone.Common.Processes { return ("mono", $"--debug {path} {args}"); } + if (OsInfo.IsOsx && path.EndsWith(".app", StringComparison.InvariantCultureIgnoreCase)) + { + return ("/usr/bin/open", $"--new {path} --args {args}"); + } if (OsInfo.IsWindows && path.EndsWith(".bat", StringComparison.InvariantCultureIgnoreCase)) { diff --git a/src/NzbDrone.Host/SpinService.cs b/src/NzbDrone.Host/SpinService.cs index 16bde2e15..04f3d4e98 100644 --- a/src/NzbDrone.Host/SpinService.cs +++ b/src/NzbDrone.Host/SpinService.cs @@ -1,5 +1,7 @@ -using System.Threading; +using System.IO; +using System.Threading; using NLog; +using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Processes; @@ -14,13 +16,15 @@ namespace NzbDrone.Host { private readonly IRuntimeInfo _runtimeInfo; private readonly IProcessProvider _processProvider; + private readonly IDiskProvider _diskProvider; private readonly IStartupContext _startupContext; private readonly Logger _logger; - public SpinService(IRuntimeInfo runtimeInfo, IProcessProvider processProvider, IStartupContext startupContext, Logger logger) + public SpinService(IRuntimeInfo runtimeInfo, IProcessProvider processProvider, IDiskProvider diskProvider, IStartupContext startupContext, Logger logger) { _runtimeInfo = runtimeInfo; _processProvider = processProvider; + _diskProvider = diskProvider; _startupContext = startupContext; _logger = logger; } @@ -38,8 +42,31 @@ namespace NzbDrone.Host { var restartArgs = GetRestartArgs(); - _logger.Info("Attempting restart with arguments: {0}", restartArgs); - _processProvider.SpawnNewProcess(_runtimeInfo.ExecutingApplication, restartArgs); + var path = _runtimeInfo.ExecutingApplication; + var installationFolder = Path.GetDirectoryName(path); + + _logger.Info("Attempting restart with arguments: {0} {1}", path, restartArgs); + + if (OsInfo.IsOsx) + { + if (installationFolder.EndsWith(".app/Contents/MacOS/bin")) + { + // New MacOS App stores Sonarr binaries in MacOS/bin and has a shim in MacOS + // Run the app bundle instead of the binary + path = Path.GetDirectoryName(installationFolder); + path = Path.GetDirectoryName(path); + path = Path.GetDirectoryName(path); + } + else if (installationFolder.EndsWith(".app/Contents/MacOS")) + { + // Old MacOS App stores Sonarr binaries in MacOS together with shell script + // Run the app bundle instead + path = Path.GetDirectoryName(installationFolder); + path = Path.GetDirectoryName(path); + } + } + + _processProvider.SpawnNewProcess(path, restartArgs); } } diff --git a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs index faa741a2d..b3a395e7a 100644 --- a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs +++ b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs @@ -113,10 +113,24 @@ namespace NzbDrone.Update.UpdateEngine _logger.Info("Copying new files to target folder"); _diskTransferService.MirrorFolder(_appFolderInfo.GetUpdatePackageFolder(), installationFolder); - // Set executable flag on Sonarr app + // Handle OSX package update and set executable flag on Sonarr app if (OsInfo.IsOsx) { - _diskProvider.SetPermissions(Path.Combine(installationFolder, "Sonarr"), "0755", null, null); + var shimPath = Path.Combine(installationFolder, "Sonarr"); + var realShimPath = Path.Combine(installationFolder, "../Sonarr"); + + if (installationFolder.EndsWith("/MacOS/bin") && _diskProvider.FileExists(realShimPath)) + { + // New MacOS App stores Sonarr binaries in MacOS/bin and has a shim in MacOS + // Delete the shim in the downloaded update, we shouldn't update the shim unnecessarily + _diskProvider.DeleteFile(shimPath); + } + else + { + // Old MacOS App stores Sonarr binaries in MacOS together with shell script + // Make shim executable + _diskProvider.SetPermissions(shimPath, "0755", null, null); + } } } catch (Exception e) diff --git a/src/NzbDrone.Update/UpdateEngine/StartNzbDrone.cs b/src/NzbDrone.Update/UpdateEngine/StartNzbDrone.cs index 3042dd157..a60b3fa9d 100644 --- a/src/NzbDrone.Update/UpdateEngine/StartNzbDrone.cs +++ b/src/NzbDrone.Update/UpdateEngine/StartNzbDrone.cs @@ -2,6 +2,7 @@ using System; using System.IO; using NLog; using NzbDrone.Common; +using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Processes; using IServiceProvider = NzbDrone.Common.IServiceProvider; @@ -18,19 +19,21 @@ namespace NzbDrone.Update.UpdateEngine private readonly IServiceProvider _serviceProvider; private readonly IProcessProvider _processProvider; private readonly IStartupContext _startupContext; + private readonly IDiskProvider _diskProvider; private readonly Logger _logger; - public StartNzbDrone(IServiceProvider serviceProvider, IProcessProvider processProvider, IStartupContext startupContext, Logger logger) + public StartNzbDrone(IServiceProvider serviceProvider, IProcessProvider processProvider, IStartupContext startupContext, IDiskProvider diskProvider, Logger logger) { _serviceProvider = serviceProvider; _processProvider = processProvider; _startupContext = startupContext; + _diskProvider = diskProvider; _logger = logger; } public void Start(AppType appType, string installationFolder) { - _logger.Info("Starting NzbDrone"); + _logger.Info("Starting Sonarr"); if (appType == AppType.Service) { try @@ -40,7 +43,7 @@ namespace NzbDrone.Update.UpdateEngine } catch (InvalidOperationException e) { - _logger.Warn(e, "Couldn't start NzbDrone Service (Most likely due to permission issues). falling back to console."); + _logger.Warn(e, "Couldn't start Sonarr Service (Most likely due to permission issues). falling back to console."); StartConsole(installationFolder); } } @@ -56,7 +59,7 @@ namespace NzbDrone.Update.UpdateEngine private void StartService() { - _logger.Info("Starting NzbDrone service"); + _logger.Info("Starting Sonarr service"); _serviceProvider.Start(ServiceProvider.SERVICE_NAME); } @@ -80,6 +83,25 @@ namespace NzbDrone.Update.UpdateEngine _startupContext.Flags.Add(StartupContext.NO_BROWSER); } + if (OsInfo.IsOsx) + { + if (installationFolder.EndsWith(".app/Contents/MacOS/bin")) + { + // New MacOS App stores Sonarr binaries in MacOS/bin and has a shim in MacOS + // Run the app bundle instead + path = Path.GetDirectoryName(installationFolder); + path = Path.GetDirectoryName(path); + path = Path.GetDirectoryName(path); + } + else if (installationFolder.EndsWith(".app/Contents/MacOS")) + { + // Old MacOS App stores Sonarr binaries in MacOS together with shell script + // Run the app bundle instead + path = Path.GetDirectoryName(installationFolder); + path = Path.GetDirectoryName(path); + } + } + _processProvider.SpawnNewProcess(path, _startupContext.PreservedArguments); } }