#!/usr/bin/python3

import sys
import os
import fcntl
import socket
import json
import datetime
import traceback
import logging
import locale
import subprocess
import threading
from operator import itemgetter
from logging import getLogger
from GRMConst import MySQL_URL

PAGE_MAX = 9

locale.setlocale(locale.LC_ALL, 'ja_JP')

logger = getLogger(__name__)
logger.setLevel(logging.DEBUG)

def debug(*args):
  print(*args,flush=True)

def error(*args):
  print(*args,flush=True)

def key_name(item):
  prefix = { True: '0', False: '1' }
  name = itemgetter("name")(item)
  return prefix[item["isdir"]] + conv_name(name)

def size_cmp(a,b):
  if a["isdir"] and b["isdir"]:
    ka = conv_name(a["name"])
    kb = conv_name(b["name"])
    return (ka>kb) - (ka<kb)
  if a["isdir"]:
    return -1
  if b["isdir"]:
    return  1
  sa = a["size"]
  sb = b["size"]
  return (sa>sb) - (sa<sb)

class key_size:
  def __init__(self, obj, *args):
    self.obj = obj
  def __lt__(self, other):
    return size_cmp(self.obj, other.obj)<0
  def __gt__(self, other):
    return size_cmp(self.obj, other.obj)>0
  def __eq__(self, other):
    return size_cmp(self.obj, other.obj)==0
  def __le__(self, other):
    return size_cmp(self.obj, other.obj)<=0
  def __ge__(self, other):
    return size_cmp(self.obj, other.obj)>=0
  def __ne__(self, other):
    return size_cmp(self.obj, other.obj)!=0

sortparams = {
  "file_u" : { "key" : key_name, "reverse" : True },
  "file_d" : { "key" : key_name, "reverse" : False },
  "size_u" : { "key" : key_size, "reverse" : True },
  "size_d" : { "key" : key_size, "reverse" : False },
  "time_u" : { "key" : itemgetter("time"), "reverse" : True },
  "time_d" : { "key" : itemgetter("time"), "reverse" : False }
}

KB = 1024.0
MB = (1024.0*1024.0)
GB = (1024.0*1024.0*1024.0)

def conv_size(size):
  if size == "":
    return ""
  if size >= GB:
    return "{0:.2f}GB".format(size/GB)
  if size >= MB:
    return "{0:.2f}MB".format(size/MB)
  if size >= KB:
    return "{0:.2f}KB".format(size/KB)
  return "{0:n}B".format(size)

def conv_time(tzoff):
  def conv(time):
    if time == '':
      return ''
    t = datetime.datetime.fromtimestamp(time-tzoff*60)
    return t.strftime('%Y/%m/%d %H:%M:%S')
  return conv

def conv(tzoff):
  time_func = conv_time(tzoff)
  def conv_item(item):
    return {
      "name" : item["name"],
      "isdir" : item["isdir"],
      "size" : conv_size(item["size"]),
      "time" : time_func(item["time"]),
      "inode" : item["inode"] if "inode" in item else 0
    }
  return conv_item

def conv_name(name):
  dec = os.fsdecode(name)
  return dec.encode('utf-8','replace').decode('utf-8')

ls_params = [ 'file_number', 'use_data', 'use_data_disp', 'cap_per', 'pathlist' ]

def merge(ret, req, params):
  for param in params:
    if param in req:
      ret[param] = req[param]
  return ret

def ls(js):
  top = js['top'] 
  dir = js['dir']
  off = js['off']
  lim = js['lim']
  srt = js['srt']
  tzoff  = js['tzoff']
  lst = []
  temp = os.path.abspath(top + dir)
  temp = temp.replace(os.path.abspath(top), '')
  if temp != '':
    temp += '/'
  #debug('top=', top, ' dir=', dir, ' temp=', temp)
  with os.scandir(top + temp) as it:
    for entry in it:
      name = conv_name(entry.name)
      if name == ".ftpquota":
        continue
      item = {
        'inode':  entry.inode(),
        'name':   name,
        'isdir':  entry.is_dir(),
        'size':   '' if entry.is_dir() else entry.stat().st_size,
        'time':   entry.stat().st_mtime
      }
      lst.append(item)

  if srt in sortparams:
    sortparam = sortparams[srt]
  else:
    sortparam = {}
  files = sorted(lst, **sortparam)

  fileCount = len(files)
  last = int((fileCount-1)/lim) + 1
  curr = int(off/lim) + 1
  if last <= PAGE_MAX:
    pages = list(range(1,last+1))
  else:
    l = int((PAGE_MAX-1)/2)
    r = PAGE_MAX-1-l
    if curr - l <= 1:
      pages = list(range(1,PAGE_MAX-1))
      pages.extend([ '...', last ])
    elif curr + r >= last:
      pages = [ 1, '...' ]
      pages.extend(list(range(last-PAGE_MAX+2,last+1)))
    else:
      pages = [ 1, '...' ]
      pages.extend(list(range(curr - l + 1, curr + r)))
      pages.extend([ '...', last ])
  outlist = []
  if temp != '':
    outlist.append({
      'name': '..',
      'isdir': True,
      'size': '',
      'time': ''
    })
  outlist.extend(list(map(conv(tzoff),files[off:off+lim])))
  result = {
    "currentDir" : temp,
    "fileCount" : fileCount,
    "currentPage" : curr,
    "lastPage" : last,
    "pages" : pages,
    "files" : outlist
  }
  return merge(result, js, ls_params)

def rm(js):
  top = js['top']
  dir = js['dir']
  files = js['files']
  temp = os.path.abspath(top + dir)
  temp = temp.replace(os.path.abspath(top), '')
  if temp != '':
    temp += '/'
  #debug('top=', top, ' dir=', dir, ' temp=', temp)
  path = top + temp
  inodes = list(map(itemgetter("inode"),files))
  fcnt = 0
  fsiz = 0
  with os.scandir(path) as it:
    for entry in it:
      if entry.inode() in inodes:
        if entry.is_dir():
          ent = path + '/' + entry.name
          if '\'' in ent:
            cmd = "/var/www/html/bin/deldir.sh \"{0}\"".format(ent)
          else:
            cmd = "/var/www/html/bin/deldir.sh '{0}'".format(ent)
          res = subprocess.check_output(cmd,shell=True,encoding='utf-8')
          val = str(res).strip().split(' ')
          fcnt += int(val[0])
          fsiz += int(val[1])
        else:
          fsiz += entry.stat().st_size
          fcnt += 1
          os.remove(path + '/' + entry.name)
  update_quota(top, -fcnt, -fsiz)
  return { "status" : "success" }

def rmchk(js):
  top = js['top']
  dir = js['dir']
  files = js['files']
  temp = os.path.abspath(top + dir)
  temp = temp.replace(os.path.abspath(top), '')
  if temp != '':
    temp += '/'
  #debug('top=', top, ' dir=', dir, ' temp=', temp)
  path = top + temp
  inodes = list(map(itemgetter("inode"),files))
  dcnt = 0
  fcnt = 0
  fsiz = 0
  with os.scandir(path) as it:
    for entry in it:
      if entry.inode() in inodes:
        if entry.is_dir():
          ent = path + '/' + entry.name
          if '\'' in ent:
            cmd = "/var/www/html/bin/chkdir.sh \"{0}\"".format(ent)
          else:
            cmd = "/var/www/html/bin/chkdir.sh '{0}'".format(ent)
          res = subprocess.check_output(cmd,shell=True,encoding='utf-8')
          val = str(res).strip().split(' ')
          fcnt += int(val[0])
          fsiz += int(val[1])
          dcnt += int(val[2])
        else:
          fsiz += entry.stat().st_size
          fcnt += 1
  return { "dcnt" : dcnt,  "fcnt" : fcnt, "fsiz" : fsiz }

def mkdir(js):
  top = js['top']
  dir = js['dir']
  newdir = js['folder']
  map = {}
  with os.scandir(top+dir) as it:
    for entry in it:
      map[entry.name.lower()] = entry.name
  if newdir.lower() in map:
    newdir = map[newdir.lower()]
  path = top + dir + '/' + newdir
  if not os.path.exists(path):
    os.mkdir(path)
    update_quota(top, 1, 0)
  os.chown(path,1122,1122)
  os.chmod(path, 0o755)
  return { "status" : "success" }

def getinfo(js):
  top = js['top']
  cmd = "cd {0}; /var/www/html/bin/update_quota.sh".format(top)
  res = subprocess.check_output(cmd,shell=True,encoding='utf-8')
  val = str(res).strip().split(' ')
  return { "use_data" : val[1], "file" : val[0] }

def update_quota(top, dcnt, dsiz):
  filename = top + '/.ftpquota'
  with open(filename, 'r+') as f:
    try:
      fcntl.flock(f, fcntl.LOCK_EX)
      data = f.readline()
      f.truncate(0)
      f.seek(os.SEEK_SET)
      val = str(data).strip().split(' ')
      dcnt += int(val[0])
      dsiz += int(val[1])
      line = "{0:d} {1:d}\n".format(dcnt,dsiz)
      f.write(line)

    except:
      error(traceback.format_exc())

def getname(js):
  top = js['top']
  dir = js['dir']
  inode = int(js['inode'])
  temp = os.path.abspath(top + dir)
  temp = temp.replace(os.path.abspath(top), '')
  path = ''
  #debug('top=', top, ' dir=', dir, ' temp=', temp)
  with os.scandir(top + temp) as it:
    for entry in it:
      if entry.inode() == inode:
        path = top + temp + '/' + conv_name(entry.name)
        break
  return { 'path' : path }

cmdlist = {
  "ls" : ls,
  "rm" : rm,
  "rmchk" : rmchk,
  "mkdir" : mkdir,
  "info" : getinfo,
  "name" : getname
}

def accepted(conn):
  leng = conn.recv(12).decode('utf-8').strip()
  params = conn.recv(int(leng))
  js  = json.loads(params)
  #debug('params: ', js)
  cmd = js['cmd']
  if cmd in cmdlist:
    try:
      if 'locale' in js:
        loc = js['locale']
      else:
        loc = 'ja_JP'
      locale.setlocale(locale.LC_ALL, loc)
      result = cmdlist[cmd](js)
    except:
      error(traceback.format_exc())
      result = { "error" : str(sys.exc_info()[1]) }
  else:
    result = {}
  #debug('result:', result)
  conn.send(json.dumps(result).encode('utf-8'))

def server():
  SOCKET_PATH='/var/www/getfiles.sock'
  if os.path.exists(SOCKET_PATH):
    os.unlink(SOCKET_PATH)

  s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  os.umask(0)
  s.bind(SOCKET_PATH)
  s.listen(1)
  while True:
    conn, addr = s.accept()

    def proc():
      try:
        accepted(conn)
      except:
        error(traceback.format_exc())
      conn.close()

    t = threading.Thread(target=proc)
    t.setDaemon(True)
    t.start()

def main():
  if len(sys.argv) == 1:
    server()
    return

  def err(req):
    return {}

  try:
    param = bytearray.fromhex(sys.argv[1]).decode('utf-8')
    req = json.loads(param)
    loc = req['locale'] if 'locale' in req else 'ja_JP'
    locale.setlocale(locale.LC_ALL, loc)
    proc = cmdlist[req['cmd']] if req['cmd'] in cmdlist else err
    json.dump(proc(req), sys.stdout)
  except:
    error(traceback.format_exc())

main()
