1# -*- coding: utf-8 -*-
2
3"""
4***************************************************************************
5    SagaUtils.py
6    ---------------------
7    Date                 : August 2012
8    Copyright            : (C) 2012 by Victor Olaya
9    Email                : volayaf at gmail dot com
10***************************************************************************
11*                                                                         *
12*   This program is free software; you can redistribute it and/or modify  *
13*   it under the terms of the GNU General Public License as published by  *
14*   the Free Software Foundation; either version 2 of the License, or     *
15*   (at your option) any later version.                                   *
16*                                                                         *
17***************************************************************************
18"""
19
20__author__ = 'Victor Olaya'
21__date__ = 'August 2012'
22__copyright__ = '(C) 2012, Victor Olaya'
23
24import os
25import platform
26import stat
27import subprocess
28import time
29
30from qgis.PyQt.QtCore import QCoreApplication
31from qgis.core import (Qgis,
32                       QgsApplication,
33                       QgsProcessingUtils,
34                       QgsMessageLog)
35from processing.core.ProcessingConfig import ProcessingConfig
36from processing.tools.system import isWindows, isMac, userFolder
37
38SAGA_LOG_COMMANDS = 'SAGA_LOG_COMMANDS'
39SAGA_LOG_CONSOLE = 'SAGA_LOG_CONSOLE'
40SAGA_IMPORT_EXPORT_OPTIMIZATION = 'SAGA_IMPORT_EXPORT_OPTIMIZATION'
41
42_installedVersion = None
43_installedVersionFound = False
44
45
46def sagaBatchJobFilename():
47    if isWindows():
48        filename = 'saga_batch_job.bat'
49    else:
50        filename = 'saga_batch_job.sh'
51
52    batchfile = os.path.join(userFolder(), filename)
53
54    return batchfile
55
56
57def findSagaFolder():
58    folder = None
59    if isMac() or platform.system() == 'FreeBSD' or platform.system() == 'DragonFly':
60        testfolder = os.path.join(QgsApplication.prefixPath(), 'bin')
61        if os.path.exists(os.path.join(testfolder, 'saga_cmd')):
62            folder = testfolder
63        else:
64            testfolder = '/usr/local/bin'
65            if os.path.exists(os.path.join(testfolder, 'saga_cmd')):
66                folder = testfolder
67    elif isWindows():
68        folders = []
69        folders.append(os.path.join(os.path.dirname(QgsApplication.prefixPath()), 'saga-ltr'))
70        folders.append(os.path.join(os.path.dirname(QgsApplication.prefixPath()), 'saga'))
71        if "OSGEO4W_ROOT" in os.environ:
72            folders.append(os.path.join(str(os.environ['OSGEO4W_ROOT']), "apps", "saga-ltr"))
73            folders.append(os.path.join(str(os.environ['OSGEO4W_ROOT']), "apps", "saga"))
74
75        for testfolder in folders:
76            if os.path.exists(os.path.join(testfolder, 'saga_cmd.exe')):
77                folder = testfolder
78                break
79
80    return folder
81
82
83def sagaPath():
84    if not isWindows() and not isMac() and not platform.system() == 'FreeBSD' and platform.system() !=  'DragonFly':
85        return ''
86
87    folder = findSagaFolder()
88    return folder or ''
89
90
91def sagaDescriptionPath():
92    return os.path.join(os.path.dirname(__file__), 'description')
93
94
95def createSagaBatchJobFileFromSagaCommands(commands):
96    with open(sagaBatchJobFilename(), 'w', encoding="utf8") as fout:
97        if isWindows():
98            fout.write('set SAGA=' + sagaPath() + '\n')
99            fout.write('set SAGA_MLB=' + os.path.join(sagaPath(), 'modules') + '\n')
100            fout.write('PATH=%PATH%;%SAGA%;%SAGA_MLB%\n')
101        elif isMac() or platform.system() == 'FreeBSD' or platform.system() == 'DragonFly':
102            fout.write('export SAGA_MLB=' + os.path.join(sagaPath(), '../lib/saga') + '\n')
103            fout.write('export PATH=' + sagaPath() + ':$PATH\n')
104        else:
105            pass
106        for command in commands:
107            if isWindows():
108                fout.write('call saga_cmd ' + command + '\n')
109            else:
110                fout.write('saga_cmd ' + command + '\n')
111
112        fout.write('exit')
113
114
115def getInstalledVersion(runSaga=False):
116    global _installedVersion
117    global _installedVersionFound
118
119    maxRetries = 5
120    retries = 0
121    if _installedVersionFound and not runSaga:
122        return _installedVersion
123
124    if isWindows():
125        commands = [os.path.join(sagaPath(), "saga_cmd.exe"), "-v"]
126    elif isMac() or platform.system() == 'FreeBSD' or platform.system() == 'DragonFly':
127        commands = [os.path.join(sagaPath(), "saga_cmd -v")]
128    else:
129        # for Linux use just one string instead of separated parameters as the list
130        # does not work well together with shell=True option
131        # (python docs advices to use subprocess32 instead of python2.7's subprocess)
132        commands = ["saga_cmd -v"]
133    while retries < maxRetries:
134        with subprocess.Popen(
135            commands,
136            shell=True,
137            stdout=subprocess.PIPE,
138            stdin=subprocess.DEVNULL,
139            stderr=subprocess.STDOUT,
140            universal_newlines=True,
141        ) as proc:
142            if isMac() or platform.system() == 'FreeBSD' or platform.system() == 'DragonFly':  # This trick avoids having an uninterrupted system call exception if SAGA is not installed
143                time.sleep(1)
144            try:
145                lines = proc.stdout.readlines()
146                for line in lines:
147                    if line.startswith("SAGA Version:"):
148                        _installedVersion = line[len("SAGA Version:"):].strip().split(" ")[0]
149                        _installedVersionFound = True
150                        return _installedVersion
151                return None
152            except IOError:
153                retries += 1
154            except:
155                return None
156
157    return _installedVersion
158
159
160def executeSaga(feedback):
161    if isWindows():
162        command = ['cmd.exe', '/C ', sagaBatchJobFilename()]
163    else:
164        os.chmod(sagaBatchJobFilename(), stat.S_IEXEC |
165                 stat.S_IREAD | stat.S_IWRITE)
166        command = ["'" + sagaBatchJobFilename() + "'"]
167    loglines = []
168    loglines.append(QCoreApplication.translate('SagaUtils', 'SAGA execution console output'))
169    with subprocess.Popen(
170        command,
171        shell=True,
172        stdout=subprocess.PIPE,
173        stdin=subprocess.DEVNULL,
174        stderr=subprocess.STDOUT,
175        universal_newlines=True,
176    ) as proc:
177        try:
178            for line in iter(proc.stdout.readline, ''):
179                if '%' in line:
180                    s = ''.join([x for x in line if x.isdigit()])
181                    try:
182                        feedback.setProgress(int(s))
183                    except:
184                        pass
185                else:
186                    line = line.strip()
187                    if line != '/' and line != '-' and line != '\\' and line != '|':
188                        loglines.append(line)
189                        feedback.pushConsoleInfo(line)
190        except:
191            pass
192
193    if ProcessingConfig.getSetting(SAGA_LOG_CONSOLE):
194        QgsMessageLog.logMessage('\n'.join(loglines), 'Processing', Qgis.Info)
195