# -*- coding: utf-8 -*-
"""萌萌的个人网盘 - 局域网文件共享"""

import os, shutil, mimetypes, html
from datetime import datetime
from pathlib import Path
from flask import Flask, request, render_template_string, send_file, redirect, jsonify

ROOT = r"D:\网盘"
PORT = 5000
os.makedirs(ROOT, exist_ok=True)

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 * 1024  # 1GB 上传限制

TEMPLATE = r'''<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>萌萌的个人网盘 💙</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Segoe UI','Microsoft YaHei',sans-serif;background:#1a1a2e;color:#eee;min-height:100vh}
.header{background:linear-gradient(135deg,#667eea,#764ba2);padding:20px 30px;display:flex;align-items:center;gap:15px;box-shadow:0 4px 20px rgba(102,126,234,.3)}
.header h1{font-size:1.6em;flex:1}
.header .icon{font-size:2em}
.path-bar{background:#16213e;padding:12px 30px;display:flex;align-items:center;gap:8px;font-size:.9em;overflow-x:auto;white-space:nowrap}
.path-bar a{color:#667eea;text-decoration:none}
.path-bar a:hover{text-decoration:underline}
.path-bar .sep{color:#555}
.container{padding:20px 30px}
.toolbar{display:flex;gap:12px;margin-bottom:20px;flex-wrap:wrap}
.upload-zone{border:2px dashed #333;border-radius:12px;padding:30px;text-align:center;cursor:pointer;transition:.3s;margin-bottom:20px}
.upload-zone:hover,.upload-zone.dragover{border-color:#667eea;background:#16213e}
.upload-zone input{display:none}
.upload-zone .icon-text{font-size:2.5em;margin-bottom:8px}
.file-list{background:#16213e;border-radius:12px;overflow:hidden}
.file-item{display:flex;align-items:center;padding:14px 20px;border-bottom:1px solid #1a1a2e;transition:.2s;gap:12px}
.file-item:hover{background:#1a2744}
.file-item:last-child{border-bottom:none}
.file-icon{font-size:1.4em;width:32px;text-align:center}
.file-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.file-name a{color:#a0c4ff;text-decoration:none;word-break:break-all}
.file-name a:hover{text-decoration:underline;color:#fff}
.file-size{color:#888;font-size:.85em;min-width:80px;text-align:right}
.file-time{color:#666;font-size:.8em;min-width:140px;text-align:right}
.file-actions{display:flex;gap:8px}
.btn{padding:8px 16px;border:none;border-radius:8px;cursor:pointer;font-size:.85em;transition:.2s;text-decoration:none;display:inline-block}
.btn-primary{background:#667eea;color:#fff}
.btn-primary:hover{background:#5a6fd6}
.btn-danger{background:#e74c3c;color:#fff}
.btn-danger:hover{background:#c0392b}
.btn-outline{background:transparent;border:1px solid #444;color:#aaa}
.btn-outline:hover{border-color:#667eea;color:#fff}
.btn-sm{padding:5px 12px;font-size:.75em}
.empty-state{text-align:center;padding:60px 20px;color:#555}
.empty-state .icon{font-size:4em;margin-bottom:15px}
.upload-progress{display:none;height:6px;background:#333;border-radius:3px;margin:10px 0;overflow:hidden}
.upload-progress .bar{height:100%;background:linear-gradient(90deg,#667eea,#764ba2);width:0%;transition:width .3s}
.toast{position:fixed;top:20px;right:20px;background:#333;color:#fff;padding:12px 20px;border-radius:8px;z-index:999;display:none;max-width:400px}
.toast.success{background:#27ae60}
.toast.error{background:#e74c3c}
.toast.show{display:block;animation:fadeInOut 2.5s forwards}
@keyframes fadeInOut{0%{opacity:0;transform:translateX(20px)}10%{opacity:1;transform:translateX(0)}80%{opacity:1}100%{opacity:0}}
@media(max-width:768px){.file-time,.file-size{display:none}.header h1{font-size:1.2em}.container{padding:15px}}
</style>
</head>
<body>
<div class="header">
  <span class="icon">☁️</span>
  <h1>萌萌的个人网盘</h1>
  <span style="font-size:.85em;color:#ccc">J4125 小主机</span>
</div>
<div class="path-bar">
  <a href="/">🏠 根目录</a>
  {% for part in path_parts %}
    <span class="sep">›</span>
    <a href="{{ part.url }}">{{ part.name }}</a>
  {% endfor %}
</div>
<div class="container">
  <div class="toolbar">
    <button class="btn btn-primary" onclick="document.getElementById('file-input').click()">📤 上传文件</button>
    <button class="btn btn-outline" id="new-folder-btn">📁 新建文件夹</button>
    <button class="btn btn-outline" onclick="location.reload()">🔄 刷新</button>
  </div>
  <div class="upload-zone" id="drop-zone">
    <div class="icon-text">📂</div>
    <p>拖拽文件到此处 或 点击上传</p>
    <p style="color:#666;font-size:.8em;margin-top:5px">单个文件最大 1GB</p>
    <input type="file" id="file-input" multiple>
  </div>
  <div class="upload-progress" id="progress-bar"><div class="bar" id="progress-fill"></div></div>
  <div class="file-list">
    {% if files %}
      {% for f in files %}
      <div class="file-item">
        <span class="file-icon">{{ f.icon }}</span>
        <span class="file-name">
          {% if f.is_dir %}
            <a href="{{ f.url }}">{{ f.name }}/</a>
          {% else %}
            <a href="{{ f.url }}" download>{{ f.name }}</a>
          {% endif %}
        </span>
        <span class="file-size">{{ f.size }}</span>
        <span class="file-time">{{ f.time }}</span>
        <span class="file-actions">
          <a href="{{ f.url }}" class="btn btn-outline btn-sm" {% if not f.is_dir %}download{% endif %}>⬇</a>
          <button class="btn btn-danger btn-sm" onclick="deleteItem('{{ f.name|e }}')">🗑</button>
        </span>
      </div>
      {% endfor %}
    {% else %}
      <div class="empty-state">
        <div class="icon">📭</div>
        <p>这里空空如也～</p>
        <p style="color:#555;margin-top:5px">上传一些文件吧！</p>
      </div>
    {% endif %}
  </div>
</div>
<div class="toast" id="toast"></div>
<script>
function formatSize(bytes){
  if(bytes===0)return'0 B';
  const k=1024,sizes=['B','KB','MB','GB'];
  const i=Math.floor(Math.log(bytes)/Math.log(k));
  return parseFloat((bytes/Math.pow(k,i)).toFixed(2))+' '+sizes[i];
}
function toast(msg,type=''){
  const t=document.getElementById('toast');
  t.textContent=msg;t.className='toast '+type+' show';
  setTimeout(()=>t.className='toast',2500);
}
function deleteItem(name){
  if(!confirm('确定删除 "'+name+'" 吗？此操作不可恢复！'))return;
  const path=window.location.pathname.endsWith('/')?window.location.pathname:window.location.pathname+'/';
  fetch('/api/delete?path='+encodeURIComponent(path+name),{method:'DELETE'})
    .then(r=>r.json())
    .then(d=>{if(d.ok)location.reload();else toast(d.error,'error')})
    .catch(()=>toast('删除失败','error'));
}
document.getElementById('new-folder-btn').onclick=function(){
  const name=prompt('请输入文件夹名称：');
  if(!name)return;
  const path=window.location.pathname;
  fetch('/api/mkdir?path='+encodeURIComponent(path)+'&name='+encodeURIComponent(name),{method:'POST'})
    .then(r=>r.json())
    .then(d=>{if(d.ok)location.reload();else toast(d.error,'error')});
};
const dz=document.getElementById('drop-zone');
const fi=document.getElementById('file-input');
dz.onclick=()=>fi.click();
dz.ondragover=e=>{e.preventDefault();dz.classList.add('dragover')};
dz.ondragleave=()=>dz.classList.remove('dragover');
dz.ondrop=e=>{e.preventDefault();dz.classList.remove('dragover');uploadFiles(e.dataTransfer.files)};
fi.onchange=()=>uploadFiles(fi.files);
function uploadFiles(files){
  if(!files.length)return;
  const form=new FormData();
  for(let f of files)form.append('files',f);
  const xhr=new XMLHttpRequest();
  document.getElementById('progress-bar').style.display='block';
  xhr.upload.onprogress=e=>{
    if(e.lengthComputable){
      document.getElementById('progress-fill').style.width=(e.loaded/e.total*100)+'%';
    }
  };
  xhr.onload=()=>{toast('上传成功！','success');location.reload()};
  xhr.onerror=()=>toast('上传失败','error');
  xhr.open('POST','/api/upload?path='+encodeURIComponent(window.location.pathname));
  xhr.send(form);
}
</script>
</body>
</html>'''

def get_rel_path(sub=''):
    sub = sub.replace('\\', '/').strip('/')
    full = os.path.abspath(os.path.join(ROOT, sub))
    if not full.startswith(os.path.abspath(ROOT)):
        return None
    return full

def get_files(rel=''):
    path = get_rel_path(rel)
    if not path or not os.path.isdir(path): return None
    items = []
    try:
        entries = sorted(os.scandir(path), key=lambda e: (not e.is_dir(), e.name.lower()))
    except:
        return None
    for entry in entries:
        is_dir = entry.is_dir()
        stat = entry.stat()
        size = format_size(stat.st_size) if not is_dir else '—'
        t = datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M')
        url = '/' + (rel + '/' + entry.name).strip('/')
        if is_dir: url += '/'
        icon = '📁' if is_dir else get_icon(entry.name)
        items.append({'name': entry.name, 'is_dir': is_dir, 'size': size, 'time': t, 'url': url, 'icon': icon})
    return items

def get_icon(name):
    ext = name.rsplit('.', 1)[-1].lower() if '.' in name else ''
    icons = {'jpg':'🖼','jpeg':'🖼','png':'🖼','gif':'🖼','webp':'🖼','bmp':'🖼','svg':'🖼',
             'mp4':'🎬','mkv':'🎬','avi':'🎬','mov':'🎬','wmv':'🎬',
             'mp3':'🎵','wav':'🎵','flac':'🎵','aac':'🎵','ogg':'🎵',
             'zip':'📦','rar':'📦','7z':'📦','tar':'📦','gz':'📦',
             'pdf':'📄','doc':'📝','docx':'📝','xls':'📊','xlsx':'📊','ppt':'📽','pptx':'📽',
             'txt':'📃','py':'🐍','js':'📜','html':'🌐','css':'🎨','json':'📋','md':'📘',
             'exe':'⚙','dll':'🔧','msi':'💿','iso':'💿','torrent':'🔽'}
    return icons.get(ext, '📄')

def format_size(n):
    if n < 1024: return f'{n} B'
    if n < 1024**2: return f'{n/1024:.1f} KB'
    if n < 1024**3: return f'{n/1024**2:.1f} MB'
    return f'{n/1024**3:.2f} GB'

def build_breadcrumb(rel=''):
    parts = []
    cum = ''
    for p in rel.strip('/').split('/'):
        if not p: continue
        cum += '/' + p
        parts.append({'name': p, 'url': cum + '/'})
    return parts

@app.route('/', defaults={'rel': ''})
@app.route('/<path:rel>')
def browse(rel):
    if rel == '' or rel.endswith('/'):
        files = get_files(rel[:-1] if rel.endswith('/') else rel)
        if files is None: return '目录不存在', 404
        return render_template_string(TEMPLATE, files=files, path_parts=build_breadcrumb(rel))
    else:
        path = get_rel_path(rel)
        if not path or not os.path.isfile(path): return '文件不存在', 404
        return send_file(path, as_attachment=False)

@app.route('/api/upload', methods=['POST'])
def upload():
    target = request.args.get('path', '').strip('/')
    dest = get_rel_path(target)
    if not dest: return jsonify(ok=False, error='路径非法'), 400
    os.makedirs(dest, exist_ok=True)
    uploaded = []
    for f in request.files.getlist('files'):
        if f.filename:
            f.save(os.path.join(dest, f.filename))
            uploaded.append(f.filename)
    return jsonify(ok=True, uploaded=uploaded)

@app.route('/api/mkdir', methods=['POST'])
def mkdir():
    base = request.args.get('path', '').strip('/')
    name = request.args.get('name', '')
    if not name: return jsonify(ok=False, error='名称不能为空'), 400
    base_path = get_rel_path(base)
    if not base_path: return jsonify(ok=False, error='路径非法'), 400
    try:
        os.makedirs(os.path.join(base_path, name), exist_ok=True)
        return jsonify(ok=True)
    except Exception as e:
        return jsonify(ok=False, error=str(e)), 500

@app.route('/api/delete', methods=['DELETE'])
def delete():
    target = request.args.get('path', '').strip('/')
    path = get_rel_path(target)
    if not path or not os.path.exists(path): return jsonify(ok=False, error='路径不存在'), 404
    try:
        if os.path.isdir(path):
            shutil.rmtree(path)
        else:
            os.remove(path)
        return jsonify(ok=True)
    except Exception as e:
        return jsonify(ok=False, error=str(e)), 500

@app.route('/api/disk')
def disk_info():
    usage = shutil.disk_usage(ROOT)
    return jsonify({
        'total': format_size(usage.total),
        'used': format_size(usage.used),
        'free': format_size(usage.free),
        'percent': f'{usage.used/usage.total*100:.1f}%'
    })

if __name__ == '__main__':
    print(f'☁️  萌萌网盘已启动 → http://0.0.0.0:{PORT}')
    print(f'📂 根目录: {ROOT}')
    app.run(host='0.0.0.0', port=PORT, debug=False, threaded=True)
