13 changes: 8 additions & 5 deletions mythtv/bindings/python/MythTV/utility/dicttoxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
version = __version__

from random import randint
import collections
try:
from collections.abc import Iterable
except ImportError:
from collections import Iterable
import numbers
import logging
from xml.dom.minidom import parseString
Expand Down Expand Up @@ -95,7 +98,7 @@ def get_xml_type(val):
return 'null'
if isinstance(val, dict):
return 'dict'
if isinstance(val, collections.Iterable):
if isinstance(val, Iterable):
return 'list'
return type(val).__name__

Expand Down Expand Up @@ -187,7 +190,7 @@ def convert(obj, ids, attr_type, item_func, cdata, parent='root'):
if isinstance(obj, dict):
return convert_dict(obj, ids, parent, attr_type, item_func, cdata)

if isinstance(obj, collections.Iterable):
if isinstance(obj, Iterable):
return convert_list(obj, ids, parent, attr_type, item_func, cdata)

raise TypeError('Unsupported data type: %s (%s)' % (obj, type(obj).__name__))
Expand Down Expand Up @@ -231,7 +234,7 @@ def convert_dict(obj, ids, parent, attr_type, item_func, cdata):
)
)

elif isinstance(val, collections.Iterable):
elif isinstance(val, Iterable):
if attr_type:
attr['type'] = get_xml_type(val)
addline('<%s%s>%s</%s>' % (
Expand Down Expand Up @@ -294,7 +297,7 @@ def convert_list(items, ids, parent, attr_type, item_func, cdata):
)
)

elif isinstance(item, collections.Iterable):
elif isinstance(item, Iterable):
if not attr_type:
addline('<%s %s>%s</%s>' % (
item_name, make_attrstring(attr),
Expand Down
4 changes: 2 additions & 2 deletions mythtv/bindings/python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ def run(self):
version='32.0.-1',
description='MythTV Python bindings.',
long_description='Provides canned database and protocol access to the MythTV database, mythproto, mythxml, services_api and frontend remote control.',
packages=['MythTV', 'MythTV/tmdb3', 'MythTV/ttvdb', 'MythTV/tvmaze',
packages=['MythTV', 'MythTV/tmdb3', 'MythTV/ttvdb', 'MythTV/tvmaze', 'MythTV/ttvdbv4',
'MythTV/wikiscripts', 'MythTV/utility',
'MythTV/services_api'],
package_dir={'MythTV/tmdb3':'./tmdb3/tmdb3', 'MythTV/tvmaze':'./tvmaze'},
package_dir={'MythTV/tmdb3':'./tmdb3/tmdb3', 'MythTV/tvmaze':'./tvmaze', 'MythTV/ttvdbv4':'./ttvdbv4'},
data_files=[('MythTV/ttvdb/XSLT', glob.glob('MythTV/ttvdb/XSLT/*'))],
url=['http://www.mythtv.org/'],
scripts=SCRIPTS,
Expand Down
4 changes: 2 additions & 2 deletions mythtv/bindings/python/tmdb3/tmdb3/lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,15 @@ def buildSingle(inetref, opts):
print("locale_language : ", locale_language)

loc_posters = movie.posters
if loc_posters[0].language != locale_language \
if len(loc_posters) and loc_posters[0].language != locale_language \
and locale_language != system_language:
if opts.debug:
print("1: No poster found for language '%s', trying to sort posters by '%s' :"
%(locale_language, system_language))
loc_posters = sorted(movie.posters,
key = lambda x: x.language==system_language, reverse = True)

if loc_posters[0].language != system_language \
if len(loc_posters) and loc_posters[0].language != system_language \
and loc_posters[0].language != locale_language:
if opts.debug:
print("2: No poster found for language '%s', trying to sort posters by '%s' :"
Expand Down
5 changes: 4 additions & 1 deletion mythtv/bindings/python/tmdb3/tmdb3/pager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
# Author: Raymond Wagner
#-----------------------

from collections import Sequence, Iterator
try:
from collections.abc import Sequence, Iterator
except ImportError:
from collections import Sequence, Iterator

try:
xrange
Expand Down
Empty file.
802 changes: 802 additions & 0 deletions mythtv/bindings/python/ttvdbv4/definitions.py

Large diffs are not rendered by default.

286 changes: 286 additions & 0 deletions mythtv/bindings/python/ttvdbv4/get_api_v4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
# -*- coding: UTF-8 -*-

# ----------------------------------------------------
# Purpose: MythTV Python Bindings for TheTVDB v4 API
# Copyright: (c) 2021 Roland Ernst
# License: GPL v2 or later, see COPYING for details
# ----------------------------------------------------

"""
Parse openapi specification for ttvdb v4.
See https://github.com/thetvdb/v4-api/blob/main/docs/swagger.yml
Create definitions and api for the files
- definitions.py
- ttvdbv4_api.py
"""

import sys
import os
import yaml
import re


# default strings for api functions
func_str = \
"def {fname}({fparams}):\n"
param_str = \
" params = {}\n"
param_item_str = \
" if %s is not None:\n\
params['%s'] = %s\n"
query_str = \
" res = _query_api(%s)\n"
query_str_params = \
" res = _query_api(%s, params)\n"
query_yielded_str = \
" return _query_yielded(%s, %s, params, %s)\n"
res_str = \
" data = res['data'] if res.get('data') is not None else None\n"
array_str = \
"[%s(x) for x in %s] if data is not None else []"
item_str = \
"%s(%s) if data is not None else None"


# default values for basic types
defaults = {'string': "''",
'integer': 0,
'number': 0.0,
'boolean': False
}


# global
pathlist = []


def read_yaml_from_file(api_spec):
with open(api_spec) as f:
data = yaml.safe_load(f)
return data


def print_api(title, version, pathlist, api_calls):
print('"""Generated API for thetvdb.com {} v {}"""'.format(title, version))
print("\n")
for i in pathlist:
print(i)
print("\n\n")
print(api_calls)


def print_api_gets(pathname, pathdata):
global pathlist
paged = False
pstring = ""
# get the function definition for 'operationId'
if pathdata.get('get') is not None:
operationId = pathdata['get']['operationId']
plist = [] # for the 'path' string
pitems = [] # for the function parameter string
params = {} # for the query parameters
if pathdata['get'].get('parameters') is not None:
# required params are member of the path
# optional params are attached as 'params' dict
for param in pathdata['get']['parameters']:
# search for paged requests
pkey = param.get('name').replace('-', '_')
pvalue = pkey
required = param.get('required', False)
if pkey == 'page':
paged = True
continue
if pkey == 'id':
pvalue = 'str(id)'
if required:
plist.append("%s=%s" % (pkey, pvalue))
pitems.append(pkey)
else:
params[pkey] = pvalue
pitems.append("%s=None" % pvalue)
if paged:
params['page'] = 'page'
pitems.append("%s=%s" % ('page', 0))
pitems.append("%s=%s" % ('yielded', False))
pstring += func_str.format(fname=operationId,
fparams=", ".join(pitems))

# define the parameter
if params:
pstring += param_str
for k, v in params.items():
pstring += param_item_str % (k, k, v)

# define the url
path = "%s_path.format(%s)" % (operationId, ", ".join(plist))
pstring += " path = %s\n" % path

# evaluate response properties['data']:
content = pathdata['get']['responses']['200']['content']
data = content['application/json']['schema']['properties']['data']

# look for references ('$ref') starting with '#/components/schemas/'
ref_str = '#/components/schemas/'
tmplst = []
tmpref = ""
listname = "listname=None"
if data.get('type') is not None:
if data.get('items'):
ref = data['items']['$ref']
if ref.startswith(ref_str):
if data.get('type') == 'array':
tmpref = ref.split('/')[-1]
tmplst.append("%s" % (array_str % (tmpref, 'data')))
elif data.get('properties'):
for prop in data['properties'].keys():
if data['properties'][prop].get('$ref') is not None:
ref = data['properties'][prop]['$ref']
if ref.startswith(ref_str):
tmpref = ref.split('/')[-1]
tmplst.append(item_str % (tmpref, "data['%s']" % prop))
elif data['properties'][prop].get('items') is not None:
if data['properties'][prop].get('type') == 'array':
ref = data['properties'][prop]['items']['$ref']
if ref.startswith(ref_str):
listname = "listname='%s'" % prop
tmpref = ref.split('/')[-1]
tmplst.append(array_str % (tmpref, "data['%s']" % prop))

elif data.get('$ref') is not None:
if data['$ref'].startswith(ref_str):
pref = data['$ref'].split('/')[-1]
tmplst.append("%s" % (item_str % (pref, 'data')))

# format output
add_ident = ""
if params:
if paged:
add_ident = " "
pstring += add_ident + "if yielded:\n"
pstring += add_ident + query_yielded_str % (tmpref, "path", listname)
pstring += add_ident + "else:\n"

pstring += add_ident + query_str_params % ("path")
pstring += add_ident + res_str
else:
pstring += query_str % ("path")
pstring += res_str

pstring += \
' %sreturn( %s )' % (add_ident,
(",\n " + add_ident).join(tmplst))

# update pathlist as well
# replace ('-', '_') in parameters identified within '{}'
pattern = re.compile(r'{[a-z]+-[a-z]+}')
s = '%s_path = TTVDBV4_path + "%s"' % (operationId, pathname)
for match in pattern.findall(s):
s = s.replace(match, match.replace('-', '_'))
pathlist.append(s)

return pstring


def print_definition(name, defitem, setdefaults=False):

# string definitions
defstr = '#/components/schemas' # openapi 3.0
classstr = \
'class %s(object):\n' % name + \
' """%s"""\n' % defitem['description'] + \
' def __init__(self, data):\n'
handle_list_str = \
" self.%s = _handle_list(%s, data.get('%s'))\n"
get_list_str = \
" self.%s = _get_list(data, '%s')\n"
handle_single_str = \
" self.%s = _handle_single(%s, data.get('%s'))\n"
translations_str = \
" self.translations = []\n"
similarity_str = \
" self.name_similarity = 0.0\n"
added_str = \
" # additional attributes needed by the mythtv grabber script:\n"

if defitem.get('properties') is None:
classstr += " pass\n"
else:
needs_translations = False
for i in defitem['properties'].keys():
if ('items') in defitem['properties'][i]:
# handle arrays and lists of basic types
if ('type') in defitem['properties'][i]:
if defitem['properties'][i]['type'] == 'array':
if ('$ref') in defitem['properties'][i]['items']:
ref = defitem['properties'][i]['items']['$ref']
atype = ref.split("/")[-1]
classstr += handle_list_str % (i, atype, i)
else:
if i == 'nameTranslations':
needs_translations = True
classstr += get_list_str % (i, i)

elif ('$ref') in defitem['properties'][i]:
# handle special types
v = defitem['properties'][i]['$ref']
stype = v.split("/")[-1]
classstr += handle_single_str % (i, stype, i)

elif ('type') in defitem['properties'][i]:
# handle basic types
stype = defitem['properties'][i]['type']
if setdefaults:
d = defaults.get(stype)
s = " self.%s = data.get('%s', %s)" % (i, i, d)
else:
s = " self.%s = data.get('%s')" % (i, i)
alignment = 80 - len(s) + len(stype)
classstr += s + ("# %s\n" % stype).rjust(alignment)
if needs_translations:
# below are additions needed by the mythtv grabber script
classstr += added_str
classstr += translations_str
classstr += similarity_str

return(classstr)


if __name__ == '__main__':
"""
Download the latest api specification from the TheTVDB official repo:
https://github.com/thetvdb/v4-api/blob/main/docs/swagger.yml
"""
api_spec = sys.argv[1]
if not os.path.isfile(api_spec):
print("Error: main() needs to be called with an OAS3 spec file (yaml)")
sys.exit(1)
apidata = read_yaml_from_file(api_spec)
api_title = apidata['info']['title']
api_version = apidata['info']['version']
apiv4_basepath = apidata['servers'][0]['url']

pathlist.append('TTVDBV4_path = "{}"'.format(apiv4_basepath))

# get api functions
api_calls = ""
for k in apidata['paths'].keys():
if apidata['paths'][k].get('get'):
api_calls += print_api_gets(k, apidata['paths'][k])
api_calls += "\n\n\n"

print_api(api_title, api_version, pathlist, api_calls)

# get api definitions
api_defs = ""
api_defs += ('"""Generated API for thetvdb.com {} v {}"""'
.format(api_title, api_version))
api_defs += "\n\n"
schemas = apidata['components']['schemas'] # openapi 3.0
for k in schemas.keys():
api_defs += "\n"
api_defs += print_definition(k, schemas[k], setdefaults=True)
api_defs += "\n"

print(api_defs)


586 changes: 586 additions & 0 deletions mythtv/bindings/python/ttvdbv4/locales.py

Large diffs are not rendered by default.

879 changes: 879 additions & 0 deletions mythtv/bindings/python/ttvdbv4/myth4ttvdbv4.py

Large diffs are not rendered by default.

598 changes: 598 additions & 0 deletions mythtv/bindings/python/ttvdbv4/ttvdbv4_api.py

Large diffs are not rendered by default.

73 changes: 73 additions & 0 deletions mythtv/bindings/python/ttvdbv4/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# -*- coding: UTF-8 -*-

# ----------------------------------------------------
# Purpose: MythTV Python Bindings for TheTVDB v4 API
# Copyright: (c) 2021 Roland Ernst
# License: GPL v2 or later, see COPYING for details
# ----------------------------------------------------


from datetime import datetime
import sys


if sys.version_info[0] == 2:
from HTMLParser import HTMLParser
from StringIO import StringIO


class MLStripper(HTMLParser):
def __init__(self):
self.reset()
self.text = StringIO()

def handle_data(self, d):
self.text.write(d)

def get_data(self):
return self.text.getvalue()

else:
from io import StringIO
from html.parser import HTMLParser

class MLStripper(HTMLParser):
def __init__(self):
super().__init__()
self.reset()
self.strict = False
self.convert_charrefs= True
self.text = StringIO()

def handle_data(self, d):
self.text.write(d)

def get_data(self):
return self.text.getvalue()


def strip_tags(html):
if html is not None and html != "":
s = MLStripper()
s.feed(html)
return s.get_data()
else:
return ""


def convert_date(tstring):
if tstring is None or tstring == '':
return None
try:
return datetime.strptime(tstring, '%Y-%m-%d').date()
except(TypeError, ValueError):
return None


def convert_time(tstring):
if tstring is None or tstring == '':
return None
try:
return datetime.strptime(tstring, '%Y-%m-%d')
except(TypeError, ValueError):
return None
35 changes: 28 additions & 7 deletions mythtv/configure
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ die(){
If you think configure made a mistake, make sure that you are using the latest
version of MythTV from git. If the latest version fails, report the problem to the
mythtv-dev@mythtv.org mailing list or IRC #mythtv on irc.freenode.net
mythtv-dev@mythtv.org mailing list or IRC #mythtv on irc.libera.chat
EOF
if disabled logging; then
cat <<EOF
Expand Down Expand Up @@ -2001,6 +2001,7 @@ MYTHTV_CONFIG_LIST='
x11
libexiv2_external
libbluray_external
soundtouch_external
profiletype
debugtype
systemd_notify
Expand Down Expand Up @@ -2805,6 +2806,7 @@ enable systemd_notify
enable systemd_journal
enable libexiv2_external
enable libbluray_external
enable soundtouch_external
enable waylandextras

# mythtv paths
Expand Down Expand Up @@ -5931,6 +5933,17 @@ if enabled libexiv2_external ; then
fi
fi

# soundtouch
if enabled soundtouch_external ; then
if $($pkg_config --atleast-version="1.8.0" soundtouch); then
# use_pkg_config does not add cxxflags
add_cxxflags $($pkg_config --cflags soundtouch)
add_extralibs $($pkg_config --libs soundtouch)
else
disable soundtouch_external
fi
fi

if [ $target_os = "linux" ] ; then
. /etc/os-release
fi
Expand All @@ -5948,8 +5961,8 @@ if enabled libbluray_external; then
# following case statement. This list should only
# shrink over time.
case $ID in
centos)
if test $VERSION_ID -gt 8; then
centos|rocky|almalinux)
if test ${VERSION_ID%\.*} -gt 8; then
die "ERROR: can not find libbluray."
fi
;;
Expand Down Expand Up @@ -6018,7 +6031,8 @@ fi
enabled pthreads &&
check_builtin sem_timedwait semaphore.h "sem_t *s; sem_init(s,0,0); sem_timedwait(s,0); sem_destroy(s)"

enabled zlib && check_lib zlib zlib.h zlibVersion -lz
require zlib zlib.h zlibVersion -lz
require zip zip.h zip_open -lzip

# On some systems dynamic loading requires no extra linker flags
check_lib libdl dlfcn.h "dlopen dlsym" || check_lib libdl dlfcn.h "dlopen dlsym" -ldl
Expand Down Expand Up @@ -7573,6 +7587,11 @@ if enabled libbluray_external; then
else
echo "bluray support yes (internal)"
fi
if enabled soundtouch_external; then
echo "soundtouch support yes (system)"
else
echo "soundtouch support yes (internal)"
fi
echo "BD-J (Bluray java) ${bdjava-no}"
echo "BD-J type ${bdj_type}"
echo "systemd_notify ${systemd_notify-no}"
Expand Down Expand Up @@ -8059,8 +8078,10 @@ echo "Configuring libmythdvdnav..."
echo "Configuring libudfread..."
(cd external/libudfread ; \
${qmakeconf} -o Makefile)
echo "Configuring libmythsoundtouch..."
(cd external/libmythsoundtouch ;
${qmakeconf} -o Makefile)
if ! enabled soundtouch_external; then
echo "Configuring libmythsoundtouch..."
(cd external/libmythsoundtouch ;
${qmakeconf} -o Makefile)
fi

test -n "$WARNINGS" && printf "\n$WARNINGS" || exit 0
2 changes: 1 addition & 1 deletion mythtv/contrib/development/MythXMLTest/CDS.html
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@
</td>
</tr>
<tr align="center">
<td colspan='2'>Can be used by clients that want to ‘poll’ for any changes in the Content Directory (as opposed to subscribing to events).
<td colspan='2'>Can be used by clients that want to ‘poll’ for any changes in the Content Directory (as opposed to subscribing to events).
</tr>
</table>
</div>
Expand Down
8 changes: 4 additions & 4 deletions mythtv/external/FFmpeg/tools/clean-diff
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ sed '/^+[^+]/!s/ /TaBBaT/g' |\
sed 's/TaBBaT/ /g' |\
sed '/^+[^+]/s/ * $//' |\
tr -d '\015' |\
tr '\n' '°' |\
sed 's/\(@@[^@]*@@°[^@]*\)/\n\1/g' |\
egrep -v '@@[^@]*@@°(( [^°]*°)|([+-][[:space:]]*°)|(-[[:space:]]*([^°]*)°\+[[:space:]]*\5°))*$' |\
tr '\n' '°' |\
sed 's/\(@@[^@]*@@°[^@]*\)/\n\1/g' |\
egrep -v '@@[^@]*@@°(( [^°]*°)|([+-][[:space:]]*°)|(-[[:space:]]*([^°]*)°\+[[:space:]]*\5°))*$' |\
tr -d '\n' |\
tr '°' '\n'
tr '°' '\n'
3 changes: 3 additions & 0 deletions mythtv/external/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
include ../config.mak

SUBDIRS = FFmpeg libmythdvdnav libudfread

ifneq ($(CONFIG_SOUNDTOUCH_EXTERNAL),yes)
SUBDIRS += libmythsoundtouch
endif

ifneq ($(CONFIG_LIBEXIV2_EXTERNAL),yes)
SUBDIRS += libexiv2
Expand Down
2 changes: 1 addition & 1 deletion mythtv/external/external.pro
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ win32-msvc* {
!using_libbluray_external: SUBDIRS += libmythbluray
SUBDIRS += libmythdvdnav
SUBDIRS += libudfread
SUBDIRS += libmythsoundtouch
!using_soundtouch_external: SUBDIRS += libmythsoundtouch

}
2 changes: 1 addition & 1 deletion mythtv/external/libmythdvdnav/dvdread/dvd_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ static dvd_input_t file_open(const char *target,
}

/* Open the device */
#if !defined(WIN32) && !defined(__OS2__)
#if !defined(__OS2__)
dev->fd = MythFileOpen(target, O_RDONLY);
#else
dev->fd = mythfile_open(target, O_RDONLY | O_BINARY);
Expand Down
2 changes: 1 addition & 1 deletion mythtv/html/apps/frontend.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion mythtv/html/apps/frontend.js.map

Large diffs are not rendered by default.

129 changes: 93 additions & 36 deletions mythtv/html/frontend/package-lock.json
8 changes: 4 additions & 4 deletions mythtv/html/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
"devDependencies": {
"@rollup/plugin-commonjs": "^14.0.0",
"@rollup/plugin-node-resolve": "^8.0.0",
"rollup": "^2.3.4",
"rollup": "^2.51.1",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-svelte": "^6.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0"
"svelte": "^3.38.2"
},
"dependencies": {
"sirv-cli": "^1.0.0",
"svelte-routing": "^1.4.2"
"sirv-cli": "^1.0.12",
"svelte-routing": "^1.6.0"
}
}
2 changes: 2 additions & 0 deletions mythtv/html/menu.qsp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
<li><a href='/Content/wsdl'><i18n>Content Service</i18n></a></li>
<li><a href='/Dvr/wsdl'><i18n>DVR Service</i18n></a></li>
<li><a href='/Guide/wsdl'><i18n>Guide Service</i18n></a></li>
<li><a href='/Image/wsdl'><i18n>Image Service</i18n></a></li>
<li><a href='/Music/wsdl'><i18n>Music Service</i18n></a></li>
<li><a href='/Myth/wsdl'><i18n>Myth Service</i18n></a></li>
<li><a href='/Video/wsdl'><i18n>Video Library Service</i18n></a></li>
</ul>
Expand Down
4 changes: 2 additions & 2 deletions mythtv/libs/libmyth/audio/eldutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#ifndef ELDUTILS_H
Expand Down
11 changes: 6 additions & 5 deletions mythtv/libs/libmyth/libmyth.pro
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,21 @@ HEADERS += netgrabbermanager.h
SOURCES += mythrssmanager.cpp netutils.cpp
SOURCES += netgrabbermanager.cpp

INCLUDEPATH += ../../external/libmythsoundtouch ../libmythfreesurround
!using_soundtouch_external:INCLUDEPATH += ../../external/libmythsoundtouch
INCLUDEPATH += ../libmythfreesurround
INCLUDEPATH += ../libmythbase
INCLUDEPATH += ../.. ../ ./ ../libmythupnp ../libmythui
INCLUDEPATH += ../.. ../../external/FFmpeg
INCLUDEPATH += ../libmythservicecontracts
INCLUDEPATH += $${POSTINC}
DEPENDPATH += ../../external/libmythsoundtouch
!using_soundtouch_external:DEPENDPATH += ../../external/libmythsoundtouch
DEPENDPATH += ../libmythfreesurround
DEPENDPATH += ../ ../libmythui ../libmythbase
DEPENDPATH += ../libmythupnp
DEPENDPATH += ./audio
DEPENDPATH += ../libmythservicecontracts

LIBS += -L../../external/libmythsoundtouch -lmythsoundtouch-$${LIBVERSION}
!using_soundtouch_external:LIBS += -L../../external/libmythsoundtouch -lmythsoundtouch-$${LIBVERSION}
LIBS += -L../libmythbase -lmythbase-$${LIBVERSION}
LIBS += -L../libmythui -lmythui-$${LIBVERSION}
LIBS += -L../libmythupnp -lmythupnp-$${LIBVERSION}
Expand All @@ -120,7 +121,7 @@ LIBS += -L../libmythservicecontracts -lmythservicecontracts-$${LIBVERSIO

!win32-msvc* {
!using_libbluray_external:POST_TARGETDEPS += ../../external/libmythbluray/libmythbluray-$${MYTH_LIB_EXT}
POST_TARGETDEPS += ../../external/libmythsoundtouch/libmythsoundtouch-$${MYTH_LIB_EXT}
!using_soundtouch_external:POST_TARGETDEPS += ../../external/libmythsoundtouch/libmythsoundtouch-$${MYTH_LIB_EXT}
POST_TARGETDEPS += ../../external/FFmpeg/libswresample/$$avLibName(swresample)
POST_TARGETDEPS += ../../external/FFmpeg/libavutil/$$avLibName(avutil)
POST_TARGETDEPS += ../../external/FFmpeg/libavcodec/$$avLibName(avcodec)
Expand Down Expand Up @@ -204,7 +205,7 @@ mingw | win32-msvc* {
SOURCES += audio/audiooutputdx.cpp
HEADERS += mediamonitor-windows.h audio/audiooutputwin.h
HEADERS += audio/audiooutputdx.h
LIBS += -lwinmm -lws2_32 -luser32
LIBS += -lwinmm -lws2_32 -luser32 -lsamplerate
}

macx {
Expand Down
4 changes: 2 additions & 2 deletions mythtv/libs/libmyth/mythcontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -833,8 +833,8 @@ QString MythContextPrivate::TestDBconnection(bool prompt)
{"start","dbAwake","dbStarted","dbConnects","beWOL","beAwake",
"success" };

auto msStartupScreenDelay =
gCoreContext->GetDurSetting<std::chrono::milliseconds>("StartupScreenDelay",2s);
auto secondsStartupScreenDelay = gCoreContext->GetDurSetting<std::chrono::seconds>("StartupScreenDelay",2s);
auto msStartupScreenDelay = std::chrono::duration_cast<std::chrono::milliseconds>(secondsStartupScreenDelay);
do
{
QElapsedTimer timer;
Expand Down
104 changes: 57 additions & 47 deletions mythtv/libs/libmyth/programinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1616,8 +1616,7 @@ void ProgramInfo::ToMap(InfoMap &progMap,
progMap["director"] = m_director;

progMap["callsign"] = m_chanSign;
progMap["commfree"] = QChar((m_programFlags & FL_CHANCOMMFREE) ? 1 : 0);
progMap["commfree_str"] = (m_programFlags & FL_CHANCOMMFREE) ? "1" : "0";
progMap["commfree"] = (m_programFlags & FL_CHANCOMMFREE) ? "1" : "0";
progMap["outputfilters"] = m_chanPlaybackFilters;
if (IsVideo())
{
Expand Down Expand Up @@ -1687,14 +1686,10 @@ void ProgramInfo::ToMap(InfoMap &progMap,
MythDate::toString(m_lastModified, date_format | kDateTimeFull | kSimplify);

if (m_recordedId)
{
progMap["recordedid"] = QChar(m_recordedId);
progMap["recordedid_str"] = QString::number(m_recordedId);
}
progMap["recordedid"] = QString::number(m_recordedId);

progMap["channum"] = m_chanStr;
progMap["chanid"] = QChar(m_chanId);
progMap["chanid_str"] = QString::number(m_chanId);
progMap["chanid"] = QString::number(m_chanId);
progMap["channame"] = m_chanName;
progMap["channel"] = ChannelText(channelFormat);
progMap["longchannel"] = ChannelText(longChannelFormat);
Expand Down Expand Up @@ -1762,10 +1757,8 @@ void ProgramInfo::ToMap(InfoMap &progMap,
progMap["inputname"] = m_inputName;
// Don't add bookmarkupdate to progMap, for now.

progMap["recpriority"] = QChar(m_recPriority);
progMap["recpriority2"] = QChar(m_recPriority2);
progMap["recpriority_str"] = QString::number(m_recPriority);
progMap["recpriority2_str"] = QString::number(m_recPriority2);
progMap["recpriority"] = QString::number(m_recPriority);
progMap["recpriority2"] = QString::number(m_recPriority2);
progMap["recordinggroup"] = (m_recGroup == "Default")
? QObject::tr("Default") : m_recGroup;
progMap["playgroup"] = m_playGroup;
Expand All @@ -1784,14 +1777,10 @@ void ProgramInfo::ToMap(InfoMap &progMap,
progMap["storagegroup"] = m_storageGroup;
}

progMap["programflags"] = QChar(m_programFlags);
progMap["audioproperties"] = QChar(m_audioProperties);
progMap["videoproperties"] = QChar(m_videoProperties);
progMap["subtitleType"] = QChar(m_subtitleProperties);
progMap["programflags_str"] = QString::number(m_programFlags);
progMap["audioproperties_str"] = QString::number(m_audioProperties);
progMap["videoproperties_str"] = QString::number(m_videoProperties);
progMap["subtitleType_str"] = QString::number(m_subtitleProperties);
progMap["programflags"] = QString::number(m_programFlags);
progMap["audioproperties"] = QString::number(m_audioProperties);
progMap["videoproperties"] = QString::number(m_videoProperties);
progMap["subtitleType"] = QString::number(m_subtitleProperties);
progMap["programflags_names"] = GetProgramFlagNames();
progMap["audioproperties_names"] = GetAudioPropertyNames();
progMap["videoproperties_names"] = GetVideoPropertyNames();
Expand Down Expand Up @@ -3714,14 +3703,14 @@ void ProgramInfo::QueryPositionMap(

if (IsVideo())
{
query.prepare("SELECT mark, offset FROM filemarkup"
query.prepare("SELECT mark, `offset` FROM filemarkup"
" WHERE filename = :PATH"
" AND type = :TYPE ;");
query.bindValue(":PATH", StorageGroup::GetRelativePathname(m_pathname));
}
else if (IsRecording())
{
query.prepare("SELECT mark, offset FROM recordedseek"
query.prepare("SELECT mark, `offset` FROM recordedseek"
" WHERE chanid = :CHANID"
" AND starttime = :STARTTIME"
" AND type = :TYPE ;");
Expand Down Expand Up @@ -3879,15 +3868,15 @@ void ProgramInfo::SavePositionMap(
QString qfields;
if (IsVideo())
{
q << "filemarkup (filename, type, mark, offset)";
q << "filemarkup (filename, type, mark, `offset`)";
qfields = QString("('%1',%2,") .
// ideally, this should be escaped
arg(videoPath) .
arg(type);
}
else // if (IsRecording())
{
q << "recordedseek (chanid, starttime, type, mark, offset)";
q << "recordedseek (chanid, starttime, type, mark, `offset`)";
qfields = QString("(%1,'%2',%3,") .
arg(m_chanId) .
arg(m_recStartTs.toString(Qt::ISODate)) .
Expand Down Expand Up @@ -3947,15 +3936,15 @@ void ProgramInfo::SavePositionMapDelta(
QString qfields;
if (IsVideo())
{
q << "filemarkup (filename, type, mark, offset)";
q << "filemarkup (filename, type, mark, `offset`)";
qfields = QString("('%1',%2,") .
// ideally, this should be escaped
arg(StorageGroup::GetRelativePathname(m_pathname)) .
arg(type);
}
else if (IsRecording())
{
q << "recordedseek (chanid, starttime, type, mark, offset)";
q << "recordedseek (chanid, starttime, type, mark, `offset`)";
qfields = QString("(%1,'%2',%3,") .
arg(m_chanId) .
arg(m_recStartTs.toString(Qt::ISODate)) .
Expand Down Expand Up @@ -3994,56 +3983,56 @@ void ProgramInfo::SavePositionMapDelta(
}

static const char *from_filemarkup_offset_asc =
"SELECT mark, offset FROM filemarkup"
"SELECT mark, `offset` FROM filemarkup"
" WHERE filename = :PATH"
" AND type = :TYPE"
" AND mark >= :QUERY_ARG"
" ORDER BY filename ASC, type ASC, mark ASC LIMIT 1;";
static const char *from_filemarkup_offset_desc =
"SELECT mark, offset FROM filemarkup"
"SELECT mark, `offset` FROM filemarkup"
" WHERE filename = :PATH"
" AND type = :TYPE"
" AND mark <= :QUERY_ARG"
" ORDER BY filename DESC, type DESC, mark DESC LIMIT 1;";
static const char *from_recordedseek_offset_asc =
"SELECT mark, offset FROM recordedseek"
"SELECT mark, `offset` FROM recordedseek"
" WHERE chanid = :CHANID"
" AND starttime = :STARTTIME"
" AND type = :TYPE"
" AND mark >= :QUERY_ARG"
" ORDER BY chanid ASC, starttime ASC, type ASC, mark ASC LIMIT 1;";
static const char *from_recordedseek_offset_desc =
"SELECT mark, offset FROM recordedseek"
"SELECT mark, `offset` FROM recordedseek"
" WHERE chanid = :CHANID"
" AND starttime = :STARTTIME"
" AND type = :TYPE"
" AND mark <= :QUERY_ARG"
" ORDER BY chanid DESC, starttime DESC, type DESC, mark DESC LIMIT 1;";
static const char *from_filemarkup_mark_asc =
"SELECT offset,mark FROM filemarkup"
"SELECT `offset`,mark FROM filemarkup"
" WHERE filename = :PATH"
" AND type = :TYPE"
" AND offset >= :QUERY_ARG"
" AND `offset` >= :QUERY_ARG"
" ORDER BY filename ASC, type ASC, mark ASC LIMIT 1;";
static const char *from_filemarkup_mark_desc =
"SELECT offset,mark FROM filemarkup"
"SELECT `offset`,mark FROM filemarkup"
" WHERE filename = :PATH"
" AND type = :TYPE"
" AND offset <= :QUERY_ARG"
" AND `offset` <= :QUERY_ARG"
" ORDER BY filename DESC, type DESC, mark DESC LIMIT 1;";
static const char *from_recordedseek_mark_asc =
"SELECT offset,mark FROM recordedseek"
"SELECT `offset`,mark FROM recordedseek"
" WHERE chanid = :CHANID"
" AND starttime = :STARTTIME"
" AND type = :TYPE"
" AND offset >= :QUERY_ARG"
" AND `offset` >= :QUERY_ARG"
" ORDER BY chanid ASC, starttime ASC, type ASC, mark ASC LIMIT 1;";
static const char *from_recordedseek_mark_desc =
"SELECT offset,mark FROM recordedseek"
"SELECT `offset`,mark FROM recordedseek"
" WHERE chanid = :CHANID"
" AND starttime = :STARTTIME"
" AND type = :TYPE"
" AND offset <= :QUERY_ARG"
" AND `offset` <= :QUERY_ARG"
" ORDER BY chanid DESC, starttime DESC, type DESC, mark DESC LIMIT 1;";

bool ProgramInfo::QueryKeyFrameInfo(uint64_t * result,
Expand Down Expand Up @@ -4504,7 +4493,7 @@ void ProgramInfo::QueryMarkup(QVector<MarkupEntry> &mapMark,
// Get the markup
if (IsVideo())
{
query.prepare("SELECT type, mark, offset FROM filemarkup"
query.prepare("SELECT type, mark, `offset` FROM filemarkup"
" WHERE filename = :PATH"
" AND type NOT IN (:KEYFRAME,:DURATION)"
" ORDER BY mark, type;");
Expand Down Expand Up @@ -4544,7 +4533,7 @@ void ProgramInfo::QueryMarkup(QVector<MarkupEntry> &mapMark,
// Get the seektable
if (IsVideo())
{
query.prepare("SELECT type, mark, offset FROM filemarkup"
query.prepare("SELECT type, mark, `offset` FROM filemarkup"
" WHERE filename = :PATH"
" AND type IN (:KEYFRAME,:DURATION)"
" ORDER BY mark, type;");
Expand All @@ -4554,7 +4543,7 @@ void ProgramInfo::QueryMarkup(QVector<MarkupEntry> &mapMark,
}
else if (IsRecording())
{
query.prepare("SELECT type, mark, offset FROM recordedseek"
query.prepare("SELECT type, mark, `offset` FROM recordedseek"
" WHERE chanid = :CHANID"
" AND STARTTIME = :STARTTIME"
" ORDER BY mark, type");
Expand Down Expand Up @@ -4617,7 +4606,7 @@ void ProgramInfo::SaveMarkup(const QVector<MarkupEntry> &mapMark,
else
{
query.prepare("INSERT INTO filemarkup"
" (filename,type,mark,offset)"
" (filename,type,mark,`offset`)"
" VALUES (:PATH,:TYPE,:MARK,:OFFSET)");
query.bindValue(":OFFSET", (quint64)entry.data);
}
Expand Down Expand Up @@ -4660,7 +4649,7 @@ void ProgramInfo::SaveMarkup(const QVector<MarkupEntry> &mapMark,
}
const MarkupEntry &entry = mapSeek[i];
query.prepare("INSERT INTO filemarkup"
" (filename,type,mark,offset)"
" (filename,type,mark,`offset`)"
" VALUES (:PATH,:TYPE,:MARK,:OFFSET)");
query.bindValue(":PATH", path);
query.bindValue(":TYPE", entry.type);
Expand Down Expand Up @@ -4749,7 +4738,7 @@ void ProgramInfo::SaveMarkup(const QVector<MarkupEntry> &mapMark,
}
const MarkupEntry &entry = mapSeek[i];
query.prepare("INSERT INTO recordedseek"
" (chanid,starttime,type,mark,offset)"
" (chanid,starttime,type,mark,`offset`)"
" VALUES (:CHANID,:STARTTIME,"
" :TYPE,:MARK,:OFFSET)");
query.bindValue(":CHANID", m_chanId);
Expand Down Expand Up @@ -5921,6 +5910,8 @@ bool LoadFromOldRecorded(ProgramList &destination, const QString &sql,
* \param sort sort order, negative for descending, 0 for
* unsorted, positive for ascending
* \param sortBy comma separated list of fields to sort by
* \param ignoreLiveTV don't return LiveTV recordings
* \param ignoreDeleted don't return deleted recordings
* \return true if it succeeds, false if it fails.
* \sa QueryInUseMap(void)
* QueryJobsRunning(int)
Expand All @@ -5933,7 +5924,9 @@ bool LoadFromRecorded(
const QMap<QString,bool> &isJobRunning,
const QMap<QString, ProgramInfo*> &recMap,
int sort,
const QString &sortBy)
const QString &sortBy,
bool ignoreLiveTV,
bool ignoreDeleted)
{
destination.clear();

Expand All @@ -5943,8 +5936,25 @@ bool LoadFromRecorded(
// ----------------------------------------------------------------------

QString thequery = ProgramInfo::kFromRecordedQuery;
if (possiblyInProgressRecordingsOnly)
thequery += "WHERE r.endtime >= NOW() AND r.starttime <= NOW() ";
if (possiblyInProgressRecordingsOnly || ignoreLiveTV || ignoreDeleted)
{
thequery += "WHERE ";
if (possiblyInProgressRecordingsOnly)
{
thequery += "(r.endtime >= NOW() AND r.starttime <= NOW()) ";
}
if (ignoreLiveTV)
{
thequery += QString("%1 r.recgroup != 'LiveTV' ")
.arg(possiblyInProgressRecordingsOnly ? "AND" : "");
}
if (ignoreDeleted)
{
thequery += QString("%1 r.recgroup != 'Deleted' ")
.arg((possiblyInProgressRecordingsOnly || ignoreLiveTV)
? "AND" : "");
}
}

if (sortBy.isEmpty())
{
Expand Down
5 changes: 3 additions & 2 deletions mythtv/libs/libmyth/programinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -886,8 +886,9 @@ MPUBLIC bool LoadFromRecorded(
const QMap<QString,bool> &isJobRunning,
const QMap<QString, ProgramInfo*> &recMap,
int sort = 0,
const QString &sortBy = "");

const QString &sortBy = "",
bool ignoreLiveTV = false,
bool ignoreDeleted = false);

template<typename TYPE>
bool LoadFromScheduler(
Expand Down
54 changes: 18 additions & 36 deletions mythtv/libs/libmyth/test/test_programinfo/test_programinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,17 @@ class TestProgramInfo : public QObject
"2016|23|106|4|66247|Prime A-0|4294967295";
InfoMap m_flash34Map = {
{"00x00", "3x04"},
{"audioproperties", QChar(8)},
{"audioproperties_str", "8"},
{"audioproperties", "8"},
{"audioproperties_names", "DOLBY"},
{"callsign", "WNUVDT"},
{"card", "-"},
{"catType", "tvshow"},
{"category", "Drama"},
{"chanid", QChar(1514)},
{"chanid_str", "1514"},
{"chanid", "1514"},
{"channame", "WNUBDT (WNUV-DT)"},
{"channel", "514 WNUVDT"},
{"channum", "514"},
{"commfree", QChar(0)},
{"commfree_str", "0"},
{"commfree", "0"},
{"description", "Barry continues to train Jesse ..."},
{"description0", "Barry continues to train Jesse ..."},
{"enddate", "Wed 26 October"},
Expand All @@ -160,19 +157,15 @@ class TestProgramInfo : public QObject
{"partnumber", "50"},
{"parttotal", "133"},
{"playgroup", "Default"},
{"programflags", QChar(528)}, // Bad
{"programflags_str", "131600"},
{"programflags", "131600"},
{"programflags_names", "BOOKMARK|WATCHED|ALLOWLASTPLAYPOS"},
{"programid", "EP019229360055"},
{"recenddate", "Wed 26"},
{"recendtime", "1:02 AM"},
{"recordedid", QChar(715)},
{"recordedid_str", "715"},
{"recordedid", "715"},
{"recordinggroup", "Default"},
{"recpriority", QChar(7)},
{"recpriority2", QChar(0)},
{"recpriority_str", "7"},
{"recpriority2_str", "0"},
{"recpriority", "7"},
{"recpriority2", "0"},
{"recstartdate", "Tue 25"},
{"recstarttime", "11:58 PM"},
{"recstatus", "Not Recording"},
Expand All @@ -199,36 +192,31 @@ class TestProgramInfo : public QObject
{"startyear", "2016"},
{"storagegroup", "Default"},
{"subtitle", "The New Rogues"},
{"subtitleType", QChar(1)},
{"subtitleType_str", "1"},
{"subtitleType", "1"},
{"subtitleType_names", "HARDHEAR"},
{"syndicatedepisode", "syndicatedepisode"},
{"timedate", "Tue 25 October, 11:58 PM - 1:02 AM"},
{"title", "The Flash (2014)"},
{"titlesubtitle", "The Flash (2014) - \"The New Rogues\""},
{"totalepisodes", "23"},
{"videoproperties", QChar(1090)},
{"videoproperties_str", "1090"},
{"videoproperties", "1090"},
{"videoproperties_names", "HDTV|1080|DAMAGED"},
{"year", "2016"},
{"yearstars", "(2016, 10 star(s))"},
};
InfoMap m_supergirl23Map = {
{"00x00", "2x03"},
{"audioproperties", QChar(33)},
{"audioproperties_str", "33"},
{"audioproperties", "33"},
{"audioproperties_names", "STEREO|VISUALIMPAIR"},
{"callsign", "WNUVDT"},
{"card", "-"},
{"catType", "tvshow"},
{"category", "Drama"},
{"chanid", QChar(1514)},
{"chanid_str", "1514"},
{"chanid", "1514"},
{"channame", "WNUBDT (WNUV-DT)"},
{"channel", "514 WNUVDT"},
{"channum", "514"},
{"commfree", QChar(0)},
{"commfree_str", "0"},
{"commfree", "0"},
{"description", "An attack is made on the President as hot-button..."},
{"description0", "An attack is made on the President as hot-button..."},
{"enddate", "Tue 25 October"},
Expand All @@ -255,19 +243,15 @@ class TestProgramInfo : public QObject
{"partnumber", "23"},
{"parttotal", "106"},
{"playgroup", "Default"},
{"programflags", QChar(1538)},
{"programflags_str", "1538"},
{"programflags", "1538"},
{"programflags_names", "CUTLIST|WATCHED|PRESERVED"},
{"programid", "EP021854510025"},
{"recenddate", "Tue 25"},
{"recendtime", "1:02 AM"},
{"recordedid", QChar(711)}, // Bad
{"recordedid_str", "66247"},
{"recordedid", "66247"},
{"recordinggroup", "Default"},
{"recpriority", QChar(-1)},
{"recpriority2", QChar(0)},
{"recpriority_str", "-1"},
{"recpriority2_str", "0"},
{"recpriority", "-1"},
{"recpriority2", "0"},
{"recstartdate", "Mon 24"},
{"recstarttime", "11:58 PM"},
{"recstatus", "Not Recording"},
Expand All @@ -294,16 +278,14 @@ class TestProgramInfo : public QObject
{"startyear", "2016"},
{"storagegroup", "Default"},
{"subtitle", "Welcome to Earth"},
{"subtitleType", QChar(8)},
{"subtitleType_str", "8"},
{"subtitleType", "8"},
{"subtitleType_names", "SIGNED"},
{"syndicatedepisode", "syndicatedepisode"},
{"timedate", "Mon 24 October, 11:58 PM - 1:02 AM"},
{"title", "Supergirl"},
{"titlesubtitle", "Supergirl - \"Welcome to Earth\""},
{"totalepisodes", "23"},
{"videoproperties", QChar(33)},
{"videoproperties_str", "33"},
{"videoproperties", "33"},
{"videoproperties_names", "WIDESCREEN|720"},
{"year", "2016"},
{"yearstars", "(2016, 9 star(s))"},
Expand Down
61 changes: 19 additions & 42 deletions mythtv/libs/libmythbase/compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,13 @@
# include <sys/wait.h> // For WIFEXITED on Mac OS X
#endif

#ifdef _WIN32
# include <cstdlib> // for rand()
# include <ctime>
# include <sys/time.h>
#ifdef _MSC_VER
#include <cstdlib> // for rand()
#include <ctime>
#include <sys/time.h>
#endif
#if defined(USING_MINGW)
#include <time.h>
#endif

#ifdef _MSC_VER
Expand Down Expand Up @@ -137,13 +140,17 @@
//used in videodevice only - that code is not windows-compatible anyway
# define minor(X) 0

using uint = unsigned int;
#if defined(__cplusplus)
using uint = unsigned int;
#else
typedef unsigned int uint;
#endif
#endif

#if defined(__cplusplus) && defined(_WIN32)
# include <QtGlobal>

#if QT_VERSION >= QT_VERSION_CHECK(5,10,0)
#if QT_VERSION >= QT_VERSION_CHECK(5,10,0) && !defined(USING_MINGW)
#include <QRandomGenerator>
static inline void srandom(unsigned int /*seed*/) { }
static inline long int random(void)
Expand All @@ -157,11 +164,6 @@
# define setenv(x, y, z) ::SetEnvironmentVariableA(x, y)
# define unsetenv(x) 0

inline unsigned sleep(unsigned int x)
{
Sleep(x * 1000);
return 0;
}

struct statfs {
// long f_type; /* type of filesystem */
Expand Down Expand Up @@ -264,7 +266,7 @@
# define seteuid(x) 0
#endif // _WIN32

#if defined(_WIN32) && !defined(gmtime_r)
#if _MSC_VER && !defined(gmtime_r)
// FFmpeg libs already have a workaround, use it if the headers are included,
// use this otherwise.
static __inline struct tm *gmtime_r(const time_t *timep, struct tm *result)
Expand All @@ -281,7 +283,7 @@ static __inline struct tm *gmtime_r(const time_t *timep, struct tm *result)
}
#endif

#if defined(_WIN32) && !defined(localtime_r)
#if _MSC_VER && !defined(localtime_r)
// FFmpeg libs already have a workaround, use it if the headers are included,
// use this otherwise.
static __inline struct tm *localtime_r(const time_t *timep, struct tm *result)
Expand Down Expand Up @@ -335,35 +337,6 @@ static __inline struct tm *localtime_r(const time_t *timep, struct tm *result)
# define ftello(stream) ftello64(stream)
#endif

#if defined(USING_MINGW) && defined(FILENAME_MAX)
# include <cerrno>
# include <cstddef>
# include <cstring>
# include <dirent.h>
static inline int readdir_r(
DIR *dirp, struct dirent *entry, struct dirent **result)
{
errno = 0;
struct dirent *tmp = readdir(dirp);
if (tmp && entry)
{
int offset = offsetof(struct dirent, d_name);
memcpy(entry, tmp, offset);
strncpy(entry->d_name, tmp->d_name, FILENAME_MAX);
tmp->d_name[strlen(entry->d_name)] = '\0';
if (result)
*result = entry;
return 0;
}
else
{
if (result)
*result = nullptr;
return errno;
}
}
#endif

#ifdef Q_OS_ANDROID
#ifndef S_IREAD
#define S_IREAD S_IRUSR
Expand All @@ -387,4 +360,8 @@ static __inline struct tm *localtime_r(const time_t *timep, struct tm *result)
# define LZO_COMPILE_TIME_ASSERT( a )
#endif

#ifndef O_NONBLOCK
# define O_NONBLOCK 04000
#endif

#endif // COMPAT_H
9 changes: 5 additions & 4 deletions mythtv/libs/libmythbase/libmythbase.pro
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ HEADERS += lcddevice.h mythstorage.h remotefile.h logging.h loggingserver.h
HEADERS += mythcorecontext.h mythsystem.h mythsystemprivate.h
HEADERS += mythlocale.h storagegroup.h
HEADERS += mythcoreutil.h mythdownloadmanager.h mythtranslation.h
HEADERS += unzip.h unzip_p.h zipentry_p.h iso639.h iso3166.h mythmedia.h
HEADERS += unzip2.h iso639.h iso3166.h mythmedia.h
HEADERS += mythmiscutil.h mythhdd.h mythcdrom.h autodeletedeque.h dbutil.h
HEADERS += mythdeque.h mythlogging.h
HEADERS += mythbaseutil.h referencecounter.h referencecounterlist.h
Expand All @@ -45,7 +45,7 @@ SOURCES += mythtimer.cpp mythdirs.cpp
SOURCES += lcddevice.cpp mythstorage.cpp remotefile.cpp
SOURCES += mythcorecontext.cpp mythsystem.cpp mythlocale.cpp storagegroup.cpp
SOURCES += mythcoreutil.cpp mythdownloadmanager.cpp mythtranslation.cpp
SOURCES += unzip.cpp iso639.cpp iso3166.cpp mythmedia.cpp mythmiscutil.cpp
SOURCES += unzip2.cpp iso639.cpp iso3166.cpp mythmedia.cpp mythmiscutil.cpp
SOURCES += mythhdd.cpp mythcdrom.cpp dbutil.cpp
SOURCES += logging.cpp loggingserver.cpp
SOURCES += referencecounter.cpp mythcommandlineparser.cpp
Expand Down Expand Up @@ -75,6 +75,7 @@ unix {
mingw | win32-msvc* {
SOURCES += mythsystemwindows.cpp
HEADERS += mythsystemwindows.h
LIBS += -lzip
}

# Install headers to same location as libmyth to make things easier
Expand All @@ -92,7 +93,7 @@ inc.files += referencecounter.h referencecounterlist.h mythcommandlineparser.h
inc.files += mthread.h mthreadpool.h mythchrono.h
inc.files += filesysteminfo.h hardwareprofile.h bonjourregister.h serverpool.h
inc.files += plist.h bswap.h signalhandling.h ffmpeg-mmx.h mythdate.h
inc.files += mythplugin.h mythpluginapi.h mythqtcompat.h
inc.files += mythplugin.h mythpluginapi.h
inc.files += remotefile.h mythsystemlegacy.h mythtypes.h
inc.files += threadedfilewriter.h mythsingledownload.h mythsession.h
inc.files += mythsorthelper.h mythdbcheck.h
Expand Down Expand Up @@ -150,7 +151,7 @@ using_libdns_sd {

using_x11:DEFINES += USING_X11

mingw:LIBS += -lws2_32
mingw:LIBS += -lws2_32 -lz

win32-msvc* {

Expand Down
4 changes: 2 additions & 2 deletions mythtv/libs/libmythbase/loggingserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,8 @@ DatabaseLogger::DatabaseLogger(const char *table) :
{
m_query = QString(
"INSERT INTO %1 "
" (host, application, pid, tid, thread, filename, "
" line, function, msgtime, level, message) "
" (`host`, `application`, `pid`, `tid`, `thread`, `filename`, "
" `line`, `function`, `msgtime`, `level`, `message`) "
"VALUES (:HOST, :APP, :PID, :TID, :THREAD, :FILENAME, "
" :LINE, :FUNCTION, :MSGTIME, :LEVEL, :MESSAGE)")
.arg(m_handle);
Expand Down
1 change: 0 additions & 1 deletion mythtv/libs/libmythbase/loggingserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ class SyslogLogger : public LoggerBase
Q_OBJECT

public:
SyslogLogger();
explicit SyslogLogger(bool open);
~SyslogLogger() override;
bool logmsg(LoggingItem *item) override; // LoggerBase
Expand Down
2 changes: 1 addition & 1 deletion mythtv/libs/libmythbase/mythcommandlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2863,7 +2863,7 @@ bool setUser(const QString &username)
return true;

#ifdef _WIN32
cerr << "--user option is not supported on Windows" << endl;
std::cerr << "--user option is not supported on Windows" << std::endl;
return false;
#else // ! _WIN32
#if defined(__linux__) || defined(__LINUX__)
Expand Down
14 changes: 13 additions & 1 deletion mythtv/libs/libmythbase/mythcorecontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1090,12 +1090,24 @@ int MythCoreContext::GetBackendServerPort(void)
return GetBackendServerPort(d->m_localHostname);
}

QHash<QString,int> MythCoreContext::s_serverPortCache;

void MythCoreContext::ClearBackendServerPortCache()
{
s_serverPortCache.clear();
}

/**
* Returns the backend "hosts"'s control port
*/
int MythCoreContext::GetBackendServerPort(const QString &host)
{
return GetNumSettingOnHost("BackendServerPort", host, 6543);
int port = s_serverPortCache.value(host, -1);
if (port != -1)
return port;
port = GetNumSettingOnHost("BackendServerPort", host, 6543);
s_serverPortCache[host] = port;
return port;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions mythtv/libs/libmythbase/mythcorecontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,10 @@ class MBASE_PUBLIC MythCoreContext : public QObject, public MythObservable, publ
int GetMasterServerStatusPort(void);
int GetBackendServerPort(void);
int GetBackendServerPort(const QString &host);
static void ClearBackendServerPortCache();
int GetBackendStatusPort(void);
int GetBackendStatusPort(const QString &host);
static QHash<QString,int> s_serverPortCache;

bool GetScopeForAddress(QHostAddress &addr) const;
void SetScopeForAddress(const QHostAddress &addr);
Expand Down
30 changes: 4 additions & 26 deletions mythtv/libs/libmythbase/mythcoreutil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
// libmythbase headers
#include "mythcorecontext.h"
#include "mythlogging.h"
#include "unzip.h"
#include "unzip2.h"

#include "version.h"
#include "mythversion.h"
Expand Down Expand Up @@ -71,32 +71,10 @@ int64_t getDiskSpace(const QString &file_on_disk,
return freespace;
}

bool extractZIP(const QString &zipFile, const QString &outDir)
bool extractZIP(QString &zipFile, const QString &outDir)
{
UnZip uz;
UnZip::ErrorCode ec = uz.openArchive(zipFile);

if (ec != UnZip::Ok)
{
LOG(VB_GENERAL, LOG_ERR,
QString("extractZIP(): Unable to open ZIP file %1")
.arg(zipFile));
return false;
}

ec = uz.extractAll(outDir);

if (ec != UnZip::Ok)
{
LOG(VB_GENERAL, LOG_ERR,
QString("extractZIP(): Error extracting ZIP file %1")
.arg(zipFile));
return false;
}

uz.closeArchive();

return true;
UnZip unzip(zipFile);
return unzip.extractFile(outDir);
}

bool gzipFile(const QString &inFilename, const QString &gzipFilename)
Expand Down
2 changes: 1 addition & 1 deletion mythtv/libs/libmythbase/mythcoreutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

MBASE_PUBLIC int64_t getDiskSpace(const QString &file_on_disk, int64_t &total, int64_t &used);

MBASE_PUBLIC bool extractZIP(const QString &zipFile, const QString &outDir);
MBASE_PUBLIC bool extractZIP(QString &zipFile, const QString &outDir);

MBASE_PUBLIC bool gzipFile(const QString &inFilename, const QString &zipFilename);
MBASE_PUBLIC bool gunzipFile(const QString &zipFilename, const QString &outFilename);
Expand Down
42 changes: 24 additions & 18 deletions mythtv/libs/libmythbase/mythdbcon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -638,12 +638,7 @@ bool MSqlQuery::exec()
bool result = QSqlQuery::exec();
qint64 elapsed = timer.elapsed();

// if the query failed with "MySQL server has gone away"
// Close and reopen the database connection and retry the query if it
// connects again
if (!result
&& QSqlQuery::lastError().nativeErrorCode() == "2006"
&& Reconnect())
if (!result && lostConnectionCheck())
result = QSqlQuery::exec();

if (!result)
Expand Down Expand Up @@ -756,12 +751,7 @@ bool MSqlQuery::exec(const QString &query)

bool result = QSqlQuery::exec(query);

// if the query failed with "MySQL server has gone away"
// Close and reopen the database connection and retry the query if it
// connects again
if (!result
&& QSqlQuery::lastError().nativeErrorCode() == "2006"
&& Reconnect())
if (!result && lostConnectionCheck())
result = QSqlQuery::exec(query);

LOG(VB_DATABASE, LOG_INFO,
Expand Down Expand Up @@ -859,12 +849,7 @@ bool MSqlQuery::prepare(const QString& query)

bool ok = QSqlQuery::prepare(query);

// if the prepare failed with "MySQL server has gone away"
// Close and reopen the database connection and retry the query if it
// connects again
if (!ok
&& QSqlQuery::lastError().nativeErrorCode() == "2006"
&& Reconnect())
if (!ok && lostConnectionCheck())
ok = true;

if (!ok && !(GetMythDB()->SuppressDBMessages()))
Expand Down Expand Up @@ -946,6 +931,27 @@ bool MSqlQuery::Reconnect(void)
return true;
}

bool MSqlQuery::lostConnectionCheck()
{
// MySQL: Error number: 2006; Symbol: CR_SERVER_GONE_ERROR
// MySQL: Error number: 2013; Symbol: CR_SERVER_LOST
// MySQL: Error number: 4031; Symbol: ER_CLIENT_INTERACTION_TIMEOUT
// Note: In MariaDB, 4031 = ER_REFERENCED_TRG_DOES_NOT_EXIST

static QStringList kLostConnectionCodes = { "2006", "2013", "4031" };

QString error_code = QSqlQuery::lastError().nativeErrorCode();

// Make capturing of new 'lost connection' like error codes easy.
LOG(VB_GENERAL, LOG_DEBUG, QString("SQL Native Error Code: %1")
.arg(error_code));

// If the query failed with any of the error codes that say the server
// is gone, close and reopen the database connection.
return (kLostConnectionCodes.contains(error_code) && Reconnect());

}

void MSqlAddMoreBindings(MSqlBindings &output, MSqlBindings &addfrom)
{
MSqlBindings::Iterator it;
Expand Down
4 changes: 4 additions & 0 deletions mythtv/libs/libmythbase/mythdbcon.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ class MBASE_PUBLIC MSqlQuery : private QSqlQuery
/// query.
bool Reconnect(void);

/// lostConnectionCheck tests for SQL error codes that indicate the
/// connection to the server has been lost.
bool lostConnectionCheck(void);

// Thunks that allow us to make QSqlQuery private
QVariant value(int i) const { return QSqlQuery::value(i); }
QString executedQuery(void) const { return QSqlQuery::executedQuery(); }
Expand Down
3 changes: 3 additions & 0 deletions mythtv/libs/libmythbase/mythmiscutil.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#ifdef _WIN32
#include <sys/stat.h>
#endif

#include "mythmiscutil.h"

Expand Down
4 changes: 4 additions & 0 deletions mythtv/libs/libmythbase/mythmiscutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
#include <QString>
#include <QDir>

#ifdef _WIN32
#undef mkdir
#endif

#include "mythbaseexp.h"
#include "mythsystem.h"

Expand Down
6 changes: 0 additions & 6 deletions mythtv/libs/libmythbase/mythqtcompat.h

This file was deleted.

2 changes: 1 addition & 1 deletion mythtv/libs/libmythbase/mythsocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ static QString to_sample(const QByteArray &payload)
}

MythSocket::MythSocket(
qt_socket_fd_t socket, MythSocketCBs *cb, bool use_shared_thread) :
qintptr socket, MythSocketCBs *cb, bool use_shared_thread) :
ReferenceCounter(QString("MythSocket(%1)").arg(socket)),
m_tcpSocket(new QTcpSocket()),
m_callback(cb),
Expand Down
5 changes: 2 additions & 3 deletions mythtv/libs/libmythbase/mythsocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

#include "referencecounter.h"
#include "mythsocket_cb.h"
#include "mythqtcompat.h"
#include "mythbaseexp.h"
#include "mthread.h"

Expand All @@ -30,7 +29,7 @@ class MBASE_PUBLIC MythSocket : public QObject, public ReferenceCounter
friend class MythSocketManager;

public:
explicit MythSocket(qt_socket_fd_t socket = -1, MythSocketCBs *cb = nullptr,
explicit MythSocket(qintptr socket = -1, MythSocketCBs *cb = nullptr,
bool use_shared_thread = false);

bool ConnectToHost(const QString &hostname, quint16 port);
Expand Down Expand Up @@ -99,7 +98,7 @@ class MBASE_PUBLIC MythSocket : public QObject, public ReferenceCounter
QTcpSocket *m_tcpSocket {nullptr}; // only set in ctor
MThread *m_thread {nullptr}; // only set in ctor
mutable QMutex m_lock;
qt_socket_fd_t m_socketDescriptor {-1}; // protected by m_lock
qintptr m_socketDescriptor {-1}; // protected by m_lock
QHostAddress m_peerAddress; // protected by m_lock
int m_peerPort {-1}; // protected by m_lock
MythSocketCBs *m_callback {nullptr}; // only set in ctor
Expand Down
3 changes: 3 additions & 0 deletions mythtv/libs/libmythbase/mythsystemlegacy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ void MythSystemLegacy::Signal(MythSignal sig)
if (m_status != GENERIC_EXIT_RUNNING)
return;

#ifndef SIGTRAP /* For Mingw */
#define SIGTRAP -1
#endif
int posix_signal = SIGTRAP;
switch (sig)
{
Expand Down
14 changes: 10 additions & 4 deletions mythtv/libs/libmythbase/mythsystemwindows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ void MythSystemLegacySignalManager::run(void)
LOG(VB_GENERAL, LOG_INFO, "Starting process signal handler");
while( run_system )
{
usleep(50000); // sleep 50ms
usleep(50ms);
while( run_system )
{
// handle cleanup and signalling for closed processes
Expand Down Expand Up @@ -569,7 +569,7 @@ void MythSystemLegacyWindows::Term(bool force)
if( force )
{
// send KILL if it does not exit within one second
if( m_parent->Wait(1) == GENERIC_EXIT_RUNNING )
if( m_parent->Wait(1s) == GENERIC_EXIT_RUNNING )
Signal(SIGKILL);
}
}
Expand Down Expand Up @@ -707,14 +707,20 @@ void MythSystemLegacyWindows::Fork(std::chrono::seconds timeout)
if (dir.length() > 0)
pDir = (LPCWSTR)dir.utf16();

char sCmdChar[256];
sprintf(sCmdChar, "%ls", (LPWSTR)sCmd.utf16() );

char pDirChar[256];
sprintf(pDirChar, "%ls", pDir);

bool success = CreateProcess( nullptr,
(LPWSTR)sCmd.utf16(), // command line
sCmdChar, // command line
nullptr, // process security attributes
nullptr, // primary thread security attributes
bInherit, // handles are inherited
0, // creation flags
nullptr, // use parent's environment
pDir, // use parent's current directory
pDirChar, // use parent's current directory
&si, // STARTUPINFO pointer
&pi); // receives PROCESS_INFORMATION

Expand Down
2 changes: 1 addition & 1 deletion mythtv/libs/libmythbase/mythversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
* mythtv/bindings/php/MythBackend.php
*/

#define MYTH_DATABASE_VERSION "1369"
#define MYTH_DATABASE_VERSION "1371"

MBASE_PUBLIC const char *GetMythSourceVersion();
MBASE_PUBLIC const char *GetMythSourcePath();
Expand Down
2 changes: 0 additions & 2 deletions mythtv/libs/libmythbase/portchecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ bool PortChecker::checkPort(QString &host, int port, std::chrono::milliseconds t
#endif
if (linkLocalOnly)
{
// cppcheck-suppress knownConditionTrueFalse
if (islinkLocal)
{
// If we already know the scope, set it here and return
Expand All @@ -101,7 +100,6 @@ bool PortChecker::checkPort(QString &host, int port, std::chrono::milliseconds t
else
return false;
}
// cppcheck-suppress unreadVariable
QList<QNetworkInterface> cards = QNetworkInterface::allInterfaces();
#ifndef _WIN32
QListIterator<QNetworkInterface> iCard = cards;
Expand Down
4 changes: 2 additions & 2 deletions mythtv/libs/libmythbase/serverpool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ PrivTcpServer::PrivTcpServer(QObject *parent, PoolServerType type)
{
}

void PrivTcpServer::incomingConnection(qt_socket_fd_t socket)
void PrivTcpServer::incomingConnection(qintptr socket)
{
emit newConnection(socket);
}
Expand Down Expand Up @@ -669,7 +669,7 @@ qint64 ServerPool::writeDatagram(const QByteArray &datagram,
return writeDatagram(datagram.data(), datagram.size(), addr, port);
}

void ServerPool::newTcpConnection(qt_socket_fd_t socket)
void ServerPool::newTcpConnection(qintptr socket)
{
// Ignore connections from an SSL server for now, these are only handled
// by HttpServer which overrides newTcpConnection
Expand Down
7 changes: 3 additions & 4 deletions mythtv/libs/libmythbase/serverpool.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include <QUdpSocket>
#include <QStringList>

#include "mythqtcompat.h"
#include "mythbaseexp.h"

/** \class ServerPool
Expand Down Expand Up @@ -47,10 +46,10 @@ class MBASE_PUBLIC PrivTcpServer : public QTcpServer
PoolServerType GetServerType(void) { return m_serverType; }

signals:
void newConnection(qt_socket_fd_t socket);
void newConnection(qintptr socket);

protected:
void incomingConnection(qt_socket_fd_t socket) override; // QTcpServer
void incomingConnection(qintptr socket) override; // QTcpServer

private:
PoolServerType m_serverType;
Expand Down Expand Up @@ -113,7 +112,7 @@ class MBASE_PUBLIC ServerPool : public QObject

protected slots:
virtual void newUdpDatagram(void);
virtual void newTcpConnection(qt_socket_fd_t socket);
virtual void newTcpConnection(qintptr socket);

private:
static void SelectDefaultListen(bool force=false);
Expand Down
1 change: 1 addition & 0 deletions mythtv/libs/libmythbase/test/test_unzip/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test_unzip
49 changes: 49 additions & 0 deletions mythtv/libs/libmythbase/test/test_unzip/data/ipsum_lorem.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis lacinia
sapien tellus, non fringilla turpis efficitur ut. Vivamus in tellus
nec mauris congue ultricies id non nulla. Praesent massa mauris,
viverra in justo sed, malesuada lobortis mauris. Suspendisse vulputate
purus vel consequat tempus. Quisque sed enim sit amet erat placerat
egestas in et dui. Sed non tellus ac arcu consectetur finibus. Proin
suscipit, felis ut consectetur ullamcorper, nisl nunc ullamcorper
elit, eget porttitor neque ipsum eu lacus. Sed dapibus diam purus,
sollicitudin ornare libero feugiat nec. Donec id enim sit amet libero
pharetra fringilla. Nulla et neque nec ex posuere sodales. Vivamus
porttitor ullamcorper porttitor. Fusce et nunc id justo interdum
laoreet.

Integer in nisl quis arcu sollicitudin tristique a et leo. Suspendisse
posuere varius sodales. Nunc rutrum diam nec est accumsan, vitae
pellentesque ex auctor. Quisque at mauris scelerisque, tempus mauris
vel, mollis ante. Pellentesque quis malesuada tellus. Etiam non nisl
mauris. Morbi quis venenatis diam.

Donec tempor lobortis turpis, ut venenatis massa viverra vel. Class
aptent taciti sociosqu ad litora torquent per conubia nostra, per
inceptos himenaeos. Vivamus vel dui tempus, pretium ligula non,
sagittis neque. Aenean ultricies malesuada enim, id elementum odio
placerat nec. Nam ut aliquet libero. Praesent quis enim tempor,
lacinia tellus id, finibus sapien. In elit sem, vehicula hendrerit
urna et, finibus ultrices risus. Sed quis felis a sem ultrices porta
vel ac tortor. Nulla a aliquet diam. Curabitur iaculis maximus
nulla. Morbi ut turpis neque.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sagittis
lectus quis sem accumsan posuere. Pellentesque nec viverra risus, ac
suscipit odio. Proin eget condimentum eros. In nunc erat, viverra eget
quam ut, luctus placerat eros. Donec et lacus tristique, varius libero
vitae, viverra massa. Donec a scelerisque orci, sit amet accumsan
erat. Mauris sollicitudin ligula vitae mi congue aliquam. Cras vitae
sagittis nulla, tempus elementum velit. Maecenas eros velit, imperdiet
sit amet ante sit amet, bibendum consectetur arcu.

Phasellus et arcu quis ante auctor tempor. Vestibulum quam nibh,
hendrerit eget consectetur sit amet, pharetra et justo. Quisque nulla
tortor, convallis ac dignissim id, aliquet nec metus. Maecenas dictum
ligula quis ligula efficitur feugiat nec vitae ligula. Nulla a
elementum velit, non efficitur magna. Vivamus euismod interdum
pulvinar. Donec eu enim commodo dui tincidunt porttitor. Etiam viverra
consequat nunc, sed mattis libero gravida eu. Aenean vitae ullamcorper
mi, ut imperdiet enim. Proin vehicula nibh quis libero ullamcorper
laoreet quis nec lacus. Nam nunc lorem, iaculis quis gravida
fringilla, ultrices consequat justo. Aliquam tempor ipsum non ultrices
ultrices.
12 changes: 12 additions & 0 deletions mythtv/libs/libmythbase/test/test_unzip/data/ipsum_lorem_p1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis lacinia
sapien tellus, non fringilla turpis efficitur ut. Vivamus in tellus
nec mauris congue ultricies id non nulla. Praesent massa mauris,
viverra in justo sed, malesuada lobortis mauris. Suspendisse vulputate
purus vel consequat tempus. Quisque sed enim sit amet erat placerat
egestas in et dui. Sed non tellus ac arcu consectetur finibus. Proin
suscipit, felis ut consectetur ullamcorper, nisl nunc ullamcorper
elit, eget porttitor neque ipsum eu lacus. Sed dapibus diam purus,
sollicitudin ornare libero feugiat nec. Donec id enim sit amet libero
pharetra fringilla. Nulla et neque nec ex posuere sodales. Vivamus
porttitor ullamcorper porttitor. Fusce et nunc id justo interdum
laoreet.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Integer in nisl quis arcu sollicitudin tristique a et leo. Suspendisse
posuere varius sodales. Nunc rutrum diam nec est accumsan, vitae
pellentesque ex auctor. Quisque at mauris scelerisque, tempus mauris
vel, mollis ante. Pellentesque quis malesuada tellus. Etiam non nisl
mauris. Morbi quis venenatis diam.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Donec tempor lobortis turpis, ut venenatis massa viverra vel. Class
aptent taciti sociosqu ad litora torquent per conubia nostra, per
inceptos himenaeos. Vivamus vel dui tempus, pretium ligula non,
sagittis neque. Aenean ultricies malesuada enim, id elementum odio
placerat nec. Nam ut aliquet libero. Praesent quis enim tempor,
lacinia tellus id, finibus sapien. In elit sem, vehicula hendrerit
urna et, finibus ultrices risus. Sed quis felis a sem ultrices porta
vel ac tortor. Nulla a aliquet diam. Curabitur iaculis maximus
nulla. Morbi ut turpis neque.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sagittis
lectus quis sem accumsan posuere. Pellentesque nec viverra risus, ac
suscipit odio. Proin eget condimentum eros. In nunc erat, viverra eget
quam ut, luctus placerat eros. Donec et lacus tristique, varius libero
vitae, viverra massa. Donec a scelerisque orci, sit amet accumsan
erat. Mauris sollicitudin ligula vitae mi congue aliquam. Cras vitae
sagittis nulla, tempus elementum velit. Maecenas eros velit, imperdiet
sit amet ante sit amet, bibendum consectetur arcu.
11 changes: 11 additions & 0 deletions mythtv/libs/libmythbase/test/test_unzip/data/ipsum_lorem_p5.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Phasellus et arcu quis ante auctor tempor. Vestibulum quam nibh,
hendrerit eget consectetur sit amet, pharetra et justo. Quisque nulla
tortor, convallis ac dignissim id, aliquet nec metus. Maecenas dictum
ligula quis ligula efficitur feugiat nec vitae ligula. Nulla a
elementum velit, non efficitur magna. Vivamus euismod interdum
pulvinar. Donec eu enim commodo dui tincidunt porttitor. Etiam viverra
consequat nunc, sed mattis libero gravida eu. Aenean vitae ullamcorper
mi, ut imperdiet enim. Proin vehicula nibh quis libero ullamcorper
laoreet quis nec lacus. Nam nunc lorem, iaculis quis gravida
fringilla, ultrices consequat justo. Aliquam tempor ipsum non ultrices
ultrices.
37 changes: 37 additions & 0 deletions mythtv/libs/libmythbase/test/test_unzip/data/willi_themeinfo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mythuitheme SYSTEM "http://www.mythtv.org/schema/mythuitheme.dtd">
<themeinfo>
<name>Willi</name>
<aspect>16:9</aspect>
<author>
<name>Elkin Fricke</name>
<email>managementboy@gmail.com</email>
</author>
<!-- The Theme Types (Required) -->
<types>
<!-- Type. Legal Values are one or more of: UI, OSD and Menu -->
<type>UI</type>
<type>OSD</type>
</types>
<baseres>1920x1080</baseres>
<version>
<major>2</major>
<minor>28</minor>
</version>
<detail>
<thumbnail name="preview">preview.jpg</thumbnail>
<description>A UI and OSD theme with focus on fanart, banners and cover display.</description>
<!-- What is not available -->
<errata>Supported: Recording/ Scheduling/ Video/ Gallery/ Music/ Stream/ Information/ Notification. Not yet suported: everything else.</errata>
<downloadurl>http://ftp.osuosl.org/pub/mythtv/themes/trunk/Willi-2.28_trunk.zip</downloadurl>
</detail>
<downloadinfo>
<url>http://ftp.osuosl.org/pub/mythtv/themes/trunk/Willi-2.28_trunk.zip</url>
<size>5252813</size>
<md5sum>dcfd19d5763b6e833781c19a2e0bb617</md5sum>
<packagedate>2021-04-28 16:18:58 UTC</packagedate>
<infourl>http://themes.mythtv.org/themes/repository/trunk/Willi/themeinfo.xml</infourl>
<sourceurl>git://github.com/MythTV-Themes/Willi.git</sourceurl>
<installedsize>9612919</installedsize>
</downloadinfo>
</themeinfo>
1 change: 1 addition & 0 deletions mythtv/libs/libmythbase/test/test_unzip/im_a_symlink
99 changes: 99 additions & 0 deletions mythtv/libs/libmythbase/test/test_unzip/test_unzip.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Class TestUnzip
*
* Copyright (c) David Hampton 2021
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <iostream>
#include "mythcoreutil.h"
#include "test_unzip.h"
#include <QTemporaryDir>

QTemporaryDir *gTmpDir {nullptr};

void TestUnzip::initTestCase()
{
QDir::setCurrent("libmythbase/test/test_unzip");

gTmpDir = new QTemporaryDir();
QVERIFY(gTmpDir != nullptr);
gTmpDir->setAutoRemove(true);
}

// After each test case
void TestUnzip::cleanup()
{
QDir dir { gTmpDir->path() };
dir.removeRecursively();
dir.mkpath(dir.absolutePath());
}

// After all test cases
void TestUnzip::cleanupTestCase()
{
delete gTmpDir;
gTmpDir = nullptr;
}

void TestUnzip::test_text_file(void)
{
QString filename { "zipfiles/ipsum_lorem.zip" };
bool result = extractZIP(filename, gTmpDir->path());
QCOMPARE(result, true);

auto fi = QFileInfo(gTmpDir->path() + "/ipsum_lorem_p1.txt");
QCOMPARE(fi.exists(), true);
QCOMPARE(fi.size(), 755);
#if QT_VERSION >= QT_VERSION_CHECK(5,10,0)
auto actualDateTime = QDateTime(QDate(2021,6,24),QTime(9,55,16));
QCOMPARE(fi.lastModified(), actualDateTime);
#endif

auto orig = QFile("data/ipsum_lorem_p1.txt");
orig.open(QIODevice::ReadOnly);
auto origData = orig.readAll();

auto unzipped = QFile(gTmpDir->path() + "/ipsum_lorem_p1.txt");
unzipped.open(QIODevice::ReadOnly);
auto unzippedData = unzipped.readAll();
QCOMPARE(origData, unzippedData);
}

void TestUnzip::test_theme_file(void)
{
QString filename { "zipfiles/themes.zip" };
bool result = extractZIP(filename, gTmpDir->path());
QCOMPARE(result, true);

auto fi = QFileInfo(gTmpDir->path() + "/trunk/Willi/themeinfo.xml");
QCOMPARE(fi.exists(), true);
QCOMPARE(fi.size(), 1461);
#if QT_VERSION >= QT_VERSION_CHECK(5,10,0)
auto actualDateTime = QDateTime(QDate(2013,7,14),QTime(16,00,56));
QCOMPARE(fi.lastModified(), actualDateTime);
#endif

auto orig = QFile("data/willi_themeinfo.xml");
orig.open(QIODevice::ReadOnly);
auto origData = orig.readAll();

auto unzipped = QFile(gTmpDir->path() + "/trunk/Willi/themeinfo.xml");
unzipped.open(QIODevice::ReadOnly);
auto unzippedData = unzipped.readAll();
QCOMPARE(origData, unzippedData);
}

QTEST_APPLESS_MAIN(TestUnzip)
35 changes: 35 additions & 0 deletions mythtv/libs/libmythbase/test/test_unzip/test_unzip.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Class TestUnzip
*
* Copyright (c) David Hampton 2021
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <QtTest/QtTest>
#include <iostream>

class TestUnzip : public QObject
{
Q_OBJECT

private slots:
static void initTestCase(void);
static void cleanup(void);
static void cleanupTestCase(void);

static void test_text_file(void);
static void test_theme_file(void);
};
20 changes: 20 additions & 0 deletions mythtv/libs/libmythbase/test/test_unzip/test_unzip.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
include ( ../../../../settings.pro )
include ( ../../../../test.pro )

QT += testlib

TEMPLATE = app
TARGET = test_unzip
DEPENDPATH += . ../.. ../../logging
INCLUDEPATH += . ../.. ../../logging
LIBS += -L../.. -lmythbase-$$LIBVERSION
LIBS += -Wl,$$_RPATH_$${PWD}/../..

# Input
HEADERS += test_unzip.h
SOURCES += test_unzip.cpp

QMAKE_CLEAN += $(TARGET)
QMAKE_CLEAN += ; ( cd $(OBJECTS_DIR) && rm -f *.gcov *.gcda *.gcno )

LIBS += $$EXTRA_LIBS $$LATE_LIBS
Binary file not shown.
Binary file not shown.
1,382 changes: 0 additions & 1,382 deletions mythtv/libs/libmythbase/unzip.cpp

This file was deleted.

148 changes: 0 additions & 148 deletions mythtv/libs/libmythbase/unzip.h

This file was deleted.

313 changes: 313 additions & 0 deletions mythtv/libs/libmythbase/unzip2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
/*
* Class UnZip
*
* Copyright (c) David Hampton 2021
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "unzip2.h"

// libmythbase headers
#include "mythdate.h"
#include "mythlogging.h"

UnZip::UnZip(QString &zipFileName)
: m_zipFileName(std::move(zipFileName))
{
int err { ZIP_ER_OK };
m_zip = zip_open(qPrintable(m_zipFileName), 0, &err);
if (m_zip != nullptr)
return;

LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Unable to open zip file %1, error %2")
.arg(m_zipFileName).arg(err));
}

UnZip::~UnZip()
{
int err = zip_close(m_zip);
if (err == 0)
return;

LOG(VB_GENERAL, LOG_DEBUG,
QString("UnZip: Error closing zip file %1, error %2")
.arg(m_zipFileName).arg(err));
}

bool UnZip::getEntryStats(zipEntry& entry)
{
zip_stat_init(&entry.m_stats);
if (-1 == zip_stat_index(m_zip, entry.m_index, 0, &entry.m_stats))
{
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Can't get info for index %1 in %2")
.arg(entry.m_index).arg(m_zipFileName));
return false;
}
if ((entry.m_stats.valid & kSTATS_REQUIRED) != kSTATS_REQUIRED)
{
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Invalid status for index %1 in %2")
.arg(entry.m_index).arg(m_zipFileName));
return false;
}
return true;
}

void UnZip::getEntryAttrs(zipEntry& entry)
{
zip_uint8_t opsys {ZIP_OPSYS_UNIX};// NOLINT(readability-uppercase-literal-suffix)

entry.m_attributes = 0;
zip_file_get_external_attributes(m_zip, entry.m_index, 0, &opsys,
&entry.m_attributes);
}

bool UnZip::zipCreateDirectory(const zipEntry& entry)
{
QDir dir = entry.m_fi.absoluteDir();
if (dir.exists())
return true;
if (dir.mkpath(dir.absolutePath()))
return true;
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Failed to create directory %1")
.arg(dir.absolutePath()));
return false;
}

// Validate that the filename is beneath the extraction directrory.
// This prevents a zip from overwriting arbitrary files by using names
// with a sequence of ".." directories.
bool UnZip::zipValidateFilename(const QFileInfo& fi)
{
if (fi.absoluteFilePath().startsWith(m_outDir.absolutePath()))
return true;
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Attempt to write outside destination directory. File: %1")
.arg(QString(fi.fileName())));
return false;
}

// Would be nice if Qt provided a unix perm to Qt perm conversion.
QFileDevice::Permissions UnZip::zipToQtPerms(const zipEntry& entry)
{
QFileDevice::Permissions qt_perms;
zip_uint32_t attrs = entry.m_attributes;

int32_t user = (attrs & ZIP_ATTR_USER_PERM_MASK) >> ZIP_ATTR_USER_PERM_SHIFT;
int32_t group = (attrs & ZIP_ATTR_GROUP_PERM_MASK) >> ZIP_ATTR_GROUP_PERM_SHIFT;
int32_t other = (attrs & ZIP_ATTR_OTHER_PERM_MASK) >> ZIP_ATTR_OTHER_PERM_SHIFT;

if (user & 4)
qt_perms |= (QFileDevice::ReadOwner | QFileDevice::ReadUser);
if (user & 2)
qt_perms |= (QFileDevice::WriteOwner | QFileDevice::WriteUser);
if (user & 1)
qt_perms |= (QFileDevice::ExeOwner | QFileDevice::ExeUser);
if (group & 4)
qt_perms |= QFileDevice::ReadGroup;
if (group & 2)
qt_perms |= QFileDevice::WriteGroup;
if (group & 1)
qt_perms |= QFileDevice::ExeGroup;
if (other & 4)
qt_perms |= QFileDevice::ReadOther;
if (other & 2)
qt_perms |= QFileDevice::WriteOther;
if (other & 1)
qt_perms |= QFileDevice::ExeOther;
return qt_perms;
}

void UnZip::zipSetFileAttributes(const zipEntry& entry, QFile& outfile)
{
#if QT_VERSION >= QT_VERSION_CHECK(5,10,0)
// Set times
auto dateTime = MythDate::fromSecsSinceEpoch(entry.m_stats.mtime);

outfile.setFileTime(dateTime, QFileDevice::FileAccessTime);
outfile.setFileTime(dateTime, QFileDevice::FileBirthTime);
outfile.setFileTime(dateTime, QFileDevice::FileMetadataChangeTime);
outfile.setFileTime(dateTime, QFileDevice::FileModificationTime);
#endif

if (entry.m_attributes == 0)
return;
outfile.setPermissions(zipToQtPerms(entry));
}

bool UnZip::zipCreateSymlink(const zipEntry& entry)
{
zip_file_t *infile = zip_fopen_index(m_zip, entry.m_index, 0);
if (infile == nullptr)
{
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Can't open index %1 in %2")
.arg(entry.m_index).arg(m_zipFileName));
return false;
}

int64_t readLen {0};
static constexpr int BLOCK_SIZE { 4096 };
QByteArray data; data.resize(BLOCK_SIZE);
readLen = zip_fread(infile, data.data(), BLOCK_SIZE);
if (readLen < 1)
{
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Invalid symlink name for index %1 in %2")
.arg(entry.m_index).arg(m_zipFileName));
return false;
}
data.resize(readLen);

auto target = QFileInfo(entry.m_fi.absolutePath() + "/" + data);
if (!zipValidateFilename(target))
return false;
if (!QFile::link(target.absoluteFilePath(), entry.m_fi.absoluteFilePath()))
{
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Failed to create symlink from %1 to %2")
.arg(entry.m_fi.absoluteFilePath(),
target.absoluteFilePath()));
return false;
}
return true;
}

bool UnZip::zipWriteOneFile(const zipEntry& entry)
{
zip_file_t *infile = zip_fopen_index(m_zip, entry.m_index, 0);
if (infile == nullptr)
{
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Can't open file at index %1 in %2")
.arg(entry.m_index).arg(m_zipFileName));
return false;
}

auto outfile = QFile(entry.m_fi.absoluteFilePath());
if (!outfile.open(QIODevice::Truncate|QIODevice::WriteOnly))
{
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Failed to open output file %1")
.arg(entry.m_fi.absoluteFilePath()));
return false;
}

int64_t readLen {0};
uint64_t bytesRead {0};
uint64_t bytesWritten {0};
static constexpr int BLOCK_SIZE { 4096 };
QByteArray data; data.resize(BLOCK_SIZE);
while ((readLen = zip_fread(infile, data.data(), BLOCK_SIZE)) > 0)
{
bytesRead += readLen;
int64_t writeLen = outfile.write(data.data(), readLen);
if (writeLen < 0)
{
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Failed to write %1/%2 bytes to output file %3")
.arg(writeLen).arg(readLen).arg(entry.m_fi.absoluteFilePath()));
return false;
}
bytesWritten += writeLen;
}

if ((entry.m_stats.size != bytesRead) ||
(entry.m_stats.size != bytesWritten))
{
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Failed to copy file %1. Read %2 and wrote %3 of %4.")
.arg(entry.m_fi.fileName()).arg(bytesRead).arg(bytesWritten)
.arg(entry.m_stats.size));
return false;
}

outfile.flush();
zipSetFileAttributes(entry, outfile);
outfile.close();
if (zip_fclose(infile) == -1)
{
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Failed to close file at index %1 in %2")
.arg(entry.m_index).arg(entry.m_fi.fileName()));
return false;
}

return true;
}

bool UnZip::extractFile(const QString &outDirName)
{
if (!isValid())
return false;

m_outDir = QDir(outDirName);
if (!m_outDir.exists())
{
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Target directory %1 doesn't exist")
.arg(outDirName));
return false;
}

m_zipFileCount = zip_get_num_entries(m_zip, 0);
if (m_zipFileCount < 1)
{
LOG(VB_GENERAL, LOG_ERR,
QString("UnZip: Zip archive %1 is empty")
.arg(m_zipFileName));
return false;
}

bool ok { true };
for (auto index = 0; ok && (index < m_zipFileCount); index++)
{
zipEntry entry;
entry.m_index = index;
if (!getEntryStats(entry))
return false;
if (entry.m_stats.encryption_method > 0)
{
LOG(VB_GENERAL, LOG_WARNING,
QString("UnZip: Skipping encryped file %1 in %2")
.arg(entry.m_index).arg(m_zipFileName));
continue;
}
getEntryAttrs(entry);

entry.m_fi = QFileInfo(outDirName + '/' + entry.m_stats.name);
ok = zipValidateFilename(entry.m_fi);
if (ok)
ok = zipCreateDirectory(entry);
if (ok && (entry.m_stats.size > 0))
{
switch (entry.m_attributes & ZIP_ATTR_FILE_TYPE_MASK)
{
case ZIP_ATTR_FILE_TYPE_SYMLINK:
ok = zipCreateSymlink(entry);
break;
default:
ok = zipWriteOneFile(entry);
break;
}
}
}

return ok;
}
78 changes: 78 additions & 0 deletions mythtv/libs/libmythbase/unzip2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Class UnZip
*
* Copyright (c) David Hampton 2021
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "zlib.h"
#undef Z_NULL
#define Z_NULL nullptr
#include "zip.h"

// Qt headers
#include <QDir>
#include <QString>
#include <QFileInfo>

class zipEntry;
class UnZip
{
// NOLINTNEXTLINE(readability-uppercase-literal-suffix)
static constexpr uint64_t kSTATS_REQUIRED {ZIP_STAT_NAME|ZIP_STAT_INDEX|ZIP_STAT_SIZE|ZIP_STAT_MTIME|ZIP_STAT_ENCRYPTION_METHOD};

#define ZIP_ATTR_FILE_TYPE_MASK 0xFE000000
#define ZIP_ATTR_FILE_TYPE_SYMLINK 0xA0000000
#define ZIP_ATTR_FILE_TYPE_NORMAL 0x80000000
#define ZIP_ATTR_USER_PERM_MASK 0x01C00000
#define ZIP_ATTR_GROUP_PERM_MASK 0x03800000
#define ZIP_ATTR_OTHER_PERM_MASK 0x00700000
#define ZIP_ATTR_USER_PERM_SHIFT 22
#define ZIP_ATTR_GROUP_PERM_SHIFT 19
#define ZIP_ATTR_OTHER_PERM_SHIFT 16

public:
UnZip(QString &zipFile);
~UnZip();
bool extractFile(const QString &outDir);

private:
bool isValid() { return m_zip != nullptr; };
bool getEntryStats(zipEntry& entry);
void getEntryAttrs(zipEntry& entry);
static QFileDevice::Permissions zipToQtPerms(const zipEntry& entry);
static bool zipCreateDirectory(const zipEntry& entry);
bool zipValidateFilename(const QFileInfo& fi);
static void zipSetFileAttributes(const zipEntry& entry, QFile& outfile);
bool zipCreateSymlink(const zipEntry& entry);
bool zipWriteOneFile(const zipEntry& entry);

QDir m_outDir;

// Info about zip file itself
QString m_zipFileName;
zip_t *m_zip {nullptr};
zip_int64_t m_zipFileCount {-1};
};

class zipEntry
{
friend class UnZip;
int m_index {0};
zip_stat_t m_stats {};
zip_uint32_t m_attributes {0};
QFileInfo m_fi;
};
Loading