File size: 10,363 Bytes
9a7a411
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
import os
import sys
import shutil
from pathlib import Path
from huggingface_hub import snapshot_download
import importlib.util

from dotenv import load_dotenv
load_dotenv()

def setup_cache_directory():
    """Create a hidden cache directory '.cache'.

    If an existing 'cache' directory exists, migrate its contents into '.cache'.
    Set restrictive permissions (owner rwx) and on macOS set the Finder hidden flag
    unless the environment variable `HIDE_CACHE` is explicitly set to '0'.
    """
    hidden_cache = Path(".cache")
    public_cache = Path("cache")

    # If the hidden cache doesn't exist but a public one does, move it.
    try:
        if not hidden_cache.exists():
            if public_cache.exists():
                # Prefer move to preserve contents and metadata
                public_cache.rename(hidden_cache)
            else:
                hidden_cache.mkdir(exist_ok=True)
        else:
            # ensure it exists
            hidden_cache.mkdir(exist_ok=True)

        # Restrict permissions to owner only (rwx------)
        try:
            hidden_cache.chmod(0o700)
        except Exception:
            # chmod may fail on some filesystems or platforms; ignore
            pass

        # On macOS, optionally set Finder hidden flag for extra concealment
        if sys.platform == "darwin":
            hide_flag = os.environ.get("HIDE_CACHE", "1")
            if hide_flag != "0":
                try:
                    # 'chflags hidden <path>' will make the folder hidden in Finder
                    os.system(f"/usr/bin/chflags hidden {hidden_cache}")
                except Exception:
                    pass

        return hidden_cache
    except Exception:
        # Fallback: try to create a simple cache folder named 'cache'
        public_cache.mkdir(exist_ok=True)
        return public_cache

def create_symbolic_links(cache_dir):
    """Create symbolic links for Generated, static, and view_session directories.
    
    The strategy is to make sure we have real directories in cache and symbolic links from root to cache.
    This ensures the BytePlus app can access these directories even when running from cache.
    """
    root_dir = Path.cwd()
    directories_to_link = ["Generated", "static", "view_session"]
    
    for dir_name in directories_to_link:
        # Root directory path (should be symlink)
        root_link = root_dir / dir_name
        # Cache directory path (should be real directory)
        cache_target = cache_dir / dir_name
        
        try:
            # Ensure we have a real directory in cache
            if cache_target.is_symlink():
                # Remove the symlink in cache if it exists
                cache_target.unlink()
                print(f"Removed cache symlink: {cache_target}")
            
            if not cache_target.exists():
                cache_target.mkdir(parents=True, exist_ok=True)
                print(f"Created cache directory: {cache_target}")
            
            # Set proper permissions on cache directory (755)
            cache_target.chmod(0o755)
            print(f"Set permissions 755 on: {cache_target}")
            
            # Ensure root has symlink to cache (if it doesn't already exist)
            if not root_link.is_symlink():
                if root_link.exists():
                    # If it's a real directory, we might need to move content or just replace
                    if root_link.is_dir():
                        # Try to move content to cache first
                        try:
                            for item in root_link.iterdir():
                                dest = cache_target / item.name
                                if not dest.exists():
                                    shutil.move(str(item), str(dest))
                            shutil.rmtree(root_link)
                            print(f"Moved content from {root_link} to {cache_target}")
                        except Exception as e:
                            print(f"Could not move content: {e}")
                            shutil.rmtree(root_link)
                    else:
                        root_link.unlink()
                
                # Create the symlink from root to cache
                root_link.symlink_to(cache_target)
                print(f"Created root symlink: {root_link} -> {cache_target}")
            else:
                print(f"Root symlink already exists: {root_link}")
            
        except Exception as e:
            print(f"Error setting up links for {dir_name}: {e}")
            # Fallback: ensure at least the cache directory exists
            try:
                if not cache_target.exists():
                    cache_target.mkdir(parents=True, exist_ok=True)
                    cache_target.chmod(0o755)
                    print(f"Created fallback cache directory: {cache_target}")
            except Exception as fe:
                print(f"Fallback failed for {dir_name}: {fe}")

def ensure_directory_permissions():
    """Ensure proper permissions on key directories in the current working directory."""
    directories_to_fix = ["Generated", "static", "view_session"]
    
    for dir_name in directories_to_fix:
        dir_path = Path(dir_name)
        try:
            if not dir_path.exists():
                # Create directory if it doesn't exist
                dir_path.mkdir(parents=True, exist_ok=True)
                print(f"Created directory: {dir_path}")
            
            # Set directory permissions to 755 (owner rwx, group rx, others rx)
            dir_path.chmod(0o755)
            print(f"Set permissions 755 on directory: {dir_path}")
            
            # Set permissions on all subdirectories and files
            for item in dir_path.rglob('*'):
                try:
                    if item.is_dir():
                        item.chmod(0o755)
                    else:
                        item.chmod(0o644)  # Files: owner rw, group r, others r
                except Exception as e:
                    print(f"Warning: Could not set permissions on {item}: {e}")
                    
        except Exception as e:
            print(f"Error ensuring permissions for {dir_name}: {e}")

def download_space(cache_dir):
    token = os.environ.get("HF_TOKEN")
    repo_id = os.environ.get("REPO_ID")
    
    if not token or not repo_id:
        return False
    
    try:
        snapshot_download(
            repo_id=repo_id,
            repo_type="space",
            local_dir=cache_dir,
            token=token
        )
        return True
    except:
        return False

def load_app(cache_dir):
    sys.path.insert(0, str(cache_dir))
    app_path = cache_dir / "app.py"
    spec = importlib.util.spec_from_file_location("app", app_path)
    app = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(app)

    # First try the common 'demo' attribute used by many Gradio Spaces
    if hasattr(app, "demo"):
        return app.demo

    # Otherwise, search for any attribute with a callable 'launch' method
    for name in dir(app):
        try:
            attr = getattr(app, name)
        except Exception:
            continue
        # Skip classes (types) since their 'launch' will be an unbound function
        if isinstance(attr, type):
            continue
        if hasattr(attr, "launch") and callable(getattr(attr, "launch")):
            return attr

    # Next, accept top-level callables that return an object with a 'launch' method.
    # This covers Spaces that expose a factory function instead of a 'demo' object.
    factory_names = [
        "create_secure_interface",
        "create_app",
        "create_demo",
        "main",
        "generator",
    ]

    for name in factory_names:
        if hasattr(app, name):
            try:
                candidate = getattr(app, name)
                if callable(candidate):
                    created = candidate()
                    if hasattr(created, "launch") and callable(getattr(created, "launch")):
                        return created
                    # If the factory itself is a Gradio Interface (callable with launch), return it
                    if hasattr(candidate, "launch") and callable(getattr(candidate, "launch")):
                        return candidate
            except Exception:
                # ignore failures from calling the factory and continue searching
                pass

    # Also accept any top-level callable that when called returns an object with 'launch'
    for name in dir(app):
        if name.startswith("__"):
            continue
        try:
            attr = getattr(app, name)
        except Exception:
            continue
        if callable(attr):
            try:
                created = attr()
                if hasattr(created, "launch") and callable(getattr(created, "launch")):
                    return created
            except Exception:
                # if calling fails, skip
                continue

    # If nothing found, raise a helpful error describing the problem
    available = [n for n in dir(app) if not n.startswith("__")]
    raise AttributeError(
        "Could not find a demo application in the downloaded space."
        f" Looked for attribute 'demo' and any attribute with a callable 'launch'."
        f" Available top-level names: {available}"
    )

if __name__ == "__main__":
    print("Setting up BytePlus Image Generation Studio...")
    
    # First ensure proper permissions on existing directories
    ensure_directory_permissions()
    
    # Setup cache directory
    cache_dir = setup_cache_directory()
    print(f"Cache directory: {cache_dir}")
    
    # Download the space
    if download_space(cache_dir):
        print("Successfully downloaded BytePlus space")
        
        # Create symbolic links for directory access
        create_symbolic_links(cache_dir)
        
        # Load and launch the app
        try:
            demo = load_app(cache_dir)
            print("Launching BytePlus Image Generation Studio...")
            demo.launch()
        except Exception as e:
            print(f"Error launching app: {e}")
            sys.exit(1)
    else:
        print("Failed to download space. Please check your HF_TOKEN and REPO_ID environment variables.")
        sys.exit(1)